From b96ebe0457b47942d3ad4567169d25fe6bfdb27b Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Sun, 12 Dec 2021 19:02:18 -0600 Subject: [PATCH 001/541] Starts memory allocation for our collection of failures for the expect keyword + BLOCKED: @lucas how do we initialize an empty slice in Zig? --- compiler/builtins/bitcode/src/main.zig | 3 +- compiler/builtins/bitcode/src/utils.zig | 48 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index c925054a75..23bcfbef61 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -141,12 +141,13 @@ comptime { } // Utils - comptime { exportUtilsFn(utils.test_panic, "test_panic"); exportUtilsFn(utils.increfC, "incref"); exportUtilsFn(utils.decrefC, "decref"); exportUtilsFn(utils.decrefCheckNullC, "decref_check_null"); + exportUtilsFn(utils.expectFailed, "expect_failed"); + exportUtilsFn(utils.getExpectFailures, "get_expect_failures"); @export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak }); } diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 940e8d945b..60978c4a37 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -217,6 +217,41 @@ pub fn allocateWithRefcount( } } +const Failure = struct{ + start_line: u32, + end_line: u32, + start_col: u16, + end_col: u16, +}; +threadlocal var failures = [_]Failure{ }; +threadlocal var failure_capacity: usize = 0; + +pub fn expectFailed( + start_line: u32, + end_line: u32, + start_col: u16, + end_col: u16, +) void { + const new_failure = Failure{ + .start_line = start_line, + .end_line = end_line, + .start_col = start_col, + .end_col = end_col + }; + + if (failures.len >= failure_capacity) { + failure_capacity += 4096; + failures.ptr = roc_alloc(failure_capacity, @alignOf(Failure)); + } + + failures[failures.len] = new_failure; + failures.len += 1; +} + +pub fn getExpectFailures() [_]Failure { + return failures; +} + pub fn unsafeReallocate( source_ptr: [*]u8, alignment: u32, @@ -281,3 +316,16 @@ test "increfC, static data" { increfC(ptr_to_refcount, 2); try std.testing.expectEqual(mock_rc, REFCOUNT_MAX_ISIZE); } + +test "expectFailure does something"{ + try std.testing.expectEqual(failures.len, 0); + expectFailed(1, 2, 3, 4); + try std.testing.expectEqual(failures.len, 1); + const what_it_should_look_like = Failure{ + .start_line = 1, + .end_line = 2, + .start_col = 3, + .end_col = 4 + }; + try std.testing.expectEqual(failures[0], what_it_should_look_like); +} \ No newline at end of file From 715c441b25eb5eb54c11cad943e9cdead772738e Mon Sep 17 00:00:00 2001 From: rvcas Date: Mon, 27 Dec 2021 19:07:52 -0500 Subject: [PATCH 002/541] fix(int_instrinsic): these should be unsigned --- compiler/builtins/src/bitcode.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 6746068a66..27442fccd5 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -204,11 +204,11 @@ macro_rules! int_intrinsic { ($name:literal) => {{ let mut output = IntrinsicName::default(); - output.options[4] = concat!($name, ".i8"); - output.options[5] = concat!($name, ".i16"); - output.options[6] = concat!($name, ".i32"); - output.options[7] = concat!($name, ".i64"); - output.options[8] = concat!($name, ".i128"); + output.options[4] = concat!($name, ".u8"); + output.options[5] = concat!($name, ".u16"); + output.options[6] = concat!($name, ".u32"); + output.options[7] = concat!($name, ".u64"); + output.options[8] = concat!($name, ".u128"); output.options[9] = concat!($name, ".i8"); output.options[10] = concat!($name, ".i16"); output.options[11] = concat!($name, ".i32"); From cd42f034b56d004f73bd9522da1e2e857126e890 Mon Sep 17 00:00:00 2001 From: rvcas Date: Mon, 27 Dec 2021 19:08:21 -0500 Subject: [PATCH 003/541] feat(wasm): start StrToNum --- compiler/gen_wasm/src/low_level.rs | 9 +- compiler/test_gen/src/wasm_str.rs | 212 +++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+), 1 deletion(-) diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index cc5cff66f6..bc40141137 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -46,7 +46,14 @@ pub fn dispatch_low_level<'a>( return NotImplemented; } StrCountGraphemes => return BuiltinCall(bitcode::STR_COUNT_GRAPEHEME_CLUSTERS), - StrToNum => return NotImplemented, // choose builtin based on storage size + StrToNum => match ret_layout { + WasmLayout::Primitive(_, _) => return NotImplemented, + WasmLayout::StackMemory { size, .. } => { + // how do I decided Float vs. Int vs. Dec + // how do I find out if it's signed or unsigned + return BuiltinCall(bitcode::STR_TO_INT); + } + }, // choose builtin based on storage size StrFromInt => { // This does not get exposed in user space. We switched to NumToStr instead. // We can probably just leave this as NotImplemented. We may want remove this LowLevel. diff --git a/compiler/test_gen/src/wasm_str.rs b/compiler/test_gen/src/wasm_str.rs index cf4b1c1957..69a88fd2b1 100644 --- a/compiler/test_gen/src/wasm_str.rs +++ b/compiler/test_gen/src/wasm_str.rs @@ -1121,3 +1121,215 @@ fn str_trim_right_small_to_small_shared() { (RocStr, RocStr) ); } + +#[test] +fn str_to_nat() { + assert_evals_to!( + indoc!( + r#" + when Str.toNat "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + usize + ); +} + +#[test] +fn str_to_i128() { + assert_evals_to!( + indoc!( + r#" + when Str.toI128 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + i128 + ); +} + +#[test] +fn str_to_u128() { + assert_evals_to!( + indoc!( + r#" + when Str.toU128 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + u128 + ); +} + +#[test] +fn str_to_i64() { + assert_evals_to!( + indoc!( + r#" + when Str.toI64 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + i64 + ); +} + +#[test] +fn str_to_u64() { + assert_evals_to!( + indoc!( + r#" + when Str.toU64 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + u64 + ); +} + +#[test] +fn str_to_i32() { + assert_evals_to!( + indoc!( + r#" + when Str.toI32 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + i32 + ); +} + +#[test] +fn str_to_u32() { + assert_evals_to!( + indoc!( + r#" + when Str.toU32 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + u32 + ); +} + +#[test] +fn str_to_i16() { + assert_evals_to!( + indoc!( + r#" + when Str.toI16 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + i16 + ); +} + +#[test] +fn str_to_u16() { + assert_evals_to!( + indoc!( + r#" + when Str.toU16 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + u16 + ); +} + +#[test] +fn str_to_i8() { + assert_evals_to!( + indoc!( + r#" + when Str.toI8 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + i8 + ); +} + +#[test] +fn str_to_u8() { + assert_evals_to!( + indoc!( + r#" + when Str.toU8 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + u8 + ); +} + +#[test] +fn str_to_f64() { + assert_evals_to!( + indoc!( + r#" + when Str.toF64 "1.0" is + Ok n -> n + Err _ -> 0 + "# + ), + 1.0, + f64 + ); +} + +#[test] +fn str_to_f32() { + assert_evals_to!( + indoc!( + r#" + when Str.toF32 "1.0" is + Ok n -> n + Err _ -> 0 + "# + ), + 1.0, + f32 + ); +} + +#[test] +fn str_to_dec() { + use roc_std::RocDec; + + assert_evals_to!( + indoc!( + r#" + when Str.toDec "1.0" is + Ok n -> n + Err _ -> 0 + "# + ), + RocDec::from_str("1.0").unwrap(), + RocDec + ); +} From c4e6349714104b4a181a453a4d342c67d4f16acb Mon Sep 17 00:00:00 2001 From: rvcas Date: Tue, 28 Dec 2021 13:07:05 -0500 Subject: [PATCH 004/541] fix(bitcode): leave a comment explaining why u64 is not used --- compiler/builtins/src/bitcode.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 27442fccd5..8492d6069b 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -204,11 +204,13 @@ macro_rules! int_intrinsic { ($name:literal) => {{ let mut output = IntrinsicName::default(); - output.options[4] = concat!($name, ".u8"); - output.options[5] = concat!($name, ".u16"); - output.options[6] = concat!($name, ".u32"); - output.options[7] = concat!($name, ".u64"); - output.options[8] = concat!($name, ".u128"); + // These are LLVM types which don't include + // u64 for example + output.options[4] = concat!($name, ".i8"); + output.options[5] = concat!($name, ".i16"); + output.options[6] = concat!($name, ".i32"); + output.options[7] = concat!($name, ".i64"); + output.options[8] = concat!($name, ".i128"); output.options[9] = concat!($name, ".i8"); output.options[10] = concat!($name, ".i16"); output.options[11] = concat!($name, ".i32"); From 0d187fb53c902586b862cf44f1d8adc1f73f5229 Mon Sep 17 00:00:00 2001 From: rvcas Date: Tue, 28 Dec 2021 21:38:43 -0500 Subject: [PATCH 005/541] feat(wasm): use mono layout to figure out the correct intrinsic --- compiler/gen_wasm/src/backend.rs | 31 ++++++++++++++++++++++++------ compiler/gen_wasm/src/low_level.rs | 25 +++++++++++++++++------- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 9d08da3b88..f14732911a 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -597,6 +597,7 @@ impl<'a> WasmBackend<'a> { arguments, *sym, wasm_layout, + layout, storage, ); } @@ -634,7 +635,7 @@ impl<'a> WasmBackend<'a> { } CallType::LowLevel { op: lowlevel, .. } => { - self.build_low_level(*lowlevel, arguments, *sym, wasm_layout, storage) + self.build_low_level(*lowlevel, arguments, *sym, wasm_layout, layout, storage) } x => todo!("call type {:?}", x), @@ -1069,14 +1070,20 @@ impl<'a> WasmBackend<'a> { arguments: &'a [Symbol], return_sym: Symbol, return_layout: WasmLayout, + mono_layout: &Layout<'a>, storage: &StoredValue, ) { use LowLevel::*; match lowlevel { - Eq | NotEq => { - self.build_eq_or_neq(lowlevel, arguments, return_sym, return_layout, storage) - } + Eq | NotEq => self.build_eq_or_neq( + lowlevel, + arguments, + return_sym, + return_layout, + mono_layout, + storage, + ), PtrCast => { // Don't want Zig calling convention when casting pointers. self.storage.load_symbols(&mut self.code_builder, arguments); @@ -1102,6 +1109,7 @@ impl<'a> WasmBackend<'a> { lowlevel, arguments, &return_layout, + mono_layout, ); // Handle the result @@ -1125,6 +1133,7 @@ impl<'a> WasmBackend<'a> { arguments: &'a [Symbol], return_sym: Symbol, return_layout: WasmLayout, + mono_layout: &Layout<'a>, storage: &StoredValue, ) { let arg_layout = self.storage.symbol_layouts[&arguments[0]]; @@ -1137,7 +1146,7 @@ impl<'a> WasmBackend<'a> { match arg_layout { Layout::Builtin( Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal, - ) => self.build_eq_or_neq_number(lowlevel, arguments, return_layout), + ) => self.build_eq_or_neq_number(lowlevel, arguments, return_layout, mono_layout), Layout::Builtin(Builtin::Str) => { let (param_types, ret_type) = self.storage.load_symbols_for_call( @@ -1192,6 +1201,7 @@ impl<'a> WasmBackend<'a> { lowlevel: LowLevel, arguments: &'a [Symbol], return_layout: WasmLayout, + mono_layout: &Layout<'a>, ) { use StoredValue::*; match self.storage.get(&arguments[0]).to_owned() { @@ -1223,7 +1233,13 @@ impl<'a> WasmBackend<'a> { .. } = self.storage.get(&arguments[1]).to_owned() { - self.build_eq_num128(format, [location0, location1], arguments, return_layout); + self.build_eq_num128( + format, + [location0, location1], + arguments, + return_layout, + mono_layout, + ); if matches!(lowlevel, LowLevel::NotEq) { self.code_builder.i32_eqz(); } @@ -1238,6 +1254,7 @@ impl<'a> WasmBackend<'a> { locations: [StackMemoryLocation; 2], arguments: &'a [Symbol], return_layout: WasmLayout, + mono_layout: &Layout<'a>, ) { match format { StackMemoryFormat::Decimal => { @@ -1250,6 +1267,7 @@ impl<'a> WasmBackend<'a> { LowLevel::NumIsFinite, &first, &return_layout, + mono_layout, ); dispatch_low_level( &mut self.code_builder, @@ -1257,6 +1275,7 @@ impl<'a> WasmBackend<'a> { LowLevel::NumIsFinite, &second, &return_layout, + mono_layout, ); self.code_builder.i32_and(); diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index bc40141137..93c22bfd27 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -1,6 +1,7 @@ use roc_builtins::bitcode::{self, FloatWidth}; use roc_module::low_level::{LowLevel, LowLevel::*}; use roc_module::symbol::Symbol; +use roc_mono::layout::{Builtin, Layout, UnionLayout}; use roc_reporting::internal_error; use crate::layout::{StackMemoryFormat::*, WasmLayout}; @@ -20,6 +21,7 @@ pub fn dispatch_low_level<'a>( lowlevel: LowLevel, args: &[Symbol], ret_layout: &WasmLayout, + mono_layout: &Layout<'a>, ) -> LowlevelBuildResult { use LowlevelBuildResult::*; @@ -46,14 +48,23 @@ pub fn dispatch_low_level<'a>( return NotImplemented; } StrCountGraphemes => return BuiltinCall(bitcode::STR_COUNT_GRAPEHEME_CLUSTERS), - StrToNum => match ret_layout { - WasmLayout::Primitive(_, _) => return NotImplemented, - WasmLayout::StackMemory { size, .. } => { - // how do I decided Float vs. Int vs. Dec - // how do I find out if it's signed or unsigned - return BuiltinCall(bitcode::STR_TO_INT); + StrToNum => { + if let Layout::Union(UnionLayout::NonRecursive(union_layout)) = mono_layout { + // match on the return layout to figure out which zig builtin we need + let intrinsic = match union_layout[1][0] { + Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width], + Layout::Builtin(Builtin::Float(float_width)) => { + &bitcode::STR_TO_FLOAT[float_width] + } + Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR, + rest => internal_error!("Unexpected builtin {:?} for StrToNum", rest), + }; + + return BuiltinCall(intrinsic); + } else { + internal_error!("Unexpected mono layout {:?} for StrToNum", mono_layout); } - }, // choose builtin based on storage size + } // choose builtin based on storage size StrFromInt => { // This does not get exposed in user space. We switched to NumToStr instead. // We can probably just leave this as NotImplemented. We may want remove this LowLevel. From 4d2e4d454b5ac6f2c8c325d75aa26a71ff7b8e4d Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 29 Dec 2021 09:45:42 +0000 Subject: [PATCH 006/541] Create code gen tests for refcounting --- compiler/test_gen/src/gen_refcount.rs | 30 +++++++ compiler/test_gen/src/helpers/wasm.rs | 86 +++++++++++++++++-- .../test_gen/src/helpers/wasm_test_platform.c | 62 +++++++++++-- compiler/test_gen/src/tests.rs | 1 + 4 files changed, 169 insertions(+), 10 deletions(-) create mode 100644 compiler/test_gen/src/gen_refcount.rs diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs new file mode 100644 index 0000000000..f7fd8cb045 --- /dev/null +++ b/compiler/test_gen/src/gen_refcount.rs @@ -0,0 +1,30 @@ +#[cfg(feature = "gen-wasm")] +use crate::helpers::wasm::assert_refcounts; + +#[allow(unused_imports)] +use indoc::indoc; + +#[allow(unused_imports)] +use roc_std::{RocList, RocStr}; + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn list_of_same_str() { + assert_refcounts!( + indoc!( + r#" + a = "A long enough string" + b = "to be heap-allocated" + c = Str.joinWith [a, b] " " + + [c, c, c] + "# + ), + RocList, + &[ + 1, // [a,b] (TODO: list decrement. This should be zero!) + 3, // c + 1 // result + ] + ); +} diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 70f6e3e987..6680a1cfe0 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -1,8 +1,9 @@ +use core::cell::Cell; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use tempfile::{tempdir, TempDir}; -use wasmer::Memory; +use wasmer::{Memory, WasmPtr}; use crate::helpers::from_wasm32_memory::FromWasm32Memory; use crate::helpers::wasm32_test_result::Wasm32TestResult; @@ -36,11 +37,11 @@ fn promote_expr_to_module(src: &str) -> String { } #[allow(dead_code)] -pub fn helper_wasm<'a, T: Wasm32TestResult>( +pub fn compile_and_load<'a, T: Wasm32TestResult>( arena: &'a bumpalo::Bump, src: &str, stdlib: &'a roc_builtins::std::StdLib, - _result_type_dummy: PhantomData, + _test_wrapper_type_info: PhantomData, ) -> wasmer::Instance { use std::path::{Path, PathBuf}; @@ -178,10 +179,11 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( // // It seems that it will not write out an export you didn't explicitly specify, // even if it's a dependency of another export! - // In our case we always export main and test_wrapper so that's OK. "--export", "test_wrapper", "--export", + "init_refcount_test", + "--export", "#UserApp_main_1", ]; @@ -225,7 +227,7 @@ where // NOTE the stdlib must be in the arena; just taking a reference will segfault let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); - let instance = crate::helpers::wasm::helper_wasm(&arena, src, stdlib, phantom); + let instance = crate::helpers::wasm::compile_and_load(&arena, src, stdlib, phantom); let memory = instance.exports.get_memory(MEMORY_NAME).unwrap(); @@ -256,6 +258,55 @@ where } } +#[allow(dead_code)] +pub fn assert_wasm_refcounts_help( + src: &str, + phantom: PhantomData, + num_refcounts: usize, +) -> Result, String> +where + T: FromWasm32Memory + Wasm32TestResult, +{ + 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 instance = crate::helpers::wasm::compile_and_load(&arena, src, stdlib, phantom); + + let memory = instance.exports.get_memory(MEMORY_NAME).unwrap(); + + let init_refcount_test = instance.exports.get_function("init_refcount_test").unwrap(); + let init_result = init_refcount_test.call(&[wasmer::Value::I32(num_refcounts as i32)]); + let refcount_array_addr = match init_result { + Err(e) => return Err(format!("{:?}", e)), + Ok(result) => match result[0] { + wasmer::Value::I32(a) => a, + _ => panic!(), + }, + }; + // An array of refcount pointers + let refcount_ptr_array: WasmPtr, wasmer::Array> = + WasmPtr::new(refcount_array_addr as u32); + let refcount_ptrs: &[Cell>] = refcount_ptr_array + .deref(memory, 0, num_refcounts as u32) + .unwrap(); + + let test_wrapper = instance.exports.get_function(TEST_WRAPPER_NAME).unwrap(); + match test_wrapper.call(&[]) { + Err(e) => return Err(format!("{:?}", e)), + Ok(_) => {} + } + + let mut refcounts = Vec::with_capacity(num_refcounts); + for i in 0..num_refcounts { + let rc_encoded = refcount_ptrs[i].get().deref(memory).unwrap().get(); + let rc = (rc_encoded - i32::MIN + 1) as u32; + refcounts.push(rc); + } + Ok(refcounts) +} + /// Print out hex bytes of the test result, and a few words on either side /// Can be handy for debugging misalignment issues etc. pub fn debug_memory_hex(memory: &Memory, address: i32, size: usize) { @@ -330,7 +381,32 @@ pub fn identity(value: T) -> T { value } +#[allow(unused_macros)] +macro_rules! assert_refcounts { + ($src: expr, $ty: ty, $expected_refcounts: expr) => { + // Same as above, except with an additional transformation argument. + { + let phantom = std::marker::PhantomData; + let num_refcounts = $expected_refcounts.len(); + let result = $crate::helpers::wasm::assert_wasm_refcounts_help::<$ty>( + $src, + phantom, + num_refcounts, + ); + match result { + Err(msg) => panic!("{:?}", msg), + Ok(actual_refcounts) => { + assert_eq!(&actual_refcounts, $expected_refcounts) + } + } + } + }; +} + #[allow(unused_imports)] pub(crate) use assert_evals_to; #[allow(unused_imports)] pub(crate) use assert_wasm_evals_to; + +#[allow(unused_imports)] +pub(crate) use assert_refcounts; diff --git a/compiler/test_gen/src/helpers/wasm_test_platform.c b/compiler/test_gen/src/helpers/wasm_test_platform.c index 5f466c707e..e2175ea963 100644 --- a/compiler/test_gen/src/helpers/wasm_test_platform.c +++ b/compiler/test_gen/src/helpers/wasm_test_platform.c @@ -1,14 +1,59 @@ #include -// If any printf is included for compilation, even if unused, test runs take 50% longer -#define DEBUG 0 +// Makes test runs take 50% longer, due to linking +#define ENABLE_PRINTF 0 + +// Globals for refcount testing +size_t **rc_pointers; // array of pointers to refcount values +size_t rc_pointers_len; +size_t rc_pointers_index; + +// The rust test passes us the max number of allocations it expects to make, +// and we tell it where we're going to write the refcount pointers. +// It won't actually read that memory until later, when the test is done. +size_t **init_refcount_test(size_t max_allocs) +{ + rc_pointers = malloc(max_allocs * sizeof(size_t *)); + rc_pointers_len = max_allocs; + rc_pointers_index = 0; + for (size_t i = 0; i < max_allocs; ++i) + rc_pointers[i] = NULL; + + return rc_pointers; +} + +#if ENABLE_PRINTF +#define ASSERT(x) \ + if (!(x)) \ + { \ + printf("FAILED: " #x "\n"); \ + abort(); \ + } +#else +#define ASSERT(x) \ + if (!(x)) \ + abort(); +#endif //-------------------------- void *roc_alloc(size_t size, unsigned int alignment) { void *allocated = malloc(size); -#if DEBUG + + if (rc_pointers) + { + ASSERT(alignment >= sizeof(size_t)); + ASSERT(rc_pointers_index < rc_pointers_len); + + size_t alloc_addr = (size_t)allocated; + size_t rc_addr = alloc_addr + alignment - sizeof(size_t); + size_t *rc_ptr = (size_t *)rc_addr; + rc_pointers[rc_pointers_index] = rc_ptr; + rc_pointers_index++; + } + +#if ENABLE_PRINTF if (!allocated) { fprintf(stderr, "roc_alloc failed\n"); @@ -27,6 +72,10 @@ void *roc_alloc(size_t size, unsigned int alignment) void *roc_realloc(void *ptr, size_t new_size, size_t old_size, unsigned int alignment) { +#if ENABLE_PRINTF + printf("roc_realloc reallocated %p from %d to %d with alignment %zd\n", + ptr, old_size, new_size, alignment); +#endif return realloc(ptr, new_size); } @@ -34,6 +83,9 @@ void *roc_realloc(void *ptr, size_t new_size, size_t old_size, void roc_dealloc(void *ptr, unsigned int alignment) { +#if ENABLE_PRINTF + printf("roc_dealloc deallocated %p with alignment %zd\n", ptr, alignment); +#endif free(ptr); } @@ -41,12 +93,12 @@ void roc_dealloc(void *ptr, unsigned int alignment) void roc_panic(void *ptr, unsigned int alignment) { -#if DEBUG +#if ENABLE_PRINTF char *msg = (char *)ptr; fprintf(stderr, "Application crashed with message\n\n %s\n\nShutting down\n", msg); #endif - exit(1); + abort(); } //-------------------------- diff --git a/compiler/test_gen/src/tests.rs b/compiler/test_gen/src/tests.rs index 7e324f608b..a6da79ca91 100644 --- a/compiler/test_gen/src/tests.rs +++ b/compiler/test_gen/src/tests.rs @@ -10,6 +10,7 @@ pub mod gen_list; pub mod gen_num; pub mod gen_primitives; pub mod gen_records; +pub mod gen_refcount; pub mod gen_result; pub mod gen_set; pub mod gen_str; From 7aa3f77b3c05d842fc81d304332228854e5833a8 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 29 Dec 2021 17:31:05 +0000 Subject: [PATCH 007/541] Handle freed values in refcount tests --- compiler/test_gen/src/helpers/wasm.rs | 11 ++++++-- .../test_gen/src/helpers/wasm_test_platform.c | 26 ++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 6680a1cfe0..3aa682bed9 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -300,8 +300,15 @@ where let mut refcounts = Vec::with_capacity(num_refcounts); for i in 0..num_refcounts { - let rc_encoded = refcount_ptrs[i].get().deref(memory).unwrap().get(); - let rc = (rc_encoded - i32::MIN + 1) as u32; + let rc_ptr = refcount_ptrs[i].get(); + let rc = if rc_ptr.offset() == 0 { + // RC pointer has been set to null, which means the value has been freed. + // In tests, we simply represent this as zero refcount. + 0 + } else { + let rc_encoded = rc_ptr.deref(memory).unwrap().get(); + (rc_encoded - i32::MIN + 1) as u32 + }; refcounts.push(rc); } Ok(refcounts) diff --git a/compiler/test_gen/src/helpers/wasm_test_platform.c b/compiler/test_gen/src/helpers/wasm_test_platform.c index e2175ea963..b69ea54525 100644 --- a/compiler/test_gen/src/helpers/wasm_test_platform.c +++ b/compiler/test_gen/src/helpers/wasm_test_platform.c @@ -35,6 +35,13 @@ size_t **init_refcount_test(size_t max_allocs) abort(); #endif +size_t *data_ptr_to_rc_ptr(void *ptr, unsigned int alignment) +{ + size_t alloc_addr = (size_t)ptr; + size_t rc_addr = alloc_addr + alignment - sizeof(size_t); + return (size_t *)rc_addr; +} + //-------------------------- void *roc_alloc(size_t size, unsigned int alignment) @@ -46,9 +53,7 @@ void *roc_alloc(size_t size, unsigned int alignment) ASSERT(alignment >= sizeof(size_t)); ASSERT(rc_pointers_index < rc_pointers_len); - size_t alloc_addr = (size_t)allocated; - size_t rc_addr = alloc_addr + alignment - sizeof(size_t); - size_t *rc_ptr = (size_t *)rc_addr; + size_t *rc_ptr = data_ptr_to_rc_ptr(allocated, alignment); rc_pointers[rc_pointers_index] = rc_ptr; rc_pointers_index++; } @@ -83,6 +88,21 @@ void *roc_realloc(void *ptr, size_t new_size, size_t old_size, void roc_dealloc(void *ptr, unsigned int alignment) { + // Null out the entry in the test array to signal that it was freed + // Then even if malloc reuses the space, everything still works + size_t *rc_ptr = data_ptr_to_rc_ptr(ptr, alignment); + int i = 0; + for (; i < rc_pointers_index; ++i) + { + if (rc_pointers[i] == rc_ptr) + { + rc_pointers[i] = NULL; + break; + } + } + int was_found = i < rc_pointers_index; + ASSERT(was_found); + #if ENABLE_PRINTF printf("roc_dealloc deallocated %p with alignment %zd\n", ptr, alignment); #endif From f90d9a74bd9649d8e0b81d860dbaa3d6a7ca7ecb Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 29 Dec 2021 21:08:16 +0000 Subject: [PATCH 008/541] Do not inline builtin wrapper for Str.toNum --- compiler/gen_wasm/src/low_level.rs | 29 ++++++++++++++--------------- compiler/module/src/low_level.rs | 28 ++++++++++++++-------------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index 93c22bfd27..b7ba2beeb4 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -49,22 +49,21 @@ pub fn dispatch_low_level<'a>( } StrCountGraphemes => return BuiltinCall(bitcode::STR_COUNT_GRAPEHEME_CLUSTERS), StrToNum => { - if let Layout::Union(UnionLayout::NonRecursive(union_layout)) = mono_layout { - // match on the return layout to figure out which zig builtin we need - let intrinsic = match union_layout[1][0] { - Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width], - Layout::Builtin(Builtin::Float(float_width)) => { - &bitcode::STR_TO_FLOAT[float_width] - } - Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR, - rest => internal_error!("Unexpected builtin {:?} for StrToNum", rest), - }; + let number_layout = match mono_layout { + Layout::Union(UnionLayout::NonRecursive(tags)) => tags[1][0], + Layout::Struct(fields) => fields[0], // TODO: why is it sometimes a struct? + _ => internal_error!("Unexpected mono layout {:?} for StrToNum", mono_layout), + }; + // match on the return layout to figure out which zig builtin we need + let intrinsic = match number_layout { + Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width], + Layout::Builtin(Builtin::Float(float_width)) => &bitcode::STR_TO_FLOAT[float_width], + Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR, + rest => internal_error!("Unexpected builtin {:?} for StrToNum", rest), + }; - return BuiltinCall(intrinsic); - } else { - internal_error!("Unexpected mono layout {:?} for StrToNum", mono_layout); - } - } // choose builtin based on storage size + return BuiltinCall(intrinsic); + } StrFromInt => { // This does not get exposed in user space. We switched to NumToStr instead. // We can probably just leave this as NotImplemented. We may want remove this LowLevel. diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 1673c55d3d..7c2607cffb 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -198,20 +198,20 @@ impl LowLevel { Symbol::STR_TRIM => Some(StrTrim), Symbol::STR_TRIM_LEFT => Some(StrTrimLeft), Symbol::STR_TRIM_RIGHT => Some(StrTrimRight), - Symbol::STR_TO_DEC => Some(StrToNum), - Symbol::STR_TO_F64 => Some(StrToNum), - Symbol::STR_TO_F32 => Some(StrToNum), - Symbol::STR_TO_NAT => Some(StrToNum), - Symbol::STR_TO_U128 => Some(StrToNum), - Symbol::STR_TO_I128 => Some(StrToNum), - Symbol::STR_TO_U64 => Some(StrToNum), - Symbol::STR_TO_I64 => Some(StrToNum), - Symbol::STR_TO_U32 => Some(StrToNum), - Symbol::STR_TO_I32 => Some(StrToNum), - Symbol::STR_TO_U16 => Some(StrToNum), - Symbol::STR_TO_I16 => Some(StrToNum), - Symbol::STR_TO_U8 => Some(StrToNum), - Symbol::STR_TO_I8 => Some(StrToNum), + Symbol::STR_TO_DEC => None, + Symbol::STR_TO_F64 => None, + Symbol::STR_TO_F32 => None, + Symbol::STR_TO_NAT => None, + Symbol::STR_TO_U128 => None, + Symbol::STR_TO_I128 => None, + Symbol::STR_TO_U64 => None, + Symbol::STR_TO_I64 => None, + Symbol::STR_TO_U32 => None, + Symbol::STR_TO_I32 => None, + Symbol::STR_TO_U16 => None, + Symbol::STR_TO_I16 => None, + Symbol::STR_TO_U8 => None, + Symbol::STR_TO_I8 => None, Symbol::LIST_LEN => Some(ListLen), Symbol::LIST_GET => None, Symbol::LIST_SET => None, From d0c40723625acc563647eccbe04f8f8761772fba Mon Sep 17 00:00:00 2001 From: rvcas Date: Wed, 29 Dec 2021 16:26:15 -0500 Subject: [PATCH 009/541] fix:(llvm): StrToNum is safer if we match on either a Union or a Struct --- compiler/gen_llvm/src/llvm/build.rs | 32 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index dbcd2f7790..7bb6ea0626 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -5308,24 +5308,24 @@ fn run_low_level<'a, 'ctx, 'env>( let (string, _string_layout) = load_symbol_and_layout(scope, &args[0]); - if let Layout::Struct(struct_layout) = layout { - // match on the return layout to figure out which zig builtin we need - let intrinsic = match struct_layout[0] { - Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width], - Layout::Builtin(Builtin::Float(float_width)) => { - &bitcode::STR_TO_FLOAT[float_width] - } - Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR, - _ => unreachable!(), - }; + let number_layout = match layout { + Layout::Union(UnionLayout::NonRecursive(tags)) => tags[1][0], + Layout::Struct(fields) => fields[0], // TODO: why is it sometimes a struct? + _ => unreachable!(), + }; - let string = - complex_bitcast(env.builder, string, env.str_list_c_abi().into(), "to_utf8"); + // match on the return layout to figure out which zig builtin we need + let intrinsic = match number_layout { + Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width], + Layout::Builtin(Builtin::Float(float_width)) => &bitcode::STR_TO_FLOAT[float_width], + Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR, + _ => unreachable!(), + }; - call_bitcode_fn(env, &[string], intrinsic) - } else { - unreachable!() - } + let string = + complex_bitcast(env.builder, string, env.str_list_c_abi().into(), "to_utf8"); + + call_bitcode_fn(env, &[string], intrinsic) } StrFromInt => { // Str.fromInt : Int -> Str From 6b932f9743a35c32907d40e8702e0d1926502c2e Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 29 Dec 2021 22:03:25 +0000 Subject: [PATCH 010/541] Clarify some lowlevel code to make it clearer what to do when adding new ones --- compiler/gen_dev/src/lib.rs | 7 +- compiler/gen_wasm/src/backend.rs | 6 +- compiler/gen_wasm/src/lib.rs | 7 +- compiler/module/src/low_level.rs | 267 ++++++++++++++++--------------- 4 files changed, 152 insertions(+), 135 deletions(-) diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index b188f28b41..20d3d6e8b4 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -6,7 +6,7 @@ use bumpalo::{collections::Vec, Bump}; use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{ModuleName, TagName}; -use roc_module::low_level::LowLevel; +use roc_module::low_level::{LowLevel, LowLevelWrapperType}; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::ir::{ @@ -260,8 +260,9 @@ trait Backend<'a> { ret_layout, .. } => { - // If this function is just a lowlevel wrapper, then inline it - if let Some(lowlevel) = LowLevel::from_inlined_wrapper(*func_sym) { + if let LowLevelWrapperType::CanBeReplacedBy(lowlevel) = + LowLevelWrapperType::from_symbol(*func_sym) + { self.build_run_low_level( sym, &lowlevel, diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 9d08da3b88..56d4116789 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -4,7 +4,7 @@ use code_builder::Align; use roc_builtins::bitcode::{self, IntWidth}; use roc_collections::all::MutMap; use roc_module::ident::Ident; -use roc_module::low_level::LowLevel; +use roc_module::low_level::{LowLevel, LowLevelWrapperType}; use roc_module::symbol::{Interns, Symbol}; use roc_mono::code_gen_help::{CodeGenHelp, REFCOUNT_MAX}; use roc_mono::ir::{ @@ -591,7 +591,9 @@ impl<'a> WasmBackend<'a> { }) => match call_type { CallType::ByName { name: func_sym, .. } => { // If this function is just a lowlevel wrapper, then inline it - if let Some(lowlevel) = LowLevel::from_inlined_wrapper(*func_sym) { + if let LowLevelWrapperType::CanBeReplacedBy(lowlevel) = + LowLevelWrapperType::from_symbol(*func_sym) + { return self.build_low_level( lowlevel, arguments, diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index ad2e9f051c..2c3ac8479c 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -8,7 +8,7 @@ use bumpalo::{self, collections::Vec, Bump}; use roc_builtins::bitcode::IntWidth; use roc_collections::all::{MutMap, MutSet}; -use roc_module::low_level::LowLevel; +use roc_module::low_level::LowLevelWrapperType; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::ir::{Proc, ProcLayout}; @@ -62,7 +62,10 @@ pub fn build_module_help<'a>( // and filter out procs we're going to inline let mut fn_index: u32 = 0; for ((sym, layout), proc) in procedures.into_iter() { - if LowLevel::from_inlined_wrapper(sym).is_some() { + if matches!( + LowLevelWrapperType::from_symbol(sym), + LowLevelWrapperType::CanBeReplacedBy(_) + ) { continue; } procs.push(proc); diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 1673c55d3d..49518adbe9 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -175,138 +175,149 @@ impl LowLevel { _ => unreachable!(), } } +} - /// Used in dev backends to inline some lowlevel wrapper functions - /// For wrappers that contain logic, we return None to prevent inlining - /// (Mention each explicitly rather than using `_`, to show they have not been forgotten) - pub fn from_inlined_wrapper(symbol: Symbol) -> Option { +/// Some wrapper functions can just be replaced by lowlevels in the backend for performance. +/// For example, Num.add should be an instruction, not a function call. +/// Variant names are chosen to help explain what to do when adding new lowlevels +pub enum LowLevelWrapperType { + /// This wrapper function contains no logic and we can remove it in code gen + CanBeReplacedBy(LowLevel), + /// This wrapper function contains important logic and we cannot remove it in code gen + WrapperIsRequired, + NotALowLevelWrapper, +} + +impl LowLevelWrapperType { + pub fn from_symbol(symbol: Symbol) -> LowLevelWrapperType { use LowLevel::*; + use LowLevelWrapperType::*; match symbol { - Symbol::STR_CONCAT => Some(StrConcat), - Symbol::STR_JOIN_WITH => Some(StrJoinWith), - Symbol::STR_IS_EMPTY => Some(StrIsEmpty), - Symbol::STR_STARTS_WITH => Some(StrStartsWith), - Symbol::STR_STARTS_WITH_CODE_PT => Some(StrStartsWithCodePt), - Symbol::STR_ENDS_WITH => Some(StrEndsWith), - Symbol::STR_SPLIT => Some(StrSplit), - Symbol::STR_COUNT_GRAPHEMES => Some(StrCountGraphemes), - Symbol::STR_FROM_UTF8 => None, - Symbol::STR_FROM_UTF8_RANGE => None, - Symbol::STR_TO_UTF8 => Some(StrToUtf8), - Symbol::STR_REPEAT => Some(StrRepeat), - Symbol::STR_TRIM => Some(StrTrim), - Symbol::STR_TRIM_LEFT => Some(StrTrimLeft), - Symbol::STR_TRIM_RIGHT => Some(StrTrimRight), - Symbol::STR_TO_DEC => Some(StrToNum), - Symbol::STR_TO_F64 => Some(StrToNum), - Symbol::STR_TO_F32 => Some(StrToNum), - Symbol::STR_TO_NAT => Some(StrToNum), - Symbol::STR_TO_U128 => Some(StrToNum), - Symbol::STR_TO_I128 => Some(StrToNum), - Symbol::STR_TO_U64 => Some(StrToNum), - Symbol::STR_TO_I64 => Some(StrToNum), - Symbol::STR_TO_U32 => Some(StrToNum), - Symbol::STR_TO_I32 => Some(StrToNum), - Symbol::STR_TO_U16 => Some(StrToNum), - Symbol::STR_TO_I16 => Some(StrToNum), - Symbol::STR_TO_U8 => Some(StrToNum), - Symbol::STR_TO_I8 => Some(StrToNum), - Symbol::LIST_LEN => Some(ListLen), - Symbol::LIST_GET => None, - Symbol::LIST_SET => None, - Symbol::LIST_SINGLE => Some(ListSingle), - Symbol::LIST_REPEAT => Some(ListRepeat), - Symbol::LIST_REVERSE => Some(ListReverse), - Symbol::LIST_CONCAT => Some(ListConcat), - Symbol::LIST_CONTAINS => Some(ListContains), - Symbol::LIST_APPEND => Some(ListAppend), - Symbol::LIST_PREPEND => Some(ListPrepend), - Symbol::LIST_JOIN => Some(ListJoin), - Symbol::LIST_RANGE => Some(ListRange), - Symbol::LIST_MAP => Some(ListMap), - Symbol::LIST_MAP2 => Some(ListMap2), - Symbol::LIST_MAP3 => Some(ListMap3), - Symbol::LIST_MAP4 => Some(ListMap4), - Symbol::LIST_MAP_WITH_INDEX => Some(ListMapWithIndex), - Symbol::LIST_KEEP_IF => Some(ListKeepIf), - Symbol::LIST_WALK => Some(ListWalk), - Symbol::LIST_WALK_UNTIL => Some(ListWalkUntil), - Symbol::LIST_WALK_BACKWARDS => Some(ListWalkBackwards), - Symbol::LIST_KEEP_OKS => Some(ListKeepOks), - Symbol::LIST_KEEP_ERRS => Some(ListKeepErrs), - Symbol::LIST_SORT_WITH => Some(ListSortWith), - Symbol::LIST_SUBLIST => Some(ListSublist), - Symbol::LIST_DROP_AT => Some(ListDropAt), - Symbol::LIST_SWAP => Some(ListSwap), - Symbol::LIST_ANY => Some(ListAny), - Symbol::LIST_ALL => Some(ListAll), - Symbol::LIST_FIND => None, - Symbol::DICT_LEN => Some(DictSize), - Symbol::DICT_EMPTY => Some(DictEmpty), - Symbol::DICT_INSERT => Some(DictInsert), - Symbol::DICT_REMOVE => Some(DictRemove), - Symbol::DICT_CONTAINS => Some(DictContains), - Symbol::DICT_GET => None, - Symbol::DICT_KEYS => Some(DictKeys), - Symbol::DICT_VALUES => Some(DictValues), - Symbol::DICT_UNION => Some(DictUnion), - Symbol::DICT_INTERSECTION => Some(DictIntersection), - Symbol::DICT_DIFFERENCE => Some(DictDifference), - Symbol::DICT_WALK => Some(DictWalk), - Symbol::SET_FROM_LIST => Some(SetFromList), - Symbol::NUM_ADD => Some(NumAdd), - Symbol::NUM_ADD_WRAP => Some(NumAddWrap), - Symbol::NUM_ADD_CHECKED => None, - Symbol::NUM_SUB => Some(NumSub), - Symbol::NUM_SUB_WRAP => Some(NumSubWrap), - Symbol::NUM_SUB_CHECKED => None, - Symbol::NUM_MUL => Some(NumMul), - Symbol::NUM_MUL_WRAP => Some(NumMulWrap), - Symbol::NUM_MUL_CHECKED => None, - Symbol::NUM_GT => Some(NumGt), - Symbol::NUM_GTE => Some(NumGte), - Symbol::NUM_LT => Some(NumLt), - Symbol::NUM_LTE => Some(NumLte), - Symbol::NUM_COMPARE => Some(NumCompare), - Symbol::NUM_DIV_FLOAT => None, - Symbol::NUM_DIV_CEIL => None, - Symbol::NUM_REM => None, - Symbol::NUM_IS_MULTIPLE_OF => Some(NumIsMultipleOf), - Symbol::NUM_ABS => Some(NumAbs), - Symbol::NUM_NEG => Some(NumNeg), - Symbol::NUM_SIN => Some(NumSin), - Symbol::NUM_COS => Some(NumCos), - Symbol::NUM_SQRT => None, - Symbol::NUM_LOG => None, - Symbol::NUM_ROUND => Some(NumRound), - Symbol::NUM_TO_FLOAT => Some(NumToFloat), - Symbol::NUM_POW => Some(NumPow), - Symbol::NUM_CEILING => Some(NumCeiling), - Symbol::NUM_POW_INT => Some(NumPowInt), - Symbol::NUM_FLOOR => Some(NumFloor), - Symbol::NUM_TO_STR => Some(NumToStr), - // => Some(NumIsFinite), - Symbol::NUM_ATAN => Some(NumAtan), - Symbol::NUM_ACOS => Some(NumAcos), - Symbol::NUM_ASIN => Some(NumAsin), - Symbol::NUM_BYTES_TO_U16 => None, - Symbol::NUM_BYTES_TO_U32 => None, - Symbol::NUM_BITWISE_AND => Some(NumBitwiseAnd), - Symbol::NUM_BITWISE_XOR => Some(NumBitwiseXor), - Symbol::NUM_BITWISE_OR => Some(NumBitwiseOr), - Symbol::NUM_SHIFT_LEFT => Some(NumShiftLeftBy), - Symbol::NUM_SHIFT_RIGHT => Some(NumShiftRightBy), - Symbol::NUM_SHIFT_RIGHT_ZERO_FILL => Some(NumShiftRightZfBy), - Symbol::NUM_INT_CAST => Some(NumIntCast), - Symbol::BOOL_EQ => Some(Eq), - Symbol::BOOL_NEQ => Some(NotEq), - Symbol::BOOL_AND => Some(And), - Symbol::BOOL_OR => Some(Or), - Symbol::BOOL_NOT => Some(Not), - // => Some(Hash), - // => Some(ExpectTrue), - _ => None, + Symbol::STR_CONCAT => CanBeReplacedBy(StrConcat), + Symbol::STR_JOIN_WITH => CanBeReplacedBy(StrJoinWith), + Symbol::STR_IS_EMPTY => CanBeReplacedBy(StrIsEmpty), + Symbol::STR_STARTS_WITH => CanBeReplacedBy(StrStartsWith), + Symbol::STR_STARTS_WITH_CODE_PT => CanBeReplacedBy(StrStartsWithCodePt), + Symbol::STR_ENDS_WITH => CanBeReplacedBy(StrEndsWith), + Symbol::STR_SPLIT => CanBeReplacedBy(StrSplit), + Symbol::STR_COUNT_GRAPHEMES => CanBeReplacedBy(StrCountGraphemes), + Symbol::STR_FROM_UTF8 => WrapperIsRequired, + Symbol::STR_FROM_UTF8_RANGE => WrapperIsRequired, + Symbol::STR_TO_UTF8 => CanBeReplacedBy(StrToUtf8), + Symbol::STR_REPEAT => CanBeReplacedBy(StrRepeat), + Symbol::STR_TRIM => CanBeReplacedBy(StrTrim), + Symbol::STR_TRIM_LEFT => CanBeReplacedBy(StrTrimLeft), + Symbol::STR_TRIM_RIGHT => CanBeReplacedBy(StrTrimRight), + Symbol::STR_TO_DEC => WrapperIsRequired, + Symbol::STR_TO_F64 => WrapperIsRequired, + Symbol::STR_TO_F32 => WrapperIsRequired, + Symbol::STR_TO_NAT => WrapperIsRequired, + Symbol::STR_TO_U128 => WrapperIsRequired, + Symbol::STR_TO_I128 => WrapperIsRequired, + Symbol::STR_TO_U64 => WrapperIsRequired, + Symbol::STR_TO_I64 => WrapperIsRequired, + Symbol::STR_TO_U32 => WrapperIsRequired, + Symbol::STR_TO_I32 => WrapperIsRequired, + Symbol::STR_TO_U16 => WrapperIsRequired, + Symbol::STR_TO_I16 => WrapperIsRequired, + Symbol::STR_TO_U8 => WrapperIsRequired, + Symbol::STR_TO_I8 => WrapperIsRequired, + Symbol::LIST_LEN => CanBeReplacedBy(ListLen), + Symbol::LIST_GET => WrapperIsRequired, + Symbol::LIST_SET => WrapperIsRequired, + Symbol::LIST_SINGLE => CanBeReplacedBy(ListSingle), + Symbol::LIST_REPEAT => CanBeReplacedBy(ListRepeat), + Symbol::LIST_REVERSE => CanBeReplacedBy(ListReverse), + Symbol::LIST_CONCAT => CanBeReplacedBy(ListConcat), + Symbol::LIST_CONTAINS => CanBeReplacedBy(ListContains), + Symbol::LIST_APPEND => CanBeReplacedBy(ListAppend), + Symbol::LIST_PREPEND => CanBeReplacedBy(ListPrepend), + Symbol::LIST_JOIN => CanBeReplacedBy(ListJoin), + Symbol::LIST_RANGE => CanBeReplacedBy(ListRange), + Symbol::LIST_MAP => CanBeReplacedBy(ListMap), + Symbol::LIST_MAP2 => CanBeReplacedBy(ListMap2), + Symbol::LIST_MAP3 => CanBeReplacedBy(ListMap3), + Symbol::LIST_MAP4 => CanBeReplacedBy(ListMap4), + Symbol::LIST_MAP_WITH_INDEX => CanBeReplacedBy(ListMapWithIndex), + Symbol::LIST_KEEP_IF => CanBeReplacedBy(ListKeepIf), + Symbol::LIST_WALK => CanBeReplacedBy(ListWalk), + Symbol::LIST_WALK_UNTIL => CanBeReplacedBy(ListWalkUntil), + Symbol::LIST_WALK_BACKWARDS => CanBeReplacedBy(ListWalkBackwards), + Symbol::LIST_KEEP_OKS => CanBeReplacedBy(ListKeepOks), + Symbol::LIST_KEEP_ERRS => CanBeReplacedBy(ListKeepErrs), + Symbol::LIST_SORT_WITH => CanBeReplacedBy(ListSortWith), + Symbol::LIST_SUBLIST => CanBeReplacedBy(ListSublist), + Symbol::LIST_DROP_AT => CanBeReplacedBy(ListDropAt), + Symbol::LIST_SWAP => CanBeReplacedBy(ListSwap), + Symbol::LIST_ANY => CanBeReplacedBy(ListAny), + Symbol::LIST_ALL => CanBeReplacedBy(ListAll), + Symbol::LIST_FIND => WrapperIsRequired, + Symbol::DICT_LEN => CanBeReplacedBy(DictSize), + Symbol::DICT_EMPTY => CanBeReplacedBy(DictEmpty), + Symbol::DICT_INSERT => CanBeReplacedBy(DictInsert), + Symbol::DICT_REMOVE => CanBeReplacedBy(DictRemove), + Symbol::DICT_CONTAINS => CanBeReplacedBy(DictContains), + Symbol::DICT_GET => WrapperIsRequired, + Symbol::DICT_KEYS => CanBeReplacedBy(DictKeys), + Symbol::DICT_VALUES => CanBeReplacedBy(DictValues), + Symbol::DICT_UNION => CanBeReplacedBy(DictUnion), + Symbol::DICT_INTERSECTION => CanBeReplacedBy(DictIntersection), + Symbol::DICT_DIFFERENCE => CanBeReplacedBy(DictDifference), + Symbol::DICT_WALK => CanBeReplacedBy(DictWalk), + Symbol::SET_FROM_LIST => CanBeReplacedBy(SetFromList), + Symbol::NUM_ADD => CanBeReplacedBy(NumAdd), + Symbol::NUM_ADD_WRAP => CanBeReplacedBy(NumAddWrap), + Symbol::NUM_ADD_CHECKED => WrapperIsRequired, + Symbol::NUM_SUB => CanBeReplacedBy(NumSub), + Symbol::NUM_SUB_WRAP => CanBeReplacedBy(NumSubWrap), + Symbol::NUM_SUB_CHECKED => WrapperIsRequired, + Symbol::NUM_MUL => CanBeReplacedBy(NumMul), + Symbol::NUM_MUL_WRAP => CanBeReplacedBy(NumMulWrap), + Symbol::NUM_MUL_CHECKED => WrapperIsRequired, + Symbol::NUM_GT => CanBeReplacedBy(NumGt), + Symbol::NUM_GTE => CanBeReplacedBy(NumGte), + Symbol::NUM_LT => CanBeReplacedBy(NumLt), + Symbol::NUM_LTE => CanBeReplacedBy(NumLte), + Symbol::NUM_COMPARE => CanBeReplacedBy(NumCompare), + Symbol::NUM_DIV_FLOAT => WrapperIsRequired, + Symbol::NUM_DIV_CEIL => WrapperIsRequired, + Symbol::NUM_REM => WrapperIsRequired, + Symbol::NUM_IS_MULTIPLE_OF => CanBeReplacedBy(NumIsMultipleOf), + Symbol::NUM_ABS => CanBeReplacedBy(NumAbs), + Symbol::NUM_NEG => CanBeReplacedBy(NumNeg), + Symbol::NUM_SIN => CanBeReplacedBy(NumSin), + Symbol::NUM_COS => CanBeReplacedBy(NumCos), + Symbol::NUM_SQRT => WrapperIsRequired, + Symbol::NUM_LOG => WrapperIsRequired, + Symbol::NUM_ROUND => CanBeReplacedBy(NumRound), + Symbol::NUM_TO_FLOAT => CanBeReplacedBy(NumToFloat), + Symbol::NUM_POW => CanBeReplacedBy(NumPow), + Symbol::NUM_CEILING => CanBeReplacedBy(NumCeiling), + Symbol::NUM_POW_INT => CanBeReplacedBy(NumPowInt), + Symbol::NUM_FLOOR => CanBeReplacedBy(NumFloor), + Symbol::NUM_TO_STR => CanBeReplacedBy(NumToStr), + // => CanBeReplacedBy(NumIsFinite), + Symbol::NUM_ATAN => CanBeReplacedBy(NumAtan), + Symbol::NUM_ACOS => CanBeReplacedBy(NumAcos), + Symbol::NUM_ASIN => CanBeReplacedBy(NumAsin), + Symbol::NUM_BYTES_TO_U16 => WrapperIsRequired, + Symbol::NUM_BYTES_TO_U32 => WrapperIsRequired, + Symbol::NUM_BITWISE_AND => CanBeReplacedBy(NumBitwiseAnd), + Symbol::NUM_BITWISE_XOR => CanBeReplacedBy(NumBitwiseXor), + Symbol::NUM_BITWISE_OR => CanBeReplacedBy(NumBitwiseOr), + Symbol::NUM_SHIFT_LEFT => CanBeReplacedBy(NumShiftLeftBy), + Symbol::NUM_SHIFT_RIGHT => CanBeReplacedBy(NumShiftRightBy), + Symbol::NUM_SHIFT_RIGHT_ZERO_FILL => CanBeReplacedBy(NumShiftRightZfBy), + Symbol::NUM_INT_CAST => CanBeReplacedBy(NumIntCast), + Symbol::BOOL_EQ => CanBeReplacedBy(Eq), + Symbol::BOOL_NEQ => CanBeReplacedBy(NotEq), + Symbol::BOOL_AND => CanBeReplacedBy(And), + Symbol::BOOL_OR => CanBeReplacedBy(Or), + Symbol::BOOL_NOT => CanBeReplacedBy(Not), + // => CanBeReplacedBy(Hash), + // => CanBeReplacedBy(ExpectTrue), + _ => NotALowLevelWrapper, } } } From 9938f7c8320bd9991535a11876926239ef508823 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 27 Dec 2021 20:11:16 -0600 Subject: [PATCH 011/541] Support multiline inputs from non-tty stdins --- cli/tests/repl_eval.rs | 14 ++++++++++++++ cli_utils/src/helpers.rs | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index 63c0ff54a4..4547bebaf9 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -584,6 +584,20 @@ mod repl_eval { ); } + #[test] + fn multiline_input() { + expect_success( + indoc!( + r#" + a : Str + a = "123" + a + "# + ), + r#""123" : Str"#, + ) + } + // #[test] // fn parse_problem() { // // can't find something that won't parse currently diff --git a/cli_utils/src/helpers.rs b/cli_utils/src/helpers.rs index 11a6a404f3..ffd0bcafb9 100644 --- a/cli_utils/src/helpers.rs +++ b/cli_utils/src/helpers.rs @@ -32,7 +32,8 @@ pub fn path_to_roc_binary() -> PathBuf { .or_else(|| { env::current_exe().ok().map(|mut path| { path.pop(); - if path.ends_with("deps") { path.pop(); + if path.ends_with("deps") { + path.pop(); } path }) From 6da9a58b22924f454029c14cd0535bdc664e296b Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 27 Dec 2021 20:12:18 -0600 Subject: [PATCH 012/541] Remove some dead code --- compiler/gen_llvm/src/llvm/convert.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/convert.rs b/compiler/gen_llvm/src/llvm/convert.rs index 7f0faa2b96..1fc8179134 100644 --- a/compiler/gen_llvm/src/llvm/convert.rs +++ b/compiler/gen_llvm/src/llvm/convert.rs @@ -203,22 +203,6 @@ pub fn block_of_memory_slices<'ctx>( block_of_memory_help(context, union_size) } -pub fn union_data_is_struct<'a, 'ctx, 'env>( - env: &crate::llvm::build::Env<'a, 'ctx, 'env>, - layouts: &[Layout<'_>], -) -> StructType<'ctx> { - let data_type = basic_type_from_record(env, layouts); - union_data_is_struct_type(env.context, data_type.into_struct_type()) -} - -pub fn union_data_is_struct_type<'ctx>( - context: &'ctx Context, - struct_type: StructType<'ctx>, -) -> StructType<'ctx> { - let tag_id_type = context.i64_type(); - context.struct_type(&[struct_type.into(), tag_id_type.into()], false) -} - pub fn block_of_memory<'ctx>( context: &'ctx Context, layout: &Layout<'_>, From 3fcc59fb8e5050ebfe14b8d9b3aa3855be3a86b3 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 27 Dec 2021 20:14:29 -0600 Subject: [PATCH 013/541] Improve repl output of nested newtypes --- cli/src/repl/eval.rs | 83 ++++++++++++++++++++++++++++++++++-------- cli/tests/repl_eval.rs | 45 +++++++++++++++++++++++ 2 files changed, 113 insertions(+), 15 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 4979f4bf46..a9c16142bd 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -38,10 +38,10 @@ pub unsafe fn jit_to_ast<'a>( lib: Library, main_fn_name: &str, layout: ProcLayout<'a>, - content: &Content, - interns: &Interns, + content: &'a Content, + interns: &'a Interns, home: ModuleId, - subs: &Subs, + subs: &'a Subs, ptr_bytes: u32, ) -> Result, ToAstProblem> { let env = Env { @@ -64,14 +64,64 @@ pub unsafe fn jit_to_ast<'a>( } } -fn jit_to_ast_help<'a>( +// Unrolls tag unions that are newtypes (i.e. are singleton variants with one type argument). +// This is sometimes important in synchronizing `Content`s with `Layout`s, since `Layout`s will +// always unwrap newtypes and use the content of the underlying type. +fn unroll_newtypes<'a>( + env: &Env<'a, 'a>, + mut content: &'a Content, +) -> (Vec<'a, &'a TagName>, &'a Content) { + let mut newtype_tags = Vec::with_capacity_in(1, env.arena); + loop { + match content { + Content::Structure(FlatType::TagUnion(tags, _)) + if tags.is_newtype_wrapper(env.subs) => + { + let (tag_name, vars): (&TagName, &[Variable]) = tags + .unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION) + .next() + .unwrap(); + newtype_tags.push(tag_name); + let var = vars[0]; + content = env.subs.get_content_without_compacting(var); + } + _ => return (newtype_tags, content), + } + } +} + +fn apply_newtypes<'a>( env: &Env<'a, '_>, + newtype_tags: Vec<'a, &'a TagName>, + mut expr: Expr<'a>, +) -> Expr<'a> { + for tag_name in newtype_tags.into_iter().rev() { + let tag_expr = tag_name_to_expr(env, tag_name); + let loc_tag_expr = &*env.arena.alloc(Loc::at_zero(tag_expr)); + let loc_arg_expr = &*env.arena.alloc(Loc::at_zero(expr)); + let loc_arg_exprs = env.arena.alloc_slice_copy(&[loc_arg_expr]); + expr = Expr::Apply(loc_tag_expr, loc_arg_exprs, CalledVia::Space); + } + expr +} + +fn unroll_aliases<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content { + while let Content::Alias(_, _, real) = content { + content = env.subs.get_content_without_compacting(*real); + } + content +} + +fn jit_to_ast_help<'a>( + env: &Env<'a, 'a>, lib: Library, main_fn_name: &str, layout: &Layout<'a>, - content: &Content, + content: &'a Content, ) -> Result, ToAstProblem> { - match layout { + let (newtype_tags, content) = unroll_newtypes(env, content); + let content = unroll_aliases(env, content); + let result = match layout { Layout::Builtin(Builtin::Bool) => Ok(run_jit_function!(lib, main_fn_name, bool, |num| { bool_to_ast(env, num, content) })), @@ -346,7 +396,8 @@ fn jit_to_ast_help<'a>( &lambda_set.runtime_representation(), content, ), - } + }; + result.map(|e| apply_newtypes(env, newtype_tags, e)) } fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> { @@ -364,10 +415,10 @@ fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> { } fn ptr_to_ast<'a>( - env: &Env<'a, '_>, + env: &Env<'a, 'a>, ptr: *const u8, layout: &Layout<'a>, - content: &Content, + content: &'a Content, ) -> Expr<'a> { macro_rules! helper { ($ty:ty) => {{ @@ -377,7 +428,8 @@ fn ptr_to_ast<'a>( }}; } - match layout { + let (newtype_tags, content) = unroll_newtypes(env, content); + let expr = match layout { Layout::Builtin(Builtin::Bool) => { // TODO: bits are not as expected here. // num is always false at the moment. @@ -452,11 +504,12 @@ fn ptr_to_ast<'a>( other ); } - } + }; + apply_newtypes(env, newtype_tags, expr) } fn list_to_ast<'a>( - env: &Env<'a, '_>, + env: &Env<'a, 'a>, ptr: *const u8, len: usize, elem_layout: &Layout<'a>, @@ -500,7 +553,7 @@ fn list_to_ast<'a>( } fn single_tag_union_to_ast<'a>( - env: &Env<'a, '_>, + env: &Env<'a, 'a>, ptr: *const u8, field_layouts: &'a [Layout<'a>], tag_name: &TagName, @@ -526,7 +579,7 @@ fn single_tag_union_to_ast<'a>( } fn sequence_of_expr<'a, I>( - env: &Env<'a, '_>, + env: &Env<'a, 'a>, ptr: *const u8, sequence: I, ) -> Vec<'a, &'a Loc>> @@ -556,7 +609,7 @@ where } fn struct_to_ast<'a>( - env: &Env<'a, '_>, + env: &Env<'a, 'a>, ptr: *const u8, field_layouts: &'a [Layout<'a>], record_fields: RecordFields, diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index 4547bebaf9..6819473173 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -177,6 +177,51 @@ mod repl_eval { expect_success("Foo Bar", "Foo Bar : [ Foo [ Bar ]* ]*"); } + #[test] + fn newtype_of_big_data() { + expect_success( + indoc!( + r#" + Either a b : [ Left a, Right b ] + lefty : Either Str Str + lefty = Left "loosey" + A lefty + "# + ), + r#"A (Left "loosey") : [ A (Either Str Str) ]*"#, + ) + } + + #[test] + fn newtype_nested() { + expect_success( + indoc!( + r#" + Either a b : [ Left a, Right b ] + lefty : Either Str Str + lefty = Left "loosey" + A (B (C lefty)) + "# + ), + r#"A (B (C (Left "loosey"))) : [ A [ B [ C (Either Str Str) ]* ]* ]*"#, + ) + } + + #[test] + fn newtype_of_big_of_newtype() { + expect_success( + indoc!( + r#" + Big a : [ Big a [ Wrapper [ Newtype a ] ] ] + big : Big Str + big = Big "s" (Wrapper (Newtype "t")) + A big + "# + ), + r#"A (Big "s" (Wrapper (Newtype "t"))) : [ A (Big Str) ]*"#, + ) + } + #[test] fn tag_with_arguments() { expect_success("True 1", "True 1 : [ True (Num *) ]*"); From 71bd77e3b2fd0385d6c659e37f9c75035987a41a Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 27 Dec 2021 20:48:03 -0600 Subject: [PATCH 014/541] Handle flat variants of recursive tag unions in repl --- cli/src/repl/eval.rs | 128 ++++++++++++++++++++++------ cli/tests/repl_eval.rs | 15 ++++ compiler/gen_llvm/src/llvm/build.rs | 21 +++-- 3 files changed, 125 insertions(+), 39 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index a9c16142bd..622c4d84d7 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -2,6 +2,8 @@ use bumpalo::collections::Vec; use bumpalo::Bump; use libloading::Library; use roc_builtins::bitcode::{FloatWidth, IntWidth}; +use roc_collections::all::MutMap; +use roc_gen_llvm::llvm::build::tag_pointer_tag_id_bits_and_mask; use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type}; use roc_module::called_via::CalledVia; use roc_module::ident::TagName; @@ -112,6 +114,44 @@ fn unroll_aliases<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Conten content } +fn get_tags_vars_and_variant<'a>( + env: &Env<'a, '_>, + tags: &UnionTags, + opt_rec_var: Option, +) -> (MutMap>, UnionVariant<'a>) { + let tags_vec: std::vec::Vec<(TagName, std::vec::Vec)> = tags + .unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION) + .map(|(a, b)| (a.clone(), b.to_vec())) + .collect(); + + let vars_of_tag: MutMap<_, _> = tags_vec.iter().cloned().collect(); + + let union_variant = + union_sorted_tags_help(env.arena, tags_vec, opt_rec_var, env.subs, env.ptr_bytes); + + (vars_of_tag, union_variant) +} + +fn expr_of_tag<'a>( + env: &Env<'a, 'a>, + ptr_to_data: *const u8, + tag_name: &TagName, + arg_layouts: &'a [Layout<'a>], + arg_vars: &[Variable], +) -> Expr<'a> { + let tag_expr = tag_name_to_expr(env, tag_name); + let loc_tag_expr = &*env.arena.alloc(Loc::at_zero(tag_expr)); + + debug_assert_eq!(arg_layouts.len(), arg_vars.len()); + + // NOTE assumes the data bytes are the first bytes + let it = arg_vars.iter().copied().zip(arg_layouts.iter()); + let output = sequence_of_expr(env, ptr_to_data, it); + let output = output.into_bump_slice(); + + Expr::Apply(loc_tag_expr, output, CalledVia::Space) +} + fn jit_to_ast_help<'a>( env: &Env<'a, 'a>, lib: Library, @@ -257,16 +297,7 @@ fn jit_to_ast_help<'a>( Content::Structure(FlatType::TagUnion(tags, _)) => { debug_assert_eq!(union_layouts.len(), tags.len()); - let tags_vec: std::vec::Vec<(TagName, std::vec::Vec)> = tags - .unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION) - .map(|(a, b)| (a.clone(), b.to_vec())) - .collect(); - - let tags_map: roc_collections::all::MutMap<_, _> = - tags_vec.iter().cloned().collect(); - - let union_variant = - union_sorted_tags_help(env.arena, tags_vec, None, env.subs, env.ptr_bytes); + let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, None); let size = layout.stack_size(env.ptr_bytes); use roc_mono::layout::WrappedVariant::*; @@ -308,21 +339,13 @@ fn jit_to_ast_help<'a>( let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; - let tag_expr = tag_name_to_expr(env, tag_name); - let loc_tag_expr = - &*env.arena.alloc(Loc::at_zero(tag_expr)); - - let variables = &tags_map[tag_name]; - - debug_assert_eq!(arg_layouts.len(), variables.len()); - - // NOTE assumes the data bytes are the first bytes - let it = - variables.iter().copied().zip(arg_layouts.iter()); - let output = sequence_of_expr(env, ptr, it); - let output = output.into_bump_slice(); - - Expr::Apply(loc_tag_expr, output, CalledVia::Space) + expr_of_tag( + env, + ptr, + tag_name, + arg_layouts, + &vars_of_tag[tag_name], + ) } )) } @@ -345,7 +368,7 @@ fn jit_to_ast_help<'a>( let loc_tag_expr = &*env.arena.alloc(Loc::at_zero(tag_expr)); - let variables = &tags_map[tag_name]; + let variables = &vars_of_tag[tag_name]; // because the arg_layouts include the tag ID, it is one longer debug_assert_eq!( @@ -382,8 +405,57 @@ fn jit_to_ast_help<'a>( other => unreachable!("Weird content for Union layout: {:?}", other), } } - Layout::Union(UnionLayout::Recursive(_)) - | Layout::Union(UnionLayout::NullableWrapped { .. }) + Layout::Union(UnionLayout::Recursive(union_layouts)) => match content { + Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => { + debug_assert_eq!(union_layouts.len(), tags.len()); + + let union_layout = UnionLayout::Recursive(union_layouts); + let (vars_of_tag, union_variant) = + get_tags_vars_and_variant(env, tags, Some(*rec_var)); + + let size = layout.stack_size(env.ptr_bytes); + use roc_mono::layout::WrappedVariant::*; + + match union_variant { + UnionVariant::Wrapped(Recursive { + sorted_tag_layouts: tags_and_layouts, + }) => Ok(run_jit_function_dynamic_type!( + lib, + main_fn_name, + size as usize, + |ptr_to_data_ptr: *const u8| { + let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.ptr_bytes); + let (tag_id, ptr_to_data) = if tag_in_ptr { + let masked_ptr_to_data = *(ptr_to_data_ptr as *const i64); + let (tag_id_bits, tag_id_mask) = + tag_pointer_tag_id_bits_and_mask(env.ptr_bytes); + let tag_id = masked_ptr_to_data & (tag_id_mask as i64); + + // Clear the tag ID data from the pointer + let ptr_to_data = ((masked_ptr_to_data >> tag_id_bits) + << tag_id_bits) + as *const u8; + (tag_id, ptr_to_data) + } else { + todo!() + }; + + let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; + expr_of_tag( + env, + ptr_to_data, + tag_name, + arg_layouts, + &vars_of_tag[tag_name], + ) + } + )), + _ => unreachable!("any other variant would have a different layout"), + } + } + _ => unreachable!("any other layout would have a different content"), + }, + Layout::Union(UnionLayout::NullableWrapped { .. }) | Layout::Union(UnionLayout::NullableUnwrapped { .. }) | Layout::Union(UnionLayout::NonNullableUnwrapped(_)) | Layout::RecursivePointer => { diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index 6819473173..9732f030d6 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -643,6 +643,21 @@ mod repl_eval { ) } + #[test] + fn recursive_tag_union_flat_variant() { + expect_success( + indoc!( + r#" + Expr : [ Sym Str, Add Expr Expr ] + s : Expr + s = Sym "levitating" + s + "# + ), + r#"Sym "levitating" : Expr"#, + ) + } + // #[test] // fn parse_problem() { // // can't find something that won't parse currently diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index dbcd2f7790..cf0b05a369 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -1763,16 +1763,19 @@ fn tag_pointer_set_tag_id<'a, 'ctx, 'env>( .build_int_to_ptr(combined, pointer.get_type(), "to_ptr") } +pub fn tag_pointer_tag_id_bits_and_mask(ptr_bytes: u32) -> (u64, u64) { + match ptr_bytes { + 8 => (3, 0b0000_0111), + 4 => (2, 0b0000_0011), + _ => unreachable!(), + } +} + pub fn tag_pointer_read_tag_id<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, pointer: PointerValue<'ctx>, ) -> IntValue<'ctx> { - let mask: u64 = match env.ptr_bytes { - 8 => 0b0000_0111, - 4 => 0b0000_0011, - _ => unreachable!(), - }; - + let (_, mask) = tag_pointer_tag_id_bits_and_mask(env.ptr_bytes); let ptr_int = env.ptr_int(); let as_int = env.builder.build_ptr_to_int(pointer, ptr_int, "to_int"); @@ -1790,11 +1793,7 @@ pub fn tag_pointer_clear_tag_id<'a, 'ctx, 'env>( ) -> PointerValue<'ctx> { let ptr_int = env.ptr_int(); - let tag_id_bits_mask = match env.ptr_bytes { - 8 => 3, - 4 => 2, - _ => unreachable!(), - }; + let (tag_id_bits_mask, _) = tag_pointer_tag_id_bits_and_mask(env.ptr_bytes); let as_int = env.builder.build_ptr_to_int(pointer, ptr_int, "to_int"); From a9ae8aea22050aec36029e122ca68a9e08906457 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 27 Dec 2021 21:08:58 -0600 Subject: [PATCH 015/541] Retrieve recursive tag union IDs when tag is stored next to data --- cli/src/repl/eval.rs | 50 +++++++++++++++++++++++------------------- cli/tests/repl_eval.rs | 16 ++++++++++++++ 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 622c4d84d7..decde5d5ef 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -152,6 +152,26 @@ fn expr_of_tag<'a>( Expr::Apply(loc_tag_expr, output, CalledVia::Space) } +/// Gets the tag ID of a union variant, assuming that the tag ID is stored alongside (after) the +/// tag data. The caller is expected to check that the tag ID is indeed stored this way. +fn tag_id_from_data(union_layout: UnionLayout, data_ptr: *const u8, ptr_bytes: u32) -> i64 { + let offset = union_layout.data_size_without_tag_id(ptr_bytes).unwrap(); + + unsafe { + match union_layout.tag_id_builtin() { + Builtin::Bool => *(data_ptr.add(offset as usize) as *const i8) as i64, + Builtin::Int(IntWidth::U8) => *(data_ptr.add(offset as usize) as *const i8) as i64, + Builtin::Int(IntWidth::U16) => *(data_ptr.add(offset as usize) as *const i16) as i64, + Builtin::Int(IntWidth::U64) => { + // used by non-recursive unions at the + // moment, remove if that is no longer the case + *(data_ptr.add(offset as usize) as *const i64) as i64 + } + _ => unreachable!("invalid tag id layout"), + } + } +} + fn jit_to_ast_help<'a>( env: &Env<'a, 'a>, lib: Library, @@ -313,27 +333,8 @@ fn jit_to_ast_help<'a>( size as usize, |ptr: *const u8| { // Because this is a `NonRecursive`, the tag ID is definitely after the data. - let offset = union_layout - .data_size_without_tag_id(env.ptr_bytes) - .unwrap(); - - let tag_id = match union_layout.tag_id_builtin() { - Builtin::Bool => { - *(ptr.add(offset as usize) as *const i8) as i64 - } - Builtin::Int(IntWidth::U8) => { - *(ptr.add(offset as usize) as *const i8) as i64 - } - Builtin::Int(IntWidth::U16) => { - *(ptr.add(offset as usize) as *const i16) as i64 - } - Builtin::Int(IntWidth::U64) => { - // used by non-recursive unions at the - // moment, remove if that is no longer the case - *(ptr.add(offset as usize) as *const i64) as i64 - } - _ => unreachable!("invalid tag id layout"), - }; + let tag_id = + tag_id_from_data(union_layout, ptr, env.ptr_bytes); // use the tag ID as an index, to get its name and layout of any arguments let (tag_name, arg_layouts) = @@ -426,6 +427,7 @@ fn jit_to_ast_help<'a>( |ptr_to_data_ptr: *const u8| { let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.ptr_bytes); let (tag_id, ptr_to_data) = if tag_in_ptr { + // TODO we should cast to a pointer the size of env.ptr_bytes let masked_ptr_to_data = *(ptr_to_data_ptr as *const i64); let (tag_id_bits, tag_id_mask) = tag_pointer_tag_id_bits_and_mask(env.ptr_bytes); @@ -437,7 +439,11 @@ fn jit_to_ast_help<'a>( as *const u8; (tag_id, ptr_to_data) } else { - todo!() + // TODO we should cast to a pointer the size of env.ptr_bytes + let ptr_to_data = *(ptr_to_data_ptr as *const i64) as *const u8; + let tag_id = + tag_id_from_data(union_layout, ptr_to_data, env.ptr_bytes); + (tag_id, ptr_to_data) }; let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index 9732f030d6..21555ee0bb 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -658,6 +658,22 @@ mod repl_eval { ) } + #[test] + fn large_recursive_tag_union_flat_variant() { + expect_success( + // > 7 variants so that to force tag storage alongside the data + indoc!( + r#" + Item : [ A Str, B Str, C Str, D Str, E Str, F Str, G Str, H Str, I Str, J Str, K Item ] + s : Item + s = H "woo" + s + "# + ), + r#"H "woo" : Item"#, + ) + } + // #[test] // fn parse_problem() { // // can't find something that won't parse currently From 2c25bac6958b10e7551040002adadafda1cdccb4 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 27 Dec 2021 21:51:07 -0600 Subject: [PATCH 016/541] Support rendering general "regular" recursive tag unions in REPL --- cli/src/repl/eval.rs | 177 ++++++++++++++++++++++++++--------------- cli/tests/repl_eval.rs | 31 ++++++++ 2 files changed, 146 insertions(+), 62 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index decde5d5ef..fe2ab8db12 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -138,6 +138,7 @@ fn expr_of_tag<'a>( tag_name: &TagName, arg_layouts: &'a [Layout<'a>], arg_vars: &[Variable], + when_recursive: WhenRecursive<'a>, ) -> Expr<'a> { let tag_expr = tag_name_to_expr(env, tag_name); let loc_tag_expr = &*env.arena.alloc(Loc::at_zero(tag_expr)); @@ -146,7 +147,7 @@ fn expr_of_tag<'a>( // NOTE assumes the data bytes are the first bytes let it = arg_vars.iter().copied().zip(arg_layouts.iter()); - let output = sequence_of_expr(env, ptr_to_data, it); + let output = sequence_of_expr(env, ptr_to_data, it, when_recursive); let output = output.into_bump_slice(); Expr::Apply(loc_tag_expr, output, CalledVia::Space) @@ -346,6 +347,7 @@ fn jit_to_ast_help<'a>( tag_name, arg_layouts, &vars_of_tag[tag_name], + WhenRecursive::Unreachable, ) } )) @@ -382,7 +384,12 @@ fn jit_to_ast_help<'a>( let it = variables.iter().copied().zip(&arg_layouts[1..]); - let output = sequence_of_expr(env, ptr, it); + let output = sequence_of_expr( + env, + ptr, + it, + WhenRecursive::Loop(Layout::Union(union_layout)), + ); let output = output.into_bump_slice(); Expr::Apply(loc_tag_expr, output, CalledVia::Space) @@ -406,61 +413,17 @@ fn jit_to_ast_help<'a>( other => unreachable!("Weird content for Union layout: {:?}", other), } } - Layout::Union(UnionLayout::Recursive(union_layouts)) => match content { - Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => { - debug_assert_eq!(union_layouts.len(), tags.len()); - - let union_layout = UnionLayout::Recursive(union_layouts); - let (vars_of_tag, union_variant) = - get_tags_vars_and_variant(env, tags, Some(*rec_var)); - - let size = layout.stack_size(env.ptr_bytes); - use roc_mono::layout::WrappedVariant::*; - - match union_variant { - UnionVariant::Wrapped(Recursive { - sorted_tag_layouts: tags_and_layouts, - }) => Ok(run_jit_function_dynamic_type!( - lib, - main_fn_name, - size as usize, - |ptr_to_data_ptr: *const u8| { - let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.ptr_bytes); - let (tag_id, ptr_to_data) = if tag_in_ptr { - // TODO we should cast to a pointer the size of env.ptr_bytes - let masked_ptr_to_data = *(ptr_to_data_ptr as *const i64); - let (tag_id_bits, tag_id_mask) = - tag_pointer_tag_id_bits_and_mask(env.ptr_bytes); - let tag_id = masked_ptr_to_data & (tag_id_mask as i64); - - // Clear the tag ID data from the pointer - let ptr_to_data = ((masked_ptr_to_data >> tag_id_bits) - << tag_id_bits) - as *const u8; - (tag_id, ptr_to_data) - } else { - // TODO we should cast to a pointer the size of env.ptr_bytes - let ptr_to_data = *(ptr_to_data_ptr as *const i64) as *const u8; - let tag_id = - tag_id_from_data(union_layout, ptr_to_data, env.ptr_bytes); - (tag_id, ptr_to_data) - }; - - let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; - expr_of_tag( - env, - ptr_to_data, - tag_name, - arg_layouts, - &vars_of_tag[tag_name], - ) - } - )), - _ => unreachable!("any other variant would have a different layout"), + Layout::Union(UnionLayout::Recursive(_)) => { + let size = layout.stack_size(env.ptr_bytes); + Ok(run_jit_function_dynamic_type!( + lib, + main_fn_name, + size as usize, + |ptr: *const u8| { + ptr_to_ast(env, ptr, layout, WhenRecursive::Loop(*layout), content) } - } - _ => unreachable!("any other layout would have a different content"), - }, + )) + } Layout::Union(UnionLayout::NullableWrapped { .. }) | Layout::Union(UnionLayout::NullableUnwrapped { .. }) | Layout::Union(UnionLayout::NonNullableUnwrapped(_)) @@ -492,10 +455,19 @@ fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> { } } +/// Represents the layout of `RecursivePointer`s in a tag union, when recursive +/// tag unions are relevant. +#[derive(Clone, Copy, Debug, PartialEq)] +enum WhenRecursive<'a> { + Unreachable, + Loop(Layout<'a>), +} + fn ptr_to_ast<'a>( env: &Env<'a, 'a>, ptr: *const u8, layout: &Layout<'a>, + when_recursive: WhenRecursive<'a>, content: &'a Content, ) -> Expr<'a> { macro_rules! helper { @@ -576,6 +548,68 @@ fn ptr_to_ast<'a>( ); } }, + Layout::RecursivePointer => { + match (content, when_recursive) { + (Content::RecursionVar { + structure, + opt_name: _, + }, WhenRecursive::Loop(union_layout)) => { + let content = env.subs.get_content_without_compacting(*structure); + ptr_to_ast(env, ptr, &union_layout, when_recursive, content) + } + other => unreachable!("Something had a RecursivePointer layout, but instead of being a RecursionVar and having a known recursive layout, I found {:?}", other), + } + } + Layout::Union(UnionLayout::Recursive(union_layouts)) => match content { + Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => { + debug_assert_eq!(union_layouts.len(), tags.len()); + + let union_layout = UnionLayout::Recursive(union_layouts); + let (vars_of_tag, union_variant) = + get_tags_vars_and_variant(env, tags, Some(*rec_var)); + + use roc_mono::layout::WrappedVariant::*; + + match union_variant { + UnionVariant::Wrapped(Recursive { + sorted_tag_layouts: tags_and_layouts, + }) => { + let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.ptr_bytes); + let (tag_id, ptr_to_data) = if tag_in_ptr { + // TODO we should cast to a pointer the size of env.ptr_bytes + let masked_ptr_to_data = unsafe { *(ptr as *const i64) }; + let (tag_id_bits, tag_id_mask) = + tag_pointer_tag_id_bits_and_mask(env.ptr_bytes); + let tag_id = masked_ptr_to_data & (tag_id_mask as i64); + + // Clear the tag ID data from the pointer + let ptr_to_data = ((masked_ptr_to_data >> tag_id_bits) + << tag_id_bits) + as *const u8; + (tag_id, ptr_to_data) + } else { + // TODO we should cast to a pointer the size of env.ptr_bytes + let ptr_to_data = unsafe { *(ptr as *const i64) as *const u8 }; + let tag_id = + tag_id_from_data(union_layout, ptr_to_data, env.ptr_bytes); + (tag_id, ptr_to_data) + }; + + let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; + expr_of_tag( + env, + ptr_to_data, + tag_name, + arg_layouts, + &vars_of_tag[tag_name], + when_recursive, + ) + } + _ => unreachable!("any other variant would have a different layout"), + } + } + _ => unreachable!("any other layout would have a different content"), + } other => { todo!( "TODO add support for rendering pointer to {:?} in the REPL", @@ -618,7 +652,13 @@ fn list_to_ast<'a>( let offset_bytes = index * elem_size; let elem_ptr = unsafe { ptr.add(offset_bytes) }; let loc_expr = &*arena.alloc(Loc { - value: ptr_to_ast(env, elem_ptr, elem_layout, elem_content), + value: ptr_to_ast( + env, + elem_ptr, + elem_layout, + WhenRecursive::Unreachable, + elem_content, + ), region: Region::zero(), }); @@ -644,11 +684,11 @@ fn single_tag_union_to_ast<'a>( let output = if field_layouts.len() == payload_vars.len() { let it = payload_vars.iter().copied().zip(field_layouts); - sequence_of_expr(env, ptr as *const u8, it).into_bump_slice() + sequence_of_expr(env, ptr as *const u8, it, WhenRecursive::Unreachable).into_bump_slice() } else if field_layouts.is_empty() && !payload_vars.is_empty() { // happens for e.g. `Foo Bar` where unit structures are nested and the inner one is dropped let it = payload_vars.iter().copied().zip([&Layout::Struct(&[])]); - sequence_of_expr(env, ptr as *const u8, it).into_bump_slice() + sequence_of_expr(env, ptr as *const u8, it, WhenRecursive::Unreachable).into_bump_slice() } else { unreachable!() }; @@ -660,6 +700,7 @@ fn sequence_of_expr<'a, I>( env: &Env<'a, 'a>, ptr: *const u8, sequence: I, + when_recursive: WhenRecursive<'a>, ) -> Vec<'a, &'a Loc>> where I: Iterator)>, @@ -674,7 +715,7 @@ where for (var, layout) in sequence { let content = subs.get_content_without_compacting(var); - let expr = ptr_to_ast(env, field_ptr, layout, content); + let expr = ptr_to_ast(env, field_ptr, layout, when_recursive, content); let loc_expr = Loc::at_zero(expr); output.push(&*arena.alloc(loc_expr)); @@ -708,7 +749,13 @@ fn struct_to_ast<'a>( let inner_content = env.subs.get_content_without_compacting(field.into_inner()); let loc_expr = &*arena.alloc(Loc { - value: ptr_to_ast(env, ptr, &Layout::Struct(field_layouts), inner_content), + value: ptr_to_ast( + env, + ptr, + &Layout::Struct(field_layouts), + WhenRecursive::Unreachable, + inner_content, + ), region: Region::zero(), }); @@ -735,7 +782,13 @@ fn struct_to_ast<'a>( let content = subs.get_content_without_compacting(var); let loc_expr = &*arena.alloc(Loc { - value: ptr_to_ast(env, field_ptr, field_layout, content), + value: ptr_to_ast( + env, + field_ptr, + field_layout, + WhenRecursive::Unreachable, + content, + ), region: Region::zero(), }); diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index 21555ee0bb..2ee8ea2e3f 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -674,6 +674,37 @@ mod repl_eval { ) } + #[test] + fn recursive_tag_union_recursive_variant() { + expect_success( + indoc!( + r#" + Expr : [ Sym Str, Add Expr Expr ] + s : Expr + s = Add (Add (Sym "one") (Sym "two")) (Sym "four") + s + "# + ), + r#"Add (Add (Sym "one") (Sym "two")) (Sym "four") : Expr"#, + ) + } + + #[test] + fn large_recursive_tag_union_recursive_variant() { + expect_success( + // > 7 variants so that to force tag storage alongside the data + indoc!( + r#" + Item : [ A Str, B Str, C Str, D Str, E Str, F Str, G Str, H Str, I Str, J Str, K Item, L Item ] + s : Item + s = K (L (E "woo")) + s + "# + ), + r#"K (L (E "woo")) : Item"#, + ) + } + // #[test] // fn parse_problem() { // // can't find something that won't parse currently From f52cca40b5b997d8ff99528d63c8127620652a8b Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 27 Dec 2021 23:01:56 -0600 Subject: [PATCH 017/541] Dereference small (non recursive) tag unions before storing them elsewhere Closes #2290 --- compiler/gen_llvm/src/llvm/build.rs | 12 ++++++++++++ compiler/test_gen/src/gen_tags.rs | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index cf0b05a369..94f3ea89ec 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -1394,6 +1394,18 @@ fn build_wrapped_tag<'a, 'ctx, 'env>( ); field_vals.push(ptr); + } else if matches!( + tag_field_layout, + Layout::Union(UnionLayout::NonRecursive(_)) + ) { + debug_assert!(val.is_pointer_value()); + + // We store non-recursive unions without any indirection. + let reified = env + .builder + .build_load(val.into_pointer_value(), "load_non_recursive"); + + field_vals.push(reified); } else { // this check fails for recursive tag unions, but can be helpful while debugging // debug_assert_eq!(tag_field_layout, val_layout); diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index b4dc57e4dd..f396534be8 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -1243,3 +1243,22 @@ fn tag_must_be_its_own_type() { i64 ); } + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn recursive_tag_union_into_flat_tag_union() { + // Comprehensive test for correctness in cli/tests/repl_eval + assert_evals_to!( + indoc!( + r#" + Item : [ Shallow [ L Str, R Str ], Deep Item ] + i : Item + i = Deep (Shallow (R "woo")) + i + "# + ), + 0, + usize, + |_| 0 + ) +} From b7c9713232b13a5471d3e20778f64fc4c8ecb3cd Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Tue, 28 Dec 2021 01:09:55 -0600 Subject: [PATCH 018/541] Support nested non-recursive unions in REPL eval --- cli/src/repl/eval.rs | 192 ++++++++++++++++++++--------------------- cli/tests/repl_eval.rs | 15 ++++ 2 files changed, 107 insertions(+), 100 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index fe2ab8db12..e00dec9a54 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -311,107 +311,16 @@ fn jit_to_ast_help<'a>( |bytes: *const u8| { ptr_to_ast(bytes as *const u8) } ) } - Layout::Union(UnionLayout::NonRecursive(union_layouts)) => { - let union_layout = UnionLayout::NonRecursive(union_layouts); - - match content { - Content::Structure(FlatType::TagUnion(tags, _)) => { - debug_assert_eq!(union_layouts.len(), tags.len()); - - let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, None); - - let size = layout.stack_size(env.ptr_bytes); - use roc_mono::layout::WrappedVariant::*; - match union_variant { - UnionVariant::Wrapped(variant) => { - match variant { - NonRecursive { - sorted_tag_layouts: tags_and_layouts, - } => { - Ok(run_jit_function_dynamic_type!( - lib, - main_fn_name, - size as usize, - |ptr: *const u8| { - // Because this is a `NonRecursive`, the tag ID is definitely after the data. - let tag_id = - tag_id_from_data(union_layout, ptr, env.ptr_bytes); - - // use the tag ID as an index, to get its name and layout of any arguments - let (tag_name, arg_layouts) = - &tags_and_layouts[tag_id as usize]; - - expr_of_tag( - env, - ptr, - tag_name, - arg_layouts, - &vars_of_tag[tag_name], - WhenRecursive::Unreachable, - ) - } - )) - } - Recursive { - sorted_tag_layouts: tags_and_layouts, - } => { - Ok(run_jit_function_dynamic_type!( - lib, - main_fn_name, - size as usize, - |ptr: *const u8| { - // Because this is a `Wrapped`, the first 8 bytes encode the tag ID - let tag_id = *(ptr as *const i64); - - // use the tag ID as an index, to get its name and layout of any arguments - let (tag_name, arg_layouts) = - &tags_and_layouts[tag_id as usize]; - - let tag_expr = tag_name_to_expr(env, tag_name); - let loc_tag_expr = - &*env.arena.alloc(Loc::at_zero(tag_expr)); - - let variables = &vars_of_tag[tag_name]; - - // because the arg_layouts include the tag ID, it is one longer - debug_assert_eq!( - arg_layouts.len() - 1, - variables.len() - ); - - // skip forward to the start of the first element, ignoring the tag id - let ptr = ptr.offset(8); - - let it = - variables.iter().copied().zip(&arg_layouts[1..]); - let output = sequence_of_expr( - env, - ptr, - it, - WhenRecursive::Loop(Layout::Union(union_layout)), - ); - let output = output.into_bump_slice(); - - Expr::Apply(loc_tag_expr, output, CalledVia::Space) - } - )) - } - _ => todo!(), - } - } - _ => unreachable!("any other variant would have a different layout"), - } + Layout::Union(UnionLayout::NonRecursive(_)) => { + let size = layout.stack_size(env.ptr_bytes); + Ok(run_jit_function_dynamic_type!( + lib, + main_fn_name, + size as usize, + |ptr: *const u8| { + ptr_to_ast(env, ptr, layout, WhenRecursive::Unreachable, content) } - Content::Structure(FlatType::RecursiveTagUnion(_, _, _)) => { - todo!("print recursive tag unions in the REPL") - } - Content::Alias(_, _, actual) => { - let content = env.subs.get_content_without_compacting(*actual); - - jit_to_ast_help(env, lib, main_fn_name, layout, content) - } - other => unreachable!("Weird content for Union layout: {:?}", other), - } + )) } Layout::Union(UnionLayout::Recursive(_)) => { let size = layout.stack_size(env.ptr_bytes); @@ -479,6 +388,7 @@ fn ptr_to_ast<'a>( } let (newtype_tags, content) = unroll_newtypes(env, content); + let content = unroll_aliases(env, content); let expr = match layout { Layout::Builtin(Builtin::Bool) => { // TODO: bits are not as expected here. @@ -560,6 +470,88 @@ fn ptr_to_ast<'a>( other => unreachable!("Something had a RecursivePointer layout, but instead of being a RecursionVar and having a known recursive layout, I found {:?}", other), } } + Layout::Union(UnionLayout::NonRecursive(union_layouts)) => { + let union_layout = UnionLayout::NonRecursive(union_layouts); + + match content { + Content::Structure(FlatType::TagUnion(tags, _)) => { + debug_assert_eq!(union_layouts.len(), tags.len()); + + let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, None); + + use roc_mono::layout::WrappedVariant::*; + match union_variant { + UnionVariant::Wrapped(variant) => { + match variant { + NonRecursive { + sorted_tag_layouts: tags_and_layouts, + } => { + // Because this is a `NonRecursive`, the tag ID is definitely after the data. + let tag_id = + tag_id_from_data(union_layout, ptr, env.ptr_bytes); + + // use the tag ID as an index, to get its name and layout of any arguments + let (tag_name, arg_layouts) = + &tags_and_layouts[tag_id as usize]; + + expr_of_tag( + env, + ptr, + tag_name, + arg_layouts, + &vars_of_tag[tag_name], + WhenRecursive::Unreachable, + ) + } + Recursive { + sorted_tag_layouts: tags_and_layouts, + } => { + // Because this is a `Wrapped`, the first 8 bytes encode the tag ID + let tag_id = unsafe { *(ptr as *const i64) }; + + // use the tag ID as an index, to get its name and layout of any arguments + let (tag_name, arg_layouts) = + &tags_and_layouts[tag_id as usize]; + + let tag_expr = tag_name_to_expr(env, tag_name); + let loc_tag_expr = + &*env.arena.alloc(Loc::at_zero(tag_expr)); + + let variables = &vars_of_tag[tag_name]; + + // because the arg_layouts include the tag ID, it is one longer + debug_assert_eq!( + arg_layouts.len() - 1, + variables.len() + ); + + // skip forward to the start of the first element, ignoring the tag id + let ptr = unsafe { ptr.offset(8) }; + + let it = + variables.iter().copied().zip(&arg_layouts[1..]); + let output = sequence_of_expr( + env, + ptr, + it, + WhenRecursive::Loop(Layout::Union(union_layout)), + ); + let output = output.into_bump_slice(); + + Expr::Apply(loc_tag_expr, output, CalledVia::Space) + } + _ => todo!(), + } + } + _ => unreachable!("any other variant would have a different layout"), + } + } + Content::Structure(FlatType::RecursiveTagUnion(_, _, _)) => { + todo!("print recursive tag unions in the REPL") + } + other => unreachable!("Weird content for Union layout: {:?}", other), + } + } Layout::Union(UnionLayout::Recursive(union_layouts)) => match content { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => { debug_assert_eq!(union_layouts.len(), tags.len()); diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index 2ee8ea2e3f..ed31e9bec3 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -705,6 +705,21 @@ mod repl_eval { ) } + #[test] + fn recursive_tag_union_into_flat_tag_union() { + expect_success( + indoc!( + r#" + Item : [ One [ A Str, B Str ], Deep Item ] + i : Item + i = Deep (One (A "woo")) + i + "# + ), + r#"Deep (One (A "woo")) : Item"#, + ) + } + // #[test] // fn parse_problem() { // // can't find something that won't parse currently From e3798dd81fdeeb698b2f1991b40229b15738e4f3 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Tue, 28 Dec 2021 01:17:01 -0600 Subject: [PATCH 019/541] Remove some unreachable code There's no need to hand recursive union variants for non recursive tag unions --- cli/src/repl/eval.rs | 44 ++------------------------------------------ 1 file changed, 2 insertions(+), 42 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index e00dec9a54..9133ddc13f 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -503,53 +503,13 @@ fn ptr_to_ast<'a>( WhenRecursive::Unreachable, ) } - Recursive { - sorted_tag_layouts: tags_and_layouts, - } => { - // Because this is a `Wrapped`, the first 8 bytes encode the tag ID - let tag_id = unsafe { *(ptr as *const i64) }; - - // use the tag ID as an index, to get its name and layout of any arguments - let (tag_name, arg_layouts) = - &tags_and_layouts[tag_id as usize]; - - let tag_expr = tag_name_to_expr(env, tag_name); - let loc_tag_expr = - &*env.arena.alloc(Loc::at_zero(tag_expr)); - - let variables = &vars_of_tag[tag_name]; - - // because the arg_layouts include the tag ID, it is one longer - debug_assert_eq!( - arg_layouts.len() - 1, - variables.len() - ); - - // skip forward to the start of the first element, ignoring the tag id - let ptr = unsafe { ptr.offset(8) }; - - let it = - variables.iter().copied().zip(&arg_layouts[1..]); - let output = sequence_of_expr( - env, - ptr, - it, - WhenRecursive::Loop(Layout::Union(union_layout)), - ); - let output = output.into_bump_slice(); - - Expr::Apply(loc_tag_expr, output, CalledVia::Space) - } - _ => todo!(), + other => unreachable!("This layout tag union layout is nonrecursive but the variant isn't; found variant {:?}", other), } } _ => unreachable!("any other variant would have a different layout"), } } - Content::Structure(FlatType::RecursiveTagUnion(_, _, _)) => { - todo!("print recursive tag unions in the REPL") - } - other => unreachable!("Weird content for Union layout: {:?}", other), + other => unreachable!("Weird content for nonrecursive Union layout: {:?}", other), } } Layout::Union(UnionLayout::Recursive(union_layouts)) => match content { From 337c2901d2e4ec3066103065f332a23537b6c5a0 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Tue, 28 Dec 2021 01:26:07 -0600 Subject: [PATCH 020/541] Simplify some nested match expressions --- cli/src/repl/eval.rs | 159 +++++++++++++++++++++---------------------- 1 file changed, 77 insertions(+), 82 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 9133ddc13f..308de7a962 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -9,7 +9,9 @@ use roc_module::called_via::CalledVia; use roc_module::ident::TagName; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_mono::ir::ProcLayout; -use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant}; +use roc_mono::layout::{ + union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant, WrappedVariant, +}; use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral}; use roc_region::all::{Loc, Region}; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; @@ -473,94 +475,87 @@ fn ptr_to_ast<'a>( Layout::Union(UnionLayout::NonRecursive(union_layouts)) => { let union_layout = UnionLayout::NonRecursive(union_layouts); - match content { - Content::Structure(FlatType::TagUnion(tags, _)) => { - debug_assert_eq!(union_layouts.len(), tags.len()); - - let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, None); - - use roc_mono::layout::WrappedVariant::*; - match union_variant { - UnionVariant::Wrapped(variant) => { - match variant { - NonRecursive { - sorted_tag_layouts: tags_and_layouts, - } => { - // Because this is a `NonRecursive`, the tag ID is definitely after the data. - let tag_id = - tag_id_from_data(union_layout, ptr, env.ptr_bytes); - - // use the tag ID as an index, to get its name and layout of any arguments - let (tag_name, arg_layouts) = - &tags_and_layouts[tag_id as usize]; - - expr_of_tag( - env, - ptr, - tag_name, - arg_layouts, - &vars_of_tag[tag_name], - WhenRecursive::Unreachable, - ) - } - other => unreachable!("This layout tag union layout is nonrecursive but the variant isn't; found variant {:?}", other), - } - } - _ => unreachable!("any other variant would have a different layout"), - } - } + let tags = match content { + Content::Structure(FlatType::TagUnion(tags, _)) => tags, other => unreachable!("Weird content for nonrecursive Union layout: {:?}", other), - } + }; + + debug_assert_eq!(union_layouts.len(), tags.len()); + + let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, None); + + let tags_and_layouts = match union_variant { + UnionVariant::Wrapped(WrappedVariant::NonRecursive { + sorted_tag_layouts + }) => sorted_tag_layouts, + other => unreachable!("This layout tag union layout is nonrecursive but the variant isn't; found variant {:?}", other), + }; + + // Because this is a `NonRecursive`, the tag ID is definitely after the data. + let tag_id = + tag_id_from_data(union_layout, ptr, env.ptr_bytes); + + // use the tag ID as an index, to get its name and layout of any arguments + let (tag_name, arg_layouts) = + &tags_and_layouts[tag_id as usize]; + + expr_of_tag( + env, + ptr, + tag_name, + arg_layouts, + &vars_of_tag[tag_name], + WhenRecursive::Unreachable, + ) } - Layout::Union(UnionLayout::Recursive(union_layouts)) => match content { - Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => { - debug_assert_eq!(union_layouts.len(), tags.len()); + Layout::Union(UnionLayout::Recursive(union_layouts)) => { + let (rec_var, tags) = match content { + Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), + _ => unreachable!("any other content would have a different layout"), + }; + debug_assert_eq!(union_layouts.len(), tags.len()); - let union_layout = UnionLayout::Recursive(union_layouts); - let (vars_of_tag, union_variant) = - get_tags_vars_and_variant(env, tags, Some(*rec_var)); + let union_layout = UnionLayout::Recursive(union_layouts); + let (vars_of_tag, union_variant) = + get_tags_vars_and_variant(env, tags, Some(*rec_var)); - use roc_mono::layout::WrappedVariant::*; + let tags_and_layouts = match union_variant { + UnionVariant::Wrapped(WrappedVariant::Recursive { + sorted_tag_layouts + }) => sorted_tag_layouts, + _ => unreachable!("any other variant would have a different layout"), + }; - match union_variant { - UnionVariant::Wrapped(Recursive { - sorted_tag_layouts: tags_and_layouts, - }) => { - let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.ptr_bytes); - let (tag_id, ptr_to_data) = if tag_in_ptr { - // TODO we should cast to a pointer the size of env.ptr_bytes - let masked_ptr_to_data = unsafe { *(ptr as *const i64) }; - let (tag_id_bits, tag_id_mask) = - tag_pointer_tag_id_bits_and_mask(env.ptr_bytes); - let tag_id = masked_ptr_to_data & (tag_id_mask as i64); + let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.ptr_bytes); + let (tag_id, ptr_to_data) = if tag_in_ptr { + // TODO we should cast to a pointer the size of env.ptr_bytes + let masked_ptr_to_data = unsafe { *(ptr as *const i64) }; + let (tag_id_bits, tag_id_mask) = + tag_pointer_tag_id_bits_and_mask(env.ptr_bytes); + let tag_id = masked_ptr_to_data & (tag_id_mask as i64); - // Clear the tag ID data from the pointer - let ptr_to_data = ((masked_ptr_to_data >> tag_id_bits) - << tag_id_bits) - as *const u8; - (tag_id, ptr_to_data) - } else { - // TODO we should cast to a pointer the size of env.ptr_bytes - let ptr_to_data = unsafe { *(ptr as *const i64) as *const u8 }; - let tag_id = - tag_id_from_data(union_layout, ptr_to_data, env.ptr_bytes); - (tag_id, ptr_to_data) - }; + // Clear the tag ID data from the pointer + let ptr_to_data = ((masked_ptr_to_data >> tag_id_bits) + << tag_id_bits) + as *const u8; + (tag_id, ptr_to_data) + } else { + // TODO we should cast to a pointer the size of env.ptr_bytes + let ptr_to_data = unsafe { *(ptr as *const i64) as *const u8 }; + let tag_id = + tag_id_from_data(union_layout, ptr_to_data, env.ptr_bytes); + (tag_id, ptr_to_data) + }; - let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; - expr_of_tag( - env, - ptr_to_data, - tag_name, - arg_layouts, - &vars_of_tag[tag_name], - when_recursive, - ) - } - _ => unreachable!("any other variant would have a different layout"), - } - } - _ => unreachable!("any other layout would have a different content"), + let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; + expr_of_tag( + env, + ptr_to_data, + tag_name, + arg_layouts, + &vars_of_tag[tag_name], + when_recursive, + ) } other => { todo!( From 9c147c8b70e493a929b067de857fcbf80bc9fe0c Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Tue, 28 Dec 2021 14:38:10 -0600 Subject: [PATCH 021/541] Support non-nullable unwrapped unions in repl --- cli/src/repl/eval.rs | 38 ++++++++++++++++++++++++++++++++++++-- cli/tests/repl_eval.rs | 19 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 308de7a962..4ba13cdcf4 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -116,6 +116,13 @@ fn unroll_aliases<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Conten content } +fn unroll_recursion_var<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content { + while let Content::RecursionVar { structure, .. } = content { + content = env.subs.get_content_without_compacting(*structure); + } + content +} + fn get_tags_vars_and_variant<'a>( env: &Env<'a, '_>, tags: &UnionTags, @@ -324,7 +331,8 @@ fn jit_to_ast_help<'a>( } )) } - Layout::Union(UnionLayout::Recursive(_)) => { + Layout::Union(UnionLayout::Recursive(_)) + | Layout::Union(UnionLayout::NonNullableUnwrapped(_)) => { let size = layout.stack_size(env.ptr_bytes); Ok(run_jit_function_dynamic_type!( lib, @@ -337,7 +345,6 @@ fn jit_to_ast_help<'a>( } Layout::Union(UnionLayout::NullableWrapped { .. }) | Layout::Union(UnionLayout::NullableUnwrapped { .. }) - | Layout::Union(UnionLayout::NonNullableUnwrapped(_)) | Layout::RecursivePointer => { todo!("add support for rendering recursive tag unions in the REPL") } @@ -557,6 +564,33 @@ fn ptr_to_ast<'a>( when_recursive, ) } + Layout::Union(UnionLayout::NonNullableUnwrapped(_)) => { + let (rec_var, tags) = match unroll_recursion_var(env, content) { + Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), + other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), + }; + debug_assert_eq!(tags.len(), 1); + + let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, Some(*rec_var)); + + let (tag_name, arg_layouts) = match union_variant { + UnionVariant::Wrapped(WrappedVariant::NonNullableUnwrapped { + tag_name, fields, + }) => (tag_name, fields), + _ => unreachable!("any other variant would have a different layout"), + }; + + let ptr_to_data = unsafe { *(ptr as *const i64) as *const u8 }; + + expr_of_tag( + env, + ptr_to_data, + &tag_name, + arg_layouts, + &vars_of_tag[&tag_name], + when_recursive, + ) + } other => { todo!( "TODO add support for rendering pointer to {:?} in the REPL", diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index ed31e9bec3..9d158c587a 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -720,6 +720,25 @@ mod repl_eval { ) } + #[test] + fn non_nullable_unwrapped_tag_union() { + expect_success( + indoc!( + r#" + RoseTree a : [ Tree a (List (RoseTree a)) ] + e1 : RoseTree Str + e1 = Tree "e1" [] + e2 : RoseTree Str + e2 = Tree "e2" [] + combo : RoseTree Str + combo = Tree "combo" [e1, e2] + combo + "# + ), + r#"Tree "combo" [ Tree "e1" [], Tree "e2" [] ] : RoseTree Str"#, + ) + } + // #[test] // fn parse_problem() { // // can't find something that won't parse currently From cdcc31f199f2480a1a4d4bdf1cefebae301bc9e3 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Tue, 28 Dec 2021 15:20:25 -0600 Subject: [PATCH 022/541] Support nullable unwrapped tag layouts in REPL --- cli/src/repl/eval.rs | 41 +++++++++++++++++++++++++++++++++++++---- cli/tests/repl_eval.rs | 19 +++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 4ba13cdcf4..0d9c659eb2 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -332,7 +332,8 @@ fn jit_to_ast_help<'a>( )) } Layout::Union(UnionLayout::Recursive(_)) - | Layout::Union(UnionLayout::NonNullableUnwrapped(_)) => { + | Layout::Union(UnionLayout::NonNullableUnwrapped(_)) + | Layout::Union(UnionLayout::NullableUnwrapped { .. }) => { let size = layout.stack_size(env.ptr_bytes); Ok(run_jit_function_dynamic_type!( lib, @@ -343,9 +344,7 @@ fn jit_to_ast_help<'a>( } )) } - Layout::Union(UnionLayout::NullableWrapped { .. }) - | Layout::Union(UnionLayout::NullableUnwrapped { .. }) - | Layout::RecursivePointer => { + Layout::Union(UnionLayout::NullableWrapped { .. }) | Layout::RecursivePointer => { todo!("add support for rendering recursive tag unions in the REPL") } Layout::LambdaSet(lambda_set) => jit_to_ast_help( @@ -591,6 +590,40 @@ fn ptr_to_ast<'a>( when_recursive, ) } + Layout::Union(UnionLayout::NullableUnwrapped { .. }) => { + let (rec_var, tags) = match unroll_recursion_var(env, content) { + Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), + other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), + }; + debug_assert!(tags.len() <= 2); + + let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, Some(*rec_var)); + + let (nullable_name, other_name, other_arg_layouts) = match union_variant { + UnionVariant::Wrapped(WrappedVariant::NullableUnwrapped { + nullable_id: _, + nullable_name, + other_name, + other_fields, + }) => (nullable_name, other_name, other_fields), + _ => unreachable!("any other variant would have a different layout"), + }; + + // TODO we should cast to a pointer the size of env.ptr_bytes + let ptr_to_data = unsafe { *(ptr as *const i64) as *const u8 }; + if ptr_to_data.is_null() { + tag_name_to_expr(env, &nullable_name) + } else { + expr_of_tag( + env, + ptr_to_data, + &other_name, + other_arg_layouts, + &vars_of_tag[&other_name], + when_recursive, + ) + } + } other => { todo!( "TODO add support for rendering pointer to {:?} in the REPL", diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index 9d158c587a..601869fc7a 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -739,6 +739,25 @@ mod repl_eval { ) } + #[test] + fn nullable_unwrapped_tag_union() { + expect_success( + indoc!( + r#" + LinkedList a : [ Nil, Cons a (LinkedList a) ] + c1 : LinkedList Str + c1 = Cons "Red" Nil + c2 : LinkedList Str + c2 = Cons "Yellow" c1 + c3 : LinkedList Str + c3 = Cons "Green" c2 + c3 + "# + ), + r#"Cons "Green" (Cons "Yellow" (Cons "Red" Nil)) : LinkedList Str"#, + ) + } + // #[test] // fn parse_problem() { // // can't find something that won't parse currently From d966d8921f7c53add702e1ae64261af2de4c2b2a Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Tue, 28 Dec 2021 16:26:19 -0600 Subject: [PATCH 023/541] Support nullable wrapped tag union layouts in REPL --- cli/src/repl/eval.rs | 63 +++++++++++++++++++++++++++++++++++-- cli/tests/repl_eval.rs | 47 +++++++++++++++++++++++++++ compiler/mono/src/layout.rs | 2 +- 3 files changed, 108 insertions(+), 4 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 0d9c659eb2..53d913b8f0 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -333,7 +333,8 @@ fn jit_to_ast_help<'a>( } Layout::Union(UnionLayout::Recursive(_)) | Layout::Union(UnionLayout::NonNullableUnwrapped(_)) - | Layout::Union(UnionLayout::NullableUnwrapped { .. }) => { + | Layout::Union(UnionLayout::NullableUnwrapped { .. }) + | Layout::Union(UnionLayout::NullableWrapped { .. }) => { let size = layout.stack_size(env.ptr_bytes); Ok(run_jit_function_dynamic_type!( lib, @@ -344,8 +345,8 @@ fn jit_to_ast_help<'a>( } )) } - Layout::Union(UnionLayout::NullableWrapped { .. }) | Layout::RecursivePointer => { - todo!("add support for rendering recursive tag unions in the REPL") + Layout::RecursivePointer => { + unreachable!("RecursivePointers can only be inside structures") } Layout::LambdaSet(lambda_set) => jit_to_ast_help( env, @@ -624,6 +625,62 @@ fn ptr_to_ast<'a>( ) } } + Layout::Union(union_layout @ UnionLayout::NullableWrapped { .. }) => { + let (rec_var, tags) = match unroll_recursion_var(env, content) { + Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), + other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), + }; + + let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, Some(*rec_var)); + + let (nullable_id, nullable_name, tags_and_layouts) = match union_variant { + UnionVariant::Wrapped(WrappedVariant::NullableWrapped { + nullable_id, + nullable_name, + sorted_tag_layouts, + }) => (nullable_id, nullable_name, sorted_tag_layouts), + _ => unreachable!("any other variant would have a different layout"), + }; + + // TODO we should cast to a pointer the size of env.ptr_bytes + let ptr_to_data = unsafe { *(ptr as *const i64) as *const u8 }; + if ptr_to_data.is_null() { + tag_name_to_expr(env, &nullable_name) + } else { + let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.ptr_bytes); + let (tag_id, ptr_to_data) = if tag_in_ptr { + // TODO we should cast to a pointer the size of env.ptr_bytes + let masked_ptr_to_data = unsafe { *(ptr as *const i64) }; + let (tag_id_bits, tag_id_mask) = + tag_pointer_tag_id_bits_and_mask(env.ptr_bytes); + let tag_id = masked_ptr_to_data & (tag_id_mask as i64); + + // Clear the tag ID data from the pointer + let ptr_to_data = ((masked_ptr_to_data >> tag_id_bits) + << tag_id_bits) + as *const u8; + (tag_id, ptr_to_data) + } else { + // TODO we should cast to a pointer the size of env.ptr_bytes + let ptr_to_data = unsafe { *(ptr as *const i64) as *const u8 }; + let tag_id = + tag_id_from_data(*union_layout, ptr_to_data, env.ptr_bytes); + (tag_id, ptr_to_data) + }; + + let tag_id = if tag_id > nullable_id.into() { tag_id - 1 } else { tag_id }; + + let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; + expr_of_tag( + env, + ptr_to_data, + tag_name, + arg_layouts, + &vars_of_tag[tag_name], + when_recursive, + ) + } + } other => { todo!( "TODO add support for rendering pointer to {:?} in the REPL", diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index 601869fc7a..90df54457f 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -758,6 +758,53 @@ mod repl_eval { ) } + #[test] + fn nullable_wrapped_tag_union() { + expect_success( + indoc!( + r#" + Container a : [ Empty, Whole a, Halved (Container a) (Container a) ] + + meats : Container Str + meats = Halved (Whole "Brisket") (Whole "Ribs") + + sides : Container Str + sides = Halved (Whole "Coleslaw") Empty + + bbqPlate : Container Str + bbqPlate = Halved meats sides + + bbqPlate + "# + ), + r#"Halved (Halved (Whole "Brisket") (Whole "Ribs")) (Halved (Whole "Coleslaw") Empty) : Container Str"#, + ) + } + + #[test] + fn large_nullable_wrapped_tag_union() { + // > 7 non-empty variants so that to force tag storage alongside the data + expect_success( + indoc!( + r#" + Cont a : [ Empty, S1 a, S2 a, S3 a, S4 a, S5 a, S6 a, S7 a, Tup (Cont a) (Cont a) ] + + fst : Cont Str + fst = Tup (S1 "S1") (S2 "S2") + + snd : Cont Str + snd = Tup (S5 "S5") Empty + + tup : Cont Str + tup = Tup fst snd + + tup + "# + ), + r#"Tup (Tup (S1 "S1") (S2 "S2")) (Tup (S5 "S5") Empty) : Cont Str"#, + ) + } + // #[test] // fn parse_problem() { // // can't find something that won't parse currently diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 2d5a541ee8..2e28822023 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -361,7 +361,7 @@ impl<'a> UnionLayout<'a> { } fn stores_tag_id_in_pointer_bits(tags: &[&[Layout<'a>]], ptr_bytes: u32) -> bool { - tags.len() <= ptr_bytes as usize + tags.len() < ptr_bytes as usize } // i.e. it is not implicit and not stored in the pointer bits From f46261c8c0449f1ab4b06bd3c591d1cbe2a309c7 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Tue, 28 Dec 2021 16:33:11 -0600 Subject: [PATCH 024/541] Pull out common code that extracts tag ID, data from recursive pointer --- cli/src/repl/eval.rs | 72 ++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 42 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 53d913b8f0..b36f346e11 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -182,6 +182,33 @@ fn tag_id_from_data(union_layout: UnionLayout, data_ptr: *const u8, ptr_bytes: u } } +/// Gets the tag ID of a union variant from its recursive pointer (that is, the pointer to the +/// pointer to the data of the union variant). Returns +/// - the tag ID +/// - the pointer to the data of the union variant, unmasked if the pointer held the tag ID +fn tag_id_from_recursive_ptr( + union_layout: UnionLayout, + rec_ptr: *const u8, + ptr_bytes: u32, +) -> (i64, *const u8) { + let tag_in_ptr = union_layout.stores_tag_id_in_pointer(ptr_bytes); + if tag_in_ptr { + // TODO we should cast to a pointer the size of env.ptr_bytes + let masked_ptr_to_data = unsafe { *(rec_ptr as *const i64) }; + let (tag_id_bits, tag_id_mask) = tag_pointer_tag_id_bits_and_mask(ptr_bytes); + let tag_id = masked_ptr_to_data & (tag_id_mask as i64); + + // Clear the tag ID data from the pointer + let ptr_to_data = ((masked_ptr_to_data >> tag_id_bits) << tag_id_bits) as *const u8; + (tag_id, ptr_to_data) + } else { + // TODO we should cast to a pointer the size of env.ptr_bytes + let ptr_to_data = unsafe { *(rec_ptr as *const i64) as *const u8 }; + let tag_id = tag_id_from_data(union_layout, ptr_to_data, ptr_bytes); + (tag_id, ptr_to_data) + } +} + fn jit_to_ast_help<'a>( env: &Env<'a, 'a>, lib: Library, @@ -515,14 +542,13 @@ fn ptr_to_ast<'a>( WhenRecursive::Unreachable, ) } - Layout::Union(UnionLayout::Recursive(union_layouts)) => { + Layout::Union(union_layout @ UnionLayout::Recursive(union_layouts)) => { let (rec_var, tags) = match content { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), _ => unreachable!("any other content would have a different layout"), }; debug_assert_eq!(union_layouts.len(), tags.len()); - let union_layout = UnionLayout::Recursive(union_layouts); let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, Some(*rec_var)); @@ -533,26 +559,7 @@ fn ptr_to_ast<'a>( _ => unreachable!("any other variant would have a different layout"), }; - let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.ptr_bytes); - let (tag_id, ptr_to_data) = if tag_in_ptr { - // TODO we should cast to a pointer the size of env.ptr_bytes - let masked_ptr_to_data = unsafe { *(ptr as *const i64) }; - let (tag_id_bits, tag_id_mask) = - tag_pointer_tag_id_bits_and_mask(env.ptr_bytes); - let tag_id = masked_ptr_to_data & (tag_id_mask as i64); - - // Clear the tag ID data from the pointer - let ptr_to_data = ((masked_ptr_to_data >> tag_id_bits) - << tag_id_bits) - as *const u8; - (tag_id, ptr_to_data) - } else { - // TODO we should cast to a pointer the size of env.ptr_bytes - let ptr_to_data = unsafe { *(ptr as *const i64) as *const u8 }; - let tag_id = - tag_id_from_data(union_layout, ptr_to_data, env.ptr_bytes); - (tag_id, ptr_to_data) - }; + let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(*union_layout, ptr, env.ptr_bytes); let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; expr_of_tag( @@ -647,26 +654,7 @@ fn ptr_to_ast<'a>( if ptr_to_data.is_null() { tag_name_to_expr(env, &nullable_name) } else { - let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.ptr_bytes); - let (tag_id, ptr_to_data) = if tag_in_ptr { - // TODO we should cast to a pointer the size of env.ptr_bytes - let masked_ptr_to_data = unsafe { *(ptr as *const i64) }; - let (tag_id_bits, tag_id_mask) = - tag_pointer_tag_id_bits_and_mask(env.ptr_bytes); - let tag_id = masked_ptr_to_data & (tag_id_mask as i64); - - // Clear the tag ID data from the pointer - let ptr_to_data = ((masked_ptr_to_data >> tag_id_bits) - << tag_id_bits) - as *const u8; - (tag_id, ptr_to_data) - } else { - // TODO we should cast to a pointer the size of env.ptr_bytes - let ptr_to_data = unsafe { *(ptr as *const i64) as *const u8 }; - let tag_id = - tag_id_from_data(*union_layout, ptr_to_data, env.ptr_bytes); - (tag_id, ptr_to_data) - }; + let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(*union_layout, ptr, env.ptr_bytes); let tag_id = if tag_id > nullable_id.into() { tag_id - 1 } else { tag_id }; From 13101101a74131f21a6aae90db0d4636b41d4514 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Tue, 28 Dec 2021 16:44:19 -0600 Subject: [PATCH 025/541] Dereference pointers of pointers with the correct size --- cli/src/repl/eval.rs | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index b36f346e11..928d71a8e1 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -182,6 +182,17 @@ fn tag_id_from_data(union_layout: UnionLayout, data_ptr: *const u8, ptr_bytes: u } } +fn deref_ptr_of_ptr(ptr_of_ptr: *const u8, ptr_bytes: u32) -> *const u8 { + unsafe { + match ptr_bytes { + // Our LLVM codegen represents pointers as i32/i64s. + 4 => *(ptr_of_ptr as *const i32) as *const u8, + 8 => *(ptr_of_ptr as *const i64) as *const u8, + _ => unreachable!(), + } + } +} + /// Gets the tag ID of a union variant from its recursive pointer (that is, the pointer to the /// pointer to the data of the union variant). Returns /// - the tag ID @@ -193,17 +204,15 @@ fn tag_id_from_recursive_ptr( ) -> (i64, *const u8) { let tag_in_ptr = union_layout.stores_tag_id_in_pointer(ptr_bytes); if tag_in_ptr { - // TODO we should cast to a pointer the size of env.ptr_bytes - let masked_ptr_to_data = unsafe { *(rec_ptr as *const i64) }; + let masked_ptr_to_data = deref_ptr_of_ptr(rec_ptr, ptr_bytes) as i64; let (tag_id_bits, tag_id_mask) = tag_pointer_tag_id_bits_and_mask(ptr_bytes); let tag_id = masked_ptr_to_data & (tag_id_mask as i64); // Clear the tag ID data from the pointer let ptr_to_data = ((masked_ptr_to_data >> tag_id_bits) << tag_id_bits) as *const u8; - (tag_id, ptr_to_data) + (tag_id as i64, ptr_to_data) } else { - // TODO we should cast to a pointer the size of env.ptr_bytes - let ptr_to_data = unsafe { *(rec_ptr as *const i64) as *const u8 }; + let ptr_to_data = deref_ptr_of_ptr(rec_ptr, ptr_bytes); let tag_id = tag_id_from_data(union_layout, ptr_to_data, ptr_bytes); (tag_id, ptr_to_data) } @@ -587,7 +596,7 @@ fn ptr_to_ast<'a>( _ => unreachable!("any other variant would have a different layout"), }; - let ptr_to_data = unsafe { *(ptr as *const i64) as *const u8 }; + let ptr_to_data = deref_ptr_of_ptr(ptr, env.ptr_bytes); expr_of_tag( env, @@ -617,8 +626,7 @@ fn ptr_to_ast<'a>( _ => unreachable!("any other variant would have a different layout"), }; - // TODO we should cast to a pointer the size of env.ptr_bytes - let ptr_to_data = unsafe { *(ptr as *const i64) as *const u8 }; + let ptr_to_data = deref_ptr_of_ptr(ptr, env.ptr_bytes); if ptr_to_data.is_null() { tag_name_to_expr(env, &nullable_name) } else { @@ -649,8 +657,7 @@ fn ptr_to_ast<'a>( _ => unreachable!("any other variant would have a different layout"), }; - // TODO we should cast to a pointer the size of env.ptr_bytes - let ptr_to_data = unsafe { *(ptr as *const i64) as *const u8 }; + let ptr_to_data = deref_ptr_of_ptr(ptr, env.ptr_bytes); if ptr_to_data.is_null() { tag_name_to_expr(env, &nullable_name) } else { From 20b1d9acdce6b507c6ce997d9ffbb9d0487fb6ce Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Tue, 28 Dec 2021 16:48:33 -0600 Subject: [PATCH 026/541] Replace code with stdlib functions max/min_by_key was stabilized in Rust 1.53.0 --- cli/src/repl/eval.rs | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 928d71a8e1..7404fe53d3 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -15,6 +15,7 @@ use roc_mono::layout::{ use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral}; use roc_region::all::{Loc, Region}; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; +use std::cmp::{max_by_key, min_by_key}; struct Env<'a, 'env> { arena: &'a Bump, @@ -1286,30 +1287,3 @@ fn str_slice_to_ast<'a>(_arena: &'a Bump, string: &'a str) -> Expr<'a> { Expr::Str(StrLiteral::PlainLine(string)) } } - -// TODO this is currently nighly-only: use the implementation in std once it's stabilized -pub fn max_by std::cmp::Ordering>(v1: T, v2: T, compare: F) -> T { - use std::cmp::Ordering; - - match compare(&v1, &v2) { - Ordering::Less | Ordering::Equal => v2, - Ordering::Greater => v1, - } -} - -pub fn min_by std::cmp::Ordering>(v1: T, v2: T, compare: F) -> T { - use std::cmp::Ordering; - - match compare(&v1, &v2) { - Ordering::Less | Ordering::Equal => v1, - Ordering::Greater => v2, - } -} - -pub fn max_by_key K, K: Ord>(v1: T, v2: T, mut f: F) -> T { - max_by(v1, v2, |v1, v2| f(v1).cmp(&f(v2))) -} - -pub fn min_by_key K, K: Ord>(v1: T, v2: T, mut f: F) -> T { - min_by(v1, v2, |v1, v2| f(v1).cmp(&f(v2))) -} From 59472d331069d81bf0e69c54251cc005708b6b3e Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 30 Dec 2021 09:01:22 +0000 Subject: [PATCH 027/541] Slightly improve pretty printing of inc statement --- compiler/mono/src/ir.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 03ba31a296..ddc7b81cd7 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -1170,7 +1170,7 @@ impl ModifyRc { .append(";"), Inc(symbol, n) => alloc .text("inc ") - .append(alloc.text(format!("{}", n))) + .append(alloc.text(format!("{} ", n))) .append(symbol_to_doc(alloc, symbol)) .append(";"), Dec(symbol) => alloc From eeb4b2039015a89d1836490b12e942e1fb100125 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 30 Dec 2021 09:09:07 +0000 Subject: [PATCH 028/541] Improve string refcounting tests --- compiler/test_gen/src/gen_refcount.rs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs index f7fd8cb045..ee6729954d 100644 --- a/compiler/test_gen/src/gen_refcount.rs +++ b/compiler/test_gen/src/gen_refcount.rs @@ -9,22 +9,35 @@ use roc_std::{RocList, RocStr}; #[test] #[cfg(any(feature = "gen-wasm"))] -fn list_of_same_str() { +fn str_inc() { assert_refcounts!( indoc!( r#" - a = "A long enough string" - b = "to be heap-allocated" - c = Str.joinWith [a, b] " " + s = Str.concat "A long enough string " "to be heap-allocated" - [c, c, c] + [s, s, s] "# ), RocList, &[ - 1, // [a,b] (TODO: list decrement. This should be zero!) - 3, // c + 3, // s 1 // result ] ); } + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn str_dealloc() { + assert_refcounts!( + indoc!( + r#" + s = Str.concat "A long enough string " "to be heap-allocated" + + Str.isEmpty s + "# + ), + bool, + &[0] + ); +} From acade334976875d9855d4ca99afb74b17b44613c Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 30 Dec 2021 15:37:09 +0000 Subject: [PATCH 029/541] Wasm bugfix: allocate using the heap alignment, not the stack alignment --- compiler/gen_wasm/src/backend.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 9d08da3b88..2895dc9193 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -678,17 +678,13 @@ impl<'a> WasmBackend<'a> { } Expr::Array { elems, elem_layout } => { - if let StoredValue::StackMemory { - location, - alignment_bytes, - .. - } = storage - { + if let StoredValue::StackMemory { location, .. } = storage { let size = elem_layout.stack_size(PTR_SIZE) * (elems.len() as u32); // Allocate heap space and store its address in a local variable let heap_local_id = self.storage.create_anonymous_local(PTR_TYPE); - self.allocate_with_refcount(Some(size), *alignment_bytes, 1); + let heap_alignment = elem_layout.alignment_bytes(PTR_SIZE); + self.allocate_with_refcount(Some(size), heap_alignment, 1); self.code_builder.set_local(heap_local_id); let (stack_local_id, stack_offset) = From 0912f8fd4509e1cd3c2cd6486da1dbb57b695080 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 30 Dec 2021 15:38:06 +0000 Subject: [PATCH 030/541] Wasm: replace a local.set/get pair with a local.tee --- compiler/gen_wasm/src/backend.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 2895dc9193..b4e36f117b 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -1044,12 +1044,11 @@ impl<'a> WasmBackend<'a> { // Save the allocation address to a temporary local variable let local_id = self.storage.create_anonymous_local(ValueType::I32); - self.code_builder.set_local(local_id); + self.code_builder.tee_local(local_id); // Write the initial refcount let refcount_offset = extra_bytes - PTR_SIZE; let encoded_refcount = (initial_refcount as i32) - 1 + i32::MIN; - self.code_builder.get_local(local_id); self.code_builder.i32_const(encoded_refcount); self.code_builder.i32_store(Align::Bytes4, refcount_offset); From 83d6c82e0bd6f97ee80ab4599f1eb3bc4340504a Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 30 Dec 2021 15:40:55 +0000 Subject: [PATCH 031/541] Wasm: avoid two memory loads for ListLen, at the cost of +1 instruction byte --- compiler/gen_wasm/src/low_level.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index cc5cff66f6..a070ca5f2e 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -69,14 +69,11 @@ pub fn dispatch_low_level<'a>( // List ListLen => { - if let StoredValue::StackMemory { location, .. } = storage.get(&args[0]) { - let (local_id, offset) = location.local_and_offset(storage.stack_frame_pointer); - - code_builder.get_local(local_id); - code_builder.i32_load(Align::Bytes4, offset + 4); - } else { - internal_error!("Unexpected storage for {:?}", args[0]); - }; + // List structure has already been loaded as i64 (Zig calling convention) + // We want the second (more significant) 32 bits. Shift and convert to i32. + code_builder.i64_const(32); + code_builder.i64_shr_u(); + code_builder.i32_wrap_i64(); } ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat From e612f51905d33379da0657a4bbb315153048dd20 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 30 Dec 2021 15:44:39 +0000 Subject: [PATCH 032/541] Tweak wasm test platform --- compiler/test_gen/src/helpers/wasm_test_platform.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/test_gen/src/helpers/wasm_test_platform.c b/compiler/test_gen/src/helpers/wasm_test_platform.c index b69ea54525..f866e93e87 100644 --- a/compiler/test_gen/src/helpers/wasm_test_platform.c +++ b/compiler/test_gen/src/helpers/wasm_test_platform.c @@ -35,7 +35,7 @@ size_t **init_refcount_test(size_t max_allocs) abort(); #endif -size_t *data_ptr_to_rc_ptr(void *ptr, unsigned int alignment) +size_t *alloc_ptr_to_rc_ptr(void *ptr, unsigned int alignment) { size_t alloc_addr = (size_t)ptr; size_t rc_addr = alloc_addr + alignment - sizeof(size_t); @@ -53,7 +53,7 @@ void *roc_alloc(size_t size, unsigned int alignment) ASSERT(alignment >= sizeof(size_t)); ASSERT(rc_pointers_index < rc_pointers_len); - size_t *rc_ptr = data_ptr_to_rc_ptr(allocated, alignment); + size_t *rc_ptr = alloc_ptr_to_rc_ptr(allocated, alignment); rc_pointers[rc_pointers_index] = rc_ptr; rc_pointers_index++; } @@ -66,7 +66,7 @@ void *roc_alloc(size_t size, unsigned int alignment) } else { - printf("roc_alloc allocated %d bytes at %p\n", size, allocated); + printf("roc_alloc allocated %d bytes with alignment %d at %p\n", size, alignment, allocated); } #endif return allocated; @@ -88,9 +88,9 @@ void *roc_realloc(void *ptr, size_t new_size, size_t old_size, void roc_dealloc(void *ptr, unsigned int alignment) { - // Null out the entry in the test array to signal that it was freed + // Null out the entry in the test array to indicate that it was freed // Then even if malloc reuses the space, everything still works - size_t *rc_ptr = data_ptr_to_rc_ptr(ptr, alignment); + size_t *rc_ptr = alloc_ptr_to_rc_ptr(ptr, alignment); int i = 0; for (; i < rc_pointers_index; ++i) { From c5663e3538b069a77a697325e254485df0b1d875 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 30 Dec 2021 15:43:54 +0000 Subject: [PATCH 033/541] Generate IR helper proc for list refcounting --- compiler/mono/src/code_gen_help/equality.rs | 33 +- compiler/mono/src/code_gen_help/mod.rs | 50 +-- compiler/mono/src/code_gen_help/refcount.rs | 388 +++++++++++++++++--- compiler/test_gen/src/gen_refcount.rs | 76 ++++ 4 files changed, 468 insertions(+), 79 deletions(-) diff --git a/compiler/mono/src/code_gen_help/equality.rs b/compiler/mono/src/code_gen_help/equality.rs index 16fe3b1ba2..db14a1e3b9 100644 --- a/compiler/mono/src/code_gen_help/equality.rs +++ b/compiler/mono/src/code_gen_help/equality.rs @@ -151,12 +151,14 @@ fn eq_struct<'a>( }; let field2_stmt = |next| Stmt::Let(field2_sym, field2_expr, *layout, next); - let eq_call_expr = root.call_specialized_op( - ident_ids, - ctx, - *layout, - root.arena.alloc([field1_sym, field2_sym]), - ); + let eq_call_expr = root + .call_specialized_op( + ident_ids, + ctx, + *layout, + root.arena.alloc([field1_sym, field2_sym]), + ) + .unwrap(); let eq_call_name = format!("eq_call_{}", i); let eq_call_sym = root.create_symbol(ident_ids, &eq_call_name); @@ -478,12 +480,14 @@ fn eq_tag_fields<'a>( structure: operands[1], }; - let eq_call_expr = root.call_specialized_op( - ident_ids, - ctx, - *layout, - root.arena.alloc([field1_sym, field2_sym]), - ); + let eq_call_expr = root + .call_specialized_op( + ident_ids, + ctx, + *layout, + root.arena.alloc([field1_sym, field2_sym]), + ) + .unwrap(); let eq_call_name = format!("eq_call_{}", i); let eq_call_sym = root.create_symbol(ident_ids, &eq_call_name); @@ -657,7 +661,10 @@ fn eq_list<'a>( // Compare the two current elements let eq_elems = root.create_symbol(ident_ids, "eq_elems"); - let eq_elems_expr = root.call_specialized_op(ident_ids, ctx, *elem_layout, &[elem1, elem2]); + let eq_elems_args = root.arena.alloc([elem1, elem2]); + let eq_elems_expr = root + .call_specialized_op(ident_ids, ctx, *elem_layout, eq_elems_args) + .unwrap(); let eq_elems_stmt = |next| Stmt::Let(eq_elems, eq_elems_expr, LAYOUT_BOOL, next); diff --git a/compiler/mono/src/code_gen_help/mod.rs b/compiler/mono/src/code_gen_help/mod.rs index f5943ba935..e62a9a5265 100644 --- a/compiler/mono/src/code_gen_help/mod.rs +++ b/compiler/mono/src/code_gen_help/mod.rs @@ -148,12 +148,14 @@ impl<'a> CodeGenHelp<'a> { // Call helper proc, passing the Roc structure and constant amount let call_result_empty = self.create_symbol(ident_ids, "call_result_empty"); - let call_expr = self.call_specialized_op( - ident_ids, - &mut ctx, - layout, - arena.alloc([*structure, amount_sym]), - ); + let call_expr = self + .call_specialized_op( + ident_ids, + &mut ctx, + layout, + arena.alloc([*structure, amount_sym]), + ) + .unwrap(); let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following); let rc_stmt = arena.alloc(amount_stmt(arena.alloc(call_stmt))); @@ -163,12 +165,9 @@ impl<'a> CodeGenHelp<'a> { ModifyRc::Dec(structure) => { // Call helper proc, passing the Roc structure let call_result_empty = self.create_symbol(ident_ids, "call_result_empty"); - let call_expr = self.call_specialized_op( - ident_ids, - &mut ctx, - layout, - arena.alloc([*structure]), - ); + let call_expr = self + .call_specialized_op(ident_ids, &mut ctx, layout, arena.alloc([*structure])) + .unwrap(); let rc_stmt = arena.alloc(Stmt::Let( call_result_empty, @@ -195,7 +194,8 @@ impl<'a> CodeGenHelp<'a> { }); let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following); - let rc_stmt = arena.alloc(refcount::rc_ptr_from_struct( + // FIXME: `structure` is a pointer to the stack, not the heap! + let rc_stmt = arena.alloc(refcount::rc_ptr_from_data_ptr( self, ident_ids, *structure, @@ -222,7 +222,9 @@ impl<'a> CodeGenHelp<'a> { op: HelperOp::Eq, }; - let expr = self.call_specialized_op(ident_ids, &mut ctx, *layout, arguments); + let expr = self + .call_specialized_op(ident_ids, &mut ctx, *layout, arguments) + .unwrap(); (expr, ctx.new_linker_data) } @@ -238,8 +240,8 @@ impl<'a> CodeGenHelp<'a> { ident_ids: &mut IdentIds, ctx: &mut Context<'a>, called_layout: Layout<'a>, - arguments: &[Symbol], - ) -> Expr<'a> { + arguments: &'a [Symbol], + ) -> Option> { use HelperOp::*; debug_assert!(self.debug_recursion_depth < 10); @@ -263,23 +265,25 @@ impl<'a> CodeGenHelp<'a> { } }; - Expr::Call(Call { + Some(Expr::Call(Call { call_type: CallType::ByName { name: proc_name, ret_layout, arg_layouts, specialization_id: CallSpecId::BACKEND_DUMMY, }, - arguments: self.arena.alloc_slice_copy(arguments), - }) - } else { - Expr::Call(Call { + arguments, + })) + } else if ctx.op == HelperOp::Eq { + Some(Expr::Call(Call { call_type: CallType::LowLevel { op: LowLevel::Eq, update_mode: UpdateModeId::BACKEND_DUMMY, }, - arguments: self.arena.alloc_slice_copy(arguments), - }) + arguments, + })) + } else { + None } } diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index f5f0e8cfe6..b81dc84082 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -1,9 +1,12 @@ use roc_builtins::bitcode::IntWidth; -use roc_module::low_level::LowLevel; +use roc_module::low_level::{LowLevel, LowLevel::*}; use roc_module::symbol::{IdentIds, Symbol}; -use crate::ir::{BranchInfo, Call, CallType, Expr, Literal, Stmt, UpdateModeId}; -use crate::layout::{Builtin, Layout}; +use crate::code_gen_help::let_lowlevel; +use crate::ir::{ + BranchInfo, Call, CallType, Expr, JoinPointId, Literal, Param, Stmt, UpdateModeId, +}; +use crate::layout::{Builtin, Layout, UnionLayout}; use super::{CodeGenHelp, Context, HelperOp}; @@ -16,7 +19,7 @@ const ARG_1: Symbol = Symbol::ARG_1; const ARG_2: Symbol = Symbol::ARG_2; pub fn refcount_generic<'a>( - root: &CodeGenHelp<'a>, + root: &mut CodeGenHelp<'a>, ident_ids: &mut IdentIds, ctx: &mut Context<'a>, layout: Layout<'a>, @@ -29,7 +32,10 @@ pub fn refcount_generic<'a>( unreachable!("Not refcounted: {:?}", layout) } Layout::Builtin(Builtin::Str) => refcount_str(root, ident_ids, ctx), - Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_)) => rc_todo(), + Layout::Builtin(Builtin::List(elem_layout)) => { + refcount_list(root, ident_ids, ctx, &layout, elem_layout) + } + Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_)) => rc_todo(), Layout::Struct(_) => rc_todo(), Layout::Union(_) => rc_todo(), Layout::LambdaSet(_) => { @@ -43,7 +49,7 @@ pub fn refcount_generic<'a>( // In the short term, it helps us to skip refcounting and let it leak, so we can make // progress incrementally. Kept in sync with generate_procs using assertions. pub fn is_rc_implemented_yet(layout: &Layout) -> bool { - matches!(layout, Layout::Builtin(Builtin::Str)) + matches!(layout, Layout::Builtin(Builtin::Str | Builtin::List(_))) } fn return_unit<'a>(root: &CodeGenHelp<'a>, ident_ids: &mut IdentIds) -> Stmt<'a> { @@ -52,11 +58,20 @@ fn return_unit<'a>(root: &CodeGenHelp<'a>, ident_ids: &mut IdentIds) -> Stmt<'a> Stmt::Let(unit, Expr::Struct(&[]), LAYOUT_UNIT, ret_stmt) } +fn refcount_args<'a>(root: &CodeGenHelp<'a>, ctx: &Context<'a>, structure: Symbol) -> &'a [Symbol] { + if ctx.op == HelperOp::Inc { + // second argument is always `amount`, passed down through the call stack + root.arena.alloc([structure, ARG_2]) + } else { + root.arena.alloc([structure]) + } +} + // Subtract a constant from a pointer to find the refcount // Also does some type casting, so that we have different Symbols and Layouts // for the 'pointer' and 'integer' versions of the address. // This helps to avoid issues with the backends Symbol->Layout mapping. -pub fn rc_ptr_from_struct<'a>( +pub fn rc_ptr_from_data_ptr<'a>( root: &CodeGenHelp<'a>, ident_ids: &mut IdentIds, structure: Symbol, @@ -87,7 +102,7 @@ pub fn rc_ptr_from_struct<'a>( op: LowLevel::NumSub, update_mode: UpdateModeId::BACKEND_DUMMY, }, - arguments: root.arena.alloc([structure, ptr_size_sym]), + arguments: root.arena.alloc([addr_sym, ptr_size_sym]), }); let rc_addr_stmt = |next| Stmt::Let(rc_addr_sym, rc_addr_expr, root.layout_isize, next); @@ -116,14 +131,58 @@ pub fn rc_ptr_from_struct<'a>( )) } +fn modify_refcount<'a>( + root: &CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + rc_ptr: Symbol, + alignment: u32, + following: &'a Stmt<'a>, +) -> Stmt<'a> { + // Call the relevant Zig lowlevel to actually modify the refcount + let zig_call_result = root.create_symbol(ident_ids, "zig_call_result"); + match ctx.op { + HelperOp::Inc => { + let zig_call_expr = Expr::Call(Call { + call_type: CallType::LowLevel { + op: LowLevel::RefCountInc, + update_mode: UpdateModeId::BACKEND_DUMMY, + }, + arguments: root.arena.alloc([rc_ptr, ARG_2]), + }); + Stmt::Let(zig_call_result, zig_call_expr, LAYOUT_UNIT, following) + } + + HelperOp::Dec | HelperOp::DecRef => { + let alignment_sym = root.create_symbol(ident_ids, "alignment"); + let alignment_expr = Expr::Literal(Literal::Int(alignment as i128)); + let alignment_stmt = |next| Stmt::Let(alignment_sym, alignment_expr, LAYOUT_U32, next); + + let zig_call_expr = Expr::Call(Call { + call_type: CallType::LowLevel { + op: LowLevel::RefCountDec, + update_mode: UpdateModeId::BACKEND_DUMMY, + }, + arguments: root.arena.alloc([rc_ptr, alignment_sym]), + }); + let zig_call_stmt = Stmt::Let(zig_call_result, zig_call_expr, LAYOUT_UNIT, following); + + alignment_stmt(root.arena.alloc( + // + zig_call_stmt, + )) + } + + _ => unreachable!(), + } +} + /// Generate a procedure to modify the reference count of a Str fn refcount_str<'a>( root: &CodeGenHelp<'a>, ident_ids: &mut IdentIds, ctx: &mut Context<'a>, ) -> Stmt<'a> { - let op = ctx.op; - let string = ARG_1; let layout_isize = root.layout_isize; @@ -164,53 +223,33 @@ fn refcount_str<'a>( // A pointer to the refcount value itself let rc_ptr = root.create_symbol(ident_ids, "rc_ptr"); + let alignment = root.ptr_size; - // Alignment constant (same value as ptr_size but different layout) - let alignment = root.create_symbol(ident_ids, "alignment"); - let alignment_expr = Expr::Literal(Literal::Int(root.ptr_size as i128)); - let alignment_stmt = |next| Stmt::Let(alignment, alignment_expr, LAYOUT_U32, next); - - // Call the relevant Zig lowlevel to actually modify the refcount - let zig_call_result = root.create_symbol(ident_ids, "zig_call_result"); - let zig_call_expr = match op { - HelperOp::Inc => Expr::Call(Call { - call_type: CallType::LowLevel { - op: LowLevel::RefCountInc, - update_mode: UpdateModeId::BACKEND_DUMMY, - }, - arguments: root.arena.alloc([rc_ptr, ARG_2]), - }), - HelperOp::Dec | HelperOp::DecRef => Expr::Call(Call { - call_type: CallType::LowLevel { - op: LowLevel::RefCountDec, - update_mode: UpdateModeId::BACKEND_DUMMY, - }, - arguments: root.arena.alloc([rc_ptr, alignment]), - }), - _ => unreachable!(), - }; - let zig_call_stmt = |next| Stmt::Let(zig_call_result, zig_call_expr, LAYOUT_UNIT, next); + let ret_unit_stmt = return_unit(root, ident_ids); + let mod_rc_stmt = modify_refcount( + root, + ident_ids, + ctx, + rc_ptr, + alignment, + root.arena.alloc(ret_unit_stmt), + ); // Generate an `if` to skip small strings but modify big strings let then_branch = elements_stmt(root.arena.alloc( // - rc_ptr_from_struct( + rc_ptr_from_data_ptr( root, ident_ids, elements, rc_ptr, root.arena.alloc( // - alignment_stmt(root.arena.alloc( - // - zig_call_stmt(root.arena.alloc( - // - Stmt::Ret(zig_call_result), - )), - )), + mod_rc_stmt, ), ), )); + let if_stmt = Stmt::Switch { cond_symbol: is_big_str, cond_layout: LAYOUT_BOOL, @@ -234,3 +273,266 @@ fn refcount_str<'a>( )), )) } + +fn refcount_list<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + layout: &Layout, + elem_layout: &'a Layout, +) -> Stmt<'a> { + let layout_isize = root.layout_isize; + let arena = root.arena; + + // A "Box" layout (heap pointer to a single list element) + let box_union_layout = UnionLayout::NonNullableUnwrapped(arena.alloc([*elem_layout])); + let box_layout = Layout::Union(box_union_layout); + + // + // Check if the list is empty + // + + let len = root.create_symbol(ident_ids, "len"); + let len_stmt = |next| let_lowlevel(arena, layout_isize, len, ListLen, &[ARG_1], next); + + // Zero + let zero = root.create_symbol(ident_ids, "zero"); + let zero_expr = Expr::Literal(Literal::Int(0)); + let zero_stmt = |next| Stmt::Let(zero, zero_expr, layout_isize, next); + + // let is_empty = lowlevel Eq len zero + let is_empty = root.create_symbol(ident_ids, "is_empty"); + let is_empty_expr = Expr::Call(Call { + call_type: CallType::LowLevel { + op: LowLevel::Eq, + update_mode: UpdateModeId::BACKEND_DUMMY, + }, + arguments: root.arena.alloc([len, zero]), + }); + let is_empty_stmt = |next| Stmt::Let(is_empty, is_empty_expr, LAYOUT_BOOL, next); + + // get elements pointer + let elements = root.create_symbol(ident_ids, "elements"); + let elements_expr = Expr::StructAtIndex { + index: 0, + field_layouts: arena.alloc([box_layout, layout_isize]), + structure: ARG_1, + }; + let elements_stmt = |next| Stmt::Let(elements, elements_expr, box_layout, next); + + // + // modify refcount of the list and its elements + // + + let rc_ptr = root.create_symbol(ident_ids, "rc_ptr"); + let alignment = layout.alignment_bytes(root.ptr_size); + + let modify_elems = if elem_layout.is_refcounted() { + refcount_list_elems( + root, + ident_ids, + ctx, + elem_layout, + LAYOUT_UNIT, + box_union_layout, + len, + elements, + ) + } else { + return_unit(root, ident_ids) + }; + let modify_list = modify_refcount( + root, + ident_ids, + ctx, + rc_ptr, + alignment, + arena.alloc(modify_elems), + ); + let modify_list_and_elems = elements_stmt(arena.alloc( + // + rc_ptr_from_data_ptr(root, ident_ids, elements, rc_ptr, arena.alloc(modify_list)), + )); + + // + // Do nothing if the list is empty + // + + let if_stmt = Stmt::Switch { + cond_symbol: is_empty, + cond_layout: LAYOUT_BOOL, + branches: root + .arena + .alloc([(1, BranchInfo::None, return_unit(root, ident_ids))]), + default_branch: (BranchInfo::None, root.arena.alloc(modify_list_and_elems)), + ret_layout: LAYOUT_UNIT, + }; + + len_stmt(arena.alloc( + // + zero_stmt(arena.alloc( + // + is_empty_stmt(arena.alloc( + // + if_stmt, + )), + )), + )) +} + +#[allow(clippy::too_many_arguments)] +fn refcount_list_elems<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + elem_layout: &Layout<'a>, + ret_layout: Layout<'a>, + box_union_layout: UnionLayout<'a>, + length: Symbol, + elements: Symbol, +) -> Stmt<'a> { + use LowLevel::*; + let layout_isize = root.layout_isize; + let arena = root.arena; + + // Cast to integer + let start = root.create_symbol(ident_ids, "start"); + let start_stmt = |next| let_lowlevel(arena, layout_isize, start, PtrCast, &[elements], next); + + // + // Loop initialisation + // + + // let size = literal int + let size = root.create_symbol(ident_ids, "size"); + let size_expr = Expr::Literal(Literal::Int(elem_layout.stack_size(root.ptr_size) as i128)); + let size_stmt = |next| Stmt::Let(size, size_expr, layout_isize, next); + + // let list_size = len * size + let list_size = root.create_symbol(ident_ids, "list_size"); + let list_size_stmt = |next| { + let_lowlevel( + arena, + layout_isize, + list_size, + NumMul, + &[length, size], + next, + ) + }; + + // let end = start + list_size + let end = root.create_symbol(ident_ids, "end"); + let end_stmt = |next| let_lowlevel(arena, layout_isize, end, NumAdd, &[start, list_size], next); + + // + // Loop name & parameter + // + + let elems_loop = JoinPointId(root.create_symbol(ident_ids, "elems_loop")); + let addr = root.create_symbol(ident_ids, "addr"); + + let param_addr = Param { + symbol: addr, + borrow: false, + layout: layout_isize, + }; + + // + // if we haven't reached the end yet... + // + + // Cast integer to box pointer + let box_ptr = root.create_symbol(ident_ids, "box"); + let box_layout = Layout::Union(box_union_layout); + let box_stmt = |next| let_lowlevel(arena, box_layout, box_ptr, PtrCast, &[addr], next); + + // Dereference the box pointer to get the current element + let elem = root.create_symbol(ident_ids, "elem"); + let elem_expr = Expr::UnionAtIndex { + structure: box_ptr, + union_layout: box_union_layout, + tag_id: 0, + index: 0, + }; + let elem_stmt = |next| Stmt::Let(elem, elem_expr, *elem_layout, next); + + // + // Modify element refcount + // + + let mod_elem_unit = root.create_symbol(ident_ids, "mod_elem_unit"); + let mod_elem_args = refcount_args(root, ctx, elem); + let mod_elem_expr = root + .call_specialized_op(ident_ids, ctx, *elem_layout, mod_elem_args) + .unwrap(); + let mod_elem_stmt = |next| Stmt::Let(mod_elem_unit, mod_elem_expr, LAYOUT_UNIT, next); + + // + // Next loop iteration + // + let next_addr = root.create_symbol(ident_ids, "next_addr"); + let next_addr_stmt = + |next| let_lowlevel(arena, layout_isize, next_addr, NumAdd, &[addr, size], next); + + // + // Control flow + // + + let is_end = root.create_symbol(ident_ids, "is_end"); + let is_end_stmt = |next| let_lowlevel(arena, LAYOUT_BOOL, is_end, NumGte, &[addr, end], next); + + let if_end_of_list = Stmt::Switch { + cond_symbol: is_end, + cond_layout: LAYOUT_BOOL, + ret_layout, + branches: root + .arena + .alloc([(1, BranchInfo::None, return_unit(root, ident_ids))]), + default_branch: ( + BranchInfo::None, + arena.alloc(box_stmt(arena.alloc( + // + elem_stmt(arena.alloc( + // + mod_elem_stmt(arena.alloc( + // + next_addr_stmt(arena.alloc( + // + Stmt::Jump(elems_loop, arena.alloc([next_addr])), + )), + )), + )), + ))), + ), + }; + + let joinpoint_loop = Stmt::Join { + id: elems_loop, + parameters: arena.alloc([param_addr]), + body: arena.alloc( + // + is_end_stmt( + // + arena.alloc(if_end_of_list), + ), + ), + remainder: root + .arena + .alloc(Stmt::Jump(elems_loop, arena.alloc([start]))), + }; + + start_stmt(arena.alloc( + // + size_stmt(arena.alloc( + // + list_size_stmt(arena.alloc( + // + end_stmt(arena.alloc( + // + joinpoint_loop, + )), + )), + )), + )) +} diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs index ee6729954d..669b1d9066 100644 --- a/compiler/test_gen/src/gen_refcount.rs +++ b/compiler/test_gen/src/gen_refcount.rs @@ -41,3 +41,79 @@ fn str_dealloc() { &[0] ); } + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn list_int_inc() { + assert_refcounts!( + indoc!( + r#" + list = [0x111, 0x222, 0x333] + [list, list, list] + "# + ), + RocList>, + &[ + 3, // list + 1 // result + ] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn list_int_dealloc() { + assert_refcounts!( + indoc!( + r#" + list = [0x111, 0x222, 0x333] + List.len [list, list, list] + "# + ), + usize, + &[ + 0, // list + 0 // result + ] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn list_str_inc() { + assert_refcounts!( + indoc!( + r#" + s = Str.concat "A long enough string " "to be heap-allocated" + list = [s, s, s] + [list, list] + "# + ), + RocList>, + &[ + 6, // s + 2, // list + 1 // result + ] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn list_str_dealloc() { + assert_refcounts!( + indoc!( + r#" + s = Str.concat "A long enough string " "to be heap-allocated" + list = [s, s, s] + List.len [list, list] + "# + ), + usize, + &[ + 0, // s + 0, // list + 0 // result + ] + ); +} From 101ac69de2b16df2ace7b20fef1b4be064183d69 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 30 Dec 2021 20:00:25 +0000 Subject: [PATCH 034/541] Disable refcounting test code when not in use --- .../test_gen/src/helpers/wasm_test_platform.c | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/compiler/test_gen/src/helpers/wasm_test_platform.c b/compiler/test_gen/src/helpers/wasm_test_platform.c index f866e93e87..5ed3093c03 100644 --- a/compiler/test_gen/src/helpers/wasm_test_platform.c +++ b/compiler/test_gen/src/helpers/wasm_test_platform.c @@ -88,20 +88,23 @@ void *roc_realloc(void *ptr, size_t new_size, size_t old_size, void roc_dealloc(void *ptr, unsigned int alignment) { - // Null out the entry in the test array to indicate that it was freed - // Then even if malloc reuses the space, everything still works - size_t *rc_ptr = alloc_ptr_to_rc_ptr(ptr, alignment); - int i = 0; - for (; i < rc_pointers_index; ++i) + if (rc_pointers) { - if (rc_pointers[i] == rc_ptr) + // Null out the entry in the test array to indicate that it was freed + // Then even if malloc reuses the space, everything still works + size_t *rc_ptr = alloc_ptr_to_rc_ptr(ptr, alignment); + int i = 0; + for (; i < rc_pointers_index; ++i) { - rc_pointers[i] = NULL; - break; + if (rc_pointers[i] == rc_ptr) + { + rc_pointers[i] = NULL; + break; + } } + int was_found = i < rc_pointers_index; + ASSERT(was_found); } - int was_found = i < rc_pointers_index; - ASSERT(was_found); #if ENABLE_PRINTF printf("roc_dealloc deallocated %p with alignment %zd\n", ptr, alignment); From fda6c708357a8b636d2e1142d6594b1436ee86d5 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 30 Dec 2021 18:21:28 -0600 Subject: [PATCH 035/541] Mark patterns in lambda argument position as having a presence constraint Closes #2299 --- compiler/constrain/src/expr.rs | 2 +- compiler/solve/tests/solve_expr.rs | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 71c05c8281..0b72c6f1ce 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -77,7 +77,7 @@ fn constrain_untyped_args( loc_pattern.region, pattern_expected, &mut pattern_state, - false, + true, ); vars.push(*pattern_var); diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 6a55b884a9..49c43e9c61 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -1327,7 +1327,7 @@ mod solve_expr { \Foo -> 42 "# ), - "[ Foo ]* -> Num *", + "[ Foo ] -> Num *", ); } @@ -1339,7 +1339,7 @@ mod solve_expr { \@Foo -> 42 "# ), - "[ @Foo ]* -> Num *", + "[ @Foo ] -> Num *", ); } @@ -1419,7 +1419,7 @@ mod solve_expr { \Foo x -> Foo x "# ), - "[ Foo a ]* -> [ Foo a ]*", + "[ Foo a ] -> [ Foo a ]*", ); } @@ -1431,7 +1431,7 @@ mod solve_expr { \Foo x _ -> Foo x "y" "# ), - "[ Foo a * ]* -> [ Foo a Str ]*", + "[ Foo a * ] -> [ Foo a Str ]*", ); } @@ -4909,4 +4909,17 @@ mod solve_expr { "{ x : [ Blue, Red ], y ? Num a }* -> Num a", ) } + + #[test] + // Issue #2299 + fn infer_union_argument_position() { + infer_eq_without_problem( + indoc!( + r#" + \UserId id -> id + 1 + "# + ), + "[ UserId (Num a) ] -> Num a", + ) + } } From 0c81302d29281a7b42a75e546a759a1f7f100ee4 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 30 Dec 2021 19:37:58 -0600 Subject: [PATCH 036/541] Admit tag destructuring in definitions --- compiler/parse/src/ast.rs | 4 ++ compiler/parse/src/expr.rs | 2 +- ...destructure_tag_assignment.expr.result-ast | 38 +++++++++++++++++++ .../pass/destructure_tag_assignment.expr.roc | 2 + compiler/parse/tests/test_parse.rs | 1 + 5 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast create mode 100644 compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.roc diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index 68c94b8217..9106623060 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -791,6 +791,10 @@ impl<'a> Expr<'a> { value: self, } } + + pub fn is_tag(&self) -> bool { + matches!(self, Expr::GlobalTag(_) | Expr::PrivateTag(_)) + } } macro_rules! impl_extract_spaces { diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 5b5e19c352..6e34e6fd0d 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -410,7 +410,7 @@ impl<'a> ExprState<'a> { let fail = EExpr::BadOperator(opchar, loc_op.region.start()); Err(fail) - } else if !self.arguments.is_empty() { + } else if !self.expr.value.is_tag() && !self.arguments.is_empty() { let region = Region::across_all(self.arguments.iter().map(|v| &v.region)); Err(argument_error(region, loc_op.region.start())) diff --git a/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast b/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast new file mode 100644 index 0000000000..58acb2a0fd --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast @@ -0,0 +1,38 @@ +Defs( + [ + |L 0-0, C 0-36| Body( + |L 0-0, C 0-5| Apply( + |L 0-0, C 0-5| GlobalTag( + "Email", + ), + [ + |L 0-0, C 6-9| Identifier( + "str", + ), + ], + ), + |L 0-0, C 12-36| Apply( + |L 0-0, C 12-17| GlobalTag( + "Email", + ), + [ + |L 0-0, C 18-36| Str( + PlainLine( + "blah@example.com", + ), + ), + ], + Space, + ), + ), + ], + |L 1-1, C 0-3| SpaceBefore( + Var { + module_name: "", + ident: "str", + }, + [ + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.roc b/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.roc new file mode 100644 index 0000000000..e97a842431 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.roc @@ -0,0 +1,2 @@ +Email str = Email "blah@example.com" +str diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 3dc061201e..e780e29e26 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -130,6 +130,7 @@ mod test_parse { pass/comment_before_op.expr, pass/comment_inside_empty_list.expr, pass/comment_with_non_ascii.expr, + pass/destructure_tag_assignment.expr, pass/empty_app_header.header, pass/empty_interface_header.header, pass/empty_list.expr, From 8e7ca57458a5cbf1d296fa3c4de0dd31ed337c60 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 30 Dec 2021 19:49:50 -0600 Subject: [PATCH 037/541] Close tag unions that are in the left hand side of an assignment --- compiler/constrain/src/expr.rs | 2 +- compiler/solve/tests/solve_expr.rs | 13 ++++++ reporting/tests/test_reporting.rs | 64 +++++++++++++++++++++++++----- 3 files changed, 69 insertions(+), 10 deletions(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 0b72c6f1ce..c0cfffa07b 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1144,7 +1144,7 @@ fn constrain_def_pattern(env: &Env, loc_pattern: &Loc, expr_type: Type) loc_pattern.region, pattern_expected, &mut state, - false, + true, ); state diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 49c43e9c61..78982c509b 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -4922,4 +4922,17 @@ mod solve_expr { "[ UserId (Num a) ] -> Num a", ) } + + fn infer_union_def_position() { + infer_eq_without_problem( + indoc!( + r#" + \email -> + Email str = email + Str.isEmpty str + "# + ), + "[ Email Str ] -> Bool", + ) + } } diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index c696c3a0a1..848d23a0e4 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -1662,7 +1662,7 @@ mod test_reporting { But you are trying to use it as: - [ Foo a ]b + [ Foo a ] "# ), ) @@ -2481,20 +2481,26 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── - This pattern does not cover all the possibilities: + This expression is used in an unexpected way: 5│ (Left y) = x - ^^^^^^ + ^ - Other possibilities include: + This `x` value is a: - Right _ + [ Left I64, Right Bool ] - I would have to crash if I saw one of those! You can use a binding to - deconstruct a value if there is only ONE possibility. Use a `when` to - account for all possibilities. + But you are trying to use it as: + + [ Left a ] + + Tip: Seems like a tag typo. Maybe `Right` should be `Left`? + + Tip: Can more type annotations be added? Type annotations always help + me give more specific messages, and I think they could help a lot in + this case "# ), ) @@ -6984,4 +6990,44 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn mismatched_single_tag_arg() { + report_problem_as( + indoc!( + r#" + isEmpty = + \email -> + Email str = email + Str.isEmpty str + + isEmpty (Name "boo") + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 1st argument to `isEmpty` is not what I expect: + + 6│ isEmpty (Name "boo") + ^^^^^^^^^^ + + This `Name` global tag application has the type: + + [ Name Str ]a + + But `isEmpty` needs the 1st argument to be: + + [ Email Str ] + + Tip: Seems like a tag typo. Maybe `Name` should be `Email`? + + Tip: Can more type annotations be added? Type annotations always help + me give more specific messages, and I think they could help a lot in + this case + "# + ), + ) + } } From 983a9f7e170c6038e1d88d1f62e3e23906ebea10 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 30 Dec 2021 20:55:43 -0600 Subject: [PATCH 038/541] Position correct basic block before calling error catcher While building the main function for tests, we may need to generate additional blocks while marshalling arugments to the error catcher into the expected calling convention. This pushes the last block in the main function down, so that the "entry" block may not be last BB in the function. Instead, look up the last insertion block before generating the catcher, and then add a call to the catcher at the end of this last block. Closes #2300 --- cli/tests/repl_eval.rs | 8 ++++++++ compiler/gen_llvm/src/llvm/build.rs | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index 90df54457f..7fed497b66 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -805,6 +805,14 @@ mod repl_eval { ) } + #[test] + fn issue_2300() { + expect_success( + r#"\Email str -> str == """#, + r#" : [ Email Str ] -> Bool"#, + ) + } + // #[test] // fn parse_problem() { // // can't find something that won't parse currently diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 94f3ea89ec..62ffd7ea0f 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -3468,9 +3468,11 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( let arguments_for_call = &arguments_for_call.into_bump_slice(); let call_result = { + let last_block = builder.get_insert_block().unwrap(); + let roc_wrapper_function = make_exception_catcher(env, roc_function, return_layout); - builder.position_at_end(entry); + builder.position_at_end(last_block); call_roc_function( env, From f56754a539c4d475cd8fa4027310c67cef34038b Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 30 Dec 2021 20:59:59 -0600 Subject: [PATCH 039/541] Remove bad test We shouldn't expect to generate code for tag destructures that are type errors --- compiler/test_gen/src/gen_primitives.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index f8443f283e..d339961dc5 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -1989,26 +1989,6 @@ fn pattern_shadowing() { ); } -#[test] -#[cfg(any(feature = "gen-llvm"))] -#[should_panic(expected = "TODO non-exhaustive pattern")] -fn non_exhaustive_pattern_let() { - assert_evals_to!( - indoc!( - r#" - x : Result (Int a) (Float b) - x = Ok 4 - - (Ok y) = x - - y - "# - ), - 0, - i64 - ); -} - #[test] #[cfg(any(feature = "gen-llvm"))] #[ignore] From 2c97c840fc454d8272083aaac8ac852dbd5b630a Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 31 Dec 2021 14:26:23 +0100 Subject: [PATCH 040/541] walk the chain till we find a tag union to make recursive --- compiler/test_gen/src/gen_primitives.rs | 46 +++++++++++++++++++++++++ compiler/unify/src/unify.rs | 13 ++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index d339961dc5..0381b0dd70 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -3138,3 +3138,49 @@ fn alias_defined_out_of_order() { RocStr ); } + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn recursively_build_effect() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + greeting = + hi = "Hello" + name = "World" + + "\(hi), \(name)!" + + main = + when nestHelp 4 is + _ -> greeting + + nestHelp : I64 -> XEffect {} + nestHelp = \m -> + when m is + 0 -> + always {} + + _ -> + always {} |> after \_ -> nestHelp (m - 1) + + + XEffect a : [ @XEffect ({} -> a) ] + + always : a -> XEffect a + always = \x -> @XEffect (\{} -> x) + + after : XEffect a, (a -> XEffect b) -> XEffect b + after = \(@XEffect e), toB -> + @XEffect \{} -> + when toB (e {}) is + @XEffect e2 -> + e2 {} + "# + ), + RocStr::from_slice(b"Hello, World!"), + RocStr + ); +} diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index e9ee4d42db..0a426b4a56 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -833,10 +833,21 @@ enum OtherTags2 { } fn maybe_mark_tag_union_recursive(subs: &mut Subs, tag_union_var: Variable) { - while let Err((recursive, _chain)) = subs.occurs(tag_union_var) { + 'outer: while let Err((recursive, chain)) = subs.occurs(tag_union_var) { let description = subs.get(recursive); if let Content::Structure(FlatType::TagUnion(tags, ext_var)) = description.content { subs.mark_tag_union_recursive(recursive, tags, ext_var); + } else { + // walk the chain till we find a tag union + for v in &chain[..chain.len() - 1] { + let description = subs.get(*v); + if let Content::Structure(FlatType::TagUnion(tags, ext_var)) = description.content { + subs.mark_tag_union_recursive(*v, tags, ext_var); + continue 'outer; + } + } + + panic!("recursive loop does not contain a tag union") } } } From 9762c983557f0c67c1c9d4f4c18233f1dbaba45b Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 31 Dec 2021 14:27:27 +0100 Subject: [PATCH 041/541] make RocStr debug print its contents as a string --- roc_std/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index 16a6d7196c..989f3bfff1 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -680,7 +680,7 @@ impl fmt::Debug for RocStr { f.debug_struct("RocStr") .field("is_small_str", &self.is_small_str()) .field("storage", &self.storage()) - .field("elements", &self.as_slice()) + .field("elements", &self.as_str()) .finish() } } From 655022be78c805d6c41bb23b02b3137b6e1f87e3 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 31 Dec 2021 15:37:53 +0100 Subject: [PATCH 042/541] fix string debug printing --- roc_std/src/lib.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index 989f3bfff1..7f27585556 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -677,11 +677,21 @@ impl From<&str> for RocStr { impl fmt::Debug for RocStr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // RocStr { is_small_str: false, storage: Refcounted(3), elements: [ 1,2,3,4] } - f.debug_struct("RocStr") - .field("is_small_str", &self.is_small_str()) - .field("storage", &self.storage()) - .field("elements", &self.as_str()) - .finish() + + match core::str::from_utf8(self.as_slice()) { + Ok(string) => f + .debug_struct("RocStr") + .field("is_small_str", &self.is_small_str()) + .field("storage", &self.storage()) + .field("string_contents", &string) + .finish(), + Err(_) => f + .debug_struct("RocStr") + .field("is_small_str", &self.is_small_str()) + .field("storage", &self.storage()) + .field("byte_contents", &self.as_slice()) + .finish(), + } } } From ee9f0b2f04debfd58f1f6d1f8a8e68692f43ac4e Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 31 Dec 2021 21:44:07 +0100 Subject: [PATCH 043/541] force imported thunks when assigned to a variable --- compiler/mono/src/ir.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index ddc7b81cd7..66e28453eb 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -6307,14 +6307,21 @@ fn handle_variable_aliasing<'a>( right: Symbol, mut result: Stmt<'a>, ) -> Stmt<'a> { - if env.is_imported_symbol(right) { + if procs.is_imported_module_thunk(right) { // if this is an imported symbol, then we must make sure it is // specialized, and wrap the original in a function pointer. add_needed_external(procs, env, variable, right); - // then we must construct its closure; since imported symbols have no closure, we use the - // empty struct + let res_layout = layout_cache.from_var(env.arena, variable, env.subs); + let layout = return_on_layout_error!(env, res_layout); + force_thunk(env, right, layout, left, env.arena.alloc(result)) + } else if env.is_imported_symbol(right) { + // if this is an imported symbol, then we must make sure it is + // specialized, and wrap the original in a function pointer. + add_needed_external(procs, env, variable, right); + + // then we must construct its closure; since imported symbols have no closure, we use the empty struct let_empty_struct(left, env.arena.alloc(result)) } else { substitute_in_exprs(env.arena, &mut result, left, right); From 06be340bee4678368062514b1069ed3c2887bfb0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 31 Dec 2021 21:45:41 +0100 Subject: [PATCH 044/541] add test for this issue --- cli/tests/cli_run.rs | 8 ++++++++ examples/benchmarks/Issue2279.roc | 9 +++++++++ examples/benchmarks/Issue2279Help.roc | 5 +++++ 3 files changed, 22 insertions(+) create mode 100644 examples/benchmarks/Issue2279.roc create mode 100644 examples/benchmarks/Issue2279Help.roc diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index c765fb8cb3..e190e33981 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -601,6 +601,14 @@ mod cli_run { expected_ending: "", use_valgrind: true, }, + issue2279 => Example { + filename: "Issue2279.roc", + executable_filename: "issue2279", + stdin: &[], + input_file: None, + expected_ending: "Hello, world!\n", + use_valgrind: true, + }, quicksort_app => Example { filename: "QuicksortApp.roc", executable_filename: "quicksortapp", diff --git a/examples/benchmarks/Issue2279.roc b/examples/benchmarks/Issue2279.roc new file mode 100644 index 0000000000..68c0046589 --- /dev/null +++ b/examples/benchmarks/Issue2279.roc @@ -0,0 +1,9 @@ +app "issue2279" + packages { pf: "platform" } + imports [ Issue2279Help, pf.Task ] + provides [ main ] to pf + +main = + text = Issue2279Help.text + + Task.putLine text diff --git a/examples/benchmarks/Issue2279Help.roc b/examples/benchmarks/Issue2279Help.roc new file mode 100644 index 0000000000..6e7332bbeb --- /dev/null +++ b/examples/benchmarks/Issue2279Help.roc @@ -0,0 +1,5 @@ +interface Issue2279Help + exposes [ text ] + imports [] + +text = "Hello, world!" From d66d432716bea70c80ab41b0bb332556cb6beb7c Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 31 Dec 2021 11:14:43 +0000 Subject: [PATCH 045/541] Wasm tests: speed up 3x by only exporting refcount symbols when used 49s vs 2m10s --- compiler/test_gen/src/helpers/wasm.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 3aa682bed9..ba29b0a129 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -36,12 +36,18 @@ fn promote_expr_to_module(src: &str) -> String { buffer } +pub enum BuildType { + Evaluate, + Refcount, +} + #[allow(dead_code)] pub fn compile_and_load<'a, T: Wasm32TestResult>( arena: &'a bumpalo::Bump, src: &str, stdlib: &'a roc_builtins::std::StdLib, _test_wrapper_type_info: PhantomData, + build_type: BuildType, ) -> wasmer::Instance { use std::path::{Path, PathBuf}; @@ -161,7 +167,7 @@ pub fn compile_and_load<'a, T: Wasm32TestResult>( // write the module to a file so the linker can access it std::fs::write(&app_o_file, &module_bytes).unwrap(); - let args = &[ + let mut args = vec![ "wasm-ld", // input files app_o_file.to_str().unwrap(), @@ -182,13 +188,16 @@ pub fn compile_and_load<'a, T: Wasm32TestResult>( "--export", "test_wrapper", "--export", - "init_refcount_test", - "--export", "#UserApp_main_1", ]; + // For some reason, this makes linking ~3x slower + if matches!(build_type, BuildType::Refcount) { + args.extend_from_slice(&["--export", "init_refcount_test"]); + } + let linker_output = std::process::Command::new(&crate::helpers::zig_executable()) - .args(args) + .args(&args) .output() .unwrap(); @@ -227,7 +236,8 @@ where // NOTE the stdlib must be in the arena; just taking a reference will segfault let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); - let instance = crate::helpers::wasm::compile_and_load(&arena, src, stdlib, phantom); + let instance = + crate::helpers::wasm::compile_and_load(&arena, src, stdlib, phantom, BuildType::Evaluate); let memory = instance.exports.get_memory(MEMORY_NAME).unwrap(); @@ -272,7 +282,8 @@ where // NOTE the stdlib must be in the arena; just taking a reference will segfault let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); - let instance = crate::helpers::wasm::compile_and_load(&arena, src, stdlib, phantom); + let instance = + crate::helpers::wasm::compile_and_load(&arena, src, stdlib, phantom, BuildType::Refcount); let memory = instance.exports.get_memory(MEMORY_NAME).unwrap(); From a2e58f85302b8813d80eb728db537574c2a6d610 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 31 Dec 2021 14:52:38 +0000 Subject: [PATCH 046/541] Split wasm test code into smaller well-named functions --- compiler/test_gen/src/helpers/wasm.rs | 197 +++++++++++++++----------- 1 file changed, 113 insertions(+), 84 deletions(-) diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index ba29b0a129..30dbefbedb 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -2,6 +2,7 @@ use core::cell::Cell; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; +use std::path::{Path, PathBuf}; use tempfile::{tempdir, TempDir}; use wasmer::{Memory, WasmPtr}; @@ -49,8 +50,33 @@ pub fn compile_and_load<'a, T: Wasm32TestResult>( _test_wrapper_type_info: PhantomData, build_type: BuildType, ) -> wasmer::Instance { - use std::path::{Path, PathBuf}; + let app_module_bytes = compile_roc_to_wasm_bytes(arena, src, stdlib, _test_wrapper_type_info); + let maybe_src_hash = if DEBUG_LOG_SETTINGS.keep_test_binary { + // Keep the output files for debugging, in a directory with a hash in the name + Some(src_hash(src)) + } else { + // Use a temporary for linking, then delete it + None + }; + + let final_bytes = run_linker(app_module_bytes, maybe_src_hash, build_type); + + load_bytes_into_runtime(final_bytes) +} + +fn src_hash(src: &str) -> u64 { + let mut hash_state = DefaultHasher::new(); + src.hash(&mut hash_state); + hash_state.finish() +} + +fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( + arena: &'a bumpalo::Bump, + src: &str, + stdlib: &'a roc_builtins::std::StdLib, + _test_wrapper_type_info: PhantomData, +) -> Vec { let filename = PathBuf::from("Test.roc"); let src_dir = Path::new("fake/test/path"); @@ -127,94 +153,97 @@ pub fn compile_and_load<'a, T: Wasm32TestResult>( T::insert_test_wrapper(arena, &mut wasm_module, TEST_WRAPPER_NAME, main_fn_index); - let mut module_bytes = std::vec::Vec::with_capacity(4096); - wasm_module.serialize_mut(&mut module_bytes); + let mut app_module_bytes = std::vec::Vec::with_capacity(4096); + wasm_module.serialize_mut(&mut app_module_bytes); - // now, do wasmer stuff + app_module_bytes +} - use wasmer::{Instance, Module, Store}; +fn run_linker( + app_module_bytes: Vec, + maybe_src_hash: Option, + build_type: BuildType, +) -> Vec { + let tmp_dir: TempDir; // directory for normal test runs, deleted when dropped + let debug_dir: String; // persistent directory for debugging - let store = Store::default(); - - let wasmer_module = { - let tmp_dir: TempDir; // directory for normal test runs, deleted when dropped - let debug_dir: String; // persistent directory for debugging - - let wasm_build_dir: &Path = if DEBUG_LOG_SETTINGS.keep_test_binary { - // Directory name based on a hash of the Roc source - let mut hash_state = DefaultHasher::new(); - src.hash(&mut hash_state); - let src_hash = hash_state.finish(); - debug_dir = format!("/tmp/roc/gen_wasm/{:016x}", src_hash); - std::fs::create_dir_all(&debug_dir).unwrap(); - println!( - "Debug commands:\n\twasm-objdump -dx {}/app.o\n\twasm-objdump -dx {}/final.wasm", - &debug_dir, &debug_dir, - ); - Path::new(&debug_dir) - } else { - tmp_dir = tempdir().unwrap(); - tmp_dir.path() - }; - - let final_wasm_file = wasm_build_dir.join("final.wasm"); - let app_o_file = wasm_build_dir.join("app.o"); - let test_out_dir = std::env::var(OUT_DIR_VAR).unwrap(); - let test_platform_o = format!("{}/{}.o", test_out_dir, PLATFORM_FILENAME); - let libc_a_file = std::env::var(LIBC_PATH_VAR).unwrap(); - let compiler_rt_o_file = std::env::var(COMPILER_RT_PATH_VAR).unwrap(); - - // write the module to a file so the linker can access it - std::fs::write(&app_o_file, &module_bytes).unwrap(); - - let mut args = vec![ - "wasm-ld", - // input files - app_o_file.to_str().unwrap(), - bitcode::BUILTINS_WASM32_OBJ_PATH, - &test_platform_o, - &libc_a_file, - &compiler_rt_o_file, - // output - "-o", - final_wasm_file.to_str().unwrap(), - // we don't define `_start` - "--no-entry", - // If you only specify test_wrapper, it will stop at the call to UserApp_main_1 - // But if you specify both exports, you get all the dependencies. - // - // It seems that it will not write out an export you didn't explicitly specify, - // even if it's a dependency of another export! - "--export", - "test_wrapper", - "--export", - "#UserApp_main_1", - ]; - - // For some reason, this makes linking ~3x slower - if matches!(build_type, BuildType::Refcount) { - args.extend_from_slice(&["--export", "init_refcount_test"]); - } - - let linker_output = std::process::Command::new(&crate::helpers::zig_executable()) - .args(&args) - .output() - .unwrap(); - - if !linker_output.status.success() { - print!("\nLINKER FAILED\n"); - for arg in args { - print!("{} ", arg); - } - println!("\n{}", std::str::from_utf8(&linker_output.stdout).unwrap()); - println!("{}", std::str::from_utf8(&linker_output.stderr).unwrap()); - } - - Module::from_file(&store, &final_wasm_file).unwrap() + let wasm_build_dir: &Path = if let Some(src_hash) = maybe_src_hash { + debug_dir = format!("/tmp/roc/gen_wasm/{:016x}", src_hash); + std::fs::create_dir_all(&debug_dir).unwrap(); + println!( + "Debug commands:\n\twasm-objdump -dx {}/app.o\n\twasm-objdump -dx {}/final.wasm", + &debug_dir, &debug_dir, + ); + Path::new(&debug_dir) + } else { + tmp_dir = tempdir().unwrap(); + tmp_dir.path() }; - // First, we create the `WasiEnv` + let final_wasm_file = wasm_build_dir.join("final.wasm"); + let app_o_file = wasm_build_dir.join("app.o"); + let test_out_dir = std::env::var(OUT_DIR_VAR).unwrap(); + let test_platform_o = format!("{}/{}.o", test_out_dir, PLATFORM_FILENAME); + let libc_a_file = std::env::var(LIBC_PATH_VAR).unwrap(); + let compiler_rt_o_file = std::env::var(COMPILER_RT_PATH_VAR).unwrap(); + + // write the module to a file so the linker can access it + std::fs::write(&app_o_file, &app_module_bytes).unwrap(); + + let mut args = vec![ + "wasm-ld", + // input files + app_o_file.to_str().unwrap(), + bitcode::BUILTINS_WASM32_OBJ_PATH, + &test_platform_o, + &libc_a_file, + &compiler_rt_o_file, + // output + "-o", + final_wasm_file.to_str().unwrap(), + // we don't define `_start` + "--no-entry", + // If you only specify test_wrapper, it will stop at the call to UserApp_main_1 + // But if you specify both exports, you get all the dependencies. + // + // It seems that it will not write out an export you didn't explicitly specify, + // even if it's a dependency of another export! + "--export", + "test_wrapper", + "--export", + "#UserApp_main_1", + ]; + + // For some reason, this makes linking ~3x slower + if matches!(build_type, BuildType::Refcount) { + args.extend_from_slice(&["--export", "init_refcount_test"]); + } + + let linker_output = std::process::Command::new(&crate::helpers::zig_executable()) + .args(&args) + .output() + .unwrap(); + + if !linker_output.status.success() { + print!("\nLINKER FAILED\n"); + for arg in args { + print!("{} ", arg); + } + println!("\n{}", std::str::from_utf8(&linker_output.stdout).unwrap()); + println!("{}", std::str::from_utf8(&linker_output.stderr).unwrap()); + } + + std::fs::read(final_wasm_file).unwrap() +} + +fn load_bytes_into_runtime(bytes: Vec) -> wasmer::Instance { + use wasmer::{Module, Store}; use wasmer_wasi::WasiState; + + let store = Store::default(); + let wasmer_module = Module::new(&store, &bytes).unwrap(); + + // First, we create the `WasiEnv` let mut wasi_env = WasiState::new("hello").finalize().unwrap(); // Then, we get the import object related to our WASI @@ -223,7 +252,7 @@ pub fn compile_and_load<'a, T: Wasm32TestResult>( .import_object(&wasmer_module) .unwrap_or_else(|_| wasmer::imports!()); - Instance::new(&wasmer_module, &import_object).unwrap() + wasmer::Instance::new(&wasmer_module, &import_object).unwrap() } #[allow(dead_code)] From 01f293125ad9c70a8aca8f462813448347385dee Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 31 Dec 2021 14:54:01 +0000 Subject: [PATCH 047/541] Delete debug code that has been superseded --- compiler/test_gen/src/helpers/wasm.rs | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 30dbefbedb..0065a0ce6b 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -115,29 +115,6 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( .. } = loaded; - // You can comment and uncomment this block out to get more useful information - // while you're working on the wasm backend! - { - // println!("=========== Procedures =========="); - // if PRETTY_PRINT_IR_SYMBOLS { - // println!(""); - // for proc in procedures.values() { - // println!("{}", proc.to_pretty(200)); - // } - // } else { - // println!("{:?}", procedures.values()); - // } - // println!("=================================\n"); - - // println!("=========== Interns =========="); - // println!("{:?}", interns); - // println!("=================================\n"); - - // println!("=========== Exposed =========="); - // println!("{:?}", exposed_to_host); - // println!("=================================\n"); - } - debug_assert_eq!(exposed_to_host.len(), 1); let exposed_to_host = exposed_to_host.keys().copied().collect::>(); From 84661b7ae1096c726622ac7c4022b0ea1d60e61c Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 31 Dec 2021 14:57:42 +0000 Subject: [PATCH 048/541] Rename BuildType -> TestType --- compiler/test_gen/src/helpers/wasm.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 0065a0ce6b..d179eeaadd 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -37,8 +37,10 @@ fn promote_expr_to_module(src: &str) -> String { buffer } -pub enum BuildType { +pub enum TestType { + /// Test that some Roc code evaluates to the right result Evaluate, + /// Test that some Roc values have the right refcount Refcount, } @@ -48,7 +50,7 @@ pub fn compile_and_load<'a, T: Wasm32TestResult>( src: &str, stdlib: &'a roc_builtins::std::StdLib, _test_wrapper_type_info: PhantomData, - build_type: BuildType, + test_type: TestType, ) -> wasmer::Instance { let app_module_bytes = compile_roc_to_wasm_bytes(arena, src, stdlib, _test_wrapper_type_info); @@ -60,7 +62,7 @@ pub fn compile_and_load<'a, T: Wasm32TestResult>( None }; - let final_bytes = run_linker(app_module_bytes, maybe_src_hash, build_type); + let final_bytes = run_linker(app_module_bytes, maybe_src_hash, test_type); load_bytes_into_runtime(final_bytes) } @@ -139,7 +141,7 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( fn run_linker( app_module_bytes: Vec, maybe_src_hash: Option, - build_type: BuildType, + test_type: TestType, ) -> Vec { let tmp_dir: TempDir; // directory for normal test runs, deleted when dropped let debug_dir: String; // persistent directory for debugging @@ -192,7 +194,7 @@ fn run_linker( ]; // For some reason, this makes linking ~3x slower - if matches!(build_type, BuildType::Refcount) { + if matches!(test_type, TestType::Refcount) { args.extend_from_slice(&["--export", "init_refcount_test"]); } @@ -243,7 +245,7 @@ where let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); let instance = - crate::helpers::wasm::compile_and_load(&arena, src, stdlib, phantom, BuildType::Evaluate); + crate::helpers::wasm::compile_and_load(&arena, src, stdlib, phantom, TestType::Evaluate); let memory = instance.exports.get_memory(MEMORY_NAME).unwrap(); @@ -289,7 +291,7 @@ where let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); let instance = - crate::helpers::wasm::compile_and_load(&arena, src, stdlib, phantom, BuildType::Refcount); + crate::helpers::wasm::compile_and_load(&arena, src, stdlib, phantom, TestType::Refcount); let memory = instance.exports.get_memory(MEMORY_NAME).unwrap(); From 4a6b79b686fd2dc8d4344299a17c4bc76d91a941 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 31 Dec 2021 15:27:05 +0000 Subject: [PATCH 049/541] Wasm tests: Skip linking step (and all filesystem access) when not needed --- compiler/test_gen/src/helpers/wasm.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index d179eeaadd..d6372c4448 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -52,17 +52,23 @@ pub fn compile_and_load<'a, T: Wasm32TestResult>( _test_wrapper_type_info: PhantomData, test_type: TestType, ) -> wasmer::Instance { - let app_module_bytes = compile_roc_to_wasm_bytes(arena, src, stdlib, _test_wrapper_type_info); + let (app_module_bytes, needs_linking) = + compile_roc_to_wasm_bytes(arena, src, stdlib, _test_wrapper_type_info); - let maybe_src_hash = if DEBUG_LOG_SETTINGS.keep_test_binary { + let keep_test_binary = DEBUG_LOG_SETTINGS.keep_test_binary; + let build_dir_hash = if keep_test_binary { // Keep the output files for debugging, in a directory with a hash in the name Some(src_hash(src)) } else { - // Use a temporary for linking, then delete it + // Use a temporary build directory for linking, then delete it None }; - let final_bytes = run_linker(app_module_bytes, maybe_src_hash, test_type); + let final_bytes = if needs_linking || keep_test_binary { + run_linker(app_module_bytes, build_dir_hash, test_type) + } else { + app_module_bytes + }; load_bytes_into_runtime(final_bytes) } @@ -78,7 +84,7 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( src: &str, stdlib: &'a roc_builtins::std::StdLib, _test_wrapper_type_info: PhantomData, -) -> Vec { +) -> (Vec, bool) { let filename = PathBuf::from("Test.roc"); let src_dir = Path::new("fake/test/path"); @@ -132,21 +138,23 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( T::insert_test_wrapper(arena, &mut wasm_module, TEST_WRAPPER_NAME, main_fn_index); + let needs_linking = !wasm_module.import.entries.is_empty(); + let mut app_module_bytes = std::vec::Vec::with_capacity(4096); wasm_module.serialize_mut(&mut app_module_bytes); - app_module_bytes + (app_module_bytes, needs_linking) } fn run_linker( app_module_bytes: Vec, - maybe_src_hash: Option, + build_dir_hash: Option, test_type: TestType, ) -> Vec { let tmp_dir: TempDir; // directory for normal test runs, deleted when dropped let debug_dir: String; // persistent directory for debugging - let wasm_build_dir: &Path = if let Some(src_hash) = maybe_src_hash { + let wasm_build_dir: &Path = if let Some(src_hash) = build_dir_hash { debug_dir = format!("/tmp/roc/gen_wasm/{:016x}", src_hash); std::fs::create_dir_all(&debug_dir).unwrap(); println!( From 6fabeb345b596bcdfa9f9cdb64a3a482401aa5e0 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 31 Dec 2021 15:33:00 +0000 Subject: [PATCH 050/541] edit comment --- compiler/test_gen/src/helpers/wasm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index d6372c4448..b27a6b6776 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -201,8 +201,8 @@ fn run_linker( "#UserApp_main_1", ]; - // For some reason, this makes linking ~3x slower if matches!(test_type, TestType::Refcount) { + // If we always export this, tests run ~2.5x slower! Not sure why. args.extend_from_slice(&["--export", "init_refcount_test"]); } From 12a330dd7603145608f8829cc56ec2db507a4571 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 31 Dec 2021 08:34:04 +0000 Subject: [PATCH 051/541] Refactor to move all refcount IR gen to the same file --- compiler/mono/src/code_gen_help/mod.rs | 75 +-------------------- compiler/mono/src/code_gen_help/refcount.rs | 73 +++++++++++++++++++- 2 files changed, 75 insertions(+), 73 deletions(-) diff --git a/compiler/mono/src/code_gen_help/mod.rs b/compiler/mono/src/code_gen_help/mod.rs index e62a9a5265..b1f399e6d7 100644 --- a/compiler/mono/src/code_gen_help/mod.rs +++ b/compiler/mono/src/code_gen_help/mod.rs @@ -6,7 +6,7 @@ use roc_module::low_level::LowLevel; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use crate::ir::{ - Call, CallSpecId, CallType, Expr, HostExposedLayouts, Literal, ModifyRc, Proc, ProcLayout, + Call, CallSpecId, CallType, Expr, HostExposedLayouts, ModifyRc, Proc, ProcLayout, SelfRecursive, Stmt, UpdateModeId, }; use crate::layout::{Builtin, Layout, UnionLayout}; @@ -129,83 +129,14 @@ impl<'a> CodeGenHelp<'a> { return (following, Vec::new_in(self.arena)); } - let arena = self.arena; - let mut ctx = Context { new_linker_data: Vec::new_in(self.arena), recursive_union: None, op: HelperOp::from(modify), }; - match modify { - ModifyRc::Inc(structure, amount) => { - let layout_isize = self.layout_isize; - - // Define a constant for the amount to increment - let amount_sym = self.create_symbol(ident_ids, "amount"); - let amount_expr = Expr::Literal(Literal::Int(*amount as i128)); - let amount_stmt = |next| Stmt::Let(amount_sym, amount_expr, layout_isize, next); - - // Call helper proc, passing the Roc structure and constant amount - let call_result_empty = self.create_symbol(ident_ids, "call_result_empty"); - let call_expr = self - .call_specialized_op( - ident_ids, - &mut ctx, - layout, - arena.alloc([*structure, amount_sym]), - ) - .unwrap(); - let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following); - let rc_stmt = arena.alloc(amount_stmt(arena.alloc(call_stmt))); - - (rc_stmt, ctx.new_linker_data) - } - - ModifyRc::Dec(structure) => { - // Call helper proc, passing the Roc structure - let call_result_empty = self.create_symbol(ident_ids, "call_result_empty"); - let call_expr = self - .call_specialized_op(ident_ids, &mut ctx, layout, arena.alloc([*structure])) - .unwrap(); - - let rc_stmt = arena.alloc(Stmt::Let( - call_result_empty, - call_expr, - LAYOUT_UNIT, - following, - )); - - (rc_stmt, ctx.new_linker_data) - } - - ModifyRc::DecRef(structure) => { - // No generated procs for DecRef, just lowlevel ops - let rc_ptr_sym = self.create_symbol(ident_ids, "rc_ptr"); - - // Pass the refcount pointer to the lowlevel call (see utils.zig) - let call_result_empty = self.create_symbol(ident_ids, "call_result_empty"); - let call_expr = Expr::Call(Call { - call_type: CallType::LowLevel { - op: LowLevel::RefCountDec, - update_mode: UpdateModeId::BACKEND_DUMMY, - }, - arguments: arena.alloc([rc_ptr_sym]), - }); - let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following); - - // FIXME: `structure` is a pointer to the stack, not the heap! - let rc_stmt = arena.alloc(refcount::rc_ptr_from_data_ptr( - self, - ident_ids, - *structure, - rc_ptr_sym, - arena.alloc(call_stmt), - )); - - (rc_stmt, ctx.new_linker_data) - } - } + let rc_stmt = refcount::refcount_stmt(self, ident_ids, &mut ctx, layout, modify, following); + (self.arena.alloc(rc_stmt), ctx.new_linker_data) } /// Replace a generic `Lowlevel::Eq` call with a specialized helper proc. diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index b81dc84082..aa56898734 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -4,7 +4,7 @@ use roc_module::symbol::{IdentIds, Symbol}; use crate::code_gen_help::let_lowlevel; use crate::ir::{ - BranchInfo, Call, CallType, Expr, JoinPointId, Literal, Param, Stmt, UpdateModeId, + BranchInfo, Call, CallType, Expr, JoinPointId, Literal, ModifyRc, Param, Stmt, UpdateModeId, }; use crate::layout::{Builtin, Layout, UnionLayout}; @@ -18,6 +18,77 @@ const LAYOUT_U32: Layout = Layout::Builtin(Builtin::Int(IntWidth::U32)); const ARG_1: Symbol = Symbol::ARG_1; const ARG_2: Symbol = Symbol::ARG_2; +pub fn refcount_stmt<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + layout: Layout<'a>, + modify: &ModifyRc, + following: &'a Stmt<'a>, +) -> Stmt<'a> { + let arena = root.arena; + + match modify { + ModifyRc::Inc(structure, amount) => { + let layout_isize = root.layout_isize; + + // Define a constant for the amount to increment + let amount_sym = root.create_symbol(ident_ids, "amount"); + let amount_expr = Expr::Literal(Literal::Int(*amount as i128)); + let amount_stmt = |next| Stmt::Let(amount_sym, amount_expr, layout_isize, next); + + // Call helper proc, passing the Roc structure and constant amount + let call_result_empty = root.create_symbol(ident_ids, "call_result_empty"); + let call_expr = root + .call_specialized_op( + ident_ids, + ctx, + layout, + arena.alloc([*structure, amount_sym]), + ) + .unwrap(); + + let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following); + amount_stmt(arena.alloc(call_stmt)) + } + + ModifyRc::Dec(structure) => { + // Call helper proc, passing the Roc structure + let call_result_empty = root.create_symbol(ident_ids, "call_result_empty"); + let call_expr = root + .call_specialized_op(ident_ids, ctx, layout, arena.alloc([*structure])) + .unwrap(); + + Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following) + } + + ModifyRc::DecRef(structure) => { + // No generated procs for DecRef, just lowlevel ops + let rc_ptr_sym = root.create_symbol(ident_ids, "rc_ptr"); + + // Pass the refcount pointer to the lowlevel call (see utils.zig) + let call_result_empty = root.create_symbol(ident_ids, "call_result_empty"); + let call_expr = Expr::Call(Call { + call_type: CallType::LowLevel { + op: LowLevel::RefCountDec, + update_mode: UpdateModeId::BACKEND_DUMMY, + }, + arguments: arena.alloc([rc_ptr_sym]), + }); + let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following); + + // FIXME: `structure` is a pointer to the stack, not the heap! + rc_ptr_from_data_ptr( + root, + ident_ids, + *structure, + rc_ptr_sym, + arena.alloc(call_stmt), + ) + } + } +} + pub fn refcount_generic<'a>( root: &mut CodeGenHelp<'a>, ident_ids: &mut IdentIds, From ff0c0766ad1171172df73829a2f9e776bb8f19c3 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 31 Dec 2021 23:52:05 +0000 Subject: [PATCH 052/541] Note on why refcount tests need result type --- compiler/test_gen/src/helpers/wasm.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index b27a6b6776..dc5aa2719b 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -417,8 +417,10 @@ pub fn identity(value: T) -> T { #[allow(unused_macros)] macro_rules! assert_refcounts { + // We need the result type to generate the test_wrapper, even though we ignore the value! + // We can't just call `main` with no args, because some tests return structs, via pointer arg! + // Also we need to know how much stack space to reserve for the struct. ($src: expr, $ty: ty, $expected_refcounts: expr) => { - // Same as above, except with an additional transformation argument. { let phantom = std::marker::PhantomData; let num_refcounts = $expected_refcounts.len(); From 310de090a3e98789780174da8eba898ded004610 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 1 Jan 2022 00:39:36 +0000 Subject: [PATCH 053/541] Wasm: Refcounting for struct fields --- compiler/mono/src/code_gen_help/refcount.rs | 47 ++++++++++++++++++++- compiler/test_gen/src/gen_refcount.rs | 33 +++++++++++++++ compiler/test_gen/src/helpers/wasm.rs | 25 +++++------ 3 files changed, 88 insertions(+), 17 deletions(-) diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index aa56898734..a1e5248840 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -107,7 +107,7 @@ pub fn refcount_generic<'a>( refcount_list(root, ident_ids, ctx, &layout, elem_layout) } Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_)) => rc_todo(), - Layout::Struct(_) => rc_todo(), + Layout::Struct(field_layouts) => refcount_struct(root, ident_ids, ctx, field_layouts), Layout::Union(_) => rc_todo(), Layout::LambdaSet(_) => { unreachable!("Refcounting on LambdaSet is invalid. Should be a Union at runtime.") @@ -120,7 +120,10 @@ pub fn refcount_generic<'a>( // In the short term, it helps us to skip refcounting and let it leak, so we can make // progress incrementally. Kept in sync with generate_procs using assertions. pub fn is_rc_implemented_yet(layout: &Layout) -> bool { - matches!(layout, Layout::Builtin(Builtin::Str | Builtin::List(_))) + matches!( + layout, + Layout::Builtin(Builtin::Str | Builtin::List(_)) | Layout::Struct(_) + ) } fn return_unit<'a>(root: &CodeGenHelp<'a>, ident_ids: &mut IdentIds) -> Stmt<'a> { @@ -607,3 +610,43 @@ fn refcount_list_elems<'a>( )), )) } + +fn refcount_struct<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + field_layouts: &'a [Layout<'a>], +) -> Stmt<'a> { + println!("refcount_struct"); + + let mut stmt = return_unit(root, ident_ids); + + for (i, field_layout) in field_layouts.iter().enumerate().rev() { + if field_layout.contains_refcounted() { + let field_val = root.create_symbol(ident_ids, &format!("field_val_{}", i)); + let field_val_expr = Expr::StructAtIndex { + index: i as u64, + field_layouts, + structure: ARG_1, + }; + let field_val_stmt = |next| Stmt::Let(field_val, field_val_expr, *field_layout, next); + + let mod_unit = root.create_symbol(ident_ids, &format!("mod_field_{}", i)); + let mod_args = refcount_args(root, ctx, field_val); + let mod_expr = root + .call_specialized_op(ident_ids, ctx, *field_layout, mod_args) + .unwrap(); + let mod_stmt = |next| Stmt::Let(mod_unit, mod_expr, LAYOUT_UNIT, next); + + stmt = field_val_stmt(root.arena.alloc( + // + mod_stmt(root.arena.alloc( + // + stmt, + )), + )) + } + } + + stmt +} diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs index 669b1d9066..cb795d6603 100644 --- a/compiler/test_gen/src/gen_refcount.rs +++ b/compiler/test_gen/src/gen_refcount.rs @@ -117,3 +117,36 @@ fn list_str_dealloc() { ] ); } + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn struct_inc() { + assert_refcounts!( + indoc!( + r#" + s = Str.concat "A long enough string " "to be heap-allocated" + r1 = { a: 123, b: s, c: s } + { y: r1, z: r1 } + "# + ), + [(i64, RocStr, RocStr); 2], + &[4] // s + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn struct_dealloc() { + assert_refcounts!( + indoc!( + r#" + s = Str.concat "A long enough string " "to be heap-allocated" + r1 = { a: 123, b: s, c: s } + r2 = { x: 456, y: r1, z: r1 } + r2.x + "# + ), + i64, + &[0] // s + ); +} diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index dc5aa2719b..47973ea05d 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -420,23 +420,18 @@ macro_rules! assert_refcounts { // We need the result type to generate the test_wrapper, even though we ignore the value! // We can't just call `main` with no args, because some tests return structs, via pointer arg! // Also we need to know how much stack space to reserve for the struct. - ($src: expr, $ty: ty, $expected_refcounts: expr) => { - { - let phantom = std::marker::PhantomData; - let num_refcounts = $expected_refcounts.len(); - let result = $crate::helpers::wasm::assert_wasm_refcounts_help::<$ty>( - $src, - phantom, - num_refcounts, - ); - match result { - Err(msg) => panic!("{:?}", msg), - Ok(actual_refcounts) => { - assert_eq!(&actual_refcounts, $expected_refcounts) - } + ($src: expr, $ty: ty, $expected_refcounts: expr) => {{ + let phantom = std::marker::PhantomData; + let num_refcounts = $expected_refcounts.len(); + let result = + $crate::helpers::wasm::assert_wasm_refcounts_help::<$ty>($src, phantom, num_refcounts); + match result { + Err(msg) => panic!("{:?}", msg), + Ok(actual_refcounts) => { + assert_eq!(&actual_refcounts, $expected_refcounts) } } - }; + }}; } #[allow(unused_imports)] From 8078afc74f743bce35e63fd3a4fa785549aeeee6 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 1 Jan 2022 11:39:55 +0000 Subject: [PATCH 054/541] Delete a stray println from debug --- compiler/mono/src/code_gen_help/refcount.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index a1e5248840..d4c1cdcb15 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -617,8 +617,6 @@ fn refcount_struct<'a>( ctx: &mut Context<'a>, field_layouts: &'a [Layout<'a>], ) -> Stmt<'a> { - println!("refcount_struct"); - let mut stmt = return_unit(root, ident_ids); for (i, field_layout) in field_layouts.iter().enumerate().rev() { From e55806fe2732b492ba2adcb28fc2e313d683490f Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 1 Jan 2022 12:32:47 +0000 Subject: [PATCH 055/541] Update is_rc_implemented_yet --- compiler/mono/src/code_gen_help/refcount.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index d4c1cdcb15..89965fa64f 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -120,10 +120,13 @@ pub fn refcount_generic<'a>( // In the short term, it helps us to skip refcounting and let it leak, so we can make // progress incrementally. Kept in sync with generate_procs using assertions. pub fn is_rc_implemented_yet(layout: &Layout) -> bool { - matches!( - layout, - Layout::Builtin(Builtin::Str | Builtin::List(_)) | Layout::Struct(_) - ) + match layout { + Layout::Builtin(Builtin::Dict(..) | Builtin::Set(_)) => false, + Layout::Builtin(Builtin::List(elem_layout)) => is_rc_implemented_yet(elem_layout), + Layout::Builtin(_) => true, + Layout::Struct(fields) => fields.iter().all(is_rc_implemented_yet), + _ => false, + } } fn return_unit<'a>(root: &CodeGenHelp<'a>, ident_ids: &mut IdentIds) -> Stmt<'a> { From 176bb6f6aae7c8ef431c0fa08db1ddc5e5aee551 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 1 Jan 2022 19:19:07 +0000 Subject: [PATCH 056/541] Use joinpoints for DecRef --- compiler/mono/src/code_gen_help/mod.rs | 34 +++++----- compiler/mono/src/code_gen_help/refcount.rs | 72 +++++++++++---------- 2 files changed, 54 insertions(+), 52 deletions(-) diff --git a/compiler/mono/src/code_gen_help/mod.rs b/compiler/mono/src/code_gen_help/mod.rs index b1f399e6d7..27da4c0044 100644 --- a/compiler/mono/src/code_gen_help/mod.rs +++ b/compiler/mono/src/code_gen_help/mod.rs @@ -6,7 +6,7 @@ use roc_module::low_level::LowLevel; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use crate::ir::{ - Call, CallSpecId, CallType, Expr, HostExposedLayouts, ModifyRc, Proc, ProcLayout, + Call, CallSpecId, CallType, Expr, HostExposedLayouts, JoinPointId, ModifyRc, Proc, ProcLayout, SelfRecursive, Stmt, UpdateModeId, }; use crate::layout::{Builtin, Layout, UnionLayout}; @@ -28,20 +28,10 @@ pub const REFCOUNT_MAX: usize = 0; enum HelperOp { Inc, Dec, - DecRef, + DecRef(JoinPointId), Eq, } -impl From<&ModifyRc> for HelperOp { - fn from(modify: &ModifyRc) -> Self { - match modify { - ModifyRc::Inc(..) => Self::Inc, - ModifyRc::Dec(_) => Self::Dec, - ModifyRc::DecRef(_) => Self::DecRef, - } - } -} - #[derive(Debug)] struct Specialization<'a> { op: HelperOp, @@ -129,10 +119,18 @@ impl<'a> CodeGenHelp<'a> { return (following, Vec::new_in(self.arena)); } + let op = match modify { + ModifyRc::Inc(..) => HelperOp::Inc, + ModifyRc::Dec(_) => HelperOp::Dec, + ModifyRc::DecRef(_) => { + HelperOp::DecRef(JoinPointId(self.create_symbol(ident_ids, "jp_decref"))) + } + }; + let mut ctx = Context { new_linker_data: Vec::new_in(self.arena), recursive_union: None, - op: HelperOp::from(modify), + op, }; let rc_stmt = refcount::refcount_stmt(self, ident_ids, &mut ctx, layout, modify, following); @@ -190,7 +188,7 @@ impl<'a> CodeGenHelp<'a> { let (ret_layout, arg_layouts): (&'a Layout<'a>, &'a [Layout<'a>]) = { match ctx.op { - Dec | DecRef => (&LAYOUT_UNIT, self.arena.alloc([layout])), + Dec | DecRef(_) => (&LAYOUT_UNIT, self.arena.alloc([layout])), Inc => (&LAYOUT_UNIT, self.arena.alloc([layout, self.layout_isize])), Eq => (&LAYOUT_BOOL, self.arena.alloc([layout, layout])), } @@ -250,7 +248,7 @@ impl<'a> CodeGenHelp<'a> { // Recursively generate the body of the Proc and sub-procs let (ret_layout, body) = match ctx.op { - Inc | Dec | DecRef => ( + Inc | Dec | DecRef(_) => ( LAYOUT_UNIT, refcount::refcount_generic(self, ident_ids, ctx, layout), ), @@ -267,7 +265,7 @@ impl<'a> CodeGenHelp<'a> { let inc_amount = (self.layout_isize, ARG_2); self.arena.alloc([roc_value, inc_amount]) } - Dec | DecRef => self.arena.alloc([roc_value]), + Dec | DecRef(_) => self.arena.alloc([roc_value]), Eq => self.arena.alloc([roc_value, (layout, ARG_2)]), } }; @@ -310,7 +308,7 @@ impl<'a> CodeGenHelp<'a> { arguments: self.arena.alloc([*layout]), result: LAYOUT_UNIT, }, - HelperOp::DecRef => unreachable!("No generated Proc for DecRef"), + HelperOp::DecRef(_) => unreachable!("No generated Proc for DecRef"), HelperOp::Eq => ProcLayout { arguments: self.arena.alloc([*layout, *layout]), result: LAYOUT_BOOL, @@ -358,7 +356,7 @@ fn layout_needs_helper_proc(layout: &Layout, op: HelperOp) -> bool { // Str type can use either Zig functions or generated IR, since it's not generic. // Eq uses a Zig function, refcount uses generated IR. // Both are fine, they were just developed at different times. - matches!(op, HelperOp::Inc | HelperOp::Dec | HelperOp::DecRef) + matches!(op, HelperOp::Inc | HelperOp::Dec | HelperOp::DecRef(_)) } Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_)) => true, diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index 89965fa64f..523f1d6f6f 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -63,28 +63,22 @@ pub fn refcount_stmt<'a>( } ModifyRc::DecRef(structure) => { - // No generated procs for DecRef, just lowlevel ops - let rc_ptr_sym = root.create_symbol(ident_ids, "rc_ptr"); - - // Pass the refcount pointer to the lowlevel call (see utils.zig) - let call_result_empty = root.create_symbol(ident_ids, "call_result_empty"); - let call_expr = Expr::Call(Call { - call_type: CallType::LowLevel { - op: LowLevel::RefCountDec, - update_mode: UpdateModeId::BACKEND_DUMMY, - }, - arguments: arena.alloc([rc_ptr_sym]), - }); - let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following); - - // FIXME: `structure` is a pointer to the stack, not the heap! - rc_ptr_from_data_ptr( - root, - ident_ids, - *structure, - rc_ptr_sym, - arena.alloc(call_stmt), - ) + if matches!(layout, Layout::Builtin(Builtin::Str)) { + ctx.op = HelperOp::Dec; + refcount_stmt(root, ident_ids, ctx, layout, modify, following) + } else if let HelperOp::DecRef(jp_decref) = ctx.op { + // Inline the body of the equivalent Dec function, without iterating fields, + // and replacing all return statements with jumps to the `following` statement. + let rc_stmt = refcount_generic(root, ident_ids, ctx, layout); + Stmt::Join { + id: jp_decref, + parameters: &[], + body: following, + remainder: root.arena.alloc(rc_stmt), + } + } else { + unreachable!() + } } } } @@ -129,10 +123,18 @@ pub fn is_rc_implemented_yet(layout: &Layout) -> bool { } } -fn return_unit<'a>(root: &CodeGenHelp<'a>, ident_ids: &mut IdentIds) -> Stmt<'a> { - let unit = root.create_symbol(ident_ids, "unit"); - let ret_stmt = root.arena.alloc(Stmt::Ret(unit)); - Stmt::Let(unit, Expr::Struct(&[]), LAYOUT_UNIT, ret_stmt) +fn rc_return_stmt<'a>( + root: &CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, +) -> Stmt<'a> { + if let HelperOp::DecRef(jp_decref) = ctx.op { + Stmt::Jump(jp_decref, &[]) + } else { + let unit = root.create_symbol(ident_ids, "unit"); + let ret_stmt = root.arena.alloc(Stmt::Ret(unit)); + Stmt::Let(unit, Expr::Struct(&[]), LAYOUT_UNIT, ret_stmt) + } } fn refcount_args<'a>(root: &CodeGenHelp<'a>, ctx: &Context<'a>, structure: Symbol) -> &'a [Symbol] { @@ -230,7 +232,7 @@ fn modify_refcount<'a>( Stmt::Let(zig_call_result, zig_call_expr, LAYOUT_UNIT, following) } - HelperOp::Dec | HelperOp::DecRef => { + HelperOp::Dec | HelperOp::DecRef(_) => { let alignment_sym = root.create_symbol(ident_ids, "alignment"); let alignment_expr = Expr::Literal(Literal::Int(alignment as i128)); let alignment_stmt = |next| Stmt::Let(alignment_sym, alignment_expr, LAYOUT_U32, next); @@ -302,7 +304,7 @@ fn refcount_str<'a>( let rc_ptr = root.create_symbol(ident_ids, "rc_ptr"); let alignment = root.ptr_size; - let ret_unit_stmt = return_unit(root, ident_ids); + let ret_unit_stmt = rc_return_stmt(root, ident_ids, ctx); let mod_rc_stmt = modify_refcount( root, ident_ids, @@ -333,7 +335,7 @@ fn refcount_str<'a>( branches: root.arena.alloc([(1, BranchInfo::None, then_branch)]), default_branch: ( BranchInfo::None, - root.arena.alloc(return_unit(root, ident_ids)), + root.arena.alloc(rc_return_stmt(root, ident_ids, ctx)), ), ret_layout: LAYOUT_UNIT, }; @@ -404,7 +406,7 @@ fn refcount_list<'a>( let rc_ptr = root.create_symbol(ident_ids, "rc_ptr"); let alignment = layout.alignment_bytes(root.ptr_size); - let modify_elems = if elem_layout.is_refcounted() { + let modify_elems = if elem_layout.is_refcounted() && !matches!(ctx.op, HelperOp::DecRef(_)) { refcount_list_elems( root, ident_ids, @@ -416,8 +418,9 @@ fn refcount_list<'a>( elements, ) } else { - return_unit(root, ident_ids) + rc_return_stmt(root, ident_ids, ctx) }; + let modify_list = modify_refcount( root, ident_ids, @@ -426,6 +429,7 @@ fn refcount_list<'a>( alignment, arena.alloc(modify_elems), ); + let modify_list_and_elems = elements_stmt(arena.alloc( // rc_ptr_from_data_ptr(root, ident_ids, elements, rc_ptr, arena.alloc(modify_list)), @@ -440,7 +444,7 @@ fn refcount_list<'a>( cond_layout: LAYOUT_BOOL, branches: root .arena - .alloc([(1, BranchInfo::None, return_unit(root, ident_ids))]), + .alloc([(1, BranchInfo::None, rc_return_stmt(root, ident_ids, ctx))]), default_branch: (BranchInfo::None, root.arena.alloc(modify_list_and_elems)), ret_layout: LAYOUT_UNIT, }; @@ -565,7 +569,7 @@ fn refcount_list_elems<'a>( ret_layout, branches: root .arena - .alloc([(1, BranchInfo::None, return_unit(root, ident_ids))]), + .alloc([(1, BranchInfo::None, rc_return_stmt(root, ident_ids, ctx))]), default_branch: ( BranchInfo::None, arena.alloc(box_stmt(arena.alloc( @@ -620,7 +624,7 @@ fn refcount_struct<'a>( ctx: &mut Context<'a>, field_layouts: &'a [Layout<'a>], ) -> Stmt<'a> { - let mut stmt = return_unit(root, ident_ids); + let mut stmt = rc_return_stmt(root, ident_ids, ctx); for (i, field_layout) in field_layouts.iter().enumerate().rev() { if field_layout.contains_refcounted() { From 98ab97083afd54fe6507fe3befefdcc94173c953 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 1 Jan 2022 19:23:48 +0000 Subject: [PATCH 057/541] For DecRef, remove assumption that the refcounted symbol is ARG_1 --- compiler/mono/src/code_gen_help/mod.rs | 2 +- compiler/mono/src/code_gen_help/refcount.rs | 24 ++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/compiler/mono/src/code_gen_help/mod.rs b/compiler/mono/src/code_gen_help/mod.rs index 27da4c0044..1174ea34ae 100644 --- a/compiler/mono/src/code_gen_help/mod.rs +++ b/compiler/mono/src/code_gen_help/mod.rs @@ -250,7 +250,7 @@ impl<'a> CodeGenHelp<'a> { let (ret_layout, body) = match ctx.op { Inc | Dec | DecRef(_) => ( LAYOUT_UNIT, - refcount::refcount_generic(self, ident_ids, ctx, layout), + refcount::refcount_generic(self, ident_ids, ctx, layout, Symbol::ARG_1), ), Eq => ( LAYOUT_BOOL, diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index 523f1d6f6f..672d742923 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -15,9 +15,6 @@ const LAYOUT_UNIT: Layout = Layout::Struct(&[]); const LAYOUT_PTR: Layout = Layout::RecursivePointer; const LAYOUT_U32: Layout = Layout::Builtin(Builtin::Int(IntWidth::U32)); -const ARG_1: Symbol = Symbol::ARG_1; -const ARG_2: Symbol = Symbol::ARG_2; - pub fn refcount_stmt<'a>( root: &mut CodeGenHelp<'a>, ident_ids: &mut IdentIds, @@ -69,7 +66,7 @@ pub fn refcount_stmt<'a>( } else if let HelperOp::DecRef(jp_decref) = ctx.op { // Inline the body of the equivalent Dec function, without iterating fields, // and replacing all return statements with jumps to the `following` statement. - let rc_stmt = refcount_generic(root, ident_ids, ctx, layout); + let rc_stmt = refcount_generic(root, ident_ids, ctx, layout, *structure); Stmt::Join { id: jp_decref, parameters: &[], @@ -88,6 +85,7 @@ pub fn refcount_generic<'a>( ident_ids: &mut IdentIds, ctx: &mut Context<'a>, layout: Layout<'a>, + structure: Symbol, ) -> Stmt<'a> { debug_assert!(is_rc_implemented_yet(&layout)); let rc_todo = || todo!("Please update is_rc_implemented_yet for `{:?}`", layout); @@ -98,10 +96,10 @@ pub fn refcount_generic<'a>( } Layout::Builtin(Builtin::Str) => refcount_str(root, ident_ids, ctx), Layout::Builtin(Builtin::List(elem_layout)) => { - refcount_list(root, ident_ids, ctx, &layout, elem_layout) + refcount_list(root, ident_ids, ctx, &layout, elem_layout, structure) } Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_)) => rc_todo(), - Layout::Struct(field_layouts) => refcount_struct(root, ident_ids, ctx, field_layouts), + Layout::Struct(field_layouts) => refcount_struct(root, ident_ids, ctx, field_layouts, structure), Layout::Union(_) => rc_todo(), Layout::LambdaSet(_) => { unreachable!("Refcounting on LambdaSet is invalid. Should be a Union at runtime.") @@ -140,7 +138,7 @@ fn rc_return_stmt<'a>( fn refcount_args<'a>(root: &CodeGenHelp<'a>, ctx: &Context<'a>, structure: Symbol) -> &'a [Symbol] { if ctx.op == HelperOp::Inc { // second argument is always `amount`, passed down through the call stack - root.arena.alloc([structure, ARG_2]) + root.arena.alloc([structure, Symbol::ARG_2]) } else { root.arena.alloc([structure]) } @@ -227,7 +225,7 @@ fn modify_refcount<'a>( op: LowLevel::RefCountInc, update_mode: UpdateModeId::BACKEND_DUMMY, }, - arguments: root.arena.alloc([rc_ptr, ARG_2]), + arguments: root.arena.alloc([rc_ptr, Symbol::ARG_2]), }); Stmt::Let(zig_call_result, zig_call_expr, LAYOUT_UNIT, following) } @@ -262,7 +260,7 @@ fn refcount_str<'a>( ident_ids: &mut IdentIds, ctx: &mut Context<'a>, ) -> Stmt<'a> { - let string = ARG_1; + let string = Symbol::ARG_1; let layout_isize = root.layout_isize; // Get the string length as a signed int @@ -359,6 +357,7 @@ fn refcount_list<'a>( ctx: &mut Context<'a>, layout: &Layout, elem_layout: &'a Layout, + structure: Symbol ) -> Stmt<'a> { let layout_isize = root.layout_isize; let arena = root.arena; @@ -372,7 +371,7 @@ fn refcount_list<'a>( // let len = root.create_symbol(ident_ids, "len"); - let len_stmt = |next| let_lowlevel(arena, layout_isize, len, ListLen, &[ARG_1], next); + let len_stmt = |next| let_lowlevel(arena, layout_isize, len, ListLen, &[structure], next); // Zero let zero = root.create_symbol(ident_ids, "zero"); @@ -395,7 +394,7 @@ fn refcount_list<'a>( let elements_expr = Expr::StructAtIndex { index: 0, field_layouts: arena.alloc([box_layout, layout_isize]), - structure: ARG_1, + structure, }; let elements_stmt = |next| Stmt::Let(elements, elements_expr, box_layout, next); @@ -623,6 +622,7 @@ fn refcount_struct<'a>( ident_ids: &mut IdentIds, ctx: &mut Context<'a>, field_layouts: &'a [Layout<'a>], + structure: Symbol, ) -> Stmt<'a> { let mut stmt = rc_return_stmt(root, ident_ids, ctx); @@ -632,7 +632,7 @@ fn refcount_struct<'a>( let field_val_expr = Expr::StructAtIndex { index: i as u64, field_layouts, - structure: ARG_1, + structure, }; let field_val_stmt = |next| Stmt::Let(field_val, field_val_expr, *field_layout, next); From 1de26c084de14881eace85369b53d904c023eefd Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 1 Jan 2022 20:17:47 +0000 Subject: [PATCH 058/541] Make Struct DecRef a no-op --- compiler/mono/src/code_gen_help/mod.rs | 5 +- compiler/mono/src/code_gen_help/refcount.rs | 52 +++++++++++++-------- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/compiler/mono/src/code_gen_help/mod.rs b/compiler/mono/src/code_gen_help/mod.rs index 1174ea34ae..65dfc811a6 100644 --- a/compiler/mono/src/code_gen_help/mod.rs +++ b/compiler/mono/src/code_gen_help/mod.rs @@ -123,7 +123,8 @@ impl<'a> CodeGenHelp<'a> { ModifyRc::Inc(..) => HelperOp::Inc, ModifyRc::Dec(_) => HelperOp::Dec, ModifyRc::DecRef(_) => { - HelperOp::DecRef(JoinPointId(self.create_symbol(ident_ids, "jp_decref"))) + let jp_decref = JoinPointId(self.create_symbol(ident_ids, "jp_decref")); + HelperOp::DecRef(jp_decref) } }; @@ -134,7 +135,7 @@ impl<'a> CodeGenHelp<'a> { }; let rc_stmt = refcount::refcount_stmt(self, ident_ids, &mut ctx, layout, modify, following); - (self.arena.alloc(rc_stmt), ctx.new_linker_data) + (rc_stmt, ctx.new_linker_data) } /// Replace a generic `Lowlevel::Eq` call with a specialized helper proc. diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index 672d742923..4a27376f25 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -22,7 +22,7 @@ pub fn refcount_stmt<'a>( layout: Layout<'a>, modify: &ModifyRc, following: &'a Stmt<'a>, -) -> Stmt<'a> { +) -> &'a Stmt<'a> { let arena = root.arena; match modify { @@ -46,7 +46,7 @@ pub fn refcount_stmt<'a>( .unwrap(); let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following); - amount_stmt(arena.alloc(call_stmt)) + arena.alloc(amount_stmt(arena.alloc(call_stmt))) } ModifyRc::Dec(structure) => { @@ -55,26 +55,36 @@ pub fn refcount_stmt<'a>( let call_expr = root .call_specialized_op(ident_ids, ctx, layout, arena.alloc([*structure])) .unwrap(); - - Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following) + let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following); + arena.alloc(call_stmt) } ModifyRc::DecRef(structure) => { - if matches!(layout, Layout::Builtin(Builtin::Str)) { - ctx.op = HelperOp::Dec; - refcount_stmt(root, ident_ids, ctx, layout, modify, following) - } else if let HelperOp::DecRef(jp_decref) = ctx.op { - // Inline the body of the equivalent Dec function, without iterating fields, - // and replacing all return statements with jumps to the `following` statement. - let rc_stmt = refcount_generic(root, ident_ids, ctx, layout, *structure); - Stmt::Join { - id: jp_decref, - parameters: &[], - body: following, - remainder: root.arena.alloc(rc_stmt), + match layout { + // Str has no children, so we might as well do what we normally do and call the helper. + Layout::Builtin(Builtin::Str) => { + ctx.op = HelperOp::Dec; + refcount_stmt(root, ident_ids, ctx, layout, modify, following) } - } else { - unreachable!() + + // Struct is stack-only, so DecRef is a no-op + Layout::Struct(_) => following, + + // Inline the refcounting code instead of making a function. Don't iterate fields, + // and replace any return statements with jumps to the `following` statement. + _ => match ctx.op { + HelperOp::DecRef(jp_decref) => { + let rc_stmt = refcount_generic(root, ident_ids, ctx, layout, *structure); + let join = Stmt::Join { + id: jp_decref, + parameters: &[], + body: following, + remainder: arena.alloc(rc_stmt), + }; + arena.alloc(join) + } + _ => unreachable!(), + }, } } } @@ -99,7 +109,9 @@ pub fn refcount_generic<'a>( refcount_list(root, ident_ids, ctx, &layout, elem_layout, structure) } Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_)) => rc_todo(), - Layout::Struct(field_layouts) => refcount_struct(root, ident_ids, ctx, field_layouts, structure), + Layout::Struct(field_layouts) => { + refcount_struct(root, ident_ids, ctx, field_layouts, structure) + } Layout::Union(_) => rc_todo(), Layout::LambdaSet(_) => { unreachable!("Refcounting on LambdaSet is invalid. Should be a Union at runtime.") @@ -357,7 +369,7 @@ fn refcount_list<'a>( ctx: &mut Context<'a>, layout: &Layout, elem_layout: &'a Layout, - structure: Symbol + structure: Symbol, ) -> Stmt<'a> { let layout_isize = root.layout_isize; let arena = root.arena; From bd7b1e5013d96cc65f52dd34c6707fad0f83a6b4 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Thu, 23 Dec 2021 17:37:15 -0800 Subject: [PATCH 059/541] Make Loc::new take Positions --- cli/src/format.rs | 4 ++-- compiler/can/src/expr.rs | 10 +++++----- compiler/can/src/module.rs | 4 ++-- compiler/can/tests/test_can.rs | 32 +++++++++++++++--------------- compiler/parse/src/expr.rs | 20 +++++++++---------- compiler/parse/src/state.rs | 3 +-- compiler/parse/tests/test_parse.rs | 24 +++++++++++----------- compiler/region/src/all.rs | 13 ++++++------ 8 files changed, 54 insertions(+), 56 deletions(-) diff --git a/cli/src/format.rs b/cli/src/format.rs index 6eb75e2bd0..2bfdae99f7 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -20,7 +20,7 @@ use roc_parse::{ parser::{Parser, SyntaxError}, state::State, }; -use roc_region::all::Loc; +use roc_region::all::{Loc, Region}; use roc_reporting::{internal_error, user_error}; pub fn format(files: std::vec::Vec) { @@ -319,7 +319,7 @@ impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Option { impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Loc { fn remove_spaces(&self, arena: &'a Bump) -> Self { let res = self.value.remove_spaces(arena); - Loc::new(0, 0, 0, 0, res) + Loc::at(Region::zero(), res) } } diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index baa558c303..8b04496d60 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -1680,22 +1680,22 @@ fn desugar_str_segments(var_store: &mut VarStore, segments: Vec) -> let mut iter = segments.into_iter().rev(); let mut loc_expr = match iter.next() { - Some(Plaintext(string)) => Loc::new(0, 0, 0, 0, Expr::Str(string)), + Some(Plaintext(string)) => Loc::at(Region::zero(), Expr::Str(string)), Some(Interpolation(loc_expr)) => loc_expr, None => { // No segments? Empty string! - Loc::new(0, 0, 0, 0, Expr::Str("".into())) + Loc::at(Region::zero(), Expr::Str("".into())) } }; for seg in iter { let loc_new_expr = match seg { - Plaintext(string) => Loc::new(0, 0, 0, 0, Expr::Str(string)), + Plaintext(string) => Loc::at(Region::zero(), Expr::Str(string)), Interpolation(loc_interpolated_expr) => loc_interpolated_expr, }; - let fn_expr = Loc::new(0, 0, 0, 0, Expr::Var(Symbol::STR_CONCAT)); + let fn_expr = Loc::at(Region::zero(), Expr::Var(Symbol::STR_CONCAT)); let expr = Expr::Call( Box::new(( var_store.fresh(), @@ -1710,7 +1710,7 @@ fn desugar_str_segments(var_store: &mut VarStore, segments: Vec) -> CalledVia::StringInterpolation, ); - loc_expr = Loc::new(0, 0, 0, 0, expr); + loc_expr = Loc::at(Region::zero(), expr); } loc_expr.value diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 48837c4d3f..07a19cc654 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -263,8 +263,8 @@ where let runtime_error = RuntimeError::ExposedButNotDefined(symbol); let def = Def { - loc_pattern: Loc::new(0, 0, 0, 0, Pattern::Identifier(symbol)), - loc_expr: Loc::new(0, 0, 0, 0, Expr::RuntimeError(runtime_error)), + loc_pattern: Loc::at(Region::zero(), Pattern::Identifier(symbol)), + loc_expr: Loc::at(Region::zero(), Expr::RuntimeError(runtime_error)), expr_var: var_store.fresh(), pattern_vars, annotation: None, diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index 99a01d3169..fb6801534b 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -944,12 +944,12 @@ mod test_can { let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![CycleEntry { symbol: interns.symbol(home, "x".into()), symbol_region: Region::new( - Position { line: 0, column: 0 }, - Position { line: 0, column: 1 }, + Position::new(0, 0), + Position::new(0, 1), ), expr_region: Region::new( - Position { line: 0, column: 4 }, - Position { line: 0, column: 5 }, + Position::new(0, 4), + Position::new(0, 5), ), }])); @@ -981,34 +981,34 @@ mod test_can { CycleEntry { symbol: interns.symbol(home, "x".into()), symbol_region: Region::new( - Position { line: 0, column: 0 }, - Position { line: 0, column: 1 }, + Position::new(0, 0), + Position::new(0, 1), ), expr_region: Region::new( - Position { line: 0, column: 4 }, - Position { line: 0, column: 5 }, + Position::new(0, 4), + Position::new(0, 5), ), }, CycleEntry { symbol: interns.symbol(home, "y".into()), symbol_region: Region::new( - Position { line: 1, column: 0 }, - Position { line: 1, column: 1 }, + Position::new(1, 0), + Position::new(1, 1), ), expr_region: Region::new( - Position { line: 1, column: 4 }, - Position { line: 1, column: 5 }, + Position::new(1, 4), + Position::new(1, 5), ), }, CycleEntry { symbol: interns.symbol(home, "z".into()), symbol_region: Region::new( - Position { line: 2, column: 0 }, - Position { line: 2, column: 1 }, + Position::new(2, 0), + Position::new(2, 1), ), expr_region: Region::new( - Position { line: 2, column: 4 }, - Position { line: 2, column: 5 }, + Position::new(2, 4), + Position::new(2, 5), ), }, ])); diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 6e34e6fd0d..a62e388818 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -339,7 +339,7 @@ fn parse_expr_operator_chain<'a>( loc_possibly_negative_or_negated_term(min_indent, options).parse(arena, state)?; let initial = state.clone(); - let end = state.get_position(); + let end = state.pos(); match space0_e(min_indent, EExpr::Space, EExpr::IndentEnd).parse(arena, state) { Err((_, _, state)) => Ok((MadeProgress, expr.value, state)), @@ -732,7 +732,7 @@ fn append_expect_definition<'a>( let def = Def::Expect(arena.alloc(loc_expect_body)); let end = loc_expect_body.region.end(); - let region = Region::between(start, end); + let region = Region::new(start, end); let mut loc_def = Loc::at(region, def); @@ -799,7 +799,7 @@ fn parse_defs_end<'a>( Err((NoProgress, _, state)) => state, }; - let start = state.get_position(); + let start = state.pos(); match space0_after_e( crate::pattern::loc_pattern_help(min_indent), @@ -949,13 +949,13 @@ fn parse_expr_operator<'a>( let op = loc_op.value; let op_start = loc_op.region.start(); let op_end = loc_op.region.end(); - let new_start = state.get_position(); + let new_start = state.pos(); match op { BinOp::Minus if expr_state.end != op_start && op_end == new_start => { // negative terms let (_, negated_expr, state) = parse_loc_term(min_indent, options, arena, state)?; - let new_end = state.get_position(); + let new_end = state.pos(); let arg = numeric_negate_expression( arena, @@ -1175,7 +1175,7 @@ fn parse_expr_operator<'a>( _ => match loc_possibly_negative_or_negated_term(min_indent, options).parse(arena, state) { Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)), Ok((_, mut new_expr, state)) => { - let new_end = state.get_position(); + let new_end = state.pos(); expr_state.initial = state.clone(); @@ -1237,7 +1237,7 @@ fn parse_expr_end<'a>( match parser.parse(arena, state.clone()) { Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)), Ok((_, mut arg, state)) => { - let new_end = state.get_position(); + let new_end = state.pos(); // now that we have `function arg1 ... argn`, attach the spaces to the `argn` if !expr_state.spaces_after.is_empty() { @@ -1393,7 +1393,7 @@ fn parse_loc_expr_with_options<'a>( arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Loc>, EExpr<'a>> { - let start = state.get_position(); + let start = state.pos(); parse_expr_start(min_indent, options, start, arena, state) } @@ -1541,7 +1541,7 @@ pub fn defs<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Loc>>, EExpr let (_, initial_space, state) = space0_e(min_indent, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?; - let start = state.get_position(); + let start = state.pos(); let options = ExprParseOptions { accept_multi_backpassing: false, @@ -1966,7 +1966,7 @@ fn expect_help<'a>( options: ExprParseOptions, ) -> impl Parser<'a, Expr<'a>, EExpect<'a>> { move |arena: &'a Bump, state: State<'a>| { - let start = state.get_position(); + let start = state.pos(); let (_, _, state) = parser::keyword_e(keyword::EXPECT, EExpect::Expect).parse(arena, state)?; diff --git a/compiler/parse/src/state.rs b/compiler/parse/src/state.rs index 7d244bb99d..e9c35312d6 100644 --- a/compiler/parse/src/state.rs +++ b/compiler/parse/src/state.rs @@ -39,8 +39,7 @@ impl<'a> State<'a> { } /// Returns the current position - // TODO: replace this with just accessing the field - pub const fn get_position(&self) -> Position { + pub const fn pos(&self) -> Position { self.pos } diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index e780e29e26..573ead2aa6 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -23,7 +23,7 @@ mod test_parse { use roc_parse::parser::{Parser, SyntaxError}; use roc_parse::state::State; use roc_parse::test_helpers::parse_expr_with; - use roc_region::all::{Loc, Region}; + use roc_region::all::{Loc, Region, Position}; use roc_test_utils::assert_multiline_str_eq; use std::{f64, i64}; @@ -366,7 +366,7 @@ mod test_parse { assert_segments(r#""Hi, \u(123)!""#, |arena| { bumpalo::vec![in arena; Plaintext("Hi, "), - Unicode(Loc::new(0, 0, 8, 11, "123")), + Unicode(Loc::new(Position::new(0, 8), Position::new(0, 11), "123")), Plaintext("!") ] }); @@ -376,7 +376,7 @@ mod test_parse { fn unicode_escape_in_front() { assert_segments(r#""\u(1234) is a unicode char""#, |arena| { bumpalo::vec![in arena; - Unicode(Loc::new(0, 0, 4, 8, "1234")), + Unicode(Loc::new(Position::new(0, 4), Position::new(0, 8), "1234")), Plaintext(" is a unicode char") ] }); @@ -387,7 +387,7 @@ mod test_parse { assert_segments(r#""this is unicode: \u(1)""#, |arena| { bumpalo::vec![in arena; Plaintext("this is unicode: "), - Unicode(Loc::new(0, 0, 21, 22, "1")) + Unicode(Loc::new(Position::new(0, 21), Position::new(0, 22), "1")) ] }); } @@ -396,11 +396,11 @@ mod test_parse { fn unicode_escape_multiple() { assert_segments(r#""\u(a1) this is \u(2Bcd) unicode \u(ef97)""#, |arena| { bumpalo::vec![in arena; - Unicode(Loc::new(0, 0, 4, 6, "a1")), + Unicode(Loc::new(Position::new(0, 4), Position::new(0, 6), "a1")), Plaintext(" this is "), - Unicode(Loc::new(0, 0, 19, 23, "2Bcd")), + Unicode(Loc::new(Position::new(0, 19), Position::new(0, 23), "2Bcd")), Plaintext(" unicode "), - Unicode(Loc::new(0, 0, 36, 40, "ef97")) + Unicode(Loc::new(Position::new(0, 36), Position::new(0, 40), "ef97")) ] }); } @@ -417,7 +417,7 @@ mod test_parse { bumpalo::vec![in arena; Plaintext("Hi, "), - Interpolated(Loc::new(0, 0, 7, 11, expr)), + Interpolated(Loc::new(Position::new(0, 7), Position::new(0, 11), expr)), Plaintext("!") ] }); @@ -432,7 +432,7 @@ mod test_parse { }); bumpalo::vec![in arena; - Interpolated(Loc::new(0, 0, 3, 7, expr)), + Interpolated(Loc::new(Position::new(0, 3), Position::new(0, 7), expr)), Plaintext(", hi!") ] }); @@ -448,7 +448,7 @@ mod test_parse { bumpalo::vec![in arena; Plaintext("Hello "), - Interpolated(Loc::new(0, 0, 9, 13, expr)) + Interpolated(Loc::new(Position::new(0, 9), Position::new(0, 13), expr)) ] }); } @@ -468,9 +468,9 @@ mod test_parse { bumpalo::vec![in arena; Plaintext("Hi, "), - Interpolated(Loc::new(0, 0, 7, 11, expr1)), + Interpolated(Loc::new(Position::new(0, 7), Position::new(0, 11), expr1)), Plaintext("! How is "), - Interpolated(Loc::new(0, 0, 23, 30, expr2)), + Interpolated(Loc::new(Position::new(0, 23), Position::new(0, 30), expr2)), Plaintext(" going?") ] }); diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index a84bbe785b..7d477db2e0 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -159,6 +159,10 @@ pub struct Position { } impl Position { + pub fn new(line: u32, column: u16) -> Position { + Position { line, column } + } + pub fn bump_column(self, count: u16) -> Self { Self { line: self.line, @@ -180,13 +184,8 @@ pub struct Loc { } impl Loc { - pub fn new(start_line: u32, end_line: u32, start_col: u16, end_col: u16, value: T) -> Loc { - let region = Region { - start_line, - end_line, - start_col, - end_col, - }; + pub fn new(start: Position, end: Position, value: T) -> Loc { + let region = Region::new(start, end); Loc { region, value } } From d2dcb462c73ac8419722b8b7a8c726f4473a9b29 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Thu, 23 Dec 2021 17:39:39 -0800 Subject: [PATCH 060/541] Rename State::pos -> xyzlcol, temporarily --- compiler/parse/src/blankspace.rs | 16 +++++----- compiler/parse/src/expr.rs | 46 +++++++++++++-------------- compiler/parse/src/header.rs | 2 +- compiler/parse/src/ident.rs | 8 ++--- compiler/parse/src/module.rs | 4 +-- compiler/parse/src/parser.rs | 32 +++++++++---------- compiler/parse/src/pattern.rs | 6 ++-- compiler/parse/src/state.rs | 22 ++++++------- compiler/parse/src/string_literal.rs | 16 +++++----- compiler/parse/src/type_annotation.rs | 12 +++---- 10 files changed, 82 insertions(+), 82 deletions(-) diff --git a/compiler/parse/src/blankspace.rs b/compiler/parse/src/blankspace.rs index 12a4c1ed98..4f40bb748e 100644 --- a/compiler/parse/src/blankspace.rs +++ b/compiler/parse/src/blankspace.rs @@ -160,10 +160,10 @@ where E: 'a, { move |_, state: State<'a>| { - if state.pos.column >= min_indent { + if state.xyzlcol.column >= min_indent { Ok((NoProgress, (), state)) } else { - Err((NoProgress, indent_problem(state.pos), state)) + Err((NoProgress, indent_problem(state.xyzlcol), state)) } } } @@ -193,11 +193,11 @@ where move |arena, mut state: State<'a>| { let comments_and_newlines = Vec::new_in(arena); - match eat_spaces(state.bytes(), state.pos, comments_and_newlines) { + match eat_spaces(state.bytes(), state.xyzlcol, comments_and_newlines) { HasTab(pos) => { // there was a tab character let mut state = state; - state.pos = pos; + state.xyzlcol = pos; // TODO: it _seems_ like if we're changing the line/column, we should also be // advancing the state by the corresponding number of bytes. // Not doing this is likely a bug! @@ -215,21 +215,21 @@ where } => { if bytes == state.bytes() { Ok((NoProgress, &[] as &[_], state)) - } else if state.pos.line != pos.line { + } else if state.xyzlcol.line != pos.line { // we parsed at least one newline state.indent_column = pos.column; if pos.column >= min_indent { - state.pos = pos; + state.xyzlcol = pos; state = state.advance(state.bytes().len() - bytes.len()); Ok((MadeProgress, comments_and_newlines.into_bump_slice(), state)) } else { - Err((MadeProgress, indent_problem(state.pos), state)) + Err((MadeProgress, indent_problem(state.xyzlcol), state)) } } else { - state.pos.column = pos.column; + state.xyzlcol.column = pos.column; state = state.advance(state.bytes().len() - bytes.len()); Ok((MadeProgress, comments_and_newlines.into_bump_slice(), state)) diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index a62e388818..0ac79b520b 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -25,7 +25,7 @@ fn expr_end<'a>() -> impl Parser<'a, (), EExpr<'a>> { if state.has_reached_end() { Ok((NoProgress, (), state)) } else { - Err((NoProgress, EExpr::BadExprEnd(state.pos), state)) + Err((NoProgress, EExpr::BadExprEnd(state.xyzlcol), state)) } } } @@ -175,7 +175,7 @@ fn record_field_access_chain<'a>() -> impl Parser<'a, Vec<'a, &'a str>, EExpr<'a } } Err((MadeProgress, fail, state)) => Err((MadeProgress, fail, state)), - Err((NoProgress, _, state)) => Err((NoProgress, EExpr::Access(state.pos), state)), + Err((NoProgress, _, state)) => Err((NoProgress, EExpr::Access(state.xyzlcol), state)), } } @@ -233,7 +233,7 @@ fn parse_loc_term<'a>( fn underscore_expression<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> { move |arena: &'a Bump, state: State<'a>| { - let start = state.pos; + let start = state.xyzlcol; let (_, _, next_state) = word1(b'_', EExpr::Underscore).parse(arena, state)?; @@ -280,7 +280,7 @@ fn loc_possibly_negative_or_negated_term<'a>( } fn fail_expr_start_e<'a, T: 'a>() -> impl Parser<'a, T, EExpr<'a>> { - |_arena, state: State<'a>| Err((NoProgress, EExpr::Start(state.pos), state)) + |_arena, state: State<'a>| Err((NoProgress, EExpr::Start(state.xyzlcol), state)) } fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> { @@ -298,11 +298,11 @@ fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> { if state.bytes().starts_with(b"-") && !followed_by_whitespace { // the negate is only unary if it is not followed by whitespace let mut state = state.advance(1); - state.pos.column += 1; + state.xyzlcol.column += 1; Ok((MadeProgress, (), state)) } else { // this is not a negated expression - Err((NoProgress, EExpr::UnaryNot(state.pos), state)) + Err((NoProgress, EExpr::UnaryNot(state.xyzlcol), state)) } } } @@ -790,7 +790,7 @@ fn parse_defs_end<'a>( let state = match space0_e(min_indent, EExpr::Space, EExpr::IndentStart).parse(arena, state) { Err((MadeProgress, _, s)) => { - return Err((MadeProgress, EExpr::DefMissingFinalExpr(s.pos), s)); + return Err((MadeProgress, EExpr::DefMissingFinalExpr(s.xyzlcol), s)); } Ok((_, spaces, state)) => { def_state.spaces_after = spaces; @@ -916,7 +916,7 @@ fn parse_defs_expr<'a>( Err((_, fail, state)) => { return Err(( MadeProgress, - EExpr::DefMissingFinalExpr2(arena.alloc(fail), state.pos), + EExpr::DefMissingFinalExpr2(arena.alloc(fail), state.xyzlcol), state, )); } @@ -1282,7 +1282,7 @@ fn parse_expr_end<'a>( // try multi-backpassing if options.accept_multi_backpassing && state.bytes().starts_with(b",") { state = state.advance(1); - state.pos.column += 1; + state.xyzlcol.column += 1; let (_, mut patterns, state) = specialize_ref( EExpr::Pattern, @@ -1342,7 +1342,7 @@ fn parse_expr_end<'a>( } } } else if options.check_for_arrow && state.bytes().starts_with(b"->") { - Err((MadeProgress, EExpr::BadOperator("->", state.pos), state)) + Err((MadeProgress, EExpr::BadOperator("->", state.xyzlcol), state)) } else { // roll back space parsing let state = expr_state.initial.clone(); @@ -1662,7 +1662,7 @@ mod when { return Err(( progress, // TODO maybe pass case_indent here? - EWhen::PatternAlignment(5, state.pos), + EWhen::PatternAlignment(5, state.xyzlcol), state, )); } @@ -1733,7 +1733,7 @@ mod when { let indent = pattern_indent_level - indent_column; Err(( MadeProgress, - EWhen::PatternAlignment(indent, state.pos), + EWhen::PatternAlignment(indent, state.xyzlcol), state, )) } @@ -1853,15 +1853,15 @@ mod when { Err((NoProgress, fail, _)) => Err((NoProgress, fail, initial)), Ok((_progress, spaces, state)) => { match pattern_indent_level { - Some(wanted) if state.pos.column > wanted => { + Some(wanted) if state.xyzlcol.column > wanted => { // this branch is indented too much - Err((NoProgress, EWhen::IndentPattern(state.pos), initial)) + Err((NoProgress, EWhen::IndentPattern(state.xyzlcol), initial)) } - Some(wanted) if state.pos.column < wanted => { - let indent = wanted - state.pos.column; + Some(wanted) if state.xyzlcol.column < wanted => { + let indent = wanted - state.xyzlcol.column; Err(( NoProgress, - EWhen::PatternAlignment(indent, state.pos), + EWhen::PatternAlignment(indent, state.xyzlcol), initial, )) } @@ -1870,7 +1870,7 @@ mod when { min_indent.max(pattern_indent_level.unwrap_or(min_indent)); // the region is not reliable for the indent column in the case of // parentheses around patterns - let pattern_indent_column = state.pos.column; + let pattern_indent_column = state.xyzlcol.column; let parser = sep_by1( word1(b'|', EWhen::Bar), @@ -2377,7 +2377,7 @@ where macro_rules! good { ($op:expr, $width:expr) => {{ - state.pos.column += $width; + state.xyzlcol.column += $width; state = state.advance($width); Ok((MadeProgress, $op, state)) @@ -2386,12 +2386,12 @@ where macro_rules! bad_made_progress { ($op:expr) => {{ - Err((MadeProgress, to_error($op, state.pos), state)) + Err((MadeProgress, to_error($op, state.xyzlcol), state)) }}; } match chomped { - "" => Err((NoProgress, to_expectation(state.pos), state)), + "" => Err((NoProgress, to_expectation(state.xyzlcol), state)), "+" => good!(BinOp::Plus, 1), "-" => good!(BinOp::Minus, 1), "*" => good!(BinOp::Star, 1), @@ -2402,7 +2402,7 @@ where "<" => good!(BinOp::LessThan, 1), "." => { // a `.` makes no progress, so it does not interfere with `.foo` access(or) - Err((NoProgress, to_error(".", state.pos), state)) + Err((NoProgress, to_error(".", state.xyzlcol), state)) } "=" => good!(BinOp::Assignment, 1), ":" => good!(BinOp::HasType, 1), @@ -2417,7 +2417,7 @@ where "%%" => good!(BinOp::DoublePercent, 2), "->" => { // makes no progress, so it does not interfere with `_ if isGood -> ...` - Err((NoProgress, to_error("->", state.pos), state)) + Err((NoProgress, to_error("->", state.xyzlcol), state)) } "<-" => good!(BinOp::Backpassing, 2), _ => bad_made_progress!(chomped), diff --git a/compiler/parse/src/header.rs b/compiler/parse/src/header.rs index b34fcebad8..59aeda3751 100644 --- a/compiler/parse/src/header.rs +++ b/compiler/parse/src/header.rs @@ -250,7 +250,7 @@ pub fn package_entry<'a>() -> impl Parser<'a, Spaced<'a, PackageEntry<'a>>, EPac pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>, EPackageName<'a>> { move |arena, state: State<'a>| { - let pos = state.pos; + let pos = state.pos(); specialize(EPackageName::BadPath, string_literal::parse()) .parse(arena, state) .and_then(|(progress, text, next_state)| match text { diff --git a/compiler/parse/src/ident.rs b/compiler/parse/src/ident.rs index 5e21301312..a43c77cb3c 100644 --- a/compiler/parse/src/ident.rs +++ b/compiler/parse/src/ident.rs @@ -80,7 +80,7 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, ()> { pub fn tag_name<'a>() -> impl Parser<'a, &'a str, ()> { move |arena, state: State<'a>| { if state.bytes().starts_with(b"@") { - match chomp_private_tag(state.bytes(), state.pos) { + match chomp_private_tag(state.bytes(), state.xyzlcol) { Err(BadIdent::Start(_)) => Err((NoProgress, (), state)), Err(_) => Err((MadeProgress, (), state)), Ok(ident) => { @@ -150,7 +150,7 @@ pub fn parse_ident<'a>(arena: &'a Bump, state: State<'a>) -> ParseResult<'a, Ide if let Some(first) = parts.first() { for keyword in crate::keyword::KEYWORDS.iter() { if first == keyword { - return Err((NoProgress, EExpr::Start(initial.pos), initial)); + return Err((NoProgress, EExpr::Start(initial.xyzlcol), initial)); } } } @@ -159,7 +159,7 @@ pub fn parse_ident<'a>(arena: &'a Bump, state: State<'a>) -> ParseResult<'a, Ide Ok((progress, ident, state)) } - Err((NoProgress, _, state)) => Err((NoProgress, EExpr::Start(state.pos), state)), + Err((NoProgress, _, state)) => Err((NoProgress, EExpr::Start(state.xyzlcol), state)), Err((MadeProgress, fail, state)) => match fail { BadIdent::Start(pos) => Err((NoProgress, EExpr::Start(pos), state)), BadIdent::Space(e, pos) => Err((NoProgress, EExpr::Space(e, pos), state)), @@ -528,7 +528,7 @@ fn parse_ident_help<'a>( arena: &'a Bump, mut state: State<'a>, ) -> ParseResult<'a, Ident<'a>, BadIdent> { - match chomp_identifier_chain(arena, state.bytes(), state.pos) { + match chomp_identifier_chain(arena, state.bytes(), state.xyzlcol) { Ok((width, ident)) => { state = advance_state!(state, width as usize)?; Ok((MadeProgress, ident, state)) diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index e420c20bb5..12119d3445 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -21,7 +21,7 @@ fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> { if state.has_reached_end() { Ok((NoProgress, (), state)) } else { - Err((NoProgress, SyntaxError::NotEndOfFile(state.pos), state)) + Err((NoProgress, SyntaxError::NotEndOfFile(state.xyzlcol), state)) } } } @@ -167,7 +167,7 @@ fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>, ()> { |_, mut state: State<'a>| match chomp_module_name(state.bytes()) { Ok(name) => { let width = name.len(); - state.pos.column += width as u16; + state.xyzlcol.column += width as u16; state = state.advance(width); Ok((MadeProgress, ModuleName::new(name), state)) diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index c6a299ffa1..a86b16b4c9 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -723,23 +723,23 @@ where let width = keyword.len(); if !state.bytes().starts_with(keyword.as_bytes()) { - return Err((NoProgress, if_error(state.pos), state)); + return Err((NoProgress, if_error(state.xyzlcol), state)); } // the next character should not be an identifier character // to prevent treating `whence` or `iffy` as keywords match state.bytes().get(width) { Some(next) if *next == b' ' || *next == b'#' || *next == b'\n' => { - state.pos.column += width as u16; + state.xyzlcol.column += width as u16; state = state.advance(width); Ok((MadeProgress, (), state)) } None => { - state.pos.column += width as u16; + state.xyzlcol.column += width as u16; state = state.advance(width); Ok((MadeProgress, (), state)) } - Some(_) => Err((NoProgress, if_error(state.pos), state)), + Some(_) => Err((NoProgress, if_error(state.xyzlcol), state)), } } } @@ -964,7 +964,7 @@ where return Err((MadeProgress, fail, state)); } Err((NoProgress, _fail, state)) => { - return Err((NoProgress, to_element_error(state.pos), state)); + return Err((NoProgress, to_element_error(state.xyzlcol), state)); } } } @@ -989,7 +989,7 @@ where Err((MadeProgress, fail, state)) => Err((MadeProgress, fail, state)), Err((NoProgress, _fail, state)) => { - Err((NoProgress, to_element_error(state.pos), state)) + Err((NoProgress, to_element_error(state.xyzlcol), state)) } } } @@ -1039,11 +1039,11 @@ macro_rules! loc { move |arena, state: $crate::state::State<'a>| { use roc_region::all::{Loc, Region}; - let start = state.pos; + let start = state.xyzlcol; match $parser.parse(arena, state) { Ok((progress, value, state)) => { - let end = state.pos; + let end = state.xyzlcol; let region = Region::new(start, end); Ok((progress, Loc { region, value }, state)) @@ -1245,7 +1245,7 @@ macro_rules! one_of_with_error { match $p1.parse(arena, state) { valid @ Ok(_) => valid, Err((MadeProgress, fail, state)) => Err((MadeProgress, fail, state )), - Err((NoProgress, _, state)) => Err((MadeProgress, $toerror(state.pos), state)), + Err((NoProgress, _, state)) => Err((MadeProgress, $toerror(state.xyzlcol), state)), } } }; @@ -1263,7 +1263,7 @@ where { move |a, s| match parser.parse(a, s) { Ok(t) => Ok(t), - Err((p, error, s)) => Err((p, map_error(error, s.pos), s)), + Err((p, error, s)) => Err((p, map_error(error, s.xyzlcol), s)), } } @@ -1276,7 +1276,7 @@ where { move |a, s| match parser.parse(a, s) { Ok(t) => Ok(t), - Err((p, error, s)) => Err((p, map_error(a.alloc(error), s.pos), s)), + Err((p, error, s)) => Err((p, map_error(a.alloc(error), s.xyzlcol), s)), } } @@ -1290,10 +1290,10 @@ where move |_arena: &'a Bump, state: State<'a>| match state.bytes().get(0) { Some(x) if *x == word => { let mut state = state.advance(1); - state.pos.column += 1; + state.xyzlcol.column += 1; Ok((MadeProgress, (), state)) } - _ => Err((NoProgress, to_error(state.pos), state)), + _ => Err((NoProgress, to_error(state.xyzlcol), state)), } } @@ -1310,10 +1310,10 @@ where move |_arena: &'a Bump, state: State<'a>| { if state.bytes().starts_with(&needle) { let mut state = state.advance(2); - state.pos.column += 2; + state.xyzlcol.column += 2; Ok((MadeProgress, (), state)) } else { - Err((NoProgress, to_error(state.pos), state)) + Err((NoProgress, to_error(state.xyzlcol), state)) } } } @@ -1326,7 +1326,7 @@ where move |_arena, state: State<'a>| { dbg!(state.indent_column, min_indent); if state.indent_column < min_indent { - Err((NoProgress, to_problem(state.pos), state)) + Err((NoProgress, to_problem(state.xyzlcol), state)) } else { Ok((NoProgress, (), state)) } diff --git a/compiler/parse/src/pattern.rs b/compiler/parse/src/pattern.rs index 27eda3caeb..b53140083d 100644 --- a/compiler/parse/src/pattern.rs +++ b/compiler/parse/src/pattern.rs @@ -232,7 +232,7 @@ fn loc_ident_pattern_help<'a>( if crate::keyword::KEYWORDS.contains(&parts[0]) { Err(( NoProgress, - EPattern::End(original_state.pos), + EPattern::End(original_state.xyzlcol), original_state, )) } else if module_name.is_empty() && parts.len() == 1 { @@ -304,7 +304,7 @@ fn lowercase_ident_pattern<'a>( arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, &'a str, EPattern<'a>> { - let pos = state.pos; + let pos = state.xyzlcol; specialize(move |_, _| EPattern::End(pos), lowercase_ident()).parse(arena, state) } @@ -339,7 +339,7 @@ fn record_pattern_field<'a>(min_indent: u16) -> impl Parser<'a, Loc> move |arena, state: State<'a>| { // You must have a field name, e.g. "email" // using the initial pos is important for error reporting - let pos = state.pos; + let pos = state.xyzlcol; let (progress, loc_label, state) = loc!(specialize( move |_, _| PRecord::Field(pos), lowercase_ident() diff --git a/compiler/parse/src/state.rs b/compiler/parse/src/state.rs index e9c35312d6..191d25ec0c 100644 --- a/compiler/parse/src/state.rs +++ b/compiler/parse/src/state.rs @@ -11,7 +11,7 @@ pub struct State<'a> { bytes: &'a [u8], /// Current position within the input (line/column) - pub pos: Position, + pub xyzlcol: Position, /// Current indentation level, in columns /// (so no indent is col 1 - this saves an arithmetic operation.) @@ -22,7 +22,7 @@ impl<'a> State<'a> { pub fn new(bytes: &'a [u8]) -> State<'a> { State { bytes, - pos: Position::default(), + xyzlcol: Position::default(), indent_column: 0, } } @@ -40,7 +40,7 @@ impl<'a> State<'a> { /// Returns the current position pub const fn pos(&self) -> Position { - self.pos + self.xyzlcol } /// Returns whether the parser has reached the end of the input @@ -71,19 +71,19 @@ impl<'a> State<'a> { where TE: Fn(Position) -> E, { - match (self.pos.column as usize).checked_add(quantity) { + match (self.xyzlcol.column as usize).checked_add(quantity) { Some(column_usize) if column_usize <= u16::MAX as usize => { Ok(State { bytes: &self.bytes[quantity..], - pos: Position { - line: self.pos.line, + xyzlcol: Position { + line: self.xyzlcol.line, column: column_usize as u16, }, // Once we hit a nonspace character, we are no longer indenting. ..self }) } - _ => Err((NoProgress, to_error(self.pos), self)), + _ => Err((NoProgress, to_error(self.xyzlcol), self)), } } @@ -93,11 +93,11 @@ impl<'a> State<'a> { /// and thus wanting a Region while not having access to loc(). pub fn len_region(&self, length: u16) -> Region { Region::new( - self.pos, + self.xyzlcol, Position { - line: self.pos.line, + line: self.xyzlcol.line, column: self - .pos + .xyzlcol .column .checked_add(length) .unwrap_or_else(|| panic!("len_region overflowed")), @@ -128,7 +128,7 @@ impl<'a> fmt::Debug for State<'a> { write!( f, "\n\t(line, col): ({}, {}),", - self.pos.line, self.pos.column + self.xyzlcol.line, self.xyzlcol.column )?; write!(f, "\n\tindent_column: {}", self.indent_column)?; write!(f, "\n}}") diff --git a/compiler/parse/src/string_literal.rs b/compiler/parse/src/string_literal.rs index dfeb4e42da..f22c10f6e0 100644 --- a/compiler/parse/src/string_literal.rs +++ b/compiler/parse/src/string_literal.rs @@ -17,7 +17,7 @@ fn ascii_hex_digits<'a>() -> impl Parser<'a, &'a str, EString<'a>> { buf.push(byte as char); } else if buf.is_empty() { // We didn't find any hex digits! - return Err((NoProgress, EString::CodePtEnd(state.pos), state)); + return Err((NoProgress, EString::CodePtEnd(state.xyzlcol), state)); } else { let state = state.advance_without_indenting_ee(buf.len(), |pos| { EString::Space(BadInputError::LineTooLong, pos) @@ -27,7 +27,7 @@ fn ascii_hex_digits<'a>() -> impl Parser<'a, &'a str, EString<'a>> { } } - Err((NoProgress, EString::CodePtEnd(state.pos), state)) + Err((NoProgress, EString::CodePtEnd(state.xyzlcol), state)) } } @@ -56,7 +56,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> { bytes = state.bytes()[1..].iter(); state = advance_state!(state, 1)?; } else { - return Err((NoProgress, EString::Open(state.pos), state)); + return Err((NoProgress, EString::Open(state.xyzlcol), state)); } // At the parsing stage we keep the entire raw string, because the formatter @@ -100,7 +100,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> { Err(_) => { return Err(( MadeProgress, - EString::Space(BadInputError::BadUtf8, state.pos), + EString::Space(BadInputError::BadUtf8, state.xyzlcol), state, )); } @@ -192,7 +192,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> { // all remaining chars. This will mask all other errors, but // it should make it easiest to debug; the file will be a giant // error starting from where the open quote appeared. - return Err((MadeProgress, EString::EndlessSingle(state.pos), state)); + return Err((MadeProgress, EString::EndlessSingle(state.xyzlcol), state)); } } b'\\' => { @@ -281,7 +281,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> { // Invalid escape! A backslash must be followed // by either an open paren or else one of the // escapable characters (\n, \t, \", \\, etc) - return Err((MadeProgress, EString::UnknownEscape(state.pos), state)); + return Err((MadeProgress, EString::UnknownEscape(state.xyzlcol), state)); } } } @@ -295,9 +295,9 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> { Err(( MadeProgress, if is_multiline { - EString::EndlessMulti(state.pos) + EString::EndlessMulti(state.xyzlcol) } else { - EString::EndlessSingle(state.pos) + EString::EndlessSingle(state.xyzlcol) }, state, )) diff --git a/compiler/parse/src/type_annotation.rs b/compiler/parse/src/type_annotation.rs index 27434dbd6d..79b3ca7ce2 100644 --- a/compiler/parse/src/type_annotation.rs +++ b/compiler/parse/src/type_annotation.rs @@ -101,7 +101,7 @@ fn parse_type_alias_after_as<'a>(min_indent: u16) -> impl Parser<'a, AliasHeader } fn fail_type_start<'a, T: 'a>() -> impl Parser<'a, T, EType<'a>> { - |_arena, state: State<'a>| Err((NoProgress, EType::TStart(state.pos), state)) + |_arena, state: State<'a>| Err((NoProgress, EType::TStart(state.xyzlcol), state)) } fn term<'a>(min_indent: u16) -> impl Parser<'a, Loc>, EType<'a>> { @@ -240,7 +240,7 @@ where { move |arena, state: State<'a>| match crate::ident::tag_name().parse(arena, state) { Ok(good) => Ok(good), - Err((progress, _, state)) => Err((progress, to_problem(state.pos), state)), + Err((progress, _, state)) => Err((progress, to_problem(state.xyzlcol), state)), } } @@ -254,7 +254,7 @@ fn record_type_field<'a>( (move |arena, state: State<'a>| { // You must have a field name, e.g. "email" // using the initial pos is important for error reporting - let pos = state.pos; + let pos = state.xyzlcol; let (progress, loc_label, state) = loc!(specialize( move |_, _| ETypeRecord::Field(pos), lowercase_ident() @@ -410,7 +410,7 @@ fn expression<'a>( ), |_, state: State<'a>| Err(( NoProgress, - EType::TFunctionArgument(state.pos), + EType::TFunctionArgument(state.xyzlcol), state )) ] @@ -503,7 +503,7 @@ fn parse_concrete_type<'a>( Ok((MadeProgress, answer, state)) } - Err((NoProgress, _, state)) => Err((NoProgress, ETypeApply::End(state.pos), state)), + Err((NoProgress, _, state)) => Err((NoProgress, ETypeApply::End(state.xyzlcol), state)), Err((MadeProgress, _, mut state)) => { // we made some progress, but ultimately failed. // that means a malformed type name @@ -531,6 +531,6 @@ fn parse_type_variable<'a>( Ok((MadeProgress, answer, state)) } - Err((progress, _, state)) => Err((progress, EType::TBadTypeVariable(state.pos), state)), + Err((progress, _, state)) => Err((progress, EType::TBadTypeVariable(state.xyzlcol), state)), } } From 1b257da35681155358b5b2591636d61f81a83518 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Thu, 23 Dec 2021 18:11:09 -0800 Subject: [PATCH 061/541] Make State::xyzlcol a LineColumn, and change everything wanting a Position to use State::pos() instead --- compiler/parse/src/blankspace.rs | 73 ++++++++++++++++----------- compiler/parse/src/expr.rs | 32 ++++++------ compiler/parse/src/ident.rs | 8 +-- compiler/parse/src/module.rs | 2 +- compiler/parse/src/parser.rs | 24 ++++----- compiler/parse/src/pattern.rs | 6 +-- compiler/parse/src/state.rs | 17 ++++--- compiler/parse/src/string_literal.rs | 16 +++--- compiler/parse/src/type_annotation.rs | 12 ++--- compiler/region/src/all.rs | 24 +++++++++ 10 files changed, 127 insertions(+), 87 deletions(-) diff --git a/compiler/parse/src/blankspace.rs b/compiler/parse/src/blankspace.rs index 4f40bb748e..999ba4ca31 100644 --- a/compiler/parse/src/blankspace.rs +++ b/compiler/parse/src/blankspace.rs @@ -4,6 +4,7 @@ use crate::parser::{self, and, backtrackable, BadInputError, Parser, Progress::* use crate::state::State; use bumpalo::collections::vec::Vec; use bumpalo::Bump; +use roc_region::all::LineColumn; use roc_region::all::Loc; use roc_region::all::Position; @@ -163,7 +164,7 @@ where if state.xyzlcol.column >= min_indent { Ok((NoProgress, (), state)) } else { - Err((NoProgress, indent_problem(state.xyzlcol), state)) + Err((NoProgress, indent_problem(state.pos()), state)) } } } @@ -193,11 +194,11 @@ where move |arena, mut state: State<'a>| { let comments_and_newlines = Vec::new_in(arena); - match eat_spaces(state.bytes(), state.xyzlcol, comments_and_newlines) { - HasTab(pos) => { + match eat_spaces(state.bytes(), state.xyzlcol, state.pos(), comments_and_newlines) { + HasTab(xyzlcol, pos) => { // there was a tab character let mut state = state; - state.xyzlcol = pos; + state.xyzlcol = xyzlcol; // TODO: it _seems_ like if we're changing the line/column, we should also be // advancing the state by the corresponding number of bytes. // Not doing this is likely a bug! @@ -209,7 +210,7 @@ where )) } Good { - pos, + xyzcol: pos, bytes, comments_and_newlines, } => { @@ -226,7 +227,7 @@ where Ok((MadeProgress, comments_and_newlines.into_bump_slice(), state)) } else { - Err((MadeProgress, indent_problem(state.xyzlcol), state)) + Err((MadeProgress, indent_problem(state.pos()), state)) } } else { state.xyzlcol.column = pos.column; @@ -241,15 +242,16 @@ where enum SpaceState<'a> { Good { - pos: Position, + xyzcol: LineColumn, bytes: &'a [u8], comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, }, - HasTab(Position), + HasTab(LineColumn, Position), } fn eat_spaces<'a>( mut bytes: &'a [u8], + mut xyzlcol: LineColumn, mut pos: Position, mut comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, ) -> SpaceState<'a> { @@ -258,31 +260,35 @@ fn eat_spaces<'a>( for c in bytes { match c { b' ' => { + pos = pos.bump_column(1); bytes = &bytes[1..]; - pos.column += 1; + xyzlcol.column += 1; } b'\n' => { bytes = &bytes[1..]; - pos.line += 1; - pos.column = 0; + pos = pos.bump_newline(); + xyzlcol.line += 1; + xyzlcol.column = 0; comments_and_newlines.push(CommentOrNewline::Newline); } b'\r' => { bytes = &bytes[1..]; + pos = pos.bump_invisible(1); } b'\t' => { - return HasTab(pos); + return HasTab(xyzlcol, pos); } b'#' => { - pos.column += 1; - return eat_line_comment(&bytes[1..], pos, comments_and_newlines); + xyzlcol.column += 1; + pos = pos.bump_column(1); + return eat_line_comment(&bytes[1..], xyzlcol, pos, comments_and_newlines); } _ => break, } } Good { - pos, + xyzcol: xyzlcol, bytes, comments_and_newlines, } @@ -290,6 +296,7 @@ fn eat_spaces<'a>( fn eat_line_comment<'a>( mut bytes: &'a [u8], + mut xyzlcol: LineColumn, mut pos: Position, mut comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, ) -> SpaceState<'a> { @@ -299,26 +306,30 @@ fn eat_line_comment<'a>( match bytes.get(1) { Some(b' ') => { bytes = &bytes[2..]; - pos.column += 2; + xyzlcol.column += 2; + pos = pos.bump_column(2); true } Some(b'\n') => { // consume the second # and the \n bytes = &bytes[2..]; + pos = pos.bump_column(1); + pos = pos.bump_newline(); comments_and_newlines.push(CommentOrNewline::DocComment("")); - pos.line += 1; - pos.column = 0; - return eat_spaces(bytes, pos, comments_and_newlines); + xyzlcol.line += 1; + xyzlcol.column = 0; + return eat_spaces(bytes, xyzlcol, pos, comments_and_newlines); } None => { // consume the second # - pos.column += 1; + xyzlcol.column += 1; bytes = &bytes[1..]; + // pos = pos.bump_column(1); return Good { - pos, + xyzcol: xyzlcol, bytes, comments_and_newlines, }; @@ -331,13 +342,13 @@ fn eat_line_comment<'a>( }; let initial = bytes; - let initial_column = pos.column; + let initial_column = xyzlcol.column; for c in bytes { match c { - b'\t' => return HasTab(pos), + b'\t' => return HasTab(xyzlcol, pos), b'\n' => { - let delta = (pos.column - initial_column) as usize; + let delta = (xyzlcol.column - initial_column) as usize; let comment = unsafe { std::str::from_utf8_unchecked(&initial[..delta]) }; if is_doc_comment { @@ -345,19 +356,21 @@ fn eat_line_comment<'a>( } else { comments_and_newlines.push(CommentOrNewline::LineComment(comment)); } - pos.line += 1; - pos.column = 0; - return eat_spaces(&bytes[1..], pos, comments_and_newlines); + pos = pos.bump_newline(); + xyzlcol.line += 1; + xyzlcol.column = 0; + return eat_spaces(&bytes[1..], xyzlcol, pos, comments_and_newlines); } _ => { bytes = &bytes[1..]; - pos.column += 1; + pos = pos.bump_column(1); + xyzlcol.column += 1; } } } // We made it to the end of the bytes. This means there's a comment without a trailing newline. - let delta = (pos.column - initial_column) as usize; + let delta = (xyzlcol.column - initial_column) as usize; let comment = unsafe { std::str::from_utf8_unchecked(&initial[..delta]) }; if is_doc_comment { @@ -367,7 +380,7 @@ fn eat_line_comment<'a>( } Good { - pos, + xyzcol: xyzlcol, bytes, comments_and_newlines, } diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 0ac79b520b..62fac7d566 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -25,7 +25,7 @@ fn expr_end<'a>() -> impl Parser<'a, (), EExpr<'a>> { if state.has_reached_end() { Ok((NoProgress, (), state)) } else { - Err((NoProgress, EExpr::BadExprEnd(state.xyzlcol), state)) + Err((NoProgress, EExpr::BadExprEnd(state.pos()), state)) } } } @@ -175,7 +175,7 @@ fn record_field_access_chain<'a>() -> impl Parser<'a, Vec<'a, &'a str>, EExpr<'a } } Err((MadeProgress, fail, state)) => Err((MadeProgress, fail, state)), - Err((NoProgress, _, state)) => Err((NoProgress, EExpr::Access(state.xyzlcol), state)), + Err((NoProgress, _, state)) => Err((NoProgress, EExpr::Access(state.pos()), state)), } } @@ -233,7 +233,7 @@ fn parse_loc_term<'a>( fn underscore_expression<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> { move |arena: &'a Bump, state: State<'a>| { - let start = state.xyzlcol; + let start = state.pos(); let (_, _, next_state) = word1(b'_', EExpr::Underscore).parse(arena, state)?; @@ -280,7 +280,7 @@ fn loc_possibly_negative_or_negated_term<'a>( } fn fail_expr_start_e<'a, T: 'a>() -> impl Parser<'a, T, EExpr<'a>> { - |_arena, state: State<'a>| Err((NoProgress, EExpr::Start(state.xyzlcol), state)) + |_arena, state: State<'a>| Err((NoProgress, EExpr::Start(state.pos()), state)) } fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> { @@ -302,7 +302,7 @@ fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> { Ok((MadeProgress, (), state)) } else { // this is not a negated expression - Err((NoProgress, EExpr::UnaryNot(state.xyzlcol), state)) + Err((NoProgress, EExpr::UnaryNot(state.pos()), state)) } } } @@ -790,7 +790,7 @@ fn parse_defs_end<'a>( let state = match space0_e(min_indent, EExpr::Space, EExpr::IndentStart).parse(arena, state) { Err((MadeProgress, _, s)) => { - return Err((MadeProgress, EExpr::DefMissingFinalExpr(s.xyzlcol), s)); + return Err((MadeProgress, EExpr::DefMissingFinalExpr(s.pos()), s)); } Ok((_, spaces, state)) => { def_state.spaces_after = spaces; @@ -916,7 +916,7 @@ fn parse_defs_expr<'a>( Err((_, fail, state)) => { return Err(( MadeProgress, - EExpr::DefMissingFinalExpr2(arena.alloc(fail), state.xyzlcol), + EExpr::DefMissingFinalExpr2(arena.alloc(fail), state.pos()), state, )); } @@ -1342,7 +1342,7 @@ fn parse_expr_end<'a>( } } } else if options.check_for_arrow && state.bytes().starts_with(b"->") { - Err((MadeProgress, EExpr::BadOperator("->", state.xyzlcol), state)) + Err((MadeProgress, EExpr::BadOperator("->", state.pos()), state)) } else { // roll back space parsing let state = expr_state.initial.clone(); @@ -1662,7 +1662,7 @@ mod when { return Err(( progress, // TODO maybe pass case_indent here? - EWhen::PatternAlignment(5, state.xyzlcol), + EWhen::PatternAlignment(5, state.pos()), state, )); } @@ -1733,7 +1733,7 @@ mod when { let indent = pattern_indent_level - indent_column; Err(( MadeProgress, - EWhen::PatternAlignment(indent, state.xyzlcol), + EWhen::PatternAlignment(indent, state.pos()), state, )) } @@ -1855,13 +1855,13 @@ mod when { match pattern_indent_level { Some(wanted) if state.xyzlcol.column > wanted => { // this branch is indented too much - Err((NoProgress, EWhen::IndentPattern(state.xyzlcol), initial)) + Err((NoProgress, EWhen::IndentPattern(state.pos()), initial)) } Some(wanted) if state.xyzlcol.column < wanted => { let indent = wanted - state.xyzlcol.column; Err(( NoProgress, - EWhen::PatternAlignment(indent, state.xyzlcol), + EWhen::PatternAlignment(indent, state.pos()), initial, )) } @@ -2386,12 +2386,12 @@ where macro_rules! bad_made_progress { ($op:expr) => {{ - Err((MadeProgress, to_error($op, state.xyzlcol), state)) + Err((MadeProgress, to_error($op, state.pos()), state)) }}; } match chomped { - "" => Err((NoProgress, to_expectation(state.xyzlcol), state)), + "" => Err((NoProgress, to_expectation(state.pos()), state)), "+" => good!(BinOp::Plus, 1), "-" => good!(BinOp::Minus, 1), "*" => good!(BinOp::Star, 1), @@ -2402,7 +2402,7 @@ where "<" => good!(BinOp::LessThan, 1), "." => { // a `.` makes no progress, so it does not interfere with `.foo` access(or) - Err((NoProgress, to_error(".", state.xyzlcol), state)) + Err((NoProgress, to_error(".", state.pos()), state)) } "=" => good!(BinOp::Assignment, 1), ":" => good!(BinOp::HasType, 1), @@ -2417,7 +2417,7 @@ where "%%" => good!(BinOp::DoublePercent, 2), "->" => { // makes no progress, so it does not interfere with `_ if isGood -> ...` - Err((NoProgress, to_error("->", state.xyzlcol), state)) + Err((NoProgress, to_error("->", state.pos()), state)) } "<-" => good!(BinOp::Backpassing, 2), _ => bad_made_progress!(chomped), diff --git a/compiler/parse/src/ident.rs b/compiler/parse/src/ident.rs index a43c77cb3c..9598dfa513 100644 --- a/compiler/parse/src/ident.rs +++ b/compiler/parse/src/ident.rs @@ -80,7 +80,7 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, ()> { pub fn tag_name<'a>() -> impl Parser<'a, &'a str, ()> { move |arena, state: State<'a>| { if state.bytes().starts_with(b"@") { - match chomp_private_tag(state.bytes(), state.xyzlcol) { + match chomp_private_tag(state.bytes(), state.pos()) { Err(BadIdent::Start(_)) => Err((NoProgress, (), state)), Err(_) => Err((MadeProgress, (), state)), Ok(ident) => { @@ -150,7 +150,7 @@ pub fn parse_ident<'a>(arena: &'a Bump, state: State<'a>) -> ParseResult<'a, Ide if let Some(first) = parts.first() { for keyword in crate::keyword::KEYWORDS.iter() { if first == keyword { - return Err((NoProgress, EExpr::Start(initial.xyzlcol), initial)); + return Err((NoProgress, EExpr::Start(initial.pos()), initial)); } } } @@ -159,7 +159,7 @@ pub fn parse_ident<'a>(arena: &'a Bump, state: State<'a>) -> ParseResult<'a, Ide Ok((progress, ident, state)) } - Err((NoProgress, _, state)) => Err((NoProgress, EExpr::Start(state.xyzlcol), state)), + Err((NoProgress, _, state)) => Err((NoProgress, EExpr::Start(state.pos()), state)), Err((MadeProgress, fail, state)) => match fail { BadIdent::Start(pos) => Err((NoProgress, EExpr::Start(pos), state)), BadIdent::Space(e, pos) => Err((NoProgress, EExpr::Space(e, pos), state)), @@ -528,7 +528,7 @@ fn parse_ident_help<'a>( arena: &'a Bump, mut state: State<'a>, ) -> ParseResult<'a, Ident<'a>, BadIdent> { - match chomp_identifier_chain(arena, state.bytes(), state.xyzlcol) { + match chomp_identifier_chain(arena, state.bytes(), state.pos()) { Ok((width, ident)) => { state = advance_state!(state, width as usize)?; Ok((MadeProgress, ident, state)) diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index 12119d3445..95db104827 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -21,7 +21,7 @@ fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> { if state.has_reached_end() { Ok((NoProgress, (), state)) } else { - Err((NoProgress, SyntaxError::NotEndOfFile(state.xyzlcol), state)) + Err((NoProgress, SyntaxError::NotEndOfFile(state.pos()), state)) } } } diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index a86b16b4c9..7092da1e4d 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -723,7 +723,7 @@ where let width = keyword.len(); if !state.bytes().starts_with(keyword.as_bytes()) { - return Err((NoProgress, if_error(state.xyzlcol), state)); + return Err((NoProgress, if_error(state.pos()), state)); } // the next character should not be an identifier character @@ -739,7 +739,7 @@ where state = state.advance(width); Ok((MadeProgress, (), state)) } - Some(_) => Err((NoProgress, if_error(state.xyzlcol), state)), + Some(_) => Err((NoProgress, if_error(state.pos()), state)), } } } @@ -964,7 +964,7 @@ where return Err((MadeProgress, fail, state)); } Err((NoProgress, _fail, state)) => { - return Err((NoProgress, to_element_error(state.xyzlcol), state)); + return Err((NoProgress, to_element_error(state.pos()), state)); } } } @@ -989,7 +989,7 @@ where Err((MadeProgress, fail, state)) => Err((MadeProgress, fail, state)), Err((NoProgress, _fail, state)) => { - Err((NoProgress, to_element_error(state.xyzlcol), state)) + Err((NoProgress, to_element_error(state.pos()), state)) } } } @@ -1039,11 +1039,11 @@ macro_rules! loc { move |arena, state: $crate::state::State<'a>| { use roc_region::all::{Loc, Region}; - let start = state.xyzlcol; + let start = state.pos(); match $parser.parse(arena, state) { Ok((progress, value, state)) => { - let end = state.xyzlcol; + let end = state.pos(); let region = Region::new(start, end); Ok((progress, Loc { region, value }, state)) @@ -1245,7 +1245,7 @@ macro_rules! one_of_with_error { match $p1.parse(arena, state) { valid @ Ok(_) => valid, Err((MadeProgress, fail, state)) => Err((MadeProgress, fail, state )), - Err((NoProgress, _, state)) => Err((MadeProgress, $toerror(state.xyzlcol), state)), + Err((NoProgress, _, state)) => Err((MadeProgress, $toerror(state.pos()), state)), } } }; @@ -1263,7 +1263,7 @@ where { move |a, s| match parser.parse(a, s) { Ok(t) => Ok(t), - Err((p, error, s)) => Err((p, map_error(error, s.xyzlcol), s)), + Err((p, error, s)) => Err((p, map_error(error, s.pos()), s)), } } @@ -1276,7 +1276,7 @@ where { move |a, s| match parser.parse(a, s) { Ok(t) => Ok(t), - Err((p, error, s)) => Err((p, map_error(a.alloc(error), s.xyzlcol), s)), + Err((p, error, s)) => Err((p, map_error(a.alloc(error), s.pos()), s)), } } @@ -1293,7 +1293,7 @@ where state.xyzlcol.column += 1; Ok((MadeProgress, (), state)) } - _ => Err((NoProgress, to_error(state.xyzlcol), state)), + _ => Err((NoProgress, to_error(state.pos()), state)), } } @@ -1313,7 +1313,7 @@ where state.xyzlcol.column += 2; Ok((MadeProgress, (), state)) } else { - Err((NoProgress, to_error(state.xyzlcol), state)) + Err((NoProgress, to_error(state.pos()), state)) } } } @@ -1326,7 +1326,7 @@ where move |_arena, state: State<'a>| { dbg!(state.indent_column, min_indent); if state.indent_column < min_indent { - Err((NoProgress, to_problem(state.xyzlcol), state)) + Err((NoProgress, to_problem(state.pos()), state)) } else { Ok((NoProgress, (), state)) } diff --git a/compiler/parse/src/pattern.rs b/compiler/parse/src/pattern.rs index b53140083d..2176d835fb 100644 --- a/compiler/parse/src/pattern.rs +++ b/compiler/parse/src/pattern.rs @@ -232,7 +232,7 @@ fn loc_ident_pattern_help<'a>( if crate::keyword::KEYWORDS.contains(&parts[0]) { Err(( NoProgress, - EPattern::End(original_state.xyzlcol), + EPattern::End(original_state.pos()), original_state, )) } else if module_name.is_empty() && parts.len() == 1 { @@ -304,7 +304,7 @@ fn lowercase_ident_pattern<'a>( arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, &'a str, EPattern<'a>> { - let pos = state.xyzlcol; + let pos = state.pos(); specialize(move |_, _| EPattern::End(pos), lowercase_ident()).parse(arena, state) } @@ -339,7 +339,7 @@ fn record_pattern_field<'a>(min_indent: u16) -> impl Parser<'a, Loc> move |arena, state: State<'a>| { // You must have a field name, e.g. "email" // using the initial pos is important for error reporting - let pos = state.xyzlcol; + let pos = state.pos(); let (progress, loc_label, state) = loc!(specialize( move |_, _| PRecord::Field(pos), lowercase_ident() diff --git a/compiler/parse/src/state.rs b/compiler/parse/src/state.rs index 191d25ec0c..36688ddadd 100644 --- a/compiler/parse/src/state.rs +++ b/compiler/parse/src/state.rs @@ -1,7 +1,7 @@ use crate::parser::Progress::*; use crate::parser::{BadInputError, Progress}; use bumpalo::Bump; -use roc_region::all::{Position, Region}; +use roc_region::all::{Position, Region, LineColumn}; use std::fmt; /// A position in a source file. @@ -11,7 +11,7 @@ pub struct State<'a> { bytes: &'a [u8], /// Current position within the input (line/column) - pub xyzlcol: Position, + pub xyzlcol: LineColumn, /// Current indentation level, in columns /// (so no indent is col 1 - this saves an arithmetic operation.) @@ -22,7 +22,7 @@ impl<'a> State<'a> { pub fn new(bytes: &'a [u8]) -> State<'a> { State { bytes, - xyzlcol: Position::default(), + xyzlcol: LineColumn::default(), indent_column: 0, } } @@ -40,7 +40,10 @@ impl<'a> State<'a> { /// Returns the current position pub const fn pos(&self) -> Position { - self.xyzlcol + Position { + line: self.xyzlcol.line, + column: self.xyzlcol.column, + } } /// Returns whether the parser has reached the end of the input @@ -75,7 +78,7 @@ impl<'a> State<'a> { Some(column_usize) if column_usize <= u16::MAX as usize => { Ok(State { bytes: &self.bytes[quantity..], - xyzlcol: Position { + xyzlcol: LineColumn { line: self.xyzlcol.line, column: column_usize as u16, }, @@ -83,7 +86,7 @@ impl<'a> State<'a> { ..self }) } - _ => Err((NoProgress, to_error(self.xyzlcol), self)), + _ => Err((NoProgress, to_error(self.pos()), self)), } } @@ -93,7 +96,7 @@ impl<'a> State<'a> { /// and thus wanting a Region while not having access to loc(). pub fn len_region(&self, length: u16) -> Region { Region::new( - self.xyzlcol, + self.pos(), Position { line: self.xyzlcol.line, column: self diff --git a/compiler/parse/src/string_literal.rs b/compiler/parse/src/string_literal.rs index f22c10f6e0..c9a93ec371 100644 --- a/compiler/parse/src/string_literal.rs +++ b/compiler/parse/src/string_literal.rs @@ -17,7 +17,7 @@ fn ascii_hex_digits<'a>() -> impl Parser<'a, &'a str, EString<'a>> { buf.push(byte as char); } else if buf.is_empty() { // We didn't find any hex digits! - return Err((NoProgress, EString::CodePtEnd(state.xyzlcol), state)); + return Err((NoProgress, EString::CodePtEnd(state.pos()), state)); } else { let state = state.advance_without_indenting_ee(buf.len(), |pos| { EString::Space(BadInputError::LineTooLong, pos) @@ -27,7 +27,7 @@ fn ascii_hex_digits<'a>() -> impl Parser<'a, &'a str, EString<'a>> { } } - Err((NoProgress, EString::CodePtEnd(state.xyzlcol), state)) + Err((NoProgress, EString::CodePtEnd(state.pos()), state)) } } @@ -56,7 +56,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> { bytes = state.bytes()[1..].iter(); state = advance_state!(state, 1)?; } else { - return Err((NoProgress, EString::Open(state.xyzlcol), state)); + return Err((NoProgress, EString::Open(state.pos()), state)); } // At the parsing stage we keep the entire raw string, because the formatter @@ -100,7 +100,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> { Err(_) => { return Err(( MadeProgress, - EString::Space(BadInputError::BadUtf8, state.xyzlcol), + EString::Space(BadInputError::BadUtf8, state.pos()), state, )); } @@ -192,7 +192,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> { // all remaining chars. This will mask all other errors, but // it should make it easiest to debug; the file will be a giant // error starting from where the open quote appeared. - return Err((MadeProgress, EString::EndlessSingle(state.xyzlcol), state)); + return Err((MadeProgress, EString::EndlessSingle(state.pos()), state)); } } b'\\' => { @@ -281,7 +281,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> { // Invalid escape! A backslash must be followed // by either an open paren or else one of the // escapable characters (\n, \t, \", \\, etc) - return Err((MadeProgress, EString::UnknownEscape(state.xyzlcol), state)); + return Err((MadeProgress, EString::UnknownEscape(state.pos()), state)); } } } @@ -295,9 +295,9 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> { Err(( MadeProgress, if is_multiline { - EString::EndlessMulti(state.xyzlcol) + EString::EndlessMulti(state.pos()) } else { - EString::EndlessSingle(state.xyzlcol) + EString::EndlessSingle(state.pos()) }, state, )) diff --git a/compiler/parse/src/type_annotation.rs b/compiler/parse/src/type_annotation.rs index 79b3ca7ce2..e6102eca08 100644 --- a/compiler/parse/src/type_annotation.rs +++ b/compiler/parse/src/type_annotation.rs @@ -101,7 +101,7 @@ fn parse_type_alias_after_as<'a>(min_indent: u16) -> impl Parser<'a, AliasHeader } fn fail_type_start<'a, T: 'a>() -> impl Parser<'a, T, EType<'a>> { - |_arena, state: State<'a>| Err((NoProgress, EType::TStart(state.xyzlcol), state)) + |_arena, state: State<'a>| Err((NoProgress, EType::TStart(state.pos()), state)) } fn term<'a>(min_indent: u16) -> impl Parser<'a, Loc>, EType<'a>> { @@ -240,7 +240,7 @@ where { move |arena, state: State<'a>| match crate::ident::tag_name().parse(arena, state) { Ok(good) => Ok(good), - Err((progress, _, state)) => Err((progress, to_problem(state.xyzlcol), state)), + Err((progress, _, state)) => Err((progress, to_problem(state.pos()), state)), } } @@ -254,7 +254,7 @@ fn record_type_field<'a>( (move |arena, state: State<'a>| { // You must have a field name, e.g. "email" // using the initial pos is important for error reporting - let pos = state.xyzlcol; + let pos = state.pos(); let (progress, loc_label, state) = loc!(specialize( move |_, _| ETypeRecord::Field(pos), lowercase_ident() @@ -410,7 +410,7 @@ fn expression<'a>( ), |_, state: State<'a>| Err(( NoProgress, - EType::TFunctionArgument(state.xyzlcol), + EType::TFunctionArgument(state.pos()), state )) ] @@ -503,7 +503,7 @@ fn parse_concrete_type<'a>( Ok((MadeProgress, answer, state)) } - Err((NoProgress, _, state)) => Err((NoProgress, ETypeApply::End(state.xyzlcol), state)), + Err((NoProgress, _, state)) => Err((NoProgress, ETypeApply::End(state.pos()), state)), Err((MadeProgress, _, mut state)) => { // we made some progress, but ultimately failed. // that means a malformed type name @@ -531,6 +531,6 @@ fn parse_type_variable<'a>( Ok((MadeProgress, answer, state)) } - Err((progress, _, state)) => Err((progress, EType::TBadTypeVariable(state.xyzlcol), state)), + Err((progress, _, state)) => Err((progress, EType::TBadTypeVariable(state.pos()), state)), } } diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index 7d477db2e0..0b156987cb 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -163,12 +163,30 @@ impl Position { Position { line, column } } + #[must_use] pub fn bump_column(self, count: u16) -> Self { Self { line: self.line, column: self.column + count, } } + + #[must_use] + pub fn bump_invisible(self, _count: u16) -> Self { + // This WILL affect the byte offset once we switch to that + Self { + line: self.line, + column: self.column, + } + } + + #[must_use] + pub fn bump_newline(self) -> Self { + Self { + line: self.line + 1, + column: 0, + } + } } impl Debug for Position { @@ -177,6 +195,12 @@ impl Debug for Position { } } +#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Default)] +pub struct LineColumn { + pub line: u32, + pub column: u16, +} + #[derive(Clone, Eq, Copy, PartialEq, PartialOrd, Ord, Hash)] pub struct Loc { pub region: Region, From 721233f9c88b66a5a2448e4d52057a5d7e26732d Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Thu, 23 Dec 2021 18:28:07 -0800 Subject: [PATCH 062/541] More incremental changes --- compiler/parse/src/expr.rs | 38 ++++++++++++++---------------- compiler/parse/src/ident.rs | 10 ++------ compiler/parse/src/parser.rs | 4 ++-- compiler/parse/tests/test_parse.rs | 2 +- compiler/region/src/all.rs | 4 ++++ 5 files changed, 27 insertions(+), 31 deletions(-) diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 62fac7d566..80c04f3efd 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -16,7 +16,7 @@ use crate::type_annotation; use bumpalo::collections::Vec; use bumpalo::Bump; use roc_module::called_via::{BinOp, CalledVia, UnaryOp}; -use roc_region::all::{Loc, Position, Region}; +use roc_region::all::{Loc, Position, Region, LineColumn}; use crate::parser::Progress::{self, *}; @@ -310,7 +310,7 @@ fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> { fn parse_expr_start<'a>( min_indent: u16, options: ExprParseOptions, - start: Position, + start: LineColumn, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Loc>, EExpr<'a>> { @@ -331,7 +331,7 @@ fn parse_expr_start<'a>( fn parse_expr_operator_chain<'a>( min_indent: u16, options: ExprParseOptions, - start: Position, + start: LineColumn, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { @@ -512,12 +512,9 @@ fn numeric_negate_expression<'a, T>( ) -> Loc> { debug_assert_eq!(state.bytes().get(0), Some(&b'-')); // for overflow reasons, we must make the unary minus part of the number literal. - let start = expr.region.start(); + let start = state.pos(); let region = Region::new( - Position { - column: start.column - 1, - ..start - }, + start, expr.region.end(), ); @@ -780,7 +777,7 @@ struct DefState<'a> { fn parse_defs_end<'a>( options: ExprParseOptions, - start: Position, + start: LineColumn, mut def_state: DefState<'a>, arena: &'a Bump, state: State<'a>, @@ -800,6 +797,7 @@ fn parse_defs_end<'a>( }; let start = state.pos(); + let xyzlcol = state.xyzlcol; match space0_after_e( crate::pattern::loc_pattern_help(min_indent), @@ -835,7 +833,7 @@ fn parse_defs_end<'a>( loc_def_expr, ); - parse_defs_end(options, start, def_state, arena, state) + parse_defs_end(options, xyzlcol, def_state, arena, state) } } } @@ -862,7 +860,7 @@ fn parse_defs_end<'a>( loc_def_expr, ); - parse_defs_end(options, start, def_state, arena, state) + parse_defs_end(options, xyzlcol, def_state, arena, state) } Ok((_, BinOp::HasType, state)) => { let (_, ann_type, state) = specialize( @@ -884,7 +882,7 @@ fn parse_defs_end<'a>( ann_type, ); - parse_defs_end(options, start, def_state, arena, state) + parse_defs_end(options, xyzlcol, def_state, arena, state) } _ => Ok((MadeProgress, def_state, initial)), @@ -894,7 +892,7 @@ fn parse_defs_end<'a>( fn parse_defs_expr<'a>( options: ExprParseOptions, - start: Position, + start: LineColumn, def_state: DefState<'a>, arena: &'a Bump, state: State<'a>, @@ -935,7 +933,7 @@ fn parse_defs_expr<'a>( fn parse_expr_operator<'a>( min_indent: u16, options: ExprParseOptions, - start: Position, + start: LineColumn, mut expr_state: ExprState<'a>, loc_op: Loc, arena: &'a Bump, @@ -1224,7 +1222,7 @@ fn parse_expr_operator<'a>( fn parse_expr_end<'a>( min_indent: u16, options: ExprParseOptions, - start: Position, + start: LineColumn, mut expr_state: ExprState<'a>, arena: &'a Bump, state: State<'a>, @@ -1393,7 +1391,7 @@ fn parse_loc_expr_with_options<'a>( arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Loc>, EExpr<'a>> { - let start = state.pos(); + let start = state.xyzlcol; parse_expr_start(min_indent, options, start, arena, state) } @@ -1541,17 +1539,17 @@ pub fn defs<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Loc>>, EExpr let (_, initial_space, state) = space0_e(min_indent, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?; - let start = state.pos(); + let xyzlcol = state.xyzlcol; let options = ExprParseOptions { accept_multi_backpassing: false, check_for_arrow: true, }; - let (_, def_state, state) = parse_defs_end(options, start, def_state, arena, state)?; + let (_, def_state, state) = parse_defs_end(options, xyzlcol, def_state, arena, state)?; let (_, final_space, state) = - space0_e(start.column, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?; + space0_e(xyzlcol.column, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?; let mut output = Vec::with_capacity_in(def_state.defs.len(), arena); @@ -1966,7 +1964,7 @@ fn expect_help<'a>( options: ExprParseOptions, ) -> impl Parser<'a, Expr<'a>, EExpect<'a>> { move |arena: &'a Bump, state: State<'a>| { - let start = state.pos(); + let start = state.xyzlcol; let (_, _, state) = parser::keyword_e(keyword::EXPECT, EExpect::Expect).parse(arena, state)?; diff --git a/compiler/parse/src/ident.rs b/compiler/parse/src/ident.rs index 9598dfa513..3effc398db 100644 --- a/compiler/parse/src/ident.rs +++ b/compiler/parse/src/ident.rs @@ -276,10 +276,7 @@ fn chomp_accessor(buffer: &[u8], pos: Position) -> Result<&str, BadIdent> { } Err(_) => { // we've already made progress with the initial `.` - Err(BadIdent::StrayDot(Position { - line: pos.line, - column: pos.column + 1, - })) + Err(BadIdent::StrayDot(pos.bump_column(1))) } } } @@ -301,10 +298,7 @@ fn chomp_private_tag(buffer: &[u8], pos: Position) -> Result<&str, BadIdent> { Ok(value) } } - Err(_) => Err(BadIdent::BadPrivateTag(Position { - line: pos.line, - column: pos.column + 1, - })), + Err(_) => Err(BadIdent::BadPrivateTag(pos.bump_column(1))), } } diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index 7092da1e4d..568d73e1d9 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -49,7 +49,7 @@ pub enum SyntaxError<'a> { Unexpected(Region), OutdentedTooFar, ConditionFailed, - LineTooLong(u32 /* which line was too long */), + LineTooLong(Position), TooManyLines, Eof(Region), InvalidPattern, @@ -219,7 +219,7 @@ pub fn bad_input_to_syntax_error<'a>(bad_input: BadInputError, pos: Position) -> use crate::parser::BadInputError::*; match bad_input { HasTab => SyntaxError::NotYetImplemented("call error on tabs".to_string()), - LineTooLong => SyntaxError::LineTooLong(pos.line), + LineTooLong => SyntaxError::LineTooLong(pos), TooManyLines => SyntaxError::TooManyLines, BadUtf8 => SyntaxError::BadUtf8, } diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 573ead2aa6..afa8d7c163 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -495,7 +495,7 @@ mod test_parse { // Make sure it's longer than our maximum line length assert_eq!(too_long_str.len(), max_line_length + 1); - assert_parsing_fails(&too_long_str, SyntaxError::LineTooLong(0)); + assert_parsing_fails(&too_long_str, SyntaxError::LineTooLong(Position::zero())); } #[quickcheck] diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index 0b156987cb..8d09b23a61 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -159,6 +159,10 @@ pub struct Position { } impl Position { + pub const fn zero() -> Position { + Position { line: 0, column: 0 } + } + pub fn new(line: u32, column: u16) -> Position { Position { line, column } } From 82d2be063522ba1b630a99d4d1972340c457d590 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Thu, 23 Dec 2021 19:15:54 -0800 Subject: [PATCH 063/541] Introduce LineColumnRegion and force conversion --- cli/src/repl/gen.rs | 8 +- compiler/build/src/program.rs | 9 +- compiler/load/src/file.rs | 7 +- compiler/region/src/all.rs | 116 ++++++++ compiler/test_gen/src/helpers/llvm.rs | 10 +- reporting/src/error/canonicalize.rs | 153 +++++----- reporting/src/error/mono.rs | 10 +- reporting/src/error/parse.rs | 383 +++++++++++++++----------- reporting/src/error/type.rs | 70 +++-- reporting/src/report.rs | 15 +- reporting/tests/test_reporting.rs | 13 +- 11 files changed, 507 insertions(+), 287 deletions(-) diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index 83593bc999..c16ba70895 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -12,6 +12,7 @@ use roc_gen_llvm::llvm::externs::add_default_roc_externs; use roc_load::file::LoadingProblem; use roc_mono::ir::OptLevel; use roc_parse::parser::SyntaxError; +use roc_region::all::LineInfo; use roc_types::pretty_print::{content_to_string, name_all_type_vars}; use std::path::{Path, PathBuf}; use std::str::from_utf8_unchecked; @@ -91,6 +92,7 @@ pub fn gen_and_eval<'a>( continue; } + let line_info = LineInfo::new(&src); let src_lines: Vec<&str> = src.split('\n').collect(); let palette = DEFAULT_PALETTE; @@ -98,7 +100,7 @@ pub fn gen_and_eval<'a>( let alloc = RocDocAllocator::new(&src_lines, home, &interns); for problem in can_problems.into_iter() { - let report = can_problem(&alloc, module_path.clone(), problem); + let report = can_problem(&alloc, &line_info, module_path.clone(), problem); let mut buf = String::new(); report.render_color_terminal(&mut buf, &alloc, &palette); @@ -107,7 +109,7 @@ pub fn gen_and_eval<'a>( } for problem in type_problems { - if let Some(report) = type_problem(&alloc, module_path.clone(), problem) { + if let Some(report) = type_problem(&alloc, &line_info, module_path.clone(), problem) { let mut buf = String::new(); report.render_color_terminal(&mut buf, &alloc, &palette); @@ -117,7 +119,7 @@ pub fn gen_and_eval<'a>( } for problem in mono_problems { - let report = mono_problem(&alloc, module_path.clone(), problem); + let report = mono_problem(&alloc, &line_info, module_path.clone(), problem); let mut buf = String::new(); report.render_color_terminal(&mut buf, &alloc, &palette); diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 8bcbb60893..98ea9de4ce 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -5,6 +5,7 @@ pub use roc_gen_llvm::llvm::build::FunctionIterator; use roc_load::file::{LoadedModule, MonomorphizedModule}; use roc_module::symbol::{Interns, ModuleId}; use roc_mono::ir::OptLevel; +use roc_region::all::LineInfo; use std::path::{Path, PathBuf}; use std::time::Duration; @@ -81,13 +82,15 @@ fn report_problems_help( src_lines.extend(src.split('\n')); } + let lines = LineInfo::new(src); + // Report parsing and canonicalization problems let alloc = RocDocAllocator::new(&src_lines, *home, interns); let problems = can_problems.remove(home).unwrap_or_default(); for problem in problems.into_iter() { - let report = can_problem(&alloc, module_path.clone(), problem); + let report = can_problem(&alloc, &lines, module_path.clone(), problem); let severity = report.severity; let mut buf = String::new(); @@ -106,7 +109,7 @@ fn report_problems_help( let problems = type_problems.remove(home).unwrap_or_default(); for problem in problems { - if let Some(report) = type_problem(&alloc, module_path.clone(), problem) { + if let Some(report) = type_problem(&alloc, &lines, module_path.clone(), problem) { let severity = report.severity; let mut buf = String::new(); @@ -126,7 +129,7 @@ fn report_problems_help( let problems = mono_problems.remove(home).unwrap_or_default(); for problem in problems { - let report = mono_problem(&alloc, module_path.clone(), problem); + let report = mono_problem(&alloc, &lines, module_path.clone(), problem); let severity = report.severity; let mut buf = String::new(); diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index fed569eabb..1b7d720c45 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -28,7 +28,7 @@ use roc_parse::header::PackageName; use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent}; use roc_parse::module::module_defs; use roc_parse::parser::{ParseProblem, Parser, SyntaxError}; -use roc_region::all::{Loc, Region}; +use roc_region::all::{Loc, Region, LineInfo}; use roc_solve::module::SolvedModule; use roc_solve::solve; use roc_types::solved_types::Solved; @@ -4331,7 +4331,10 @@ fn to_parse_problem_report<'a>( let alloc = RocDocAllocator::new(&src_lines, module_id, &interns); let starting_line = 0; - let report = parse_problem(&alloc, problem.filename.clone(), starting_line, problem); + + let lines = LineInfo::new(src); + + let report = parse_problem(&alloc, &lines, problem.filename.clone(), starting_line, problem); let mut buf = String::new(); let palette = DEFAULT_PALETTE; diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index 8d09b23a61..a6dbd94cbc 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -205,6 +205,96 @@ pub struct LineColumn { pub column: u16, } +impl LineColumn { + pub const fn zero() -> Self { + LineColumn { + line: 0, + column: 0, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Default)] +pub struct LineColumnRegion { + pub start: LineColumn, + pub end: LineColumn, +} + +impl LineColumnRegion { + pub const fn zero() -> Self { + LineColumnRegion { + start: LineColumn::zero(), + end: LineColumn::zero(), + } + } + + pub fn contains(&self, other: &Self) -> bool { + use std::cmp::Ordering::*; + match self.start.line.cmp(&other.start.line) { + Greater => false, + Equal => match self.end.line.cmp(&other.end.line) { + Less => false, + Equal => self.start.column <= other.start.column && self.end.column >= other.end.column, + Greater => self.start.column >= other.start.column, + }, + Less => match self.end.line.cmp(&other.end.line) { + Less => false, + Equal => self.end.column >= other.end.column, + Greater => true, + }, + } + } + + pub fn is_empty(&self) -> bool { + self.end.line == self.start.line && self.start.column == self.end.column + } + + pub fn span_across(start: &LineColumnRegion, end: &LineColumnRegion) -> Self { + LineColumnRegion { + start: start.start, + end: end.end, + } + } + + pub fn across_all<'a, I>(regions: I) -> Self + where + I: IntoIterator, + { + let mut it = regions.into_iter(); + + if let Some(first) = it.next() { + let mut result = *first; + + for r in it { + result = Self::span_across(&result, r); + } + + result + } else { + Self::zero() + } + } + + pub fn lines_between(&self, other: &LineColumnRegion) -> u32 { + if self.end.line <= other.start.line { + other.start.line - self.end.line + } else if self.start.line >= other.end.line { + self.start.line - other.end.line + } else { + // intersection + 0 + } + } + + pub const fn start(&self) -> LineColumn { + self.start + } + + pub const fn end(&self) -> LineColumn { + self.end + } +} + #[derive(Clone, Eq, Copy, PartialEq, PartialOrd, Ord, Hash)] pub struct Loc { pub region: Region, @@ -269,3 +359,29 @@ where } } } + +pub struct LineInfo { + // TODO +} + +impl LineInfo { + pub fn new(_text: &str) -> LineInfo { + // TODO + LineInfo {} + } + + pub fn convert_pos(&self, pos: Position) -> LineColumn { + // TODO + LineColumn { + line: pos.line, + column: pos.column, + } + } + + pub fn convert_region(&self, region: Region) -> LineColumnRegion { + LineColumnRegion { + start: self.convert_pos(region.start()), + end: self.convert_pos(region.end()), + } + } +} \ No newline at end of file diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index d9bdafef43..e1bec306b1 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -9,6 +9,7 @@ use roc_collections::all::{MutMap, MutSet}; use roc_gen_llvm::llvm::externs::add_default_roc_externs; use roc_module::symbol::Symbol; use roc_mono::ir::OptLevel; +use roc_region::all::LineInfo; use roc_types::subs::VarStore; use target_lexicon::Triple; @@ -105,6 +106,7 @@ fn create_llvm_module<'a>( continue; } + let line_info = LineInfo::new(&src); let src_lines: Vec<&str> = src.split('\n').collect(); let palette = DEFAULT_PALETTE; @@ -121,7 +123,7 @@ fn create_llvm_module<'a>( | RuntimeError(_) | UnsupportedPattern(_, _) | ExposedButNotDefined(_) => { - let report = can_problem(&alloc, module_path.clone(), problem); + let report = can_problem(&alloc, &line_info, module_path.clone(), problem); let mut buf = String::new(); report.render_color_terminal(&mut buf, &alloc, &palette); @@ -130,7 +132,7 @@ fn create_llvm_module<'a>( lines.push(buf); } _ => { - let report = can_problem(&alloc, module_path.clone(), problem); + let report = can_problem(&alloc, &line_info, module_path.clone(), problem); let mut buf = String::new(); report.render_color_terminal(&mut buf, &alloc, &palette); @@ -141,7 +143,7 @@ fn create_llvm_module<'a>( } for problem in type_problems { - if let Some(report) = type_problem(&alloc, module_path.clone(), problem) { + if let Some(report) = type_problem(&alloc, &line_info, module_path.clone(), problem) { let mut buf = String::new(); report.render_color_terminal(&mut buf, &alloc, &palette); @@ -151,7 +153,7 @@ fn create_llvm_module<'a>( } for problem in mono_problems { - let report = mono_problem(&alloc, module_path.clone(), problem); + let report = mono_problem(&alloc, &line_info, module_path.clone(), problem); let mut buf = String::new(); report.render_color_terminal(&mut buf, &alloc, &palette); diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index f57d106952..d2ee07610b 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -2,7 +2,7 @@ use roc_collections::all::MutSet; use roc_module::ident::{Ident, Lowercase, ModuleName}; use roc_problem::can::PrecedenceProblem::BothNonAssociative; use roc_problem::can::{BadPattern, FloatErrorKind, IntErrorKind, Problem, RuntimeError}; -use roc_region::all::{Loc, Position, Region}; +use roc_region::all::{Loc, Position, Region, LineInfo}; use std::path::PathBuf; use crate::error::r#type::suggest; @@ -27,6 +27,7 @@ const MODULE_NOT_IMPORTED: &str = "MODULE NOT IMPORTED"; pub fn can_problem<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, filename: PathBuf, problem: Problem, ) -> Report<'b> { @@ -43,7 +44,7 @@ pub fn can_problem<'b>( alloc .symbol_unqualified(symbol) .append(alloc.reflow(" is not used anywhere in your code.")), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc .reflow("If you didn't intend on using ") .append(alloc.symbol_unqualified(symbol)) @@ -60,7 +61,7 @@ pub fn can_problem<'b>( alloc.module(module_id), alloc.reflow(" is used in this module."), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("Since "), alloc.module(module_id), @@ -97,7 +98,7 @@ pub fn can_problem<'b>( alloc.symbol_unqualified(argument_symbol), alloc.text("."), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("If you don't need "), alloc.symbol_unqualified(argument_symbol), @@ -137,7 +138,7 @@ pub fn can_problem<'b>( )), ]) }, - alloc.region(region), + alloc.region(lines.convert_region(region)), ]); title = SYNTAX_PROBLEM.to_string(); @@ -146,7 +147,7 @@ pub fn can_problem<'b>( Problem::UnsupportedPattern(BadPattern::UnderscoreInDef, region) => { doc = alloc.stack(vec![ alloc.reflow("Underscore patterns are not allowed in definitions"), - alloc.region(region), + alloc.region(lines.convert_region(region)), ]); title = SYNTAX_PROBLEM.to_string(); @@ -176,7 +177,7 @@ pub fn can_problem<'b>( alloc .reflow("This pattern is not allowed in ") .append(alloc.reflow(this_thing)), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(suggestion), ]); @@ -187,13 +188,13 @@ pub fn can_problem<'b>( original_region, shadow, } => { - doc = report_shadowing(alloc, original_region, shadow); + doc = report_shadowing(alloc, lines, original_region, shadow); title = DUPLICATE_NAME.to_string(); severity = Severity::RuntimeError; } Problem::CyclicAlias(symbol, region, others) => { - let answer = crate::error::r#type::cyclic_alias(alloc, symbol, region, others); + let answer = crate::error::r#type::cyclic_alias(alloc, lines, symbol, region, others); doc = answer.0; title = answer.1; @@ -212,7 +213,7 @@ pub fn can_problem<'b>( alloc.symbol_unqualified(alias), alloc.reflow(" alias definition:"), ]), - alloc.region(variable_region), + alloc.region(lines.convert_region(variable_region)), alloc.reflow("Roc does not allow unused type alias parameters!"), // TODO add link to this guide section alloc.tip().append(alloc.reflow( @@ -225,7 +226,7 @@ pub fn can_problem<'b>( severity = Severity::RuntimeError; } Problem::BadRecursion(entries) => { - doc = to_circular_def_doc(alloc, &entries); + doc = to_circular_def_doc(alloc, lines, &entries); title = CIRCULAR_DEF.to_string(); severity = Severity::RuntimeError; } @@ -242,16 +243,16 @@ pub fn can_problem<'b>( alloc.reflow(" field twice!"), ]), alloc.region_all_the_things( - record_region, - replaced_region, - field_region, + lines.convert_region(record_region), + lines.convert_region(replaced_region), + lines.convert_region(field_region), Annotation::Error, ), alloc.reflow(r"In the rest of the program, I will only use the latter definition:"), alloc.region_all_the_things( - record_region, - field_region, - field_region, + lines.convert_region(record_region), + lines.convert_region(field_region), + lines.convert_region(field_region), Annotation::TypoSuggestion, ), alloc.concat(vec![ @@ -271,6 +272,7 @@ pub fn can_problem<'b>( } => { return to_invalid_optional_value_report( alloc, + lines, filename, field_name, field_region, @@ -290,16 +292,16 @@ pub fn can_problem<'b>( alloc.reflow(" field twice!"), ]), alloc.region_all_the_things( - record_region, - replaced_region, - field_region, + lines.convert_region(record_region), + lines.convert_region(replaced_region), + lines.convert_region(field_region), Annotation::Error, ), alloc.reflow("In the rest of the program, I will only use the latter definition:"), alloc.region_all_the_things( - record_region, - field_region, - field_region, + lines.convert_region(record_region), + lines.convert_region(field_region), + lines.convert_region(field_region), Annotation::TypoSuggestion, ), alloc.concat(vec![ @@ -325,16 +327,16 @@ pub fn can_problem<'b>( alloc.reflow(" tag twice!"), ]), alloc.region_all_the_things( - tag_union_region, - replaced_region, - tag_region, + lines.convert_region(tag_union_region), + lines.convert_region(replaced_region), + lines.convert_region(tag_region), Annotation::Error, ), alloc.reflow("In the rest of the program, I will only use the latter definition:"), alloc.region_all_the_things( - tag_union_region, - tag_region, - tag_region, + lines.convert_region(tag_union_region), + lines.convert_region(tag_region), + lines.convert_region(tag_region), Annotation::TypoSuggestion, ), alloc.concat(vec![ @@ -355,7 +357,8 @@ pub fn can_problem<'b>( alloc.reflow( "This annotation does not match the definition immediately following it:", ), - alloc.region(Region::span_across(annotation_pattern, def_pattern)), + alloc.region(lines.convert_region( + Region::span_across(annotation_pattern, def_pattern))), alloc.reflow("Is it a typo? If not, put either a newline or comment between them."), ]); @@ -369,7 +372,7 @@ pub fn can_problem<'b>( alloc.symbol_unqualified(alias_name), alloc.reflow(" is not what I expect:"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("Only type variables like "), alloc.type_variable("a".into()), @@ -385,7 +388,7 @@ pub fn can_problem<'b>( Problem::InvalidHexadecimal(region) => { doc = alloc.stack(vec![ alloc.reflow("This unicode code point is invalid:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"I was expecting a hexadecimal number, like "), alloc.parser_suggestion("\\u(1100)"), @@ -402,7 +405,7 @@ pub fn can_problem<'b>( Problem::InvalidUnicodeCodePt(region) => { doc = alloc.stack(vec![ alloc.reflow("This unicode code point is invalid:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("Learn more about working with unicode in roc at TODO"), ]); @@ -412,7 +415,7 @@ pub fn can_problem<'b>( Problem::InvalidInterpolation(region) => { doc = alloc.stack(vec![ alloc.reflow("This string interpolation is invalid:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"I was expecting an identifier, like "), alloc.parser_suggestion("\\u(message)"), @@ -427,7 +430,7 @@ pub fn can_problem<'b>( severity = Severity::RuntimeError; } Problem::RuntimeError(runtime_error) => { - let answer = pretty_runtime_error(alloc, runtime_error); + let answer = pretty_runtime_error(alloc, lines, runtime_error); doc = answer.0; title = answer.1.to_string(); @@ -445,12 +448,13 @@ pub fn can_problem<'b>( fn to_invalid_optional_value_report<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, filename: PathBuf, field_name: Lowercase, field_region: Region, record_region: Region, -) -> Report { - let doc = to_invalid_optional_value_report_help(alloc, field_name, field_region, record_region); +) -> Report<'b> { + let doc = to_invalid_optional_value_report_help(alloc, lines, field_name, field_region, record_region); Report { title: "BAD OPTIONAL VALUE".to_string(), @@ -462,6 +466,7 @@ fn to_invalid_optional_value_report<'b>( fn to_invalid_optional_value_report_help<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, field_name: Lowercase, field_region: Region, record_region: Region, @@ -472,7 +477,7 @@ fn to_invalid_optional_value_report_help<'b>( alloc.record_field(field_name), alloc.reflow(" field in an incorrect context!"), ]), - alloc.region_all_the_things(record_region, field_region, field_region, Annotation::Error), + alloc.region_all_the_things(lines.convert_region(record_region), lines.convert_region(field_region), lines.convert_region(field_region), Annotation::Error), alloc.reflow(r"You can only use optional values in record destructuring, like:"), alloc .reflow(r"{ answer ? 42, otherField } = myRecord") @@ -482,6 +487,7 @@ fn to_invalid_optional_value_report_help<'b>( fn to_bad_ident_expr_report<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, bad_ident: roc_parse::ident::BadIdent, surroundings: Region, ) -> RocDocBuilder<'b> { @@ -494,7 +500,7 @@ fn to_bad_ident_expr_report<'b>( alloc.stack(vec![ alloc.reflow(r"I trying to parse a record field access here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("So I expect to see a lowercase letter next, like "), alloc.parser_suggestion(".name"), @@ -507,7 +513,7 @@ fn to_bad_ident_expr_report<'b>( WeirdAccessor(_pos) => alloc.stack(vec![ alloc.reflow("I am very confused by this field access"), - alloc.region(surroundings), + alloc.region(lines.convert_region(surroundings)), alloc.concat(vec![ alloc.reflow("It looks like a field access on an accessor. I parse"), alloc.parser_suggestion(".client.name"), @@ -525,7 +531,7 @@ fn to_bad_ident_expr_report<'b>( alloc.stack(vec![ alloc.reflow("I am trying to parse a qualified name here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I was expecting to see an identifier next, like "), alloc.parser_suggestion("height"), @@ -540,7 +546,7 @@ fn to_bad_ident_expr_report<'b>( alloc.stack(vec![ alloc.reflow("I am trying to parse a qualified name here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"This looks like a qualified tag name to me, "), alloc.reflow(r"but tags cannot be qualified! "), @@ -555,7 +561,7 @@ fn to_bad_ident_expr_report<'b>( let region = Region::new(surroundings.start(), pos); alloc.stack(vec![ alloc.reflow("Underscores are not allowed in identifier names:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![alloc.reflow( r"I recommend using camelCase, it is the standard in the Roc ecosystem.", )]), @@ -569,7 +575,7 @@ fn to_bad_ident_expr_report<'b>( let region = Region::new(pos, pos.bump_column(width)); alloc.stack(vec![ alloc.reflow("I am very confused by this field access:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"It looks like a record field access on a private tag.") ]), @@ -579,7 +585,7 @@ fn to_bad_ident_expr_report<'b>( let region = Region::new(pos, pos.bump_column(width)); alloc.stack(vec![ alloc.reflow("I am very confused by this expression:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow( r"Looks like a private tag is treated like a module name. ", @@ -595,7 +601,7 @@ fn to_bad_ident_expr_report<'b>( Region::new(surroundings.start().bump_column(1), pos.bump_column(1)); alloc.stack(vec![ alloc.reflow("I am trying to parse a private tag here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"But after the "), alloc.keyword("@"), @@ -617,6 +623,7 @@ fn to_bad_ident_expr_report<'b>( fn to_bad_ident_pattern_report<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, bad_ident: roc_parse::ident::BadIdent, surroundings: Region, ) -> RocDocBuilder<'b> { @@ -629,7 +636,7 @@ fn to_bad_ident_pattern_report<'b>( alloc.stack(vec![ alloc.reflow(r"I trying to parse a record field accessor here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("Something like "), alloc.parser_suggestion(".name"), @@ -642,7 +649,7 @@ fn to_bad_ident_pattern_report<'b>( WeirdAccessor(_pos) => alloc.stack(vec![ alloc.reflow("I am very confused by this field access"), - alloc.region(surroundings), + alloc.region(lines.convert_region(surroundings)), alloc.concat(vec![ alloc.reflow("It looks like a field access on an accessor. I parse"), alloc.parser_suggestion(".client.name"), @@ -660,7 +667,7 @@ fn to_bad_ident_pattern_report<'b>( alloc.stack(vec![ alloc.reflow("I am trying to parse a qualified name here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I was expecting to see an identifier next, like "), alloc.parser_suggestion("height"), @@ -675,7 +682,7 @@ fn to_bad_ident_pattern_report<'b>( alloc.stack(vec![ alloc.reflow("I am trying to parse a qualified name here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"This looks like a qualified tag name to me, "), alloc.reflow(r"but tags cannot be qualified! "), @@ -694,7 +701,7 @@ fn to_bad_ident_pattern_report<'b>( alloc.stack(vec![ alloc.reflow("I am trying to parse an identifier here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![alloc.reflow( r"Underscores are not allowed in identifiers. Use camelCase instead!", )]), @@ -770,6 +777,7 @@ where fn report_shadowing<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, original_region: Region, shadow: Loc, ) -> RocDocBuilder<'b> { @@ -780,15 +788,16 @@ fn report_shadowing<'b>( .text("The ") .append(alloc.ident(shadow.value)) .append(alloc.reflow(" name is first defined here:")), - alloc.region(original_region), + alloc.region(lines.convert_region(original_region)), alloc.reflow("But then it's defined a second time here:"), - alloc.region(shadow.region), + alloc.region(lines.convert_region(shadow.region)), alloc.reflow(line), ]) } fn pretty_runtime_error<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, runtime_error: RuntimeError, ) -> (RocDocBuilder<'b>, &'static str) { let doc; @@ -810,16 +819,16 @@ fn pretty_runtime_error<'b>( original_region, shadow, } => { - doc = report_shadowing(alloc, original_region, shadow); + doc = report_shadowing(alloc, lines, original_region, shadow); title = DUPLICATE_NAME; } RuntimeError::LookupNotInScope(loc_name, options) => { - doc = not_found(alloc, loc_name.region, &loc_name.value, "value", options); + doc = not_found(alloc, lines, loc_name.region, &loc_name.value, "value", options); title = UNRECOGNIZED_NAME; } RuntimeError::CircularDef(entries) => { - doc = to_circular_def_doc(alloc, &entries); + doc = to_circular_def_doc(alloc, lines, &entries); title = CIRCULAR_DEF; } RuntimeError::MalformedPattern(problem, region) => { @@ -835,7 +844,7 @@ fn pretty_runtime_error<'b>( MalformedBase(Base::Decimal) => " integer ", BadIdent(bad_ident) => { title = NAMING_PROBLEM; - doc = to_bad_ident_pattern_report(alloc, bad_ident, region); + doc = to_bad_ident_pattern_report(alloc, lines, bad_ident, region); return (doc, title); } @@ -859,7 +868,7 @@ fn pretty_runtime_error<'b>( alloc.text(name), alloc.reflow("pattern is malformed:"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), tip, ]); @@ -900,7 +909,7 @@ fn pretty_runtime_error<'b>( alloc.string(ident.to_string()), alloc.reflow("`:"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), did_you_mean, ]); @@ -912,7 +921,7 @@ fn pretty_runtime_error<'b>( imported_modules, region, } => { - doc = module_not_found(alloc, region, &module_name, imported_modules); + doc = module_not_found(alloc, lines, region, &module_name, imported_modules); title = MODULE_NOT_IMPORTED; } @@ -921,14 +930,14 @@ fn pretty_runtime_error<'b>( unreachable!(); } RuntimeError::MalformedIdentifier(_box_str, bad_ident, surroundings) => { - doc = to_bad_ident_expr_report(alloc, bad_ident, surroundings); + doc = to_bad_ident_expr_report(alloc, lines, bad_ident, surroundings); title = SYNTAX_PROBLEM; } RuntimeError::MalformedTypeName(_box_str, surroundings) => { doc = alloc.stack(vec![ alloc.reflow(r"I am confused by this type name:"), - alloc.region(surroundings), + alloc.region(lines.convert_region(surroundings)), alloc.concat(vec![ alloc.reflow("Type names start with an uppercase letter, "), alloc.reflow("and can optionally be qualified by a module name, like "), @@ -962,7 +971,7 @@ fn pretty_runtime_error<'b>( alloc.text(big_or_small), alloc.reflow(":"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc .reflow("Roc uses signed 64-bit floating points, allowing values between "), @@ -984,7 +993,7 @@ fn pretty_runtime_error<'b>( alloc.concat(vec![ alloc.reflow("This float literal contains an invalid digit:"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("Floating point literals can only contain the digits 0-9, or use scientific notation 10e4"), ]), @@ -1042,7 +1051,7 @@ fn pretty_runtime_error<'b>( alloc.text(problem), alloc.text(":"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc.text(plurals), contains, @@ -1072,7 +1081,7 @@ fn pretty_runtime_error<'b>( alloc.text(big_or_small), alloc.reflow(":"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("Roc uses signed 64-bit integers, allowing values between −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807."), tip, ]); @@ -1086,6 +1095,7 @@ fn pretty_runtime_error<'b>( } => { doc = to_invalid_optional_value_report_help( alloc, + lines, field_name, field_region, record_region, @@ -1099,7 +1109,7 @@ fn pretty_runtime_error<'b>( alloc.reflow("This expression cannot be updated"), alloc.reflow(":"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("Only variables can be updated with record update syntax."), ]); @@ -1147,6 +1157,7 @@ fn pretty_runtime_error<'b>( fn to_circular_def_doc<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, entries: &[roc_problem::can::CycleEntry], ) -> RocDocBuilder<'b> { // TODO "are you trying to mutate a variable? @@ -1165,7 +1176,7 @@ fn to_circular_def_doc<'b>( .reflow("The ") .append(alloc.symbol_unqualified(first.symbol)) .append(alloc.reflow(" definition is causing a very tricky infinite loop:")), - alloc.region(first.symbol_region), + alloc.region(lines.convert_region(first.symbol_region)), alloc .reflow("The ") .append(alloc.symbol_unqualified(first.symbol)) @@ -1189,6 +1200,7 @@ fn to_circular_def_doc<'b>( fn not_found<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, region: roc_region::all::Region, name: &Ident, thing: &'b str, @@ -1230,13 +1242,14 @@ fn not_found<'b>( alloc.reflow("` "), alloc.reflow(thing), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), to_details(default_no, default_yes), ]) } fn module_not_found<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, region: roc_region::all::Region, name: &ModuleName, options: MutSet>, @@ -1275,7 +1288,7 @@ fn module_not_found<'b>( alloc.string(name.to_string()), alloc.reflow("` module is not imported:"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), to_details(default_no, default_yes), ]) } diff --git a/reporting/src/error/mono.rs b/reporting/src/error/mono.rs index 3a7e0e7bf0..3612ed7c57 100644 --- a/reporting/src/error/mono.rs +++ b/reporting/src/error/mono.rs @@ -1,9 +1,11 @@ use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; use std::path::PathBuf; +use roc_region::all::LineInfo; use ven_pretty::DocAllocator; pub fn mono_problem<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, filename: PathBuf, problem: roc_mono::ir::MonoProblem, ) -> Report<'b> { @@ -16,7 +18,7 @@ pub fn mono_problem<'b>( BadArg => { let doc = alloc.stack(vec![ alloc.reflow("This pattern does not cover all the possibilities:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("Other possibilities include:"), unhandled_patterns_to_doc_block(alloc, missing), alloc.concat(vec![ @@ -39,7 +41,7 @@ pub fn mono_problem<'b>( BadDestruct => { let doc = alloc.stack(vec![ alloc.reflow("This pattern does not cover all the possibilities:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("Other possibilities include:"), unhandled_patterns_to_doc_block(alloc, missing), alloc.concat(vec![ @@ -67,7 +69,7 @@ pub fn mono_problem<'b>( alloc.keyword("when"), alloc.reflow(" does not cover all the possibilities:"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("Other possibilities include:"), unhandled_patterns_to_doc_block(alloc, missing), alloc.reflow( @@ -96,7 +98,7 @@ pub fn mono_problem<'b>( alloc.string(index.ordinal()), alloc.reflow(" pattern is redundant:"), ]), - alloc.region_with_subregion(overall_region, branch_region), + alloc.region_with_subregion(lines.convert_region(overall_region), lines.convert_region(branch_region)), alloc.reflow( "Any value of this shape will be handled by \ a previous pattern, so this one should be removed.", diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index 259239ac50..d26d9557db 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -1,5 +1,5 @@ use roc_parse::parser::{ParseProblem, SyntaxError}; -use roc_region::all::{Position, Region}; +use roc_region::all::{Position, Region, LineInfo}; use std::path::PathBuf; use crate::report::{Report, RocDocAllocator, RocDocBuilder, Severity}; @@ -7,11 +7,12 @@ use ven_pretty::DocAllocator; pub fn parse_problem<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, _starting_line: u32, parse_problem: ParseProblem>, ) -> Report<'a> { - to_syntax_report(alloc, filename, &parse_problem.problem, parse_problem.pos) + to_syntax_report(alloc, lines, filename, &parse_problem.problem, parse_problem.pos) } fn note_for_record_type_indent<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> { @@ -58,6 +59,7 @@ fn record_patterns_look_like<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilde fn to_syntax_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::SyntaxError<'a>, start: Position, @@ -77,7 +79,7 @@ fn to_syntax_report<'a>( SyntaxError::ConditionFailed => { let doc = alloc.stack(vec![ alloc.reflow("A condition failed:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), ]); Report { @@ -90,7 +92,7 @@ fn to_syntax_report<'a>( SyntaxError::ArgumentsBeforeEquals(region) => { let doc = alloc.stack(vec![ alloc.reflow("Unexpected tokens in front of the `=` symbol:"), - alloc.region(*region), + alloc.region(lines.convert_region(*region)), ]); Report { @@ -111,7 +113,7 @@ fn to_syntax_report<'a>( // context(alloc, &parse_problem.context_stack, "here"), alloc.text(":"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), ]); report(doc) @@ -122,7 +124,7 @@ fn to_syntax_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I expected to reach the end of the file, but got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![alloc.reflow("no hints")]), ]); @@ -134,7 +136,7 @@ fn to_syntax_report<'a>( } } SyntaxError::Eof(region) => { - let doc = alloc.stack(vec![alloc.reflow("End of Field"), alloc.region(*region)]); + let doc = alloc.stack(vec![alloc.reflow("End of Field"), alloc.region(lines.convert_region(*region))]); Report { filename, @@ -153,16 +155,17 @@ fn to_syntax_report<'a>( severity: Severity::RuntimeError, } } - Type(typ) => to_type_report(alloc, filename, typ, Position::default()), - Pattern(pat) => to_pattern_report(alloc, filename, pat, Position::default()), + Type(typ) => to_type_report(alloc, lines, filename, typ, Position::default()), + Pattern(pat) => to_pattern_report(alloc, lines, filename, pat, Position::default()), Expr(expr) => to_expr_report( alloc, + lines, filename, Context::InDef(start), expr, Position::default(), ), - Header(header) => to_header_report(alloc, filename, header, Position::default()), + Header(header) => to_header_report(alloc, lines, filename, header, Position::default()), _ => todo!("unhandled parse error: {:?}", parse_problem), } } @@ -189,6 +192,7 @@ enum Node { fn to_expr_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, context: Context, parse_problem: &roc_parse::parser::EExpr<'a>, @@ -197,22 +201,22 @@ fn to_expr_report<'a>( use roc_parse::parser::EExpr; match parse_problem { - EExpr::If(if_, pos) => to_if_report(alloc, filename, context, if_, *pos), - EExpr::When(when, pos) => to_when_report(alloc, filename, context, when, *pos), - EExpr::Lambda(lambda, pos) => to_lambda_report(alloc, filename, context, lambda, *pos), - EExpr::List(list, pos) => to_list_report(alloc, filename, context, list, *pos), - EExpr::Str(string, pos) => to_str_report(alloc, filename, context, string, *pos), + EExpr::If(if_, pos) => to_if_report(alloc, lines, filename, context, if_, *pos), + EExpr::When(when, pos) => to_when_report(alloc, lines, filename, context, when, *pos), + EExpr::Lambda(lambda, pos) => to_lambda_report(alloc, lines, filename, context, lambda, *pos), + EExpr::List(list, pos) => to_list_report(alloc, lines, filename, context, list, *pos), + EExpr::Str(string, pos) => to_str_report(alloc, lines, filename, context, string, *pos), EExpr::InParens(expr, pos) => { - to_expr_in_parens_report(alloc, filename, context, expr, *pos) + to_expr_in_parens_report(alloc, lines, filename, context, expr, *pos) } - EExpr::Type(tipe, pos) => to_type_report(alloc, filename, tipe, *pos), + EExpr::Type(tipe, pos) => to_type_report(alloc, lines, filename, tipe, *pos), EExpr::ElmStyleFunction(region, pos) => { let surroundings = Region::new(start, *pos); let region = *region; let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("Looks like you are trying to define a function. "), alloc.reflow("In roc, functions are always written as a lambda, like "), @@ -263,7 +267,7 @@ fn to_expr_report<'a>( ])], "->" => match context { Context::InNode(Node::WhenBranch, _pos, _) => { - return to_unexpected_arrow_report(alloc, filename, *pos, start); + return to_unexpected_arrow_report(alloc, lines, filename, *pos, start); } _ => { vec![alloc.stack(vec![ @@ -301,7 +305,7 @@ fn to_expr_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"This looks like an operator, but it's not one I recognize!"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(suggestion), ]); @@ -321,7 +325,7 @@ fn to_expr_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am very confused by this identifier:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("Are you trying to qualify a name? I am execting something like "), alloc.parser_suggestion("Json.Decode.string"), @@ -407,7 +411,7 @@ fn to_expr_report<'a>( a_thing, alloc.reflow(", but I got stuck here:"), ]), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), expecting, ]); @@ -425,7 +429,7 @@ fn to_expr_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("This definition is missing a final expression."), alloc.reflow(" A nested definition must be followed by"), @@ -448,7 +452,7 @@ fn to_expr_report<'a>( } EExpr::DefMissingFinalExpr2(expr, pos) => { - to_expr_report(alloc, filename, Context::InDefFinalExpr(start), expr, *pos) + to_expr_report(alloc, lines, filename, Context::InDefFinalExpr(start), expr, *pos) } EExpr::BadExprEnd(pos) => { @@ -457,7 +461,7 @@ fn to_expr_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("Whatever I am running into is confusing me a lot! "), alloc.reflow("Normally I can give fairly specific hints, "), @@ -479,7 +483,7 @@ fn to_expr_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("Looks like you are trying to define a function. "), alloc.reflow("In roc, functions are always written as a lambda, like "), @@ -502,7 +506,7 @@ fn to_expr_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing an expression, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("Looks like you are trying to define a function. ") ]), @@ -516,7 +520,7 @@ fn to_expr_report<'a>( } } - EExpr::Space(error, pos) => to_space_report(alloc, filename, error, *pos), + EExpr::Space(error, pos) => to_space_report(alloc, lines, filename, error, *pos), _ => todo!("unhandled parse error: {:?}", parse_problem), } @@ -524,6 +528,7 @@ fn to_expr_report<'a>( fn to_lambda_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, _context: Context, parse_problem: &roc_parse::parser::ELambda<'a>, @@ -540,7 +545,7 @@ fn to_lambda_report<'a>( let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I was expecting a "), alloc.parser_suggestion("->"), @@ -562,7 +567,7 @@ fn to_lambda_report<'a>( let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I was expecting a "), alloc.parser_suggestion("->"), @@ -587,7 +592,7 @@ fn to_lambda_report<'a>( let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I was expecting a "), alloc.parser_suggestion("->"), @@ -609,7 +614,7 @@ fn to_lambda_report<'a>( let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I was expecting a "), alloc.parser_suggestion("->"), @@ -634,7 +639,7 @@ fn to_lambda_report<'a>( let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck at this comma:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I was expecting an argument pattern before this, "), alloc.reflow("so try adding an argument before the comma and see if that helps?"), @@ -655,7 +660,7 @@ fn to_lambda_report<'a>( let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I was expecting an argument pattern before this, "), alloc.reflow("so try adding an argument and see if that helps?"), @@ -674,13 +679,14 @@ fn to_lambda_report<'a>( ELambda::Start(_pos) => unreachable!("another branch would have been taken"), ELambda::Body(expr, pos) => { - to_expr_report(alloc, filename, Context::InDef(start), expr, pos) + to_expr_report(alloc, lines, filename, Context::InDef(start), expr, pos) } - ELambda::Pattern(ref pattern, pos) => to_pattern_report(alloc, filename, pattern, pos), - ELambda::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + ELambda::Pattern(ref pattern, pos) => to_pattern_report(alloc, lines, filename, pattern, pos), + ELambda::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), ELambda::IndentArrow(pos) => to_unfinished_lambda_report( alloc, + lines, filename, pos, start, @@ -693,6 +699,7 @@ fn to_lambda_report<'a>( ELambda::IndentBody(pos) => to_unfinished_lambda_report( alloc, + lines, filename, pos, start, @@ -705,6 +712,7 @@ fn to_lambda_report<'a>( ELambda::IndentArg(pos) => to_unfinished_lambda_report( alloc, + lines, filename, pos, start, @@ -720,6 +728,7 @@ fn to_lambda_report<'a>( fn to_unfinished_lambda_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, pos: Position, start: Position, @@ -733,7 +742,7 @@ fn to_unfinished_lambda_report<'a>( alloc.reflow(r"I was partway through parsing a "), alloc.reflow(r" function, but I got stuck here:"), ]), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), message, ]); @@ -747,6 +756,7 @@ fn to_unfinished_lambda_report<'a>( fn to_str_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, context: Context, parse_problem: &roc_parse::parser::EString<'a>, @@ -758,12 +768,13 @@ fn to_str_report<'a>( EString::Open(_pos) => unreachable!("another branch would be taken"), EString::Format(expr, pos) => to_expr_report( alloc, + lines, filename, Context::InNode(Node::StringFormat, start, Box::new(context)), expr, pos, ), - EString::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EString::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), EString::UnknownEscape(pos) => { let surroundings = Region::new(start, pos); let region = Region::new(pos, pos.bump_column(2)); @@ -780,7 +791,7 @@ fn to_str_report<'a>( alloc.reflow(r"I was partway through parsing a "), alloc.reflow(r" string literal, but I got stuck here:"), ]), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"This is not an escape sequence I recognize."), alloc.reflow(r" After a backslash, I am looking for one of these:"), @@ -813,7 +824,7 @@ fn to_str_report<'a>( alloc.reflow( r"I am partway through parsing a unicode code point, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"I was expecting a hexadecimal number, like "), alloc.parser_suggestion("\\u(1100)"), @@ -837,7 +848,7 @@ fn to_str_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I cannot find the end of this format expression:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"You could change it to something like "), alloc.parser_suggestion("\"The count is \\(count\\)\""), @@ -858,7 +869,7 @@ fn to_str_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I cannot find the end of this string:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"You could change it to something like "), alloc.parser_suggestion("\"to be or not to be\""), @@ -881,7 +892,7 @@ fn to_str_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I cannot find the end of this block string:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"You could change it to something like "), alloc.parser_suggestion("\"\"\"to be or not to be\"\"\""), @@ -902,6 +913,7 @@ fn to_str_report<'a>( } fn to_expr_in_parens_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, context: Context, parse_problem: &roc_parse::parser::EInParens<'a>, @@ -910,9 +922,10 @@ fn to_expr_in_parens_report<'a>( use roc_parse::parser::EInParens; match *parse_problem { - EInParens::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EInParens::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), EInParens::Expr(expr, pos) => to_expr_report( alloc, + lines, filename, Context::InNode(Node::InsideParens, start, Box::new(context)), expr, @@ -925,7 +938,7 @@ fn to_expr_in_parens_report<'a>( let doc = alloc.stack(vec![ alloc .reflow("I am partway through parsing a record pattern, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing parenthesis next, so try adding a ", @@ -950,7 +963,7 @@ fn to_expr_in_parens_report<'a>( alloc.reflow( r"I just started parsing an expression in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"An expression in parentheses looks like "), alloc.parser_suggestion("(32)"), @@ -972,6 +985,7 @@ fn to_expr_in_parens_report<'a>( fn to_list_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, context: Context, parse_problem: &roc_parse::parser::EList<'a>, @@ -980,10 +994,11 @@ fn to_list_report<'a>( use roc_parse::parser::EList; match *parse_problem { - EList::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EList::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), EList::Expr(expr, pos) => to_expr_report( alloc, + lines, filename, Context::InNode(Node::ListElement, start, Box::new(context)), expr, @@ -999,7 +1014,7 @@ fn to_list_report<'a>( alloc.reflow( r"I am partway through started parsing a list, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"I was expecting to see a list entry before this comma, "), alloc.reflow(r"so try adding a list entry"), @@ -1021,7 +1036,7 @@ fn to_list_report<'a>( alloc.reflow( r"I am partway through started parsing a list, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing square bracket before this, ", @@ -1054,7 +1069,7 @@ fn to_list_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I cannot find the end of this list:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"You could change it to something like "), alloc.parser_suggestion("[ 1, 2, 3 ]"), @@ -1078,6 +1093,7 @@ fn to_list_report<'a>( fn to_if_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, context: Context, parse_problem: &roc_parse::parser::EIf<'a>, @@ -1086,10 +1102,11 @@ fn to_if_report<'a>( use roc_parse::parser::EIf; match *parse_problem { - EIf::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EIf::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), EIf::Condition(expr, pos) => to_expr_report( alloc, + lines, filename, Context::InNode(Node::IfCondition, start, Box::new(context)), expr, @@ -1098,6 +1115,7 @@ fn to_if_report<'a>( EIf::ThenBranch(expr, pos) => to_expr_report( alloc, + lines, filename, Context::InNode(Node::IfThenBranch, start, Box::new(context)), expr, @@ -1106,6 +1124,7 @@ fn to_if_report<'a>( EIf::ElseBranch(expr, pos) => to_expr_report( alloc, + lines, filename, Context::InNode(Node::IfElseBranch, start, Box::new(context)), expr, @@ -1118,6 +1137,7 @@ fn to_if_report<'a>( EIf::Then(pos) | EIf::IndentThenBranch(pos) | EIf::IndentThenToken(pos) => { to_unfinished_if_report( alloc, + lines, filename, pos, start, @@ -1132,6 +1152,7 @@ fn to_if_report<'a>( EIf::Else(pos) | EIf::IndentElseBranch(pos) | EIf::IndentElseToken(pos) => { to_unfinished_if_report( alloc, + lines, filename, pos, start, @@ -1145,6 +1166,7 @@ fn to_if_report<'a>( EIf::IndentCondition(pos) => to_unfinished_if_report( alloc, + lines, filename, pos, start, @@ -1157,6 +1179,7 @@ fn to_if_report<'a>( fn to_unfinished_if_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, pos: Position, start: Position, @@ -1171,7 +1194,7 @@ fn to_unfinished_if_report<'a>( alloc.keyword("if"), alloc.reflow(r" expression, but I got stuck here:"), ]), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), message, ]); @@ -1185,6 +1208,7 @@ fn to_unfinished_if_report<'a>( fn to_when_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, context: Context, parse_problem: &roc_parse::parser::EWhen<'a>, @@ -1202,7 +1226,7 @@ fn to_when_report<'a>( alloc.reflow( r"I just started parsing an if guard, but there is no guard condition:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("Try adding an expression before the arrow!") ]), @@ -1217,6 +1241,7 @@ fn to_when_report<'a>( } _ => to_expr_report( alloc, + lines, filename, Context::InNode(Node::WhenIfGuard, start, Box::new(context)), nested, @@ -1233,7 +1258,7 @@ fn to_when_report<'a>( alloc.keyword("when"), alloc.reflow(r" expression, but got stuck here:"), ]), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![alloc.reflow("I was expecting to see an arrow next.")]), note_for_when_indent_error(alloc), ]); @@ -1246,10 +1271,11 @@ fn to_when_report<'a>( } } - EWhen::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EWhen::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), EWhen::Branch(expr, pos) => to_expr_report( alloc, + lines, filename, Context::InNode(Node::WhenBranch, start, Box::new(context)), expr, @@ -1258,6 +1284,7 @@ fn to_when_report<'a>( EWhen::Condition(expr, pos) => to_expr_report( alloc, + lines, filename, Context::InNode(Node::WhenCondition, start, Box::new(context)), expr, @@ -1266,6 +1293,7 @@ fn to_when_report<'a>( EWhen::Bar(pos) => to_unfinished_when_report( alloc, + lines, filename, pos, start, @@ -1281,6 +1309,7 @@ fn to_when_report<'a>( EWhen::Is(pos) | EWhen::IndentIs(pos) => to_unfinished_when_report( alloc, + lines, filename, pos, start, @@ -1293,6 +1322,7 @@ fn to_when_report<'a>( EWhen::IndentCondition(pos) => to_unfinished_when_report( alloc, + lines, filename, pos, start, @@ -1303,6 +1333,7 @@ fn to_when_report<'a>( EWhen::IndentPattern(pos) => to_unfinished_when_report( alloc, + lines, filename, pos, start, @@ -1311,6 +1342,7 @@ fn to_when_report<'a>( EWhen::IndentArrow(pos) => to_unfinished_when_report( alloc, + lines, filename, pos, start, @@ -1323,6 +1355,7 @@ fn to_when_report<'a>( EWhen::IndentIfGuard(pos) => to_unfinished_when_report( alloc, + lines, filename, pos, start, @@ -1335,6 +1368,7 @@ fn to_when_report<'a>( EWhen::IndentBranch(pos) => to_unfinished_when_report( alloc, + lines, filename, pos, start, @@ -1346,6 +1380,7 @@ fn to_when_report<'a>( EWhen::PatternAlignment(indent, pos) => to_unfinished_when_report( alloc, + lines, filename, pos, start, @@ -1355,19 +1390,20 @@ fn to_when_report<'a>( alloc.reflow(" spaces)"), ]), ), - EWhen::Pattern(ref pat, pos) => to_pattern_report(alloc, filename, pat, pos), + EWhen::Pattern(ref pat, pos) => to_pattern_report(alloc, lines, filename, pat, pos), } } fn to_unfinished_when_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, pos: Position, start: Position, message: RocDocBuilder<'a>, ) -> Report<'a> { match what_is_next(alloc.src_lines, pos) { - Next::Token("->") => to_unexpected_arrow_report(alloc, filename, pos, start), + Next::Token("->") => to_unexpected_arrow_report(alloc, lines, filename, pos, start), _ => { let surroundings = Region::new(start, pos); @@ -1379,7 +1415,7 @@ fn to_unfinished_when_report<'a>( alloc.keyword("when"), alloc.reflow(r" expression, but I got stuck here:"), ]), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), message, note_for_when_error(alloc), ]); @@ -1396,6 +1432,7 @@ fn to_unfinished_when_report<'a>( fn to_unexpected_arrow_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, pos: Position, start: Position, @@ -1409,7 +1446,7 @@ fn to_unexpected_arrow_report<'a>( alloc.keyword("when"), alloc.reflow(r" expression right now, but this arrow is confusing me:"), ]), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"It makes sense to see arrows around here, "), alloc.reflow(r"so I suspect it is something earlier."), @@ -1478,6 +1515,7 @@ fn note_for_when_indent_error<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuild fn to_pattern_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::EPattern<'a>, start: Position, @@ -1491,7 +1529,7 @@ fn to_pattern_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a pattern, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.note("I may be confused by indentation"), ]); @@ -1502,9 +1540,9 @@ fn to_pattern_report<'a>( severity: Severity::RuntimeError, } } - EPattern::Record(record, pos) => to_precord_report(alloc, filename, record, *pos), + EPattern::Record(record, pos) => to_precord_report(alloc, lines, filename, record, *pos), EPattern::PInParens(inparens, pos) => { - to_pattern_in_parens_report(alloc, filename, inparens, *pos) + to_pattern_in_parens_report(alloc, lines, filename, inparens, *pos) } _ => todo!("unhandled parse error: {:?}", parse_problem), } @@ -1512,6 +1550,7 @@ fn to_pattern_report<'a>( fn to_precord_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::PRecord<'a>, start: Position, @@ -1526,7 +1565,7 @@ fn to_precord_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record pattern, but I got stuck on this field name:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), @@ -1547,7 +1586,7 @@ fn to_precord_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record pattern, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), record_patterns_look_like(alloc), ]); @@ -1568,7 +1607,7 @@ fn to_precord_report<'a>( Next::Other(Some(c)) if c.is_alphabetic() => { let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a record pattern, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a colon, question mark, comma or closing curly brace.", @@ -1586,7 +1625,7 @@ fn to_precord_report<'a>( _ => { let doc = alloc.stack(vec![ alloc.reflow("I am partway through parsing a record pattern, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing curly brace before this, so try adding a ", @@ -1613,7 +1652,7 @@ fn to_precord_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record pattern, but I got stuck on this field name:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), @@ -1636,7 +1675,7 @@ fn to_precord_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a record pattern, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"I was expecting to see another record field defined next, so I am looking for a name like "), alloc.parser_suggestion("userName"), @@ -1662,10 +1701,11 @@ fn to_precord_report<'a>( unreachable!("because `{ foo }` is a valid field; the question mark is not required") } - PRecord::Pattern(pattern, pos) => to_pattern_report(alloc, filename, pattern, pos), + PRecord::Pattern(pattern, pos) => to_pattern_report(alloc, lines, filename, pattern, pos), PRecord::Expr(expr, pos) => to_expr_report( alloc, + lines, filename, Context::InNode( Node::RecordConditionalDefault, @@ -1682,7 +1722,7 @@ fn to_precord_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record pattern, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), record_patterns_look_like(alloc), note_for_record_pattern_indent(alloc), ]); @@ -1704,7 +1744,7 @@ fn to_precord_report<'a>( alloc.reflow( "I am partway through parsing a record pattern, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I need this curly brace to be indented more. Try adding more spaces before it!"), ]), @@ -1725,7 +1765,7 @@ fn to_precord_report<'a>( alloc.reflow( r"I am partway through parsing a record pattern, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I was expecting to see a closing curly "), alloc.reflow("brace before this, so try adding a "), @@ -1752,12 +1792,13 @@ fn to_precord_report<'a>( unreachable!("because `{ foo }` is a valid field; the question mark is not required") } - PRecord::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + PRecord::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), } } fn to_pattern_in_parens_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::PInParens<'a>, start: Position, @@ -1774,7 +1815,7 @@ fn to_pattern_in_parens_report<'a>( alloc.reflow( r"I just started parsing a pattern in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"A pattern in parentheses looks like "), alloc.parser_suggestion("(Ok 32)"), @@ -1798,7 +1839,7 @@ fn to_pattern_in_parens_report<'a>( let doc = alloc.stack(vec![ alloc.reflow("I am partway through parsing a pattern in parentheses, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing parenthesis before this, so try adding a ", @@ -1816,7 +1857,7 @@ fn to_pattern_in_parens_report<'a>( } } - PInParens::Pattern(pattern, pos) => to_pattern_report(alloc, filename, pattern, pos), + PInParens::Pattern(pattern, pos) => to_pattern_report(alloc, lines, filename, pattern, pos), PInParens::IndentOpen(pos) => { let surroundings = Region::new(start, pos); @@ -1826,7 +1867,7 @@ fn to_pattern_in_parens_report<'a>( alloc.reflow( r"I just started parsing a pattern in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), record_patterns_look_like(alloc), note_for_record_pattern_indent(alloc), ]); @@ -1849,7 +1890,7 @@ fn to_pattern_in_parens_report<'a>( alloc.reflow( "I am partway through parsing a pattern in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I need this parenthesis to be indented more. Try adding more spaces before it!"), ]), @@ -1870,7 +1911,7 @@ fn to_pattern_in_parens_report<'a>( alloc.reflow( r"I am partway through parsing a pattern in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I was expecting to see a closing parenthesis "), alloc.reflow("before this, so try adding a "), @@ -1890,12 +1931,13 @@ fn to_pattern_in_parens_report<'a>( } } - PInParens::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + PInParens::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), } } fn to_type_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::EType<'a>, start: Position, @@ -1903,11 +1945,11 @@ fn to_type_report<'a>( use roc_parse::parser::EType; match parse_problem { - EType::TRecord(record, pos) => to_trecord_report(alloc, filename, record, *pos), - EType::TTagUnion(tag_union, pos) => to_ttag_union_report(alloc, filename, tag_union, *pos), - EType::TInParens(tinparens, pos) => to_tinparens_report(alloc, filename, tinparens, *pos), - EType::TApply(tapply, pos) => to_tapply_report(alloc, filename, tapply, *pos), - EType::TInlineAlias(talias, _) => to_talias_report(alloc, filename, talias), + EType::TRecord(record, pos) => to_trecord_report(alloc, lines, filename, record, *pos), + EType::TTagUnion(tag_union, pos) => to_ttag_union_report(alloc, lines, filename, tag_union, *pos), + EType::TInParens(tinparens, pos) => to_tinparens_report(alloc, lines, filename, tinparens, *pos), + EType::TApply(tapply, pos) => to_tapply_report(alloc, lines, filename, tapply, *pos), + EType::TInlineAlias(talias, _) => to_talias_report(alloc, lines, filename, talias), EType::TFunctionArgument(pos) => match what_is_next(alloc.src_lines, *pos) { Next::Other(Some(',')) => { @@ -1916,7 +1958,7 @@ fn to_type_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a function argument type, but I encountered two commas in a row:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![alloc.reflow("Try removing one of them.")]), ]); @@ -1936,7 +1978,7 @@ fn to_type_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"I am expecting a type next, like "), alloc.parser_suggestion("Bool"), @@ -1960,7 +2002,7 @@ fn to_type_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.note("I may be confused by indentation"), ]); @@ -1978,7 +2020,7 @@ fn to_type_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.note("I may be confused by indentation"), ]); @@ -1996,7 +2038,7 @@ fn to_type_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing an inline type alias, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.note("I may be confused by indentation"), ]); @@ -2014,7 +2056,7 @@ fn to_type_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am expecting a type variable, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), ]); Report { @@ -2031,6 +2073,7 @@ fn to_type_report<'a>( fn to_trecord_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::ETypeRecord<'a>, start: Position, @@ -2045,7 +2088,7 @@ fn to_trecord_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record type, but I got stuck on this field name:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), @@ -2066,7 +2109,7 @@ fn to_trecord_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"Record types look like "), alloc.parser_suggestion("{ name : String, age : Int },"), @@ -2091,7 +2134,7 @@ fn to_trecord_report<'a>( Next::Other(Some(c)) if c.is_alphabetic() => { let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a record type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a colon, question mark, comma or closing curly brace.", @@ -2109,7 +2152,7 @@ fn to_trecord_report<'a>( _ => { let doc = alloc.stack(vec![ alloc.reflow("I am partway through parsing a record type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing curly brace before this, so try adding a ", @@ -2136,7 +2179,7 @@ fn to_trecord_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record type, but I got stuck on this field name:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), @@ -2159,7 +2202,7 @@ fn to_trecord_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a record type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"I was expecting to see another record field defined next, so I am looking for a name like "), alloc.parser_suggestion("userName"), @@ -2185,7 +2228,7 @@ fn to_trecord_report<'a>( unreachable!("because `{ foo }` is a valid field; the question mark is not required") } - ETypeRecord::Type(tipe, pos) => to_type_report(alloc, filename, tipe, pos), + ETypeRecord::Type(tipe, pos) => to_type_report(alloc, lines, filename, tipe, pos), ETypeRecord::IndentOpen(pos) => { let surroundings = Region::new(start, pos); @@ -2193,7 +2236,7 @@ fn to_trecord_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"Record types look like "), alloc.parser_suggestion("{ name : String, age : Int },"), @@ -2220,7 +2263,7 @@ fn to_trecord_report<'a>( alloc.reflow( "I am partway through parsing a record type, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I need this curly brace to be indented more. Try adding more spaces before it!"), ]), @@ -2241,7 +2284,7 @@ fn to_trecord_report<'a>( alloc.reflow( r"I am partway through parsing a record type, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I was expecting to see a closing curly "), alloc.reflow("brace before this, so try adding a "), @@ -2269,12 +2312,13 @@ fn to_trecord_report<'a>( unreachable!("because `{ foo }` is a valid field; the question mark is not required") } - ETypeRecord::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + ETypeRecord::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), } } fn to_ttag_union_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::ETypeTagUnion<'a>, start: Position, @@ -2289,7 +2333,7 @@ fn to_ttag_union_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a tag union, but I got stuck on this field name:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), @@ -2314,7 +2358,7 @@ fn to_ttag_union_report<'a>( alloc.reflow( r"I am partway through parsing a tag union type, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.reflow(r"I was expecting to see a tag name."), hint_for_tag_name(alloc), ]); @@ -2332,7 +2376,7 @@ fn to_ttag_union_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a tag union type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"Tag unions look like "), alloc.parser_suggestion("[ Many I64, None ],"), @@ -2361,7 +2405,7 @@ fn to_ttag_union_report<'a>( alloc.reflow( r"I am partway through parsing a tag union type, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.reflow(r"I was expecting to see a tag name."), hint_for_tag_name(alloc), ]); @@ -2378,7 +2422,7 @@ fn to_ttag_union_report<'a>( alloc.reflow( r"I am partway through parsing a tag union type, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.reflow(r"I was expecting to see a private tag name."), hint_for_private_tag_name(alloc), ]); @@ -2393,7 +2437,7 @@ fn to_ttag_union_report<'a>( _ => { let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a tag union type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing square bracket before this, so try adding a ", @@ -2413,7 +2457,7 @@ fn to_ttag_union_report<'a>( } } - ETypeTagUnion::Type(tipe, pos) => to_type_report(alloc, filename, tipe, pos), + ETypeTagUnion::Type(tipe, pos) => to_type_report(alloc, lines, filename, tipe, pos), ETypeTagUnion::IndentOpen(pos) => { let surroundings = Region::new(start, pos); @@ -2421,7 +2465,7 @@ fn to_ttag_union_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a tag union type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"Tag unions look like "), alloc.parser_suggestion("[ Many I64, None ],"), @@ -2448,7 +2492,7 @@ fn to_ttag_union_report<'a>( alloc.reflow( "I am partway through parsing a tag union type, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I need this square bracket to be indented more. Try adding more spaces before it!"), ]), @@ -2469,7 +2513,7 @@ fn to_ttag_union_report<'a>( alloc.reflow( r"I am partway through parsing a tag union type, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I was expecting to see a closing square "), alloc.reflow("bracket before this, so try adding a "), @@ -2489,12 +2533,13 @@ fn to_ttag_union_report<'a>( } } - ETypeTagUnion::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + ETypeTagUnion::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), } } fn to_tinparens_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::ETypeInParens<'a>, start: Position, @@ -2510,7 +2555,7 @@ fn to_tinparens_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I just saw an open parenthesis, so I was expecting to see a type next."), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"Something like "), alloc.parser_suggestion("(List Person)"), @@ -2536,7 +2581,7 @@ fn to_tinparens_report<'a>( alloc.reflow( r"I am partway through parsing a type in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.reflow(r"I was expecting to see a tag name."), hint_for_tag_name(alloc), ]); @@ -2556,7 +2601,7 @@ fn to_tinparens_report<'a>( alloc.reflow( r"I just started parsing a type in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"Tag unions look like "), alloc.parser_suggestion("[ Many I64, None ],"), @@ -2587,7 +2632,7 @@ fn to_tinparens_report<'a>( alloc.reflow( r"I am partway through parsing a type in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.reflow(r"I was expecting to see a tag name."), hint_for_tag_name(alloc), ]); @@ -2602,7 +2647,7 @@ fn to_tinparens_report<'a>( _ => { let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a type in parentheses, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing parenthesis before this, so try adding a ", @@ -2622,7 +2667,7 @@ fn to_tinparens_report<'a>( } } - ETypeInParens::Type(tipe, pos) => to_type_report(alloc, filename, tipe, pos), + ETypeInParens::Type(tipe, pos) => to_type_report(alloc, lines, filename, tipe, pos), ETypeInParens::IndentOpen(pos) => { let surroundings = Region::new(start, pos); @@ -2631,7 +2676,7 @@ fn to_tinparens_report<'a>( let doc = alloc.stack(vec![ alloc .reflow(r"I just started parsing a type in parentheses, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"Tag unions look like "), alloc.parser_suggestion("[ Many I64, None ],"), @@ -2658,7 +2703,7 @@ fn to_tinparens_report<'a>( alloc.reflow( "I am partway through parsing a type in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I need this parenthesis to be indented more. Try adding more spaces before it!"), ]), @@ -2679,7 +2724,7 @@ fn to_tinparens_report<'a>( alloc.reflow( r"I am partway through parsing a type in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I was expecting to see a parenthesis "), alloc.reflow("before this, so try adding a "), @@ -2699,12 +2744,13 @@ fn to_tinparens_report<'a>( } } - ETypeInParens::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + ETypeInParens::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), } } fn to_tapply_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::ETypeApply, _start: Position, @@ -2717,7 +2763,7 @@ fn to_tapply_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I encountered two dots in a row:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![alloc.reflow("Try removing one of them.")]), ]); @@ -2733,7 +2779,7 @@ fn to_tapply_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I encountered a dot with nothing after it:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("Dots are used to refer to a type in a qualified way, like "), alloc.parser_suggestion("Num.I64"), @@ -2755,7 +2801,7 @@ fn to_tapply_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I encountered a number at the start of a qualified name segment:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("All parts of a qualified type name must start with an uppercase letter, like "), alloc.parser_suggestion("Num.I64"), @@ -2777,7 +2823,7 @@ fn to_tapply_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I encountered a lowercase letter at the start of a qualified name segment:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("All parts of a qualified type name must start with an uppercase letter, like "), alloc.parser_suggestion("Num.I64"), @@ -2802,7 +2848,7 @@ fn to_tapply_report<'a>( alloc.reflow( r"I reached the end of the input file while parsing a qualified type name", ), - alloc.region(region), + alloc.region(lines.convert_region(region)), ]); Report { @@ -2813,12 +2859,13 @@ fn to_tapply_report<'a>( } } - ETypeApply::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + ETypeApply::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), } } fn to_talias_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::ETypeInlineAlias, ) -> Report<'a> { @@ -2834,7 +2881,7 @@ fn to_talias_report<'a>( alloc.keyword("as"), alloc.reflow(" is not a type alias:"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("Inline alias types must start with an uppercase identifier and be followed by zero or more type arguments, like "), alloc.type_str("Point"), @@ -2856,7 +2903,7 @@ fn to_talias_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"This type alias has a qualified name:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("An alias introduces a new name to the current scope, so it must be unqualified."), ]); @@ -2872,7 +2919,7 @@ fn to_talias_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"This alias type argument is not lowercase:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("All type arguments must be lowercase."), ]); @@ -2888,6 +2935,7 @@ fn to_talias_report<'a>( fn to_header_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::EHeader<'a>, start: Position, @@ -2895,17 +2943,17 @@ fn to_header_report<'a>( use roc_parse::parser::EHeader; match parse_problem { - EHeader::Provides(provides, pos) => to_provides_report(alloc, filename, provides, *pos), + EHeader::Provides(provides, pos) => to_provides_report(alloc, lines, filename, provides, *pos), - EHeader::Exposes(exposes, pos) => to_exposes_report(alloc, filename, exposes, *pos), + EHeader::Exposes(exposes, pos) => to_exposes_report(alloc, lines, filename, exposes, *pos), - EHeader::Imports(imports, pos) => to_imports_report(alloc, filename, imports, *pos), + EHeader::Imports(imports, pos) => to_imports_report(alloc, lines, filename, imports, *pos), - EHeader::Requires(requires, pos) => to_requires_report(alloc, filename, requires, *pos), + EHeader::Requires(requires, pos) => to_requires_report(alloc, lines, filename, requires, *pos), - EHeader::Packages(packages, pos) => to_packages_report(alloc, filename, packages, *pos), + EHeader::Packages(packages, pos) => to_packages_report(alloc, lines, filename, packages, *pos), - EHeader::Effects(effects, pos) => to_effects_report(alloc, filename, effects, *pos), + EHeader::Effects(effects, pos) => to_effects_report(alloc, lines, filename, effects, *pos), EHeader::IndentStart(pos) => { let surroundings = Region::new(start, *pos); @@ -2913,7 +2961,7 @@ fn to_header_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![alloc.reflow("I may be confused by indentation.")]), ]); @@ -2931,7 +2979,7 @@ fn to_header_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am expecting a header, but got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I am expecting a module keyword next, one of "), alloc.keyword("interface"), @@ -2957,7 +3005,7 @@ fn to_header_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I am expecting a module name next, like "), alloc.parser_suggestion("BigNum"), @@ -2981,7 +3029,7 @@ fn to_header_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I am expecting an application name next, like "), alloc.parser_suggestion("app \"main\""), @@ -3005,7 +3053,7 @@ fn to_header_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I am expecting a platform name next, like "), alloc.parser_suggestion("\"roc/core\""), @@ -3021,12 +3069,13 @@ fn to_header_report<'a>( } } - EHeader::Space(error, pos) => to_space_report(alloc, filename, error, *pos), + EHeader::Space(error, pos) => to_space_report(alloc, lines, filename, error, *pos), } } fn to_provides_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::EProvides, start: Position, @@ -3042,7 +3091,7 @@ fn to_provides_report<'a>( let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a provides list, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![alloc.reflow( "I was expecting a type name, value name or function name next, like", )]), @@ -3065,7 +3114,7 @@ fn to_provides_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("provides"), @@ -3084,7 +3133,7 @@ fn to_provides_report<'a>( } } - EProvides::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EProvides::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), _ => todo!("unhandled parse error {:?}", parse_problem), } @@ -3092,6 +3141,7 @@ fn to_provides_report<'a>( fn to_exposes_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::EExposes, start: Position, @@ -3106,7 +3156,7 @@ fn to_exposes_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a exposes list, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![alloc.reflow( "I was expecting a type name, value name or function name next, like", )]), @@ -3129,7 +3179,7 @@ fn to_exposes_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("exposes"), @@ -3148,7 +3198,7 @@ fn to_exposes_report<'a>( } } - EExposes::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EExposes::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), _ => todo!("unhandled parse error {:?}", parse_problem), } @@ -3156,6 +3206,7 @@ fn to_exposes_report<'a>( fn to_imports_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::EImports, start: Position, @@ -3169,7 +3220,7 @@ fn to_imports_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a imports list, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![alloc.reflow( "I was expecting a type name, value name or function name next, like ", )]), @@ -3192,7 +3243,7 @@ fn to_imports_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("imports"), @@ -3211,7 +3262,7 @@ fn to_imports_report<'a>( } } - EImports::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EImports::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), EImports::ModuleName(pos) => { let surroundings = Region::new(start, pos); @@ -3219,7 +3270,7 @@ fn to_imports_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I am expecting a module name next, like "), alloc.parser_suggestion("BigNum"), @@ -3243,6 +3294,7 @@ fn to_imports_report<'a>( fn to_requires_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::ERequires<'a>, start: Position, @@ -3256,7 +3308,7 @@ fn to_requires_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("requires"), @@ -3275,7 +3327,7 @@ fn to_requires_report<'a>( } } - ERequires::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + ERequires::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), ERequires::ListStart(pos) => { let surroundings = Region::new(start, pos); @@ -3283,7 +3335,7 @@ fn to_requires_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("requires"), @@ -3308,7 +3360,7 @@ fn to_requires_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I am expecting a list of rigids like "), alloc.keyword("{}"), @@ -3337,6 +3389,7 @@ fn to_requires_report<'a>( fn to_packages_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::EPackages, start: Position, @@ -3350,7 +3403,7 @@ fn to_packages_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("packages"), @@ -3367,7 +3420,7 @@ fn to_packages_report<'a>( } } - EPackages::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EPackages::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), _ => todo!("unhandled parse error {:?}", parse_problem), } @@ -3375,6 +3428,7 @@ fn to_packages_report<'a>( fn to_effects_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::EEffects, start: Position, @@ -3388,7 +3442,7 @@ fn to_effects_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("effects"), @@ -3405,7 +3459,7 @@ fn to_effects_report<'a>( } } - EEffects::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EEffects::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), _ => todo!("unhandled parse error {:?}", parse_problem), } @@ -3413,6 +3467,7 @@ fn to_effects_report<'a>( fn to_space_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::BadInputError, pos: Position, @@ -3425,7 +3480,7 @@ fn to_space_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I encountered a tab character"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![alloc.reflow("Tab characters are not allowed.")]), ]); diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index 75ff1f8962..7e485699e0 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -3,7 +3,7 @@ use roc_collections::all::{Index, MutSet, SendMap}; use roc_module::called_via::{BinOp, CalledVia}; use roc_module::ident::{Ident, IdentStr, Lowercase, TagName}; use roc_module::symbol::Symbol; -use roc_region::all::{Loc, Region}; +use roc_region::all::{Loc, Region, LineInfo}; use roc_solve::solve; use roc_types::pretty_print::{Parens, WILDCARD}; use roc_types::types::{Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt}; @@ -18,6 +18,7 @@ const ADD_ANNOTATIONS: &str = r#"Can more type annotations be added? Type annota pub fn type_problem<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, filename: PathBuf, problem: solve::TypeError, ) -> Option> { @@ -34,13 +35,14 @@ pub fn type_problem<'b>( match problem { BadExpr(region, category, found, expected) => Some(to_expr_report( - alloc, filename, region, category, found, expected, + alloc, lines, filename, region, category, found, expected, )), BadPattern(region, category, found, expected) => Some(to_pattern_report( - alloc, filename, region, category, found, expected, + alloc, lines, filename, region, category, found, expected, )), CircularType(region, symbol, overall_type) => Some(to_circular_report( alloc, + lines, filename, region, symbol, @@ -87,7 +89,7 @@ pub fn type_problem<'b>( found_arguments, alloc.reflow(" instead:"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("Are there missing parentheses?"), ]); @@ -100,7 +102,7 @@ pub fn type_problem<'b>( report(title, doc, filename) } CyclicAlias(symbol, region, others) => { - let (doc, title) = cyclic_alias(alloc, symbol, region, others); + let (doc, title) = cyclic_alias(alloc, lines, symbol, region, others); report(title, doc, filename) } @@ -108,7 +110,7 @@ pub fn type_problem<'b>( SolvedTypeError => None, // Don't re-report cascading errors - see https://github.com/rtfeldman/roc/pull/1711 Shadowed(original_region, shadow) => { - let doc = report_shadowing(alloc, original_region, shadow); + let doc = report_shadowing(alloc, lines, original_region, shadow); let title = DUPLICATE_NAME.to_string(); report(title, doc, filename) @@ -122,6 +124,7 @@ pub fn type_problem<'b>( fn report_shadowing<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, original_region: Region, shadow: Loc, ) -> RocDocBuilder<'b> { @@ -132,15 +135,16 @@ fn report_shadowing<'b>( .text("The ") .append(alloc.ident(shadow.value)) .append(alloc.reflow(" name is first defined here:")), - alloc.region(original_region), + alloc.region(lines.convert_region(original_region)), alloc.reflow("But then it's defined a second time here:"), - alloc.region(shadow.region), + alloc.region(lines.convert_region(shadow.region)), alloc.reflow(line), ]) } pub fn cyclic_alias<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, symbol: Symbol, region: roc_region::all::Region, others: Vec, @@ -151,7 +155,7 @@ pub fn cyclic_alias<'b>( .reflow("The ") .append(alloc.symbol_unqualified(symbol)) .append(alloc.reflow(" alias is self-recursive in an invalid way:")), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("Recursion in aliases is only allowed if recursion happens behind a tag."), ]) } else { @@ -160,7 +164,7 @@ pub fn cyclic_alias<'b>( .reflow("The ") .append(alloc.symbol_unqualified(symbol)) .append(alloc.reflow(" alias is recursive in an invalid way:")), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc .reflow("The ") .append(alloc.symbol_unqualified(symbol)) @@ -186,6 +190,7 @@ pub fn cyclic_alias<'b>( #[allow(clippy::too_many_arguments)] fn report_mismatch<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, filename: PathBuf, category: &Category, found: ErrorType, @@ -198,9 +203,9 @@ fn report_mismatch<'b>( further_details: Option>, ) -> Report<'b> { let snippet = if let Some(highlight) = opt_highlight { - alloc.region_with_subregion(highlight, region) + alloc.region_with_subregion(lines.convert_region(highlight), lines.convert_region(region)) } else { - alloc.region(region) + alloc.region(lines.convert_region(region)) }; let lines = vec![ problem, @@ -227,6 +232,7 @@ fn report_mismatch<'b>( #[allow(clippy::too_many_arguments)] fn report_bad_type<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, filename: PathBuf, category: &Category, found: ErrorType, @@ -238,9 +244,9 @@ fn report_bad_type<'b>( further_details: RocDocBuilder<'b>, ) -> Report<'b> { let snippet = if let Some(highlight) = opt_highlight { - alloc.region_with_subregion(highlight, region) + alloc.region_with_subregion(lines.convert_region(highlight), lines.convert_region(region)) } else { - alloc.region(region) + alloc.region(lines.convert_region(region)) }; let lines = vec![ problem, @@ -285,6 +291,7 @@ fn lowercase_first(s: &str) -> String { fn to_expr_report<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, filename: PathBuf, expr_region: roc_region::all::Region, category: Category, @@ -308,7 +315,7 @@ fn to_expr_report<'b>( title: "TYPE MISMATCH".to_string(), doc: alloc.stack(vec![ alloc.text("This expression is used in an unexpected way:"), - alloc.region(expr_region), + alloc.region(lines.convert_region(expr_region)), comparison, ]), severity: Severity::RuntimeError, @@ -421,7 +428,7 @@ fn to_expr_report<'b>( // for typed bodies, include the line(s) with the signature let joined = roc_region::all::Region::span_across(&ann_region, &expr_region); - alloc.region_with_subregion(joined, expr_region) + alloc.region_with_subregion(lines.convert_region(joined), lines.convert_region(expr_region)) }, comparison, ]), @@ -440,6 +447,7 @@ fn to_expr_report<'b>( report_bad_type( alloc, + lines, filename, &category, found, @@ -478,6 +486,7 @@ fn to_expr_report<'b>( report_bad_type( alloc, + lines, filename, &category, found, @@ -515,6 +524,7 @@ fn to_expr_report<'b>( ]); report_bad_type( alloc, + lines, filename, &category, found, @@ -542,6 +552,7 @@ fn to_expr_report<'b>( } => match total_branches { 2 => report_mismatch( alloc, + lines, filename, &category, found, @@ -575,6 +586,7 @@ fn to_expr_report<'b>( ), _ => report_mismatch( alloc, + lines, filename, &category, found, @@ -599,6 +611,7 @@ fn to_expr_report<'b>( }, Reason::WhenBranch { index } => report_mismatch( alloc, + lines, filename, &category, found, @@ -637,6 +650,7 @@ fn to_expr_report<'b>( report_mismatch( alloc, + lines, filename, &category, found, @@ -651,6 +665,7 @@ fn to_expr_report<'b>( } Reason::RecordUpdateValue(field) => report_mismatch( alloc, + lines, filename, &category, found, @@ -685,6 +700,7 @@ fn to_expr_report<'b>( match diff.next().and_then(|k| Some((k, expected_fields.get(k)?))) { None => report_mismatch( alloc, + lines, filename, &category, found, @@ -719,7 +735,7 @@ fn to_expr_report<'b>( let doc = alloc.stack(vec![ header, - alloc.region(*field_region), + alloc.region(lines.convert_region(*field_region)), if suggestions.is_empty() { alloc.concat(vec![ alloc.reflow("In fact, "), @@ -764,6 +780,7 @@ fn to_expr_report<'b>( } _ => report_bad_type( alloc, + lines, filename, &category, found, @@ -798,7 +815,7 @@ fn to_expr_report<'b>( } )), ]), - alloc.region(expr_region), + alloc.region(lines.convert_region(expr_region)), alloc.reflow("Are there any missing commas? Or missing parentheses?"), ]; @@ -833,7 +850,7 @@ fn to_expr_report<'b>( arity )), ]), - alloc.region(expr_region), + alloc.region(lines.convert_region(expr_region)), alloc.reflow("Are there any missing commas? Or missing parentheses?"), ]; @@ -857,7 +874,7 @@ fn to_expr_report<'b>( arity )), ]), - alloc.region(expr_region), + alloc.region(lines.convert_region(expr_region)), alloc.reflow( "Roc does not allow functions to be partially applied. \ Use a closure to make partial application explicit.", @@ -883,6 +900,7 @@ fn to_expr_report<'b>( report_mismatch( alloc, + lines, filename, &category, found, @@ -1230,6 +1248,7 @@ fn add_category<'b>( fn to_pattern_report<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, filename: PathBuf, expr_region: roc_region::all::Region, category: PatternCategory, @@ -1242,7 +1261,7 @@ fn to_pattern_report<'b>( PExpected::NoExpectation(expected_type) => { let doc = alloc.stack(vec![ alloc.text("This pattern is being used in an unexpected way:"), - alloc.region(expr_region), + alloc.region(lines.convert_region(expr_region)), pattern_type_comparison( alloc, found, @@ -1275,7 +1294,7 @@ fn to_pattern_report<'b>( .append(alloc.text(" argument to ")) .append(name.clone()) .append(alloc.text(" is weird:")), - alloc.region(region), + alloc.region(lines.convert_region(region)), pattern_type_comparison( alloc, found, @@ -1310,7 +1329,7 @@ fn to_pattern_report<'b>( .text("The 1st pattern in this ") .append(alloc.keyword("when")) .append(alloc.text(" is causing a mismatch:")), - alloc.region(region), + alloc.region(lines.convert_region(region)), pattern_type_comparison( alloc, found, @@ -1343,7 +1362,7 @@ fn to_pattern_report<'b>( .string(format!("The {} pattern in this ", index.ordinal())) .append(alloc.keyword("when")) .append(alloc.text(" does not match the previous ones:")), - alloc.region(region), + alloc.region(lines.convert_region(region)), pattern_type_comparison( alloc, found, @@ -1432,6 +1451,7 @@ fn add_pattern_category<'b>( fn to_circular_report<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, filename: PathBuf, region: roc_region::all::Region, symbol: Symbol, @@ -1446,7 +1466,7 @@ fn to_circular_report<'b>( .reflow("I'm inferring a weird self-referential type for ") .append(alloc.symbol_unqualified(symbol)) .append(alloc.text(":")), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.stack(vec![ alloc.reflow( "Here is my best effort at writing down the type. \ diff --git a/reporting/src/report.rs b/reporting/src/report.rs index 94b9bb70ca..07a7cbfca7 100644 --- a/reporting/src/report.rs +++ b/reporting/src/report.rs @@ -1,6 +1,7 @@ use roc_module::ident::Ident; use roc_module::ident::{Lowercase, ModuleName, TagName, Uppercase}; use roc_module::symbol::{Interns, ModuleId, Symbol}; +use roc_region::all::LineColumnRegion; use std::fmt; use std::path::PathBuf; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated}; @@ -391,9 +392,9 @@ impl<'a> RocDocAllocator<'a> { pub fn region_all_the_things( &'a self, - region: roc_region::all::Region, - sub_region1: roc_region::all::Region, - sub_region2: roc_region::all::Region, + region: LineColumnRegion, + sub_region1: LineColumnRegion, + sub_region2: LineColumnRegion, error_annotation: Annotation, ) -> DocBuilder<'a, Self, Annotation> { debug_assert!(region.contains(&sub_region1)); @@ -502,8 +503,8 @@ impl<'a> RocDocAllocator<'a> { pub fn region_with_subregion( &'a self, - region: roc_region::all::Region, - sub_region: roc_region::all::Region, + region: LineColumnRegion, + sub_region: LineColumnRegion, ) -> DocBuilder<'a, Self, Annotation> { // debug_assert!(region.contains(&sub_region)); @@ -588,13 +589,13 @@ impl<'a> RocDocAllocator<'a> { result } - pub fn region(&'a self, region: roc_region::all::Region) -> DocBuilder<'a, Self, Annotation> { + pub fn region(&'a self, region: LineColumnRegion) -> DocBuilder<'a, Self, Annotation> { self.region_with_subregion(region, region) } pub fn region_without_error( &'a self, - region: roc_region::all::Region, + region: LineColumnRegion, ) -> DocBuilder<'a, Self, Annotation> { let mut result = self.nil(); for i in region.start().line..=region.end().line { diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 848d23a0e4..8c5dfbe506 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -15,6 +15,7 @@ mod test_reporting { use roc_module::symbol::{Interns, ModuleId}; use roc_mono::ir::{Procs, Stmt, UpdateModeIds}; use roc_mono::layout::LayoutCache; + use roc_region::all::LineInfo; use roc_reporting::report::{ can_problem, mono_problem, parse_problem, type_problem, Report, Severity, BLUE_CODE, BOLD_CODE, CYAN_CODE, DEFAULT_PALETTE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, @@ -126,6 +127,7 @@ mod test_reporting { use ven_pretty::DocAllocator; let src_lines: Vec<&str> = src.split('\n').collect(); + let lines = LineInfo::new(src); let filename = filename_from_string(r"\code\proj\Main.roc"); @@ -140,7 +142,7 @@ mod test_reporting { let alloc = RocDocAllocator::new(&src_lines, home, &interns); let problem = fail.into_parse_problem(filename.clone(), "", src.as_bytes()); - let doc = parse_problem(&alloc, filename, 0, problem); + let doc = parse_problem(&alloc, &lines, filename, 0, problem); callback(doc.pretty(&alloc).append(alloc.line()), buf) } @@ -150,18 +152,18 @@ mod test_reporting { let alloc = RocDocAllocator::new(&src_lines, home, &interns); for problem in can_problems { - let report = can_problem(&alloc, filename.clone(), problem.clone()); + let report = can_problem(&alloc, &lines, filename.clone(), problem.clone()); reports.push(report); } for problem in type_problems { - if let Some(report) = type_problem(&alloc, filename.clone(), problem.clone()) { + if let Some(report) = type_problem(&alloc, &lines, filename.clone(), problem.clone()) { reports.push(report); } } for problem in mono_problems { - let report = mono_problem(&alloc, filename.clone(), problem.clone()); + let report = mono_problem(&alloc, &lines, filename.clone(), problem.clone()); reports.push(report); } @@ -192,6 +194,7 @@ mod test_reporting { let filename = filename_from_string(r"\code\proj\Main.roc"); let src_lines: Vec<&str> = src.split('\n').collect(); + let lines = LineInfo::new(src); match roc_parse::module::parse_header(arena, state) { Err(fail) => { @@ -206,7 +209,7 @@ mod test_reporting { "", src.as_bytes(), ); - let doc = parse_problem(&alloc, filename, 0, problem); + let doc = parse_problem(&alloc, &lines, filename, 0, problem); callback(doc.pretty(&alloc).append(alloc.line()), buf) } From 34e24fa2ba751affdb5c6e8372372911aa30ec9a Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Thu, 23 Dec 2021 19:28:02 -0800 Subject: [PATCH 064/541] improve test_reporting error reports --- Cargo.lock | 1 + reporting/Cargo.toml | 1 + reporting/tests/test_reporting.rs | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 44b65c1fc8..1548053545 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3671,6 +3671,7 @@ dependencies = [ "roc_problem", "roc_region", "roc_solve", + "roc_test_utils", "roc_types", "ven_pretty", ] diff --git a/reporting/Cargo.toml b/reporting/Cargo.toml index 7fa1d2cfee..195406c968 100644 --- a/reporting/Cargo.toml +++ b/reporting/Cargo.toml @@ -24,5 +24,6 @@ roc_constrain = { path = "../compiler/constrain" } roc_builtins = { path = "../compiler/builtins" } roc_problem = { path = "../compiler/problem" } roc_parse = { path = "../compiler/parse" } +roc_test_utils = { path = "../test_utils" } pretty_assertions = "1.0.0" indoc = "1.0.3" diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 8c5dfbe506..aabc59ec80 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -23,6 +23,7 @@ mod test_reporting { }; use roc_reporting::report::{RocDocAllocator, RocDocBuilder}; use roc_solve::solve; + use roc_test_utils::assert_multiline_str_eq; use roc_types::pretty_print::name_all_type_vars; use roc_types::subs::Subs; use std::path::PathBuf; @@ -236,7 +237,7 @@ mod test_reporting { } } - assert_eq!(buf, expected_rendering); + assert_multiline_str_eq!(expected_rendering, buf.as_str()); } fn report_header_problem_as(src: &str, expected_rendering: &str) { From fae1bb4458f2faf96bac25d937d7f2a20d553749 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Thu, 23 Dec 2021 19:30:55 -0800 Subject: [PATCH 065/541] Add real impl of LineInfo --- compiler/region/src/all.rs | 96 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 5 deletions(-) diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index a6dbd94cbc..24aa291ed5 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -361,13 +361,27 @@ where } pub struct LineInfo { - // TODO + line_offsets: Vec, } impl LineInfo { - pub fn new(_text: &str) -> LineInfo { - // TODO - LineInfo {} + pub fn new(src: &str) -> LineInfo { + let mut line_offsets = Vec::new(); + line_offsets.push(0); + line_offsets.extend(src.match_indices('\n').map(|(offset, _)| offset as u32 + 1)); + LineInfo { + line_offsets, + } + } + + pub fn convert_offset(&self, offset: u32) -> LineColumn { + let search = self.line_offsets.binary_search(&offset); + let line = match search { + Ok(i) => i, + Err(i) => i - 1, + }; + let column = offset - self.line_offsets[line]; + LineColumn { line: line as u32, column: column as u16 } } pub fn convert_pos(&self, pos: Position) -> LineColumn { @@ -384,4 +398,76 @@ impl LineInfo { end: self.convert_pos(region.end()), } } -} \ No newline at end of file +} + +#[test] +fn test_line_info() { + + fn char_at_line<'a>(lines: &[&'a str], line_column: LineColumn) -> &'a str { + let line = line_column.line as usize; + let line_text = if line < lines.len() { + lines[line] + } else { + "" + }; + let column = line_column.column as usize; + if column == line_text.len() { + "\n" + } else { + &line_text[column .. column + 1] + } + } + + fn check_correctness(lines: &[&str]) { + let mut input = String::new(); + for (i, line) in lines.iter().enumerate() { + if i > 0 { + input.push('\n'); + } + input.push_str(line); + } + let info = LineInfo::new(&input); + + let mut last: Option = None; + + for offset in 0..=input.len() { + let expected = if offset < input.len() { + &input[offset..offset + 1] + } else { + "\n" // HACK! pretend there's an extra newline on the end, strictly so we can do the comparison + }; + println!("checking {:?} {:?}, expecting {:?}", input, offset, expected); + let line_column = info.convert_offset(offset as u32); + assert!(Some(line_column) > last, "{:?} > {:?}", Some(line_column), last); + assert_eq!( + expected, + char_at_line(lines, line_column)); + last = Some(line_column); + } + + assert_eq!( + info.convert_offset(input.len() as u32), + LineColumn { + line: lines.len().saturating_sub(1) as u32, + column: lines.last().map(|l| l.len()).unwrap_or(0) as u16, + } + ) + } + + check_correctness(&[ + "", + "abc", + "def", + "", + "gi", + ]); + + check_correctness(&[]); + + check_correctness(&["a"]); + + check_correctness(&[ + "", + "", + ]); +} From 443d738f9b3e044a11608bdd3a01b3fd0743174f Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Thu, 23 Dec 2021 20:44:43 -0800 Subject: [PATCH 066/541] Make Position::{line, column} fields private --- compiler/fmt/src/expr.rs | 9 +- compiler/parse/src/state.rs | 13 ++- compiler/region/src/all.rs | 38 +++++++- reporting/src/error/canonicalize.rs | 11 +-- reporting/src/error/parse.rs | 132 ++++++++++++++-------------- 5 files changed, 115 insertions(+), 88 deletions(-) diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index 2241c1b5f3..bbbbbfd0fc 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -514,11 +514,10 @@ fn fmt_when<'a, 'buf>( let patterns = &branch.patterns; let expr = &branch.value; let (first_pattern, rest) = patterns.split_first().unwrap(); - let is_multiline = match rest.last() { - None => false, - Some(last_pattern) => { - first_pattern.region.start().line != last_pattern.region.end().line - } + let is_multiline = if rest.is_empty() { + false + } else { + patterns.iter().any(|p| p.is_multiline()) }; fmt_pattern( diff --git a/compiler/parse/src/state.rs b/compiler/parse/src/state.rs index 36688ddadd..b7ab5a6fd0 100644 --- a/compiler/parse/src/state.rs +++ b/compiler/parse/src/state.rs @@ -40,10 +40,7 @@ impl<'a> State<'a> { /// Returns the current position pub const fn pos(&self) -> Position { - Position { - line: self.xyzlcol.line, - column: self.xyzlcol.column, - } + Position::new(self.xyzlcol.line, self.xyzlcol.column) } /// Returns whether the parser has reached the end of the input @@ -97,14 +94,14 @@ impl<'a> State<'a> { pub fn len_region(&self, length: u16) -> Region { Region::new( self.pos(), - Position { - line: self.xyzlcol.line, - column: self + Position::new( + self.xyzlcol.line, + self .xyzlcol .column .checked_add(length) .unwrap_or_else(|| panic!("len_region overflowed")), - }, + ), ) } diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index 24aa291ed5..c74174ea31 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -154,8 +154,8 @@ impl fmt::Debug for Region { #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Default)] pub struct Position { - pub line: u32, - pub column: u16, + line: u32, + column: u16, } impl Position { @@ -163,12 +163,12 @@ impl Position { Position { line: 0, column: 0 } } - pub fn new(line: u32, column: u16) -> Position { + pub const fn new(line: u32, column: u16) -> Position { Position { line, column } } #[must_use] - pub fn bump_column(self, count: u16) -> Self { + pub const fn bump_column(self, count: u16) -> Self { Self { line: self.line, column: self.column + count, @@ -191,6 +191,14 @@ impl Position { column: 0, } } + + #[must_use] + pub const fn sub(self, count: u16) -> Self { + Self { + line: self.line, + column: self.column - count, + } + } } impl Debug for Position { @@ -212,6 +220,14 @@ impl LineColumn { column: 0, } } + + #[must_use] + pub const fn bump_column(self, count: u16) -> Self { + Self { + line: self.line, + column: self.column + count, + } + } } #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Default)] @@ -221,6 +237,13 @@ pub struct LineColumnRegion { } impl LineColumnRegion { + pub const fn new(start: LineColumn, end: LineColumn) -> Self { + LineColumnRegion { + start, + end, + } + } + pub const fn zero() -> Self { LineColumnRegion { start: LineColumn::zero(), @@ -245,6 +268,13 @@ impl LineColumnRegion { } } + pub const fn from_pos(pos: LineColumn) -> Self { + Self { + start: pos, + end: pos.bump_column(1), + } + } + pub fn is_empty(&self) -> bool { self.end.line == self.start.line && self.start.column == self.end.column } diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index d2ee07610b..dda841bd88 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -2,7 +2,7 @@ use roc_collections::all::MutSet; use roc_module::ident::{Ident, Lowercase, ModuleName}; use roc_problem::can::PrecedenceProblem::BothNonAssociative; use roc_problem::can::{BadPattern, FloatErrorKind, IntErrorKind, Problem, RuntimeError}; -use roc_region::all::{Loc, Position, Region, LineInfo}; +use roc_region::all::{Loc, Region, LineInfo, LineColumn}; use std::path::PathBuf; use crate::error::r#type::suggest; @@ -570,7 +570,7 @@ fn to_bad_ident_expr_report<'b>( BadPrivateTag(pos) => { use BadIdentNext::*; - match what_is_next(alloc.src_lines, pos) { + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { LowercaseAccess(width) => { let region = Region::new(pos, pos.bump_column(width)); alloc.stack(vec![ @@ -694,10 +694,7 @@ fn to_bad_ident_pattern_report<'b>( } Underscore(pos) => { - let region = Region::from_pos(Position { - line: pos.line, - column: pos.column - 1, - }); + let region = Region::from_pos(pos.sub(1)); alloc.stack(vec![ alloc.reflow("I am trying to parse an identifier here:"), @@ -722,7 +719,7 @@ enum BadIdentNext<'a> { Other(Option), } -fn what_is_next<'a>(source_lines: &'a [&'a str], pos: Position) -> BadIdentNext<'a> { +fn what_is_next<'a>(source_lines: &'a [&'a str], pos: LineColumn) -> BadIdentNext<'a> { let row_index = pos.line as usize; let col_index = pos.column as usize; match source_lines.get(row_index) { diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index d26d9557db..c9abf64d9e 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -1,5 +1,5 @@ use roc_parse::parser::{ParseProblem, SyntaxError}; -use roc_region::all::{Position, Region, LineInfo}; +use roc_region::all::{Position, Region, LineInfo, LineColumn, LineColumnRegion}; use std::path::PathBuf; use crate::report::{Report, RocDocAllocator, RocDocBuilder, Severity}; @@ -102,9 +102,10 @@ fn to_syntax_report<'a>( severity: Severity::RuntimeError, } } - Unexpected(mut region) => { + Unexpected(region) => { + let mut region = lines.convert_region(*region); if region.start().column == region.end().column { - region = Region::new(region.start(), region.end().bump_column(1)); + region = LineColumnRegion::new(region.start(), region.end().bump_column(1)); } let doc = alloc.stack(vec![ @@ -113,7 +114,7 @@ fn to_syntax_report<'a>( // context(alloc, &parse_problem.context_stack, "here"), alloc.text(":"), ]), - alloc.region(lines.convert_region(region)), + alloc.region(region), ]); report(doc) @@ -537,7 +538,7 @@ fn to_lambda_report<'a>( use roc_parse::parser::ELambda; match *parse_problem { - ELambda::Arrow(pos) => match what_is_next(alloc.src_lines, pos) { + ELambda::Arrow(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Token("=>") => { let surroundings = Region::new(start, pos); let region = Region::from_pos(pos); @@ -584,7 +585,7 @@ fn to_lambda_report<'a>( } }, - ELambda::Comma(pos) => match what_is_next(alloc.src_lines, pos) { + ELambda::Comma(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Token("=>") => { let surroundings = Region::new(start, pos); let region = Region::from_pos(pos); @@ -631,7 +632,7 @@ fn to_lambda_report<'a>( } }, - ELambda::Arg(pos) => match what_is_next(alloc.src_lines, pos) { + ELambda::Arg(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(',')) => { let surroundings = Region::new(start, pos); let region = Region::from_pos(pos); @@ -1005,7 +1006,7 @@ fn to_list_report<'a>( pos, ), - EList::Open(pos) | EList::End(pos) => match what_is_next(alloc.src_lines, pos) { + EList::Open(pos) | EList::End(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(',')) => { let surroundings = Region::new(start, pos); let region = Region::from_pos(pos); @@ -1217,7 +1218,7 @@ fn to_when_report<'a>( use roc_parse::parser::EWhen; match *parse_problem { - EWhen::IfGuard(nested, pos) => match what_is_next(alloc.src_lines, pos) { + EWhen::IfGuard(nested, pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Token("->") => { let surroundings = Region::new(start, pos); let region = Region::from_pos(pos); @@ -1402,7 +1403,7 @@ fn to_unfinished_when_report<'a>( start: Position, message: RocDocBuilder<'a>, ) -> Report<'a> { - match what_is_next(alloc.src_lines, pos) { + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Token("->") => to_unexpected_arrow_report(alloc, lines, filename, pos, start), _ => { @@ -1558,14 +1559,14 @@ fn to_precord_report<'a>( use roc_parse::parser::PRecord; match *parse_problem { - PRecord::Open(pos) => match what_is_next(alloc.src_lines, pos) { + PRecord::Open(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Keyword(keyword) => { let surroundings = Region::new(start, pos); - let region = to_keyword_region(pos, keyword); + let region = to_keyword_region(lines.convert_pos(pos), keyword); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record pattern, but I got stuck on this field name:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), @@ -1603,7 +1604,7 @@ fn to_precord_report<'a>( let surroundings = Region::new(start, pos); let region = Region::from_pos(pos); - match what_is_next(alloc.src_lines, pos) { + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(c)) if c.is_alphabetic() => { let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a record pattern, but I got stuck here:"), @@ -1645,14 +1646,14 @@ fn to_precord_report<'a>( } } - PRecord::Field(pos) => match what_is_next(alloc.src_lines, pos) { + PRecord::Field(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Keyword(keyword) => { let surroundings = Region::new(start, pos); - let region = to_keyword_region(pos, keyword); + let region = to_keyword_region(lines.convert_pos(pos), keyword); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record pattern, but I got stuck on this field name:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), @@ -1735,16 +1736,16 @@ fn to_precord_report<'a>( } } - PRecord::IndentEnd(pos) => match next_line_starts_with_close_curly(alloc.src_lines, pos) { + PRecord::IndentEnd(pos) => match next_line_starts_with_close_curly(alloc.src_lines, lines.convert_pos(pos)) { Some(curly_pos) => { - let surroundings = Region::new(start, curly_pos); - let region = Region::from_pos(curly_pos); + let surroundings = LineColumnRegion::new(lines.convert_pos(start), curly_pos); + let region = LineColumnRegion::from_pos(curly_pos); let doc = alloc.stack(vec![ alloc.reflow( "I am partway through parsing a record pattern, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow("I need this curly brace to be indented more. Try adding more spaces before it!"), ]), @@ -1881,16 +1882,16 @@ fn to_pattern_in_parens_report<'a>( } PInParens::IndentEnd(pos) => { - match next_line_starts_with_close_parenthesis(alloc.src_lines, pos) { + match next_line_starts_with_close_parenthesis(alloc.src_lines, lines.convert_pos(pos)) { Some(curly_pos) => { - let surroundings = Region::new(start, curly_pos); - let region = Region::from_pos(curly_pos); + let surroundings = LineColumnRegion::new(lines.convert_pos(start), curly_pos); + let region = LineColumnRegion::from_pos(curly_pos); let doc = alloc.stack(vec![ alloc.reflow( "I am partway through parsing a pattern in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow("I need this parenthesis to be indented more. Try adding more spaces before it!"), ]), @@ -1951,7 +1952,7 @@ fn to_type_report<'a>( EType::TApply(tapply, pos) => to_tapply_report(alloc, lines, filename, tapply, *pos), EType::TInlineAlias(talias, _) => to_talias_report(alloc, lines, filename, talias), - EType::TFunctionArgument(pos) => match what_is_next(alloc.src_lines, *pos) { + EType::TFunctionArgument(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(*pos)) { Next::Other(Some(',')) => { let surroundings = Region::new(start, *pos); let region = Region::from_pos(*pos); @@ -2081,14 +2082,14 @@ fn to_trecord_report<'a>( use roc_parse::parser::ETypeRecord; match *parse_problem { - ETypeRecord::Open(pos) => match what_is_next(alloc.src_lines, pos) { + ETypeRecord::Open(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Keyword(keyword) => { let surroundings = Region::new(start, pos); - let region = to_keyword_region(pos, keyword); + let region = to_keyword_region(lines.convert_pos(pos), keyword); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record type, but I got stuck on this field name:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), @@ -2130,7 +2131,7 @@ fn to_trecord_report<'a>( let surroundings = Region::new(start, pos); let region = Region::from_pos(pos); - match what_is_next(alloc.src_lines, pos) { + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(c)) if c.is_alphabetic() => { let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a record type, but I got stuck here:"), @@ -2172,14 +2173,14 @@ fn to_trecord_report<'a>( } } - ETypeRecord::Field(pos) => match what_is_next(alloc.src_lines, pos) { + ETypeRecord::Field(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Keyword(keyword) => { let surroundings = Region::new(start, pos); - let region = to_keyword_region(pos, keyword); + let region = to_keyword_region(lines.convert_pos(pos), keyword); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record type, but I got stuck on this field name:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), @@ -2254,16 +2255,16 @@ fn to_trecord_report<'a>( } ETypeRecord::IndentEnd(pos) => { - match next_line_starts_with_close_curly(alloc.src_lines, pos) { + match next_line_starts_with_close_curly(alloc.src_lines, lines.convert_pos(pos)) { Some(curly_pos) => { - let surroundings = Region::new(start, curly_pos); - let region = Region::from_pos(curly_pos); + let surroundings = LineColumnRegion::new(lines.convert_pos(start), curly_pos); + let region = LineColumnRegion::from_pos(curly_pos); let doc = alloc.stack(vec![ alloc.reflow( "I am partway through parsing a record type, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow("I need this curly brace to be indented more. Try adding more spaces before it!"), ]), @@ -2326,14 +2327,14 @@ fn to_ttag_union_report<'a>( use roc_parse::parser::ETypeTagUnion; match *parse_problem { - ETypeTagUnion::Open(pos) => match what_is_next(alloc.src_lines, pos) { + ETypeTagUnion::Open(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Keyword(keyword) => { let surroundings = Region::new(start, pos); - let region = to_keyword_region(pos, keyword); + let region = to_keyword_region(lines.convert_pos(pos), keyword); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a tag union, but I got stuck on this field name:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), @@ -2397,7 +2398,7 @@ fn to_ttag_union_report<'a>( let surroundings = Region::new(start, pos); let region = Region::from_pos(pos); - match what_is_next(alloc.src_lines, pos) { + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(c)) if c.is_alphabetic() => { debug_assert!(c.is_lowercase()); @@ -2483,16 +2484,16 @@ fn to_ttag_union_report<'a>( } ETypeTagUnion::IndentEnd(pos) => { - match next_line_starts_with_close_square_bracket(alloc.src_lines, pos) { + match next_line_starts_with_close_square_bracket(alloc.src_lines, lines.convert_pos(pos)) { Some(curly_pos) => { - let surroundings = Region::new(start, curly_pos); - let region = Region::from_pos(curly_pos); + let surroundings = LineColumnRegion::new(lines.convert_pos(start), curly_pos); + let region = LineColumnRegion::from_pos(curly_pos); let doc = alloc.stack(vec![ alloc.reflow( "I am partway through parsing a tag union type, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow("I need this square bracket to be indented more. Try adding more spaces before it!"), ]), @@ -2548,14 +2549,14 @@ fn to_tinparens_report<'a>( match *parse_problem { ETypeInParens::Open(pos) => { - match what_is_next(alloc.src_lines, pos) { + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Keyword(keyword) => { let surroundings = Region::new(start, pos); - let region = to_keyword_region(pos, keyword); + let region = to_keyword_region(lines.convert_pos(pos), keyword); let doc = alloc.stack(vec![ alloc.reflow(r"I just saw an open parenthesis, so I was expecting to see a type next."), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Something like "), alloc.parser_suggestion("(List Person)"), @@ -2623,7 +2624,7 @@ fn to_tinparens_report<'a>( let surroundings = Region::new(start, pos); let region = Region::from_pos(pos); - match what_is_next(alloc.src_lines, pos) { + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(c)) if c.is_alphabetic() => { debug_assert!(c.is_lowercase()); @@ -2694,16 +2695,16 @@ fn to_tinparens_report<'a>( } ETypeInParens::IndentEnd(pos) => { - match next_line_starts_with_close_parenthesis(alloc.src_lines, pos) { + match next_line_starts_with_close_parenthesis(alloc.src_lines, lines.convert_pos(pos)) { Some(curly_pos) => { - let surroundings = Region::new(start, curly_pos); - let region = Region::from_pos(curly_pos); + let surroundings = LineColumnRegion::new(lines.convert_pos(start), curly_pos); + let region = LineColumnRegion::from_pos(curly_pos); let doc = alloc.stack(vec![ alloc.reflow( "I am partway through parsing a type in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow("I need this parenthesis to be indented more. Try adding more spaces before it!"), ]), @@ -3505,7 +3506,7 @@ enum Next<'a> { Other(Option), } -fn what_is_next<'a>(source_lines: &'a [&'a str], pos: Position) -> Next<'a> { +fn what_is_next<'a>(source_lines: &'a [&'a str], pos: LineColumn) -> Next<'a> { let row_index = pos.line as usize; let col_index = pos.column as usize; match source_lines.get(row_index) { @@ -3547,36 +3548,36 @@ pub fn starts_with_keyword(rest_of_line: &str, keyword: &str) -> bool { } } -fn next_line_starts_with_close_curly(source_lines: &[&str], pos: Position) -> Option { +fn next_line_starts_with_close_curly(source_lines: &[&str], pos: LineColumn) -> Option { next_line_starts_with_char(source_lines, pos, '}') } fn next_line_starts_with_close_parenthesis( source_lines: &[&str], - pos: Position, -) -> Option { + pos: LineColumn, +) -> Option { next_line_starts_with_char(source_lines, pos, ')') } fn next_line_starts_with_close_square_bracket( source_lines: &[&str], - pos: Position, -) -> Option { + pos: LineColumn, +) -> Option { next_line_starts_with_char(source_lines, pos, ']') } fn next_line_starts_with_char( source_lines: &[&str], - pos: Position, + pos: LineColumn, character: char, -) -> Option { +) -> Option { match source_lines.get(pos.line as usize + 1) { None => None, Some(line) => { let spaces_dropped = line.trim_start_matches(' '); match spaces_dropped.chars().next() { - Some(c) if c == character => Some(Position { + Some(c) if c == character => Some(LineColumn { line: pos.line + 1, column: (line.len() - spaces_dropped.len()) as u16, }), @@ -3586,6 +3587,9 @@ fn next_line_starts_with_char( } } -fn to_keyword_region(pos: Position, keyword: &str) -> Region { - Region::new(pos, pos.bump_column(keyword.len() as u16)) +fn to_keyword_region(pos: LineColumn, keyword: &str) -> LineColumnRegion { + LineColumnRegion { + start: pos, + end: pos.bump_column(keyword.len() as u16) + } } From 4b04ec6bbc11152219e9e6a100aee33e5e4d0a2c Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Thu, 23 Dec 2021 21:06:08 -0800 Subject: [PATCH 067/541] Add Position::offset, and recompute line/column info based on source --- compiler/can/tests/test_can.rs | 32 +-- compiler/parse/src/state.rs | 11 +- compiler/parse/tests/test_parse.rs | 22 +- compiler/region/src/all.rs | 148 +++++------- compiler/types/src/subs.rs | 2 +- reporting/src/error/canonicalize.rs | 26 +-- reporting/src/error/parse.rs | 338 ++++++++++++++-------------- 7 files changed, 279 insertions(+), 300 deletions(-) diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index fb6801534b..bad0f296ac 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -944,12 +944,12 @@ mod test_can { let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![CycleEntry { symbol: interns.symbol(home, "x".into()), symbol_region: Region::new( - Position::new(0, 0), - Position::new(0, 1), + Position::new(0, 0, 0), + Position::new(0, 0, 0), ), expr_region: Region::new( - Position::new(0, 4), - Position::new(0, 5), + Position::new(0, 0, 0), + Position::new(0, 0, 0), ), }])); @@ -981,34 +981,34 @@ mod test_can { CycleEntry { symbol: interns.symbol(home, "x".into()), symbol_region: Region::new( - Position::new(0, 0), - Position::new(0, 1), + Position::new(0, 0, 0), + Position::new(0, 0, 0), ), expr_region: Region::new( - Position::new(0, 4), - Position::new(0, 5), + Position::new(0, 0, 0), + Position::new(0, 0, 0), ), }, CycleEntry { symbol: interns.symbol(home, "y".into()), symbol_region: Region::new( - Position::new(1, 0), - Position::new(1, 1), + Position::new(0, 0, 0), + Position::new(0, 0, 0), ), expr_region: Region::new( - Position::new(1, 4), - Position::new(1, 5), + Position::new(0, 0, 0), + Position::new(0, 0, 0), ), }, CycleEntry { symbol: interns.symbol(home, "z".into()), symbol_region: Region::new( - Position::new(2, 0), - Position::new(2, 1), + Position::new(0, 0, 0), + Position::new(0, 0, 0), ), expr_region: Region::new( - Position::new(2, 4), - Position::new(2, 5), + Position::new(0, 0, 0), + Position::new(0, 0, 0), ), }, ])); diff --git a/compiler/parse/src/state.rs b/compiler/parse/src/state.rs index b7ab5a6fd0..4945c2e6e5 100644 --- a/compiler/parse/src/state.rs +++ b/compiler/parse/src/state.rs @@ -8,8 +8,12 @@ use std::fmt; #[derive(Clone)] pub struct State<'a> { /// The raw input bytes from the file. + /// Beware: bytes[0] always points the the current byte the parser is examining. bytes: &'a [u8], + /// Length of the original input in bytes + input_len: usize, + /// Current position within the input (line/column) pub xyzlcol: LineColumn, @@ -22,6 +26,7 @@ impl<'a> State<'a> { pub fn new(bytes: &'a [u8]) -> State<'a> { State { bytes, + input_len: bytes.len(), xyzlcol: LineColumn::default(), indent_column: 0, } @@ -40,7 +45,10 @@ impl<'a> State<'a> { /// Returns the current position pub const fn pos(&self) -> Position { - Position::new(self.xyzlcol.line, self.xyzlcol.column) + Position::new( + (self.input_len - self.bytes.len()) as u32, + self.xyzlcol.line, + self.xyzlcol.column) } /// Returns whether the parser has reached the end of the input @@ -95,6 +103,7 @@ impl<'a> State<'a> { Region::new( self.pos(), Position::new( + self.pos().bump_column(length).offset, self.xyzlcol.line, self .xyzlcol diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index afa8d7c163..387b36305a 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -366,7 +366,7 @@ mod test_parse { assert_segments(r#""Hi, \u(123)!""#, |arena| { bumpalo::vec![in arena; Plaintext("Hi, "), - Unicode(Loc::new(Position::new(0, 8), Position::new(0, 11), "123")), + Unicode(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), "123")), Plaintext("!") ] }); @@ -376,7 +376,7 @@ mod test_parse { fn unicode_escape_in_front() { assert_segments(r#""\u(1234) is a unicode char""#, |arena| { bumpalo::vec![in arena; - Unicode(Loc::new(Position::new(0, 4), Position::new(0, 8), "1234")), + Unicode(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), "1234")), Plaintext(" is a unicode char") ] }); @@ -387,7 +387,7 @@ mod test_parse { assert_segments(r#""this is unicode: \u(1)""#, |arena| { bumpalo::vec![in arena; Plaintext("this is unicode: "), - Unicode(Loc::new(Position::new(0, 21), Position::new(0, 22), "1")) + Unicode(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), "1")) ] }); } @@ -396,11 +396,11 @@ mod test_parse { fn unicode_escape_multiple() { assert_segments(r#""\u(a1) this is \u(2Bcd) unicode \u(ef97)""#, |arena| { bumpalo::vec![in arena; - Unicode(Loc::new(Position::new(0, 4), Position::new(0, 6), "a1")), + Unicode(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), "a1")), Plaintext(" this is "), - Unicode(Loc::new(Position::new(0, 19), Position::new(0, 23), "2Bcd")), + Unicode(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), "2Bcd")), Plaintext(" unicode "), - Unicode(Loc::new(Position::new(0, 36), Position::new(0, 40), "ef97")) + Unicode(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), "ef97")) ] }); } @@ -417,7 +417,7 @@ mod test_parse { bumpalo::vec![in arena; Plaintext("Hi, "), - Interpolated(Loc::new(Position::new(0, 7), Position::new(0, 11), expr)), + Interpolated(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), expr)), Plaintext("!") ] }); @@ -432,7 +432,7 @@ mod test_parse { }); bumpalo::vec![in arena; - Interpolated(Loc::new(Position::new(0, 3), Position::new(0, 7), expr)), + Interpolated(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), expr)), Plaintext(", hi!") ] }); @@ -448,7 +448,7 @@ mod test_parse { bumpalo::vec![in arena; Plaintext("Hello "), - Interpolated(Loc::new(Position::new(0, 9), Position::new(0, 13), expr)) + Interpolated(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), expr)) ] }); } @@ -468,9 +468,9 @@ mod test_parse { bumpalo::vec![in arena; Plaintext("Hi, "), - Interpolated(Loc::new(Position::new(0, 7), Position::new(0, 11), expr1)), + Interpolated(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), expr1)), Plaintext("! How is "), - Interpolated(Loc::new(Position::new(0, 23), Position::new(0, 30), expr2)), + Interpolated(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), expr2)), Plaintext(" going?") ] }); diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index c74174ea31..f5a093065a 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -2,58 +2,37 @@ use std::fmt::{self, Debug}; #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Default)] pub struct Region { - start_line: u32, - end_line: u32, - start_col: u16, - end_col: u16, + start: Position, + end: Position, } impl Region { pub const fn zero() -> Self { Region { - start_line: 0, - end_line: 0, - start_col: 0, - end_col: 0, + start: Position::zero(), + end: Position::zero(), } } pub const fn new(start: Position, end: Position) -> Self { Self { - start_line: start.line, - end_line: end.line, - start_col: start.column, - end_col: end.column, + start, + end, } } pub fn contains(&self, other: &Self) -> bool { - use std::cmp::Ordering::*; - match self.start_line.cmp(&other.start_line) { - Greater => false, - Equal => match self.end_line.cmp(&other.end_line) { - Less => false, - Equal => self.start_col <= other.start_col && self.end_col >= other.end_col, - Greater => self.start_col >= other.start_col, - }, - Less => match self.end_line.cmp(&other.end_line) { - Less => false, - Equal => self.end_col >= other.end_col, - Greater => true, - }, - } + self.start <= other.start && self.end >= other.end } pub fn is_empty(&self) -> bool { - self.end_line == self.start_line && self.start_col == self.end_col + self.start == self.end } pub fn span_across(start: &Region, end: &Region) -> Self { Region { - start_line: start.start_line, - end_line: end.end_line, - start_col: start.start_col, - end_col: end.end_col, + start: start.start, + end: end.end, } } @@ -76,56 +55,23 @@ impl Region { } } - pub fn lines_between(&self, other: &Region) -> u32 { - if self.end_line <= other.start_line { - other.start_line - self.end_line - } else if self.start_line >= other.end_line { - self.start_line - other.end_line - } else { - // intersection - 0 - } - } - pub const fn from_pos(pos: Position) -> Self { Region { - start_col: pos.column, - start_line: pos.line, - end_col: pos.column + 1, - end_line: pos.line, - } - } - - pub const fn from_rows_cols( - start_line: u32, - start_col: u16, - end_line: u32, - end_col: u16, - ) -> Self { - Region { - start_line, - end_line, - start_col, - end_col, + start: pos, + end: pos.bump_column(1), } } pub const fn start(&self) -> Position { - Position { - line: self.start_line, - column: self.start_col, - } + self.start } pub const fn end(&self) -> Position { - Position { - line: self.end_line, - column: self.end_col, - } + self.end } pub const fn between(start: Position, end: Position) -> Self { - Self::from_rows_cols(start.line, start.column, end.line, end.column) + Self::new(start, end) } } @@ -137,7 +83,7 @@ fn region_size() { impl fmt::Debug for Region { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.start_line == 0 && self.start_col == 0 && self.end_line == 0 && self.end_col == 0 { + if self.start == Position::zero() && self.end == Position::zero() { // In tests, it's super common to set all Located values to 0. // Also in tests, we don't want to bother printing the locations // because it makes failed assertions much harder to read. @@ -145,8 +91,8 @@ impl fmt::Debug for Region { } else { write!( f, - "|L {}-{}, C {}-{}|", - self.start_line, self.end_line, self.start_col, self.end_col, + "@{}-{}", + self.start.offset, self.end.offset, ) } } @@ -154,17 +100,18 @@ impl fmt::Debug for Region { #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Default)] pub struct Position { + pub offset: u32, line: u32, column: u16, } impl Position { pub const fn zero() -> Position { - Position { line: 0, column: 0 } + Position { offset: 0, line: 0, column: 0 } } - pub const fn new(line: u32, column: u16) -> Position { - Position { line, column } + pub const fn new(offset: u32, line: u32, column: u16) -> Position { + Position { offset, line, column } } #[must_use] @@ -172,15 +119,16 @@ impl Position { Self { line: self.line, column: self.column + count, + offset: self.offset + count as u32, } } #[must_use] - pub fn bump_invisible(self, _count: u16) -> Self { - // This WILL affect the byte offset once we switch to that + pub fn bump_invisible(self, count: u16) -> Self { Self { line: self.line, column: self.column, + offset: self.offset + count as u32, } } @@ -189,12 +137,14 @@ impl Position { Self { line: self.line + 1, column: 0, + offset: self.offset + 1, } } #[must_use] pub const fn sub(self, count: u16) -> Self { Self { + offset: self.offset - count as u32, line: self.line, column: self.column - count, } @@ -203,7 +153,7 @@ impl Position { impl Debug for Position { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}:{}", self.line, self.column) + write!(f, "@{}", self.offset) } } @@ -325,6 +275,23 @@ impl LineColumnRegion { } } +impl fmt::Debug for LineColumnRegion { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.start.line == 0 && self.start.column == 0 && self.end.line == 0 && self.end.column == 0 { + // In tests, it's super common to set all Located values to 0. + // Also in tests, we don't want to bother printing the locations + // because it makes failed assertions much harder to read. + write!(f, "…") + } else { + write!( + f, + "|L {}-{}, C {}-{}|", + self.start.line, self.end.line, self.start.column, self.end.column, + ) + } + } +} + #[derive(Clone, Eq, Copy, PartialEq, PartialOrd, Ord, Hash)] pub struct Loc { pub region: Region, @@ -373,10 +340,8 @@ where fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let region = self.region; - if region.start_line == 0 - && region.start_col == 0 - && region.end_line == 0 - && region.end_col == 0 + if region.start == Position::zero() + && region.end == Position::zero() { // In tests, it's super common to set all Located values to 0. // Also in tests, we don't want to bother printing the locations @@ -415,18 +380,23 @@ impl LineInfo { } pub fn convert_pos(&self, pos: Position) -> LineColumn { - // TODO - LineColumn { - line: pos.line, - column: pos.column, - } + let res = self.convert_offset(pos.offset); + // let expected = LineColumn { line: pos.line, column: pos.column }; + // assert_eq!(expected, res); + res } pub fn convert_region(&self, region: Region) -> LineColumnRegion { - LineColumnRegion { + let res = LineColumnRegion { start: self.convert_pos(region.start()), end: self.convert_pos(region.end()), - } + }; + let expected = LineColumnRegion::new( + LineColumn { line: region.start.line, column: region.start.column }, + LineColumn { line: region.end.line, column: region.end.column }, + ); + assert_eq!(expected, res); + res } } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 0f758d1f13..c656df1986 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -12,7 +12,7 @@ use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey}; static_assertions::assert_eq_size!([u8; 6 * 8], Descriptor); static_assertions::assert_eq_size!([u8; 4 * 8], Content); static_assertions::assert_eq_size!([u8; 3 * 8], FlatType); -static_assertions::assert_eq_size!([u8; 6 * 8], Problem); +// static_assertions::assert_eq_size!([u8; 6 * 8], Problem); static_assertions::assert_eq_size!([u8; 12], UnionTags); static_assertions::assert_eq_size!([u8; 2 * 8], RecordFields); diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index dda841bd88..3533777f65 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -2,7 +2,7 @@ use roc_collections::all::MutSet; use roc_module::ident::{Ident, Lowercase, ModuleName}; use roc_problem::can::PrecedenceProblem::BothNonAssociative; use roc_problem::can::{BadPattern, FloatErrorKind, IntErrorKind, Problem, RuntimeError}; -use roc_region::all::{Loc, Region, LineInfo, LineColumn}; +use roc_region::all::{Loc, Region, LineInfo, LineColumn, LineColumnRegion}; use std::path::PathBuf; use crate::error::r#type::suggest; @@ -496,11 +496,11 @@ fn to_bad_ident_expr_report<'b>( match bad_ident { Start(_) | Space(_, _) => unreachable!("these are handled in the parser"), WeirdDotAccess(pos) | StrayDot(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); alloc.stack(vec![ alloc.reflow(r"I trying to parse a record field access here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("So I expect to see a lowercase letter next, like "), alloc.parser_suggestion(".name"), @@ -527,11 +527,11 @@ fn to_bad_ident_expr_report<'b>( ]), WeirdDotQualified(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); alloc.stack(vec![ alloc.reflow("I am trying to parse a qualified name here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting to see an identifier next, like "), alloc.parser_suggestion("height"), @@ -542,11 +542,11 @@ fn to_bad_ident_expr_report<'b>( ]) } QualifiedTag(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); alloc.stack(vec![ alloc.reflow("I am trying to parse a qualified name here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"This looks like a qualified tag name to me, "), alloc.reflow(r"but tags cannot be qualified! "), @@ -632,11 +632,11 @@ fn to_bad_ident_pattern_report<'b>( match bad_ident { Start(_) | Space(_, _) => unreachable!("these are handled in the parser"), WeirdDotAccess(pos) | StrayDot(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); alloc.stack(vec![ alloc.reflow(r"I trying to parse a record field accessor here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("Something like "), alloc.parser_suggestion(".name"), @@ -663,11 +663,11 @@ fn to_bad_ident_pattern_report<'b>( ]), WeirdDotQualified(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); alloc.stack(vec![ alloc.reflow("I am trying to parse a qualified name here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting to see an identifier next, like "), alloc.parser_suggestion("height"), @@ -678,11 +678,11 @@ fn to_bad_ident_pattern_report<'b>( ]) } QualifiedTag(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); alloc.stack(vec![ alloc.reflow("I am trying to parse a qualified name here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"This looks like a qualified tag name to me, "), alloc.reflow(r"but tags cannot be qualified! "), diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index c9abf64d9e..c40bd88ca4 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -121,11 +121,11 @@ fn to_syntax_report<'a>( } NotEndOfFile(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I expected to reach the end of the file, but got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![alloc.reflow("no hints")]), ]); @@ -213,11 +213,11 @@ fn to_expr_report<'a>( EExpr::Type(tipe, pos) => to_type_report(alloc, lines, filename, tipe, *pos), EExpr::ElmStyleFunction(region, pos) => { let surroundings = Region::new(start, *pos); - let region = *region; + let region = lines.convert_region(*region); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("Looks like you are trying to define a function. "), alloc.reflow("In roc, functions are always written as a lambda, like "), @@ -322,11 +322,11 @@ fn to_expr_report<'a>( EExpr::QualifiedTag(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am very confused by this identifier:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("Are you trying to qualify a name? I am execting something like "), alloc.parser_suggestion("Json.Decode.string"), @@ -404,7 +404,7 @@ fn to_expr_report<'a>( }; let surroundings = Region::new(context_pos, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.concat(vec![ @@ -412,7 +412,7 @@ fn to_expr_report<'a>( a_thing, alloc.reflow(", but I got stuck here:"), ]), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), expecting, ]); @@ -426,11 +426,11 @@ fn to_expr_report<'a>( EExpr::DefMissingFinalExpr(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("This definition is missing a final expression."), alloc.reflow(" A nested definition must be followed by"), @@ -458,11 +458,11 @@ fn to_expr_report<'a>( EExpr::BadExprEnd(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("Whatever I am running into is confusing me a lot! "), alloc.reflow("Normally I can give fairly specific hints, "), @@ -480,11 +480,11 @@ fn to_expr_report<'a>( EExpr::Colon(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("Looks like you are trying to define a function. "), alloc.reflow("In roc, functions are always written as a lambda, like "), @@ -503,11 +503,11 @@ fn to_expr_report<'a>( EExpr::BackpassArrow(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing an expression, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("Looks like you are trying to define a function. ") ]), @@ -541,12 +541,12 @@ fn to_lambda_report<'a>( ELambda::Arrow(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Token("=>") => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting a "), alloc.parser_suggestion("->"), @@ -563,12 +563,12 @@ fn to_lambda_report<'a>( } _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting a "), alloc.parser_suggestion("->"), @@ -588,12 +588,12 @@ fn to_lambda_report<'a>( ELambda::Comma(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Token("=>") => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting a "), alloc.parser_suggestion("->"), @@ -610,12 +610,12 @@ fn to_lambda_report<'a>( } _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting a "), alloc.parser_suggestion("->"), @@ -635,12 +635,12 @@ fn to_lambda_report<'a>( ELambda::Arg(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(',')) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck at this comma:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting an argument pattern before this, "), alloc.reflow("so try adding an argument before the comma and see if that helps?"), @@ -656,12 +656,12 @@ fn to_lambda_report<'a>( } _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting an argument pattern before this, "), alloc.reflow("so try adding an argument and see if that helps?"), @@ -736,14 +736,14 @@ fn to_unfinished_lambda_report<'a>( message: RocDocBuilder<'a>, ) -> Report<'a> { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.concat(vec![ alloc.reflow(r"I was partway through parsing a "), alloc.reflow(r" function, but I got stuck here:"), ]), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), message, ]); @@ -819,13 +819,13 @@ fn to_str_report<'a>( } EString::CodePtOpen(pos) | EString::CodePtEnd(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I am partway through parsing a unicode code point, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"I was expecting a hexadecimal number, like "), alloc.parser_suggestion("\\u(1100)"), @@ -845,11 +845,11 @@ fn to_str_report<'a>( } EString::FormatEnd(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I cannot find the end of this format expression:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"You could change it to something like "), alloc.parser_suggestion("\"The count is \\(count\\)\""), @@ -866,11 +866,11 @@ fn to_str_report<'a>( } EString::EndlessSingle(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I cannot find the end of this string:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"You could change it to something like "), alloc.parser_suggestion("\"to be or not to be\""), @@ -889,11 +889,11 @@ fn to_str_report<'a>( } EString::EndlessMulti(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I cannot find the end of this block string:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"You could change it to something like "), alloc.parser_suggestion("\"\"\"to be or not to be\"\"\""), @@ -934,12 +934,12 @@ fn to_expr_in_parens_report<'a>( ), EInParens::End(pos) | EInParens::IndentEnd(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc .reflow("I am partway through parsing a record pattern, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing parenthesis next, so try adding a ", @@ -958,13 +958,13 @@ fn to_expr_in_parens_report<'a>( } EInParens::Open(pos) | EInParens::IndentOpen(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I just started parsing an expression in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"An expression in parentheses looks like "), alloc.parser_suggestion("(32)"), @@ -1009,13 +1009,13 @@ fn to_list_report<'a>( EList::Open(pos) | EList::End(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(',')) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I am partway through started parsing a list, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"I was expecting to see a list entry before this comma, "), alloc.reflow(r"so try adding a list entry"), @@ -1031,13 +1031,13 @@ fn to_list_report<'a>( } _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I am partway through started parsing a list, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing square bracket before this, ", @@ -1066,11 +1066,11 @@ fn to_list_report<'a>( EList::IndentOpen(pos) | EList::IndentEnd(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I cannot find the end of this list:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"You could change it to something like "), alloc.parser_suggestion("[ 1, 2, 3 ]"), @@ -1187,7 +1187,7 @@ fn to_unfinished_if_report<'a>( message: RocDocBuilder<'a>, ) -> Report<'a> { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.concat(vec![ @@ -1195,7 +1195,7 @@ fn to_unfinished_if_report<'a>( alloc.keyword("if"), alloc.reflow(r" expression, but I got stuck here:"), ]), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), message, ]); @@ -1221,13 +1221,13 @@ fn to_when_report<'a>( EWhen::IfGuard(nested, pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Token("->") => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I just started parsing an if guard, but there is no guard condition:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("Try adding an expression before the arrow!") ]), @@ -1251,7 +1251,7 @@ fn to_when_report<'a>( }, EWhen::Arrow(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.concat(vec![ @@ -1259,7 +1259,7 @@ fn to_when_report<'a>( alloc.keyword("when"), alloc.reflow(r" expression, but got stuck here:"), ]), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![alloc.reflow("I was expecting to see an arrow next.")]), note_for_when_indent_error(alloc), ]); @@ -1408,7 +1408,7 @@ fn to_unfinished_when_report<'a>( _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.concat(vec![ @@ -1416,7 +1416,7 @@ fn to_unfinished_when_report<'a>( alloc.keyword("when"), alloc.reflow(r" expression, but I got stuck here:"), ]), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), message, note_for_when_error(alloc), ]); @@ -1526,11 +1526,11 @@ fn to_pattern_report<'a>( match parse_problem { EPattern::Start(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a pattern, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.note("I may be confused by indentation"), ]); @@ -1583,11 +1583,11 @@ fn to_precord_report<'a>( } _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record pattern, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), record_patterns_look_like(alloc), ]); @@ -1602,13 +1602,13 @@ fn to_precord_report<'a>( PRecord::End(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(c)) if c.is_alphabetic() => { let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a record pattern, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a colon, question mark, comma or closing curly brace.", @@ -1626,7 +1626,7 @@ fn to_precord_report<'a>( _ => { let doc = alloc.stack(vec![ alloc.reflow("I am partway through parsing a record pattern, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing curly brace before this, so try adding a ", @@ -1672,11 +1672,11 @@ fn to_precord_report<'a>( Next::Other(Some('}')) => unreachable!("or is it?"), _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a record pattern, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"I was expecting to see another record field defined next, so I am looking for a name like "), alloc.parser_suggestion("userName"), @@ -1719,11 +1719,11 @@ fn to_precord_report<'a>( PRecord::IndentOpen(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record pattern, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), record_patterns_look_like(alloc), note_for_record_pattern_indent(alloc), ]); @@ -1760,13 +1760,13 @@ fn to_precord_report<'a>( } None => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I am partway through parsing a record pattern, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting to see a closing curly "), alloc.reflow("brace before this, so try adding a "), @@ -1810,13 +1810,13 @@ fn to_pattern_in_parens_report<'a>( PInParens::Open(pos) => { // `Open` case is for exhaustiveness, this case shouldn not be reachable practically. let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I just started parsing a pattern in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"A pattern in parentheses looks like "), alloc.parser_suggestion("(Ok 32)"), @@ -1836,11 +1836,11 @@ fn to_pattern_in_parens_report<'a>( PInParens::End(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow("I am partway through parsing a pattern in parentheses, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing parenthesis before this, so try adding a ", @@ -1862,13 +1862,13 @@ fn to_pattern_in_parens_report<'a>( PInParens::IndentOpen(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I just started parsing a pattern in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), record_patterns_look_like(alloc), note_for_record_pattern_indent(alloc), ]); @@ -1906,13 +1906,13 @@ fn to_pattern_in_parens_report<'a>( } None => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I am partway through parsing a pattern in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting to see a closing parenthesis "), alloc.reflow("before this, so try adding a "), @@ -1955,11 +1955,11 @@ fn to_type_report<'a>( EType::TFunctionArgument(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(*pos)) { Next::Other(Some(',')) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a function argument type, but I encountered two commas in a row:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![alloc.reflow("Try removing one of them.")]), ]); @@ -1975,11 +1975,11 @@ fn to_type_report<'a>( EType::TStart(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a type, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"I am expecting a type next, like "), alloc.parser_suggestion("Bool"), @@ -1999,11 +1999,11 @@ fn to_type_report<'a>( EType::TIndentStart(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a type, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.note("I may be confused by indentation"), ]); @@ -2017,11 +2017,11 @@ fn to_type_report<'a>( EType::TIndentEnd(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a type, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.note("I may be confused by indentation"), ]); @@ -2035,11 +2035,11 @@ fn to_type_report<'a>( EType::TAsIndentStart(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing an inline type alias, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.note("I may be confused by indentation"), ]); @@ -2053,11 +2053,11 @@ fn to_type_report<'a>( EType::TBadTypeVariable(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am expecting a type variable, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), ]); Report { @@ -2106,11 +2106,11 @@ fn to_trecord_report<'a>( } _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record type, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Record types look like "), alloc.parser_suggestion("{ name : String, age : Int },"), @@ -2129,13 +2129,13 @@ fn to_trecord_report<'a>( ETypeRecord::End(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(c)) if c.is_alphabetic() => { let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a record type, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a colon, question mark, comma or closing curly brace.", @@ -2153,7 +2153,7 @@ fn to_trecord_report<'a>( _ => { let doc = alloc.stack(vec![ alloc.reflow("I am partway through parsing a record type, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing curly brace before this, so try adding a ", @@ -2199,11 +2199,11 @@ fn to_trecord_report<'a>( Next::Other(Some('}')) => unreachable!("or is it?"), _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a record type, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"I was expecting to see another record field defined next, so I am looking for a name like "), alloc.parser_suggestion("userName"), @@ -2233,11 +2233,11 @@ fn to_trecord_report<'a>( ETypeRecord::IndentOpen(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record type, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Record types look like "), alloc.parser_suggestion("{ name : String, age : Int },"), @@ -2279,13 +2279,13 @@ fn to_trecord_report<'a>( } None => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I am partway through parsing a record type, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting to see a closing curly "), alloc.reflow("brace before this, so try adding a "), @@ -2353,13 +2353,13 @@ fn to_ttag_union_report<'a>( debug_assert!(c.is_lowercase()); let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I am partway through parsing a tag union type, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.reflow(r"I was expecting to see a tag name."), hint_for_tag_name(alloc), ]); @@ -2373,11 +2373,11 @@ fn to_ttag_union_report<'a>( } _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a tag union type, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Tag unions look like "), alloc.parser_suggestion("[ Many I64, None ],"), @@ -2396,7 +2396,7 @@ fn to_ttag_union_report<'a>( ETypeTagUnion::End(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(c)) if c.is_alphabetic() => { @@ -2406,7 +2406,7 @@ fn to_ttag_union_report<'a>( alloc.reflow( r"I am partway through parsing a tag union type, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.reflow(r"I was expecting to see a tag name."), hint_for_tag_name(alloc), ]); @@ -2423,7 +2423,7 @@ fn to_ttag_union_report<'a>( alloc.reflow( r"I am partway through parsing a tag union type, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.reflow(r"I was expecting to see a private tag name."), hint_for_private_tag_name(alloc), ]); @@ -2438,7 +2438,7 @@ fn to_ttag_union_report<'a>( _ => { let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a tag union type, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing square bracket before this, so try adding a ", @@ -2462,11 +2462,11 @@ fn to_ttag_union_report<'a>( ETypeTagUnion::IndentOpen(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a tag union type, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Tag unions look like "), alloc.parser_suggestion("[ Many I64, None ],"), @@ -2508,13 +2508,13 @@ fn to_ttag_union_report<'a>( } None => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I am partway through parsing a tag union type, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting to see a closing square "), alloc.reflow("bracket before this, so try adding a "), @@ -2576,13 +2576,13 @@ fn to_tinparens_report<'a>( debug_assert!(c.is_lowercase()); let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I am partway through parsing a type in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.reflow(r"I was expecting to see a tag name."), hint_for_tag_name(alloc), ]); @@ -2596,13 +2596,13 @@ fn to_tinparens_report<'a>( } _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I just started parsing a type in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Tag unions look like "), alloc.parser_suggestion("[ Many I64, None ],"), @@ -2622,7 +2622,7 @@ fn to_tinparens_report<'a>( ETypeInParens::End(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(c)) if c.is_alphabetic() => { @@ -2633,7 +2633,7 @@ fn to_tinparens_report<'a>( alloc.reflow( r"I am partway through parsing a type in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.reflow(r"I was expecting to see a tag name."), hint_for_tag_name(alloc), ]); @@ -2648,7 +2648,7 @@ fn to_tinparens_report<'a>( _ => { let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a type in parentheses, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing parenthesis before this, so try adding a ", @@ -2672,12 +2672,12 @@ fn to_tinparens_report<'a>( ETypeInParens::IndentOpen(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc .reflow(r"I just started parsing a type in parentheses, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Tag unions look like "), alloc.parser_suggestion("[ Many I64, None ],"), @@ -2719,13 +2719,13 @@ fn to_tinparens_report<'a>( } None => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I am partway through parsing a type in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting to see a parenthesis "), alloc.reflow("before this, so try adding a "), @@ -2760,11 +2760,11 @@ fn to_tapply_report<'a>( match *parse_problem { ETypeApply::DoubleDot(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I encountered two dots in a row:"), - alloc.region(lines.convert_region(region)), + alloc.region(region), alloc.concat(vec![alloc.reflow("Try removing one of them.")]), ]); @@ -2776,11 +2776,11 @@ fn to_tapply_report<'a>( } } ETypeApply::TrailingDot(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I encountered a dot with nothing after it:"), - alloc.region(lines.convert_region(region)), + alloc.region(region), alloc.concat(vec![ alloc.reflow("Dots are used to refer to a type in a qualified way, like "), alloc.parser_suggestion("Num.I64"), @@ -2798,11 +2798,11 @@ fn to_tapply_report<'a>( } } ETypeApply::StartIsNumber(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I encountered a number at the start of a qualified name segment:"), - alloc.region(lines.convert_region(region)), + alloc.region(region), alloc.concat(vec![ alloc.reflow("All parts of a qualified type name must start with an uppercase letter, like "), alloc.parser_suggestion("Num.I64"), @@ -2820,11 +2820,11 @@ fn to_tapply_report<'a>( } } ETypeApply::StartNotUppercase(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I encountered a lowercase letter at the start of a qualified name segment:"), - alloc.region(lines.convert_region(region)), + alloc.region(region), alloc.concat(vec![ alloc.reflow("All parts of a qualified type name must start with an uppercase letter, like "), alloc.parser_suggestion("Num.I64"), @@ -2843,13 +2843,13 @@ fn to_tapply_report<'a>( } ETypeApply::End(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I reached the end of the input file while parsing a qualified type name", ), - alloc.region(lines.convert_region(region)), + alloc.region(region), ]); Report { @@ -2958,11 +2958,11 @@ fn to_header_report<'a>( EHeader::IndentStart(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![alloc.reflow("I may be confused by indentation.")]), ]); @@ -2976,11 +2976,11 @@ fn to_header_report<'a>( EHeader::Start(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am expecting a header, but got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting a module keyword next, one of "), alloc.keyword("interface"), @@ -3002,11 +3002,11 @@ fn to_header_report<'a>( EHeader::ModuleName(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting a module name next, like "), alloc.parser_suggestion("BigNum"), @@ -3026,11 +3026,11 @@ fn to_header_report<'a>( EHeader::AppName(_, pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting an application name next, like "), alloc.parser_suggestion("app \"main\""), @@ -3050,11 +3050,11 @@ fn to_header_report<'a>( EHeader::PlatformName(_, pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting a platform name next, like "), alloc.parser_suggestion("\"roc/core\""), @@ -3087,12 +3087,12 @@ fn to_provides_report<'a>( EProvides::ListEnd(pos) | // TODO: give this its own error message EProvides::Identifier(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a provides list, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![alloc.reflow( "I was expecting a type name, value name or function name next, like", )]), @@ -3111,11 +3111,11 @@ fn to_provides_report<'a>( EProvides::Provides(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("provides"), @@ -3153,11 +3153,11 @@ fn to_exposes_report<'a>( EExposes::ListEnd(pos) | // TODO: give this its own error message EExposes::Identifier(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a exposes list, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![alloc.reflow( "I was expecting a type name, value name or function name next, like", )]), @@ -3176,11 +3176,11 @@ fn to_exposes_report<'a>( EExposes::Exposes(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("exposes"), @@ -3217,11 +3217,11 @@ fn to_imports_report<'a>( match *parse_problem { EImports::Identifier(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a imports list, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![alloc.reflow( "I was expecting a type name, value name or function name next, like ", )]), @@ -3240,11 +3240,11 @@ fn to_imports_report<'a>( EImports::Imports(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("imports"), @@ -3267,11 +3267,11 @@ fn to_imports_report<'a>( EImports::ModuleName(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting a module name next, like "), alloc.parser_suggestion("BigNum"), @@ -3305,11 +3305,11 @@ fn to_requires_report<'a>( match *parse_problem { ERequires::Requires(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("requires"), @@ -3332,11 +3332,11 @@ fn to_requires_report<'a>( ERequires::ListStart(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("requires"), @@ -3357,11 +3357,11 @@ fn to_requires_report<'a>( ERequires::Rigid(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting a list of rigids like "), alloc.keyword("{}"), @@ -3400,11 +3400,11 @@ fn to_packages_report<'a>( match *parse_problem { EPackages::Packages(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("packages"), @@ -3439,11 +3439,11 @@ fn to_effects_report<'a>( match *parse_problem { EEffects::Effects(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("effects"), @@ -3477,11 +3477,11 @@ fn to_space_report<'a>( match parse_problem { BadInputError::HasTab => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I encountered a tab character"), - alloc.region(lines.convert_region(region)), + alloc.region(region), alloc.concat(vec![alloc.reflow("Tab characters are not allowed.")]), ]); From eb35e9914ffff63ba98fc71e5ac50feebed9dce6 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Thu, 23 Dec 2021 21:11:41 -0800 Subject: [PATCH 068/541] Fix tests --- compiler/can/tests/test_can.rs | 28 +++++----- .../type_argument_no_arrow.expr.result-ast | 2 +- .../fail/type_double_comma.expr.result-ast | 2 +- .../pass/add_var_with_spaces.expr.result-ast | 6 +-- .../pass/add_with_spaces.expr.result-ast | 6 +-- ...notated_record_destructure.expr.result-ast | 32 ++++++------ .../annotated_tag_destructure.expr.result-ast | 30 +++++------ .../pass/apply_global_tag.expr.result-ast | 6 +-- ...enthetical_global_tag_args.expr.result-ast | 6 +-- .../pass/apply_private_tag.expr.result-ast | 6 +-- .../pass/apply_three_args.expr.result-ast | 8 +-- .../pass/apply_two_args.expr.result-ast | 6 +-- .../pass/apply_unary_negation.expr.result-ast | 10 ++-- .../pass/apply_unary_not.expr.result-ast | 10 ++-- .../pass/basic_apply.expr.result-ast | 4 +- .../snapshots/pass/basic_docs.expr.result-ast | 8 +-- .../closure_with_underscores.expr.result-ast | 6 +-- .../pass/comment_after_op.expr.result-ast | 6 +-- .../pass/comment_before_op.expr.result-ast | 6 +-- .../comment_with_non_ascii.expr.result-ast | 6 +-- .../pass/empty_app_header.header.result-ast | 4 +- .../empty_interface_header.header.result-ast | 2 +- .../empty_platform_header.header.result-ast | 8 +-- .../snapshots/pass/equals.expr.result-ast | 6 +-- .../pass/equals_with_spaces.expr.result-ast | 6 +-- .../snapshots/pass/expect.expr.result-ast | 10 ++-- .../pass/full_app_header.header.result-ast | 12 ++--- ...p_header_trailing_commas.header.result-ast | 16 +++--- .../function_effect_types.header.result-ast | 52 +++++++++---------- .../snapshots/pass/if_def.expr.result-ast | 8 +-- ...ed_ident_due_to_underscore.expr.result-ast | 6 +-- ...ormed_pattern_field_access.expr.result-ast | 10 ++-- ...formed_pattern_module_name.expr.result-ast | 10 ++-- .../pass/minimal_app_header.header.result-ast | 4 +- .../minus_twelve_minus_five.expr.result-ast | 6 +-- .../snapshots/pass/mixed_docs.expr.result-ast | 8 +-- .../pass/module_def_newline.module.result-ast | 14 ++--- .../pass/multi_backpassing.expr.result-ast | 20 +++---- .../multiline_type_signature.expr.result-ast | 8 +-- ...ype_signature_with_comment.expr.result-ast | 8 +-- .../pass/multiple_operators.expr.result-ast | 10 ++-- .../pass/neg_inf_float.expr.result-ast | 4 +- .../nested_def_annotation.module.result-ast | 42 +++++++-------- .../snapshots/pass/nested_if.expr.result-ast | 10 ++-- .../pass/nested_module.header.result-ast | 2 +- .../pass/newline_after_equals.expr.result-ast | 8 +-- .../pass/newline_after_mul.expr.result-ast | 6 +-- .../pass/newline_after_sub.expr.result-ast | 6 +-- ...nd_spaces_before_less_than.expr.result-ast | 14 ++--- .../pass/newline_before_add.expr.result-ast | 6 +-- .../pass/newline_before_sub.expr.result-ast | 6 +-- .../newline_singleton_list.expr.result-ast | 2 +- ...nonempty_platform_header.header.result-ast | 16 +++--- .../snapshots/pass/not_docs.expr.result-ast | 8 +-- .../pass/one_backpassing.expr.result-ast | 10 ++-- .../snapshots/pass/one_def.expr.result-ast | 8 +-- .../pass/one_minus_two.expr.result-ast | 6 +-- .../pass/one_plus_two.expr.result-ast | 6 +-- .../pass/one_spaced_def.expr.result-ast | 8 +-- .../pass/ops_with_newlines.expr.result-ast | 6 +-- .../packed_singleton_list.expr.result-ast | 2 +- .../pass/parenthetical_apply.expr.result-ast | 4 +- .../pass/parse_alias.expr.result-ast | 16 +++--- .../pass/parse_as_ann.expr.result-ast | 20 +++---- ...ttern_with_space_in_parens.expr.result-ast | 36 ++++++------- .../private_qualified_tag.expr.result-ast | 2 +- .../pass/qualified_global_tag.expr.result-ast | 2 +- .../record_destructure_def.expr.result-ast | 18 +++---- .../record_func_type_decl.expr.result-ast | 42 +++++++-------- .../pass/record_update.expr.result-ast | 14 ++--- .../pass/record_with_if.expr.result-ast | 18 +++---- .../pass/single_arg_closure.expr.result-ast | 4 +- .../single_underscore_closure.expr.result-ast | 4 +- .../space_only_after_minus.expr.result-ast | 6 +-- .../spaced_singleton_list.expr.result-ast | 2 +- .../standalone_module_defs.module.result-ast | 18 +++---- .../pass/sub_var_with_spaces.expr.result-ast | 6 +-- .../pass/sub_with_spaces.expr.result-ast | 6 +-- .../pass/tag_pattern.expr.result-ast | 4 +- .../pass/ten_times_eleven.expr.result-ast | 6 +-- .../pass/three_arg_closure.expr.result-ast | 8 +-- .../pass/two_arg_closure.expr.result-ast | 6 +-- .../pass/two_backpassing.expr.result-ast | 16 +++--- .../pass/two_branch_when.expr.result-ast | 10 ++-- .../pass/two_spaced_def.expr.result-ast | 14 ++--- .../type_decl_with_underscore.expr.result-ast | 16 +++--- .../pass/unary_negation.expr.result-ast | 4 +- .../unary_negation_access.expr.result-ast | 4 +- .../pass/unary_negation_arg.expr.result-ast | 10 ++-- ...unary_negation_with_parens.expr.result-ast | 10 ++-- .../snapshots/pass/unary_not.expr.result-ast | 4 +- .../unary_not_with_parens.expr.result-ast | 10 ++-- .../underscore_backpassing.expr.result-ast | 10 ++-- .../pass/var_minus_two.expr.result-ast | 6 +-- .../pass/when_if_guard.expr.result-ast | 14 ++--- .../pass/when_in_parens.expr.result-ast | 6 +-- .../when_in_parens_indented.expr.result-ast | 6 +-- ..._with_alternative_patterns.expr.result-ast | 14 ++--- ..._with_function_application.expr.result-ast | 14 ++--- ...when_with_negative_numbers.expr.result-ast | 10 ++-- .../pass/when_with_numbers.expr.result-ast | 10 ++-- .../pass/when_with_records.expr.result-ast | 16 +++--- compiler/parse/tests/test_parse.rs | 22 ++++---- compiler/region/src/all.rs | 8 ++- 104 files changed, 540 insertions(+), 534 deletions(-) diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index bad0f296ac..7a17b80989 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -945,11 +945,11 @@ mod test_can { symbol: interns.symbol(home, "x".into()), symbol_region: Region::new( Position::new(0, 0, 0), - Position::new(0, 0, 0), + Position::new(1, 0, 0), ), expr_region: Region::new( - Position::new(0, 0, 0), - Position::new(0, 0, 0), + Position::new(4, 0, 0), + Position::new(5, 0, 0), ), }])); @@ -982,33 +982,33 @@ mod test_can { symbol: interns.symbol(home, "x".into()), symbol_region: Region::new( Position::new(0, 0, 0), - Position::new(0, 0, 0), + Position::new(1, 0, 0), ), expr_region: Region::new( - Position::new(0, 0, 0), - Position::new(0, 0, 0), + Position::new(4, 0, 0), + Position::new(5, 0, 0), ), }, CycleEntry { symbol: interns.symbol(home, "y".into()), symbol_region: Region::new( - Position::new(0, 0, 0), - Position::new(0, 0, 0), + Position::new(6, 0, 0), + Position::new(7, 0, 0), ), expr_region: Region::new( - Position::new(0, 0, 0), - Position::new(0, 0, 0), + Position::new(10, 0, 0), + Position::new(11, 0, 0), ), }, CycleEntry { symbol: interns.symbol(home, "z".into()), symbol_region: Region::new( - Position::new(0, 0, 0), - Position::new(0, 0, 0), + Position::new(12, 0, 0), + Position::new(13, 0, 0), ), expr_region: Region::new( - Position::new(0, 0, 0), - Position::new(0, 0, 0), + Position::new(16, 0, 0), + Position::new(17, 0, 0), ), }, ])); diff --git a/compiler/parse/tests/snapshots/fail/type_argument_no_arrow.expr.result-ast b/compiler/parse/tests/snapshots/fail/type_argument_no_arrow.expr.result-ast index bff7c1a23a..2601637ebe 100644 --- a/compiler/parse/tests/snapshots/fail/type_argument_no_arrow.expr.result-ast +++ b/compiler/parse/tests/snapshots/fail/type_argument_no_arrow.expr.result-ast @@ -1 +1 @@ -Expr(Type(TIndentEnd(0:12), 0:4)) \ No newline at end of file +Expr(Type(TIndentEnd(@12), @4)) \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/fail/type_double_comma.expr.result-ast b/compiler/parse/tests/snapshots/fail/type_double_comma.expr.result-ast index 15c6461fec..c020db9200 100644 --- a/compiler/parse/tests/snapshots/fail/type_double_comma.expr.result-ast +++ b/compiler/parse/tests/snapshots/fail/type_double_comma.expr.result-ast @@ -1 +1 @@ -Expr(Type(TFunctionArgument(0:8), 0:4)) \ No newline at end of file +Expr(Type(TFunctionArgument(@8), @4)) \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast index 5d07b7897e..cd27503529 100644 --- a/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast @@ -1,14 +1,14 @@ BinOps( [ ( - |L 0-0, C 0-1| Var { + @0-1 Var { module_name: "", ident: "x", }, - |L 0-0, C 2-3| Plus, + @2-3 Plus, ), ], - |L 0-0, C 4-5| Num( + @4-5 Num( "2", ), ) diff --git a/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast index b3ba87f0bf..4d75339647 100644 --- a/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast @@ -1,13 +1,13 @@ BinOps( [ ( - |L 0-0, C 0-1| Num( + @0-1 Num( "1", ), - |L 0-0, C 3-4| Plus, + @3-4 Plus, ), ], - |L 0-0, C 7-8| Num( + @7-8 Num( "2", ), ) diff --git a/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast b/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast index d6960ede3c..3b4a561069 100644 --- a/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast @@ -1,47 +1,47 @@ Defs( [ - |L 1-1, C 0-34| AnnotatedBody { - ann_pattern: |L 0-0, C 0-8| RecordDestructure( + @15-49 AnnotatedBody { + ann_pattern: @0-8 RecordDestructure( [ - |L 0-0, C 2-3| Identifier( + @2-3 Identifier( "x", ), - |L 0-0, C 5-7| Identifier( + @5-7 Identifier( "y", ), ], ), - ann_type: |L 0-0, C 11-14| Apply( + ann_type: @11-14 Apply( "", "Foo", [], ), comment: None, - body_pattern: |L 1-1, C 0-8| RecordDestructure( + body_pattern: @15-23 RecordDestructure( [ - |L 1-1, C 2-3| Identifier( + @17-18 Identifier( "x", ), - |L 1-1, C 5-6| Identifier( + @20-21 Identifier( "y", ), ], ), - body_expr: |L 1-1, C 11-34| Record( + body_expr: @26-49 Record( [ - |L 1-1, C 13-22| RequiredValue( - |L 1-1, C 13-14| "x", + @28-37 RequiredValue( + @28-29 "x", [], - |L 1-1, C 17-22| Str( + @32-37 Str( PlainLine( "foo", ), ), ), - |L 1-1, C 24-32| RequiredValue( - |L 1-1, C 24-25| "y", + @39-47 RequiredValue( + @39-40 "y", [], - |L 1-1, C 28-32| Float( + @43-47 Float( "3.14", ), ), @@ -49,7 +49,7 @@ Defs( ), }, ], - |L 3-3, C 0-1| SpaceBefore( + @51-52 SpaceBefore( Var { module_name: "", ident: "x", diff --git a/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast b/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast index cf1dd8c0a9..178aca2667 100644 --- a/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast @@ -1,23 +1,23 @@ Defs( [ - |L 1-1, C 0-20| AnnotatedBody { - ann_pattern: |L 0-0, C 0-8| Apply( - |L 0-0, C 0-6| GlobalTag( + @26-46 AnnotatedBody { + ann_pattern: @0-8 Apply( + @0-6 GlobalTag( "UserId", ), [ - |L 0-0, C 7-8| Identifier( + @7-8 Identifier( "x", ), ], ), - ann_type: |L 0-0, C 11-25| TagUnion { + ann_type: @11-25 TagUnion { ext: None, tags: [ - |L 0-0, C 13-23| Global { - name: |L 0-0, C 13-19| "UserId", + @13-23 Global { + name: @13-19 "UserId", args: [ - |L 0-0, C 20-23| Apply( + @20-23 Apply( "", "I64", [], @@ -27,22 +27,22 @@ Defs( ], }, comment: None, - body_pattern: |L 1-1, C 0-8| Apply( - |L 1-1, C 0-6| GlobalTag( + body_pattern: @26-34 Apply( + @26-32 GlobalTag( "UserId", ), [ - |L 1-1, C 7-8| Identifier( + @33-34 Identifier( "x", ), ], ), - body_expr: |L 1-1, C 11-20| Apply( - |L 1-1, C 11-17| GlobalTag( + body_expr: @37-46 Apply( + @37-43 GlobalTag( "UserId", ), [ - |L 1-1, C 18-20| Num( + @44-46 Num( "42", ), ], @@ -50,7 +50,7 @@ Defs( ), }, ], - |L 3-3, C 0-1| SpaceBefore( + @48-49 SpaceBefore( Var { module_name: "", ident: "x", diff --git a/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast index 8e6245cb9f..f939664412 100644 --- a/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast @@ -1,12 +1,12 @@ Apply( - |L 0-0, C 0-4| GlobalTag( + @0-4 GlobalTag( "Whee", ), [ - |L 0-0, C 5-7| Num( + @5-7 Num( "12", ), - |L 0-0, C 8-10| Num( + @8-10 Num( "34", ), ], diff --git a/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast index 14cf85eed0..483292bdcf 100644 --- a/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast @@ -1,14 +1,14 @@ Apply( - |L 0-0, C 0-4| GlobalTag( + @0-4 GlobalTag( "Whee", ), [ - |L 0-0, C 6-8| ParensAround( + @6-8 ParensAround( Num( "12", ), ), - |L 0-0, C 11-13| ParensAround( + @11-13 ParensAround( Num( "34", ), diff --git a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast index 47719397a0..185e681f5b 100644 --- a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast @@ -1,12 +1,12 @@ Apply( - |L 0-0, C 0-5| PrivateTag( + @0-5 PrivateTag( "@Whee", ), [ - |L 0-0, C 6-8| Num( + @6-8 Num( "12", ), - |L 0-0, C 9-11| Num( + @9-11 Num( "34", ), ], diff --git a/compiler/parse/tests/snapshots/pass/apply_three_args.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_three_args.expr.result-ast index 45d7902727..bdf0bf721a 100644 --- a/compiler/parse/tests/snapshots/pass/apply_three_args.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_three_args.expr.result-ast @@ -1,18 +1,18 @@ Apply( - |L 0-0, C 0-1| Var { + @0-1 Var { module_name: "", ident: "a", }, [ - |L 0-0, C 2-3| Var { + @2-3 Var { module_name: "", ident: "b", }, - |L 0-0, C 4-5| Var { + @4-5 Var { module_name: "", ident: "c", }, - |L 0-0, C 6-7| Var { + @6-7 Var { module_name: "", ident: "d", }, diff --git a/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast index a4e39d6fd5..882b37c09d 100644 --- a/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast @@ -1,13 +1,13 @@ Apply( - |L 0-0, C 0-4| Var { + @0-4 Var { module_name: "", ident: "whee", }, [ - |L 0-0, C 6-8| Num( + @6-8 Num( "12", ), - |L 0-0, C 10-12| Num( + @10-12 Num( "34", ), ], diff --git a/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast index 54e180b592..d74301a614 100644 --- a/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast @@ -1,16 +1,16 @@ Apply( - |L 0-0, C 0-5| UnaryOp( - |L 0-0, C 1-5| Var { + @0-5 UnaryOp( + @1-5 Var { module_name: "", ident: "whee", }, - |L 0-0, C 0-1| Negate, + @0-1 Negate, ), [ - |L 0-0, C 7-9| Num( + @7-9 Num( "12", ), - |L 0-0, C 10-13| Var { + @10-13 Var { module_name: "", ident: "foo", }, diff --git a/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast index 86eee8cafa..7a50a7af86 100644 --- a/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast @@ -1,16 +1,16 @@ Apply( - |L 0-0, C 0-5| UnaryOp( - |L 0-0, C 1-5| Var { + @0-5 UnaryOp( + @1-5 Var { module_name: "", ident: "whee", }, - |L 0-0, C 0-1| Not, + @0-1 Not, ), [ - |L 0-0, C 7-9| Num( + @7-9 Num( "12", ), - |L 0-0, C 10-13| Var { + @10-13 Var { module_name: "", ident: "foo", }, diff --git a/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast index 6d906548f6..ae21012711 100644 --- a/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast @@ -1,10 +1,10 @@ Apply( - |L 0-0, C 0-4| Var { + @0-4 Var { module_name: "", ident: "whee", }, [ - |L 0-0, C 5-6| Num( + @5-6 Num( "1", ), ], diff --git a/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast index 3e891ee430..8f016cf433 100644 --- a/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast @@ -1,16 +1,16 @@ SpaceBefore( Defs( [ - |L 6-6, C 0-5| Body( - |L 6-6, C 0-1| Identifier( + @107-112 Body( + @107-108 Identifier( "x", ), - |L 6-6, C 4-5| Num( + @111-112 Num( "5", ), ), ], - |L 8-8, C 0-2| SpaceBefore( + @114-116 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast b/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast index 61ea2b2405..83a2f401e4 100644 --- a/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast @@ -1,13 +1,13 @@ Closure( [ - |L 0-0, C 1-2| Underscore( + @1-2 Underscore( "", ), - |L 0-0, C 4-9| Underscore( + @4-9 Underscore( "name", ), ], - |L 0-0, C 13-15| Num( + @13-15 Num( "42", ), ) diff --git a/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast index 34d2472071..4930aa1a55 100644 --- a/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast @@ -1,13 +1,13 @@ BinOps( [ ( - |L 0-0, C 0-2| Num( + @0-2 Num( "12", ), - |L 0-0, C 4-5| Star, + @4-5 Star, ), ], - |L 1-1, C 1-3| SpaceBefore( + @15-17 SpaceBefore( Num( "92", ), diff --git a/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast index af6abd380a..8d2a5f0b63 100644 --- a/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast @@ -1,7 +1,7 @@ BinOps( [ ( - |L 0-0, C 0-1| SpaceAfter( + @0-1 SpaceAfter( Num( "3", ), @@ -11,10 +11,10 @@ BinOps( ), ], ), - |L 1-1, C 0-1| Plus, + @11-12 Plus, ), ], - |L 1-1, C 2-3| Num( + @13-14 Num( "4", ), ) diff --git a/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast index ded9ef9ea0..5bf526ed0e 100644 --- a/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast @@ -1,7 +1,7 @@ BinOps( [ ( - |L 0-0, C 0-1| SpaceAfter( + @0-1 SpaceAfter( Num( "3", ), @@ -11,10 +11,10 @@ BinOps( ), ], ), - |L 1-1, C 0-1| Plus, + @12-13 Plus, ), ], - |L 1-1, C 2-3| Num( + @14-15 Num( "4", ), ) diff --git a/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast b/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast index 74bf278e99..7b14020ab4 100644 --- a/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast @@ -1,12 +1,12 @@ App { header: AppHeader { - name: |L 0-0, C 4-14| PlainLine( + name: @4-14 PlainLine( "test-app", ), packages: [], imports: [], provides: [], - to: |L 0-0, C 53-57| ExistingPackage( + to: @53-57 ExistingPackage( "blah", ), before_header: [], diff --git a/compiler/parse/tests/snapshots/pass/empty_interface_header.header.result-ast b/compiler/parse/tests/snapshots/pass/empty_interface_header.header.result-ast index 0e71559779..1bd388d31d 100644 --- a/compiler/parse/tests/snapshots/pass/empty_interface_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/empty_interface_header.header.result-ast @@ -1,6 +1,6 @@ Interface { header: InterfaceHeader { - name: |L 0-0, C 10-13| ModuleName( + name: @10-13 ModuleName( "Foo", ), exposes: [], diff --git a/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast b/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast index cb2368db17..6821a8a463 100644 --- a/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast @@ -1,14 +1,14 @@ Platform { header: PlatformHeader { - name: |L 0-0, C 9-25| PackageName( + name: @9-25 PackageName( "rtfeldman/blah", ), requires: PlatformRequires { rigids: [], - signature: |L 0-0, C 40-49| TypedIdent { - ident: |L 0-0, C 40-44| "main", + signature: @40-49 TypedIdent { + ident: @40-44 "main", spaces_before_colon: [], - ann: |L 0-0, C 47-49| Record { + ann: @47-49 Record { fields: [], ext: None, }, diff --git a/compiler/parse/tests/snapshots/pass/equals.expr.result-ast b/compiler/parse/tests/snapshots/pass/equals.expr.result-ast index e69ba3d3da..76f314ce4f 100644 --- a/compiler/parse/tests/snapshots/pass/equals.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/equals.expr.result-ast @@ -1,14 +1,14 @@ BinOps( [ ( - |L 0-0, C 0-1| Var { + @0-1 Var { module_name: "", ident: "x", }, - |L 0-0, C 1-3| Equals, + @1-3 Equals, ), ], - |L 0-0, C 3-4| Var { + @3-4 Var { module_name: "", ident: "y", }, diff --git a/compiler/parse/tests/snapshots/pass/equals_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/equals_with_spaces.expr.result-ast index 10e4d471e7..420f432778 100644 --- a/compiler/parse/tests/snapshots/pass/equals_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/equals_with_spaces.expr.result-ast @@ -1,14 +1,14 @@ BinOps( [ ( - |L 0-0, C 0-1| Var { + @0-1 Var { module_name: "", ident: "x", }, - |L 0-0, C 2-4| Equals, + @2-4 Equals, ), ], - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "y", }, diff --git a/compiler/parse/tests/snapshots/pass/expect.expr.result-ast b/compiler/parse/tests/snapshots/pass/expect.expr.result-ast index 06d5710e1a..fb56585387 100644 --- a/compiler/parse/tests/snapshots/pass/expect.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/expect.expr.result-ast @@ -1,18 +1,18 @@ Expect( - |L 0-0, C 7-13| BinOps( + @7-13 BinOps( [ ( - |L 0-0, C 7-8| Num( + @7-8 Num( "1", ), - |L 0-0, C 9-11| Equals, + @9-11 Equals, ), ], - |L 0-0, C 12-13| Num( + @12-13 Num( "1", ), ), - |L 2-2, C 0-1| SpaceBefore( + @15-16 SpaceBefore( Num( "4", ), diff --git a/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast b/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast index 1d0739ba94..5576408d8b 100644 --- a/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast @@ -1,19 +1,19 @@ App { header: AppHeader { - name: |L 0-0, C 4-15| PlainLine( + name: @4-15 PlainLine( "quicksort", ), packages: [ - |L 1-1, C 15-31| PackageEntry { + @31-47 PackageEntry { shorthand: "pf", spaces_after_shorthand: [], - package_name: |L 1-1, C 19-31| PackageName( + package_name: @35-47 PackageName( "./platform", ), }, ], imports: [ - |L 2-2, C 14-25| Package( + @64-75 Package( "foo", ModuleName( "Bar.Baz", @@ -22,11 +22,11 @@ App { ), ], provides: [ - |L 3-3, C 15-24| ExposedName( + @93-102 ExposedName( "quicksort", ), ], - to: |L 3-3, C 30-32| ExistingPackage( + to: @108-110 ExistingPackage( "pf", ), before_header: [], diff --git a/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast b/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast index 543965a8c4..236aed2d63 100644 --- a/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast @@ -1,26 +1,26 @@ App { header: AppHeader { - name: |L 0-0, C 4-15| PlainLine( + name: @4-15 PlainLine( "quicksort", ), packages: [ - |L 1-1, C 15-31| PackageEntry { + @31-47 PackageEntry { shorthand: "pf", spaces_after_shorthand: [], - package_name: |L 1-1, C 19-31| PackageName( + package_name: @35-47 PackageName( "./platform", ), }, ], imports: [ - |L 2-6, C 14-5| Package( + @65-141 Package( "foo", ModuleName( "Bar", ), Collection { items: [ - |L 3-3, C 8-11| SpaceBefore( + @83-86 SpaceBefore( ExposedName( "Baz", ), @@ -28,7 +28,7 @@ App { Newline, ], ), - |L 4-4, C 8-16| SpaceBefore( + @96-104 SpaceBefore( ExposedName( "FortyTwo", ), @@ -47,11 +47,11 @@ App { ), ], provides: [ - |L 7-7, C 15-24| ExposedName( + @159-168 ExposedName( "quicksort", ), ], - to: |L 7-7, C 31-33| ExistingPackage( + to: @175-177 ExistingPackage( "pf", ), before_header: [], diff --git a/compiler/parse/tests/snapshots/pass/function_effect_types.header.result-ast b/compiler/parse/tests/snapshots/pass/function_effect_types.header.result-ast index 9db9377a6f..08f163c4f0 100644 --- a/compiler/parse/tests/snapshots/pass/function_effect_types.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/function_effect_types.header.result-ast @@ -1,22 +1,22 @@ Platform { header: PlatformHeader { - name: |L 0-0, C 9-23| PackageName( + name: @9-23 PackageName( "examples/cli", ), requires: PlatformRequires { rigids: [], - signature: |L 1-1, C 17-34| TypedIdent { - ident: |L 1-1, C 17-21| "main", + signature: @41-58 TypedIdent { + ident: @41-45 "main", spaces_before_colon: [], - ann: |L 1-1, C 24-34| Apply( + ann: @48-58 Apply( "", "Task", [ - |L 1-1, C 29-31| Record { + @53-55 Record { fields: [], ext: None, }, - |L 1-1, C 32-34| TagUnion { + @56-58 TagUnion { ext: None, tags: [], }, @@ -27,19 +27,19 @@ Platform { exposes: [], packages: [], imports: [ - |L 4-4, C 14-27| Module( + @119-132 Module( ModuleName( "Task", ), [ - |L 4-4, C 21-25| ExposedName( + @126-130 ExposedName( "Task", ), ], ), ], provides: [ - |L 5-5, C 15-26| ExposedName( + @150-161 ExposedName( "mainForHost", ), ], @@ -54,15 +54,15 @@ Platform { effect_shortname: "fx", effect_type_name: "Effect", entries: [ - |L 8-8, C 12-32| SpaceBefore( + @208-228 SpaceBefore( TypedIdent { - ident: |L 8-8, C 12-19| "getLine", + ident: @208-215 "getLine", spaces_before_colon: [], - ann: |L 8-8, C 22-32| Apply( + ann: @218-228 Apply( "", "Effect", [ - |L 8-8, C 29-32| Apply( + @225-228 Apply( "", "Str", [], @@ -74,23 +74,23 @@ Platform { Newline, ], ), - |L 9-9, C 12-38| SpaceBefore( + @242-268 SpaceBefore( TypedIdent { - ident: |L 9-9, C 12-19| "putLine", + ident: @242-249 "putLine", spaces_before_colon: [], - ann: |L 9-9, C 29-38| Function( + ann: @259-268 Function( [ - |L 9-9, C 22-25| Apply( + @252-255 Apply( "", "Str", [], ), ], - |L 9-9, C 29-38| Apply( + @259-268 Apply( "", "Effect", [ - |L 9-9, C 36-38| Record { + @266-268 Record { fields: [], ext: None, }, @@ -102,29 +102,29 @@ Platform { Newline, ], ), - |L 10-10, C 12-48| SpaceBefore( + @282-318 SpaceBefore( SpaceAfter( TypedIdent { - ident: |L 10-10, C 12-24| "twoArguments", + ident: @282-294 "twoArguments", spaces_before_colon: [], - ann: |L 10-10, C 39-48| Function( + ann: @309-318 Function( [ - |L 10-10, C 27-30| Apply( + @297-300 Apply( "", "Int", [], ), - |L 10-10, C 32-35| Apply( + @302-305 Apply( "", "Int", [], ), ], - |L 10-10, C 39-48| Apply( + @309-318 Apply( "", "Effect", [ - |L 10-10, C 46-48| Record { + @316-318 Record { fields: [], ext: None, }, diff --git a/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast index 08f0ebe98f..08284e3121 100644 --- a/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast @@ -1,15 +1,15 @@ Defs( [ - |L 0-0, C 0-6| Body( - |L 0-0, C 0-4| Identifier( + @0-6 Body( + @0-4 Identifier( "iffy", ), - |L 0-0, C 5-6| Num( + @5-6 Num( "5", ), ), ], - |L 2-2, C 0-2| SpaceBefore( + @8-10 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast b/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast index c393e3defa..b776a10113 100644 --- a/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast @@ -1,13 +1,13 @@ Closure( [ - |L 0-0, C 1-11| MalformedIdent( + @1-11 MalformedIdent( "the_answer", Underscore( - 0:5, + @5, ), ), ], - |L 0-0, C 15-17| Num( + @15-17 Num( "42", ), ) diff --git a/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast b/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast index 038dab88da..526bf8a5cd 100644 --- a/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast @@ -1,12 +1,12 @@ When( - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 4-11| SpaceBefore( + @14-21 SpaceBefore( Malformed( "bar.and", ), @@ -15,14 +15,14 @@ When( ], ), ], - value: |L 1-1, C 15-16| Num( + value: @25-26 Num( "1", ), guard: None, }, WhenBranch { patterns: [ - |L 2-2, C 4-5| SpaceBefore( + @31-32 SpaceBefore( Underscore( "", ), @@ -31,7 +31,7 @@ When( ], ), ], - value: |L 2-2, C 9-10| Num( + value: @36-37 Num( "4", ), guard: None, diff --git a/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast b/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast index 65b4c3c94f..fa23b429e9 100644 --- a/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast @@ -1,12 +1,12 @@ When( - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 4-11| SpaceBefore( + @14-21 SpaceBefore( Malformed( "Foo.and", ), @@ -15,14 +15,14 @@ When( ], ), ], - value: |L 1-1, C 15-16| Num( + value: @25-26 Num( "1", ), guard: None, }, WhenBranch { patterns: [ - |L 2-2, C 4-5| SpaceBefore( + @31-32 SpaceBefore( Underscore( "", ), @@ -31,7 +31,7 @@ When( ], ), ], - value: |L 2-2, C 9-10| Num( + value: @36-37 Num( "4", ), guard: None, diff --git a/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast b/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast index ba70384b58..b3d4004d77 100644 --- a/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast @@ -1,12 +1,12 @@ App { header: AppHeader { - name: |L 0-0, C 4-14| PlainLine( + name: @4-14 PlainLine( "test-app", ), packages: [], imports: [], provides: [], - to: |L 0-0, C 30-38| NewPackage( + to: @30-38 NewPackage( PackageName( "./blah", ), diff --git a/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast b/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast index 2540766a37..1d8f6c4e68 100644 --- a/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast @@ -1,13 +1,13 @@ BinOps( [ ( - |L 0-0, C 0-3| Num( + @0-3 Num( "-12", ), - |L 0-0, C 3-4| Minus, + @3-4 Minus, ), ], - |L 0-0, C 4-5| Num( + @4-5 Num( "5", ), ) diff --git a/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast b/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast index eb8ed8b3ea..190e21ca40 100644 --- a/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast @@ -1,16 +1,16 @@ SpaceBefore( Defs( [ - |L 4-4, C 0-5| Body( - |L 4-4, C 0-1| Identifier( + @113-118 Body( + @113-114 Identifier( "x", ), - |L 4-4, C 4-5| Num( + @117-118 Num( "5", ), ), ], - |L 6-6, C 0-2| SpaceBefore( + @120-122 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast b/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast index 1a9e243989..11a0ded5c2 100644 --- a/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast +++ b/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast @@ -1,23 +1,23 @@ [ - |L 0-3, C 0-5| SpaceAfter( + @0-24 SpaceAfter( SpaceBefore( Body( - |L 0-0, C 0-4| Identifier( + @0-4 Identifier( "main", ), - |L 1-3, C 4-5| SpaceBefore( + @11-24 SpaceBefore( Defs( [ - |L 1-1, C 4-10| Body( - |L 1-1, C 4-5| Identifier( + @11-17 Body( + @11-12 Identifier( "i", ), - |L 1-1, C 8-10| Num( + @15-17 Num( "64", ), ), ], - |L 3-3, C 4-5| SpaceBefore( + @23-24 SpaceBefore( Var { module_name: "", ident: "i", diff --git a/compiler/parse/tests/snapshots/pass/multi_backpassing.expr.result-ast b/compiler/parse/tests/snapshots/pass/multi_backpassing.expr.result-ast index d077cef709..3db758a0e5 100644 --- a/compiler/parse/tests/snapshots/pass/multi_backpassing.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/multi_backpassing.expr.result-ast @@ -1,39 +1,39 @@ Backpassing( [ - |L 0-0, C 0-1| Identifier( + @0-1 Identifier( "x", ), - |L 0-0, C 3-4| Identifier( + @3-4 Identifier( "y", ), ], - |L 0-0, C 8-23| Apply( - |L 0-0, C 8-17| Var { + @8-23 Apply( + @8-17 Var { module_name: "List", ident: "map2", }, [ - |L 0-0, C 18-20| List( + @18-20 List( [], ), - |L 0-0, C 21-23| List( + @21-23 List( [], ), ], Space, ), - |L 2-2, C 0-5| SpaceBefore( + @25-30 SpaceBefore( BinOps( [ ( - |L 2-2, C 0-1| Var { + @25-26 Var { module_name: "", ident: "x", }, - |L 2-2, C 2-3| Plus, + @27-28 Plus, ), ], - |L 2-2, C 4-5| Var { + @29-30 Var { module_name: "", ident: "y", }, diff --git a/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast index c6e1c801ac..8946c66262 100644 --- a/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast @@ -1,10 +1,10 @@ Defs( [ - |L 0-1, C 0-6| Annotation( - |L 0-0, C 0-1| Identifier( + @0-10 Annotation( + @0-1 Identifier( "f", ), - |L 1-1, C 4-6| SpaceBefore( + @8-10 SpaceBefore( Record { fields: [], ext: None, @@ -15,7 +15,7 @@ Defs( ), ), ], - |L 3-3, C 0-2| SpaceBefore( + @12-14 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast index 8ba2bc2b26..8c30fd95c0 100644 --- a/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast @@ -1,10 +1,10 @@ Defs( [ - |L 0-1, C 0-6| Annotation( - |L 0-0, C 0-1| Identifier( + @0-19 Annotation( + @0-1 Identifier( "f", ), - |L 1-1, C 4-6| SpaceBefore( + @17-19 SpaceBefore( Record { fields: [], ext: None, @@ -17,7 +17,7 @@ Defs( ), ), ], - |L 3-3, C 0-2| SpaceBefore( + @21-23 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast index f5e1463092..c5582c91c6 100644 --- a/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast @@ -1,19 +1,19 @@ BinOps( [ ( - |L 0-0, C 0-2| Num( + @0-2 Num( "31", ), - |L 0-0, C 2-3| Star, + @2-3 Star, ), ( - |L 0-0, C 3-5| Num( + @3-5 Num( "42", ), - |L 0-0, C 5-6| Plus, + @5-6 Plus, ), ], - |L 0-0, C 6-9| Num( + @6-9 Num( "534", ), ) diff --git a/compiler/parse/tests/snapshots/pass/neg_inf_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/neg_inf_float.expr.result-ast index 617306a026..8ef03b6192 100644 --- a/compiler/parse/tests/snapshots/pass/neg_inf_float.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/neg_inf_float.expr.result-ast @@ -1,7 +1,7 @@ UnaryOp( - |L 0-0, C 1-4| Var { + @1-4 Var { module_name: "", ident: "inf", }, - |L 0-0, C 0-1| Negate, + @0-1 Negate, ) diff --git a/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast b/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast index 0c597f8d28..33722ca531 100644 --- a/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast +++ b/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast @@ -1,57 +1,57 @@ [ - |L 0-5, C 0-20| SpaceAfter( + @0-115 SpaceAfter( SpaceBefore( Body( - |L 0-0, C 0-4| Identifier( + @0-4 Identifier( "main", ), - |L 1-5, C 4-20| SpaceBefore( + @11-115 SpaceBefore( Defs( [ - |L 2-3, C 4-20| AnnotatedBody { - ann_pattern: |L 1-1, C 4-16| Identifier( + @43-93 AnnotatedBody { + ann_pattern: @11-23 Identifier( "wrappedNotEq", ), - ann_type: |L 1-1, C 27-31| Function( + ann_type: @34-38 Function( [ - |L 1-1, C 19-20| BoundVariable( + @26-27 BoundVariable( "a", ), - |L 1-1, C 22-23| BoundVariable( + @29-30 BoundVariable( "a", ), ], - |L 1-1, C 27-31| Apply( + @34-38 Apply( "", "Bool", [], ), ), comment: None, - body_pattern: |L 2-2, C 4-16| Identifier( + body_pattern: @43-55 Identifier( "wrappedNotEq", ), - body_expr: |L 2-3, C 19-20| Closure( + body_expr: @58-93 Closure( [ - |L 2-2, C 20-24| Identifier( + @59-63 Identifier( "num1", ), - |L 2-2, C 26-30| Identifier( + @65-69 Identifier( "num2", ), ], - |L 3-3, C 8-20| SpaceBefore( + @81-93 SpaceBefore( BinOps( [ ( - |L 3-3, C 8-12| Var { + @81-85 Var { module_name: "", ident: "num1", }, - |L 3-3, C 13-15| NotEquals, + @86-88 NotEquals, ), ], - |L 3-3, C 16-20| Var { + @89-93 Var { module_name: "", ident: "num2", }, @@ -63,17 +63,17 @@ ), }, ], - |L 5-5, C 4-20| SpaceBefore( + @99-115 SpaceBefore( Apply( - |L 5-5, C 4-16| Var { + @99-111 Var { module_name: "", ident: "wrappedNotEq", }, [ - |L 5-5, C 17-18| Num( + @112-113 Num( "2", ), - |L 5-5, C 19-20| Num( + @114-115 Num( "3", ), ], diff --git a/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast b/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast index 9a2e97e67c..c4542c48eb 100644 --- a/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast @@ -1,11 +1,11 @@ If( [ ( - |L 0-0, C 3-5| Var { + @3-5 Var { module_name: "", ident: "t1", }, - |L 1-1, C 2-3| SpaceBefore( + @13-14 SpaceBefore( SpaceAfter( Num( "1", @@ -20,11 +20,11 @@ If( ), ), ( - |L 2-2, C 8-10| Var { + @23-25 Var { module_name: "", ident: "t2", }, - |L 3-3, C 2-3| SpaceBefore( + @33-34 SpaceBefore( SpaceAfter( Num( "2", @@ -39,7 +39,7 @@ If( ), ), ], - |L 5-5, C 2-3| SpaceBefore( + @42-43 SpaceBefore( Num( "3", ), diff --git a/compiler/parse/tests/snapshots/pass/nested_module.header.result-ast b/compiler/parse/tests/snapshots/pass/nested_module.header.result-ast index 532089580b..50f7b410ed 100644 --- a/compiler/parse/tests/snapshots/pass/nested_module.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/nested_module.header.result-ast @@ -1,6 +1,6 @@ Interface { header: InterfaceHeader { - name: |L 0-0, C 10-21| ModuleName( + name: @10-21 ModuleName( "Foo.Bar.Baz", ), exposes: [], diff --git a/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast index 70cc7456f6..3cdf6755b6 100644 --- a/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast @@ -1,10 +1,10 @@ Defs( [ - |L 0-1, C 0-5| Body( - |L 0-0, C 0-1| Identifier( + @0-9 Body( + @0-1 Identifier( "x", ), - |L 1-1, C 4-5| SpaceBefore( + @8-9 SpaceBefore( Num( "5", ), @@ -14,7 +14,7 @@ Defs( ), ), ], - |L 3-3, C 0-2| SpaceBefore( + @11-13 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast index 977121c8ea..ee6690e728 100644 --- a/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast @@ -1,13 +1,13 @@ BinOps( [ ( - |L 0-0, C 0-1| Num( + @0-1 Num( "3", ), - |L 0-0, C 3-4| Star, + @3-4 Star, ), ], - |L 1-1, C 2-3| SpaceBefore( + @7-8 SpaceBefore( Num( "4", ), diff --git a/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast index f6ac47b32a..444199db82 100644 --- a/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast @@ -1,13 +1,13 @@ BinOps( [ ( - |L 0-0, C 0-1| Num( + @0-1 Num( "3", ), - |L 0-0, C 3-4| Minus, + @3-4 Minus, ), ], - |L 1-1, C 2-3| SpaceBefore( + @7-8 SpaceBefore( Num( "4", ), diff --git a/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast index 78bbc9865e..cbc30d8ba0 100644 --- a/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast @@ -1,13 +1,13 @@ Defs( [ - |L 0-1, C 0-7| Body( - |L 0-0, C 0-1| Identifier( + @0-13 Body( + @0-1 Identifier( "x", ), - |L 0-1, C 4-7| BinOps( + @4-13 BinOps( [ ( - |L 0-0, C 4-5| SpaceAfter( + @4-5 SpaceAfter( Num( "1", ), @@ -15,16 +15,16 @@ Defs( Newline, ], ), - |L 1-1, C 4-5| LessThan, + @10-11 LessThan, ), ], - |L 1-1, C 6-7| Num( + @12-13 Num( "2", ), ), ), ], - |L 3-3, C 0-2| SpaceBefore( + @15-17 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast index 73c9b6b40d..b5c5538c1f 100644 --- a/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast @@ -1,7 +1,7 @@ BinOps( [ ( - |L 0-0, C 0-1| SpaceAfter( + @0-1 SpaceAfter( Num( "3", ), @@ -9,10 +9,10 @@ BinOps( Newline, ], ), - |L 1-1, C 0-1| Plus, + @4-5 Plus, ), ], - |L 1-1, C 2-3| Num( + @6-7 Num( "4", ), ) diff --git a/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast index 4b8d5725c4..4ce4377f5e 100644 --- a/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast @@ -1,7 +1,7 @@ BinOps( [ ( - |L 0-0, C 0-1| SpaceAfter( + @0-1 SpaceAfter( Num( "3", ), @@ -9,10 +9,10 @@ BinOps( Newline, ], ), - |L 1-1, C 0-1| Minus, + @4-5 Minus, ), ], - |L 1-1, C 2-3| Num( + @6-7 Num( "4", ), ) diff --git a/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast index 15f065505b..77f172790b 100644 --- a/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast @@ -1,6 +1,6 @@ List( [ - |L 1-1, C 0-1| SpaceBefore( + @2-3 SpaceBefore( SpaceAfter( Num( "1", diff --git a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast index 318ec551ac..a36b138082 100644 --- a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast @@ -1,19 +1,19 @@ Platform { header: PlatformHeader { - name: |L 0-0, C 9-21| PackageName( + name: @9-21 PackageName( "foo/barbaz", ), requires: PlatformRequires { rigids: [ - |L 1-1, C 14-26| PlatformRigid { + @36-48 PlatformRigid { rigid: "model", alias: "Model", }, ], - signature: |L 1-1, C 30-39| TypedIdent { - ident: |L 1-1, C 30-34| "main", + signature: @52-61 TypedIdent { + ident: @52-56 "main", spaces_before_colon: [], - ann: |L 1-1, C 37-39| Record { + ann: @59-61 Record { fields: [], ext: None, }, @@ -21,17 +21,17 @@ Platform { }, exposes: [], packages: [ - |L 3-3, C 15-27| PackageEntry { + @94-106 PackageEntry { shorthand: "foo", spaces_after_shorthand: [], - package_name: |L 3-3, C 20-27| PackageName( + package_name: @99-106 PackageName( "./foo", ), }, ], imports: [], provides: [ - |L 5-5, C 15-26| ExposedName( + @139-150 ExposedName( "mainForHost", ), ], diff --git a/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast b/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast index 0a5c470bc0..e9a5247b17 100644 --- a/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast @@ -1,16 +1,16 @@ SpaceBefore( Defs( [ - |L 4-4, C 0-5| Body( - |L 4-4, C 0-1| Identifier( + @46-51 Body( + @46-47 Identifier( "x", ), - |L 4-4, C 4-5| Num( + @50-51 Num( "5", ), ), ], - |L 6-6, C 0-2| SpaceBefore( + @53-55 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/one_backpassing.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_backpassing.expr.result-ast index 257e698479..405df4abf8 100644 --- a/compiler/parse/tests/snapshots/pass/one_backpassing.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_backpassing.expr.result-ast @@ -1,24 +1,24 @@ SpaceBefore( Backpassing( [ - |L 1-1, C 0-1| Identifier( + @18-19 Identifier( "x", ), ], - |L 1-1, C 5-14| ParensAround( + @23-32 ParensAround( Closure( [ - |L 1-1, C 7-8| Identifier( + @25-26 Identifier( "y", ), ], - |L 1-1, C 12-13| Var { + @30-31 Var { module_name: "", ident: "y", }, ), ), - |L 3-3, C 0-1| SpaceBefore( + @34-35 SpaceBefore( Var { module_name: "", ident: "x", diff --git a/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast index daa047957c..7caa61c752 100644 --- a/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast @@ -1,16 +1,16 @@ SpaceBefore( Defs( [ - |L 1-1, C 0-3| Body( - |L 1-1, C 0-1| Identifier( + @18-21 Body( + @18-19 Identifier( "x", ), - |L 1-1, C 2-3| Num( + @20-21 Num( "5", ), ), ], - |L 3-3, C 0-2| SpaceBefore( + @23-25 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast index 77fd849f59..4dc9b56640 100644 --- a/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast @@ -1,13 +1,13 @@ BinOps( [ ( - |L 0-0, C 0-1| Num( + @0-1 Num( "1", ), - |L 0-0, C 1-2| Minus, + @1-2 Minus, ), ], - |L 0-0, C 2-3| Num( + @2-3 Num( "2", ), ) diff --git a/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast index 7e45139380..e6bcba4260 100644 --- a/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast @@ -1,13 +1,13 @@ BinOps( [ ( - |L 0-0, C 0-1| Num( + @0-1 Num( "1", ), - |L 0-0, C 1-2| Plus, + @1-2 Plus, ), ], - |L 0-0, C 2-3| Num( + @2-3 Num( "2", ), ) diff --git a/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast index 001962d745..a1fab8b814 100644 --- a/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast @@ -1,16 +1,16 @@ SpaceBefore( Defs( [ - |L 1-1, C 0-5| Body( - |L 1-1, C 0-1| Identifier( + @18-23 Body( + @18-19 Identifier( "x", ), - |L 1-1, C 4-5| Num( + @22-23 Num( "5", ), ), ], - |L 3-3, C 0-2| SpaceBefore( + @25-27 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast index f35a0b6c44..2905c1e745 100644 --- a/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast @@ -1,7 +1,7 @@ BinOps( [ ( - |L 0-0, C 0-1| SpaceAfter( + @0-1 SpaceAfter( Num( "3", ), @@ -9,10 +9,10 @@ BinOps( Newline, ], ), - |L 1-1, C 0-1| Plus, + @4-5 Plus, ), ], - |L 3-3, C 2-3| SpaceBefore( + @10-11 SpaceBefore( Num( "4", ), diff --git a/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast index 2548a5221e..859505b0f3 100644 --- a/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast @@ -1,6 +1,6 @@ List( [ - |L 0-0, C 1-2| Num( + @1-2 Num( "1", ), ], diff --git a/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast b/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast index ea1031f76d..c8000965eb 100644 --- a/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast @@ -1,12 +1,12 @@ Apply( - |L 0-0, C 1-5| ParensAround( + @1-5 ParensAround( Var { module_name: "", ident: "whee", }, ), [ - |L 0-0, C 7-8| Num( + @7-8 Num( "1", ), ], diff --git a/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast b/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast index f0006fe0da..3deb39c07f 100644 --- a/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast @@ -1,32 +1,32 @@ Defs( [ - |L 0-0, C 0-26| Alias { + @0-26 Alias { header: AliasHeader { - name: |L 0-0, C 0-4| "Blah", + name: @0-4 "Blah", vars: [ - |L 0-0, C 5-6| Identifier( + @5-6 Identifier( "a", ), - |L 0-0, C 7-8| Identifier( + @7-8 Identifier( "b", ), ], }, - ann: |L 0-0, C 11-26| Apply( + ann: @11-26 Apply( "Foo.Bar", "Baz", [ - |L 0-0, C 23-24| BoundVariable( + @23-24 BoundVariable( "x", ), - |L 0-0, C 25-26| BoundVariable( + @25-26 BoundVariable( "y", ), ], ), }, ], - |L 2-2, C 0-2| SpaceBefore( + @28-30 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast b/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast index edefb5bf4f..f99bd7c8fb 100644 --- a/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast @@ -1,30 +1,30 @@ Defs( [ - |L 0-0, C 0-33| Annotation( - |L 0-0, C 0-3| Identifier( + @0-33 Annotation( + @0-3 Identifier( "foo", ), - |L 0-0, C 6-33| As( - |L 0-0, C 6-21| Apply( + @6-33 As( + @6-21 Apply( "Foo.Bar", "Baz", [ - |L 0-0, C 18-19| BoundVariable( + @18-19 BoundVariable( "x", ), - |L 0-0, C 20-21| BoundVariable( + @20-21 BoundVariable( "y", ), ], ), [], AliasHeader { - name: |L 0-0, C 25-29| "Blah", + name: @25-29 "Blah", vars: [ - |L 0-0, C 30-31| Identifier( + @30-31 Identifier( "a", ), - |L 0-0, C 32-33| Identifier( + @32-33 Identifier( "b", ), ], @@ -32,7 +32,7 @@ Defs( ), ), ], - |L 2-2, C 0-2| SpaceBefore( + @35-37 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast index 962f52971a..d45bbdf656 100644 --- a/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast @@ -1,16 +1,16 @@ When( - |L 0-0, C 5-22| Apply( - |L 0-0, C 5-11| GlobalTag( + @5-22 Apply( + @5-11 GlobalTag( "Delmin", ), [ - |L 0-0, C 13-19| ParensAround( + @13-19 ParensAround( Apply( - |L 0-0, C 13-16| GlobalTag( + @13-16 GlobalTag( "Del", ), [ - |L 0-0, C 17-19| Var { + @17-19 Var { module_name: "", ident: "rx", }, @@ -18,7 +18,7 @@ When( Space, ), ), - |L 0-0, C 21-22| Num( + @21-22 Num( "0", ), ], @@ -27,23 +27,23 @@ When( [ WhenBranch { patterns: [ - |L 1-1, C 4-22| SpaceBefore( + @30-48 SpaceBefore( Apply( - |L 1-1, C 4-10| GlobalTag( + @30-36 GlobalTag( "Delmin", ), [ - |L 1-1, C 12-18| Apply( - |L 1-1, C 12-15| GlobalTag( + @38-44 Apply( + @38-41 GlobalTag( "Del", ), [ - |L 1-1, C 16-18| Identifier( + @42-44 Identifier( "ry", ), ], ), - |L 1-1, C 21-22| Underscore( + @47-48 Underscore( "", ), ], @@ -53,21 +53,21 @@ When( ], ), ], - value: |L 1-1, C 26-47| Apply( - |L 1-1, C 26-30| GlobalTag( + value: @52-73 Apply( + @52-56 GlobalTag( "Node", ), [ - |L 1-1, C 31-36| GlobalTag( + @57-62 GlobalTag( "Black", ), - |L 1-1, C 37-38| Num( + @63-64 Num( "0", ), - |L 1-1, C 39-44| GlobalTag( + @65-70 GlobalTag( "False", ), - |L 1-1, C 45-47| Var { + @71-73 Var { module_name: "", ident: "ry", }, diff --git a/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.result-ast index b66c7b77c0..b770fe6077 100644 --- a/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.result-ast @@ -1,6 +1,6 @@ MalformedIdent( "@One.Two.Whee", BadPrivateTag( - 0:4, + @4, ), ) diff --git a/compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.result-ast index 531d2fb377..44c9a08d3d 100644 --- a/compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.result-ast @@ -1,6 +1,6 @@ MalformedIdent( "One.Two.Whee", QualifiedTag( - 0:12, + @12, ), ) diff --git a/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast index db061e68f7..498e786bb0 100644 --- a/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast @@ -1,27 +1,27 @@ SpaceBefore( Defs( [ - |L 1-1, C 0-12| Body( - |L 1-1, C 0-8| RecordDestructure( + @18-30 Body( + @18-26 RecordDestructure( [ - |L 1-1, C 2-3| Identifier( + @20-21 Identifier( "x", ), - |L 1-1, C 5-7| Identifier( + @23-25 Identifier( "y", ), ], ), - |L 1-1, C 11-12| Num( + @29-30 Num( "5", ), ), - |L 2-2, C 0-5| SpaceBefore( + @31-36 SpaceBefore( Body( - |L 2-2, C 0-1| Identifier( + @31-32 Identifier( "y", ), - |L 2-2, C 4-5| Num( + @35-36 Num( "6", ), ), @@ -30,7 +30,7 @@ SpaceBefore( ], ), ], - |L 4-4, C 0-2| SpaceBefore( + @38-40 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast index 5c2599233c..6ae895c6e0 100644 --- a/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast @@ -1,21 +1,21 @@ Defs( [ - |L 0-6, C 0-5| Annotation( - |L 0-0, C 0-1| Identifier( + @0-122 Annotation( + @0-1 Identifier( "f", ), - |L 1-6, C 4-5| SpaceBefore( + @8-122 SpaceBefore( Record { fields: [ - |L 2-2, C 8-28| SpaceBefore( + @18-38 SpaceBefore( RequiredValue( - |L 2-2, C 8-15| "getLine", + @18-25 "getLine", [], - |L 2-2, C 18-28| Apply( + @28-38 Apply( "", "Effect", [ - |L 2-2, C 25-28| Apply( + @35-38 Apply( "", "Str", [], @@ -27,23 +27,23 @@ Defs( Newline, ], ), - |L 3-3, C 8-35| SpaceBefore( + @48-75 SpaceBefore( RequiredValue( - |L 3-3, C 8-15| "putLine", + @48-55 "putLine", [], - |L 3-3, C 25-35| Function( + @65-75 Function( [ - |L 3-3, C 18-21| Apply( + @58-61 Apply( "", "Str", [], ), ], - |L 3-3, C 25-35| Apply( + @65-75 Apply( "", "Effect", [ - |L 3-3, C 32-35| Apply( + @72-75 Apply( "", "Int", [], @@ -56,11 +56,11 @@ Defs( Newline, ], ), - |L 4-4, C 8-17| SpaceBefore( + @85-94 SpaceBefore( RequiredValue( - |L 4-4, C 8-12| "text", + @85-89 "text", [], - |L 4-4, C 14-17| Apply( + @91-94 Apply( "", "Str", [], @@ -70,16 +70,16 @@ Defs( Newline, ], ), - |L 5-5, C 8-20| SpaceBefore( + @104-116 SpaceBefore( SpaceAfter( RequiredValue( - |L 5-5, C 8-13| "value", + @104-109 "value", [], - |L 5-5, C 15-20| Apply( + @111-116 Apply( "", "Int", [ - |L 5-5, C 19-20| Wildcard, + @115-116 Wildcard, ], ), ), @@ -100,7 +100,7 @@ Defs( ), ), ], - |L 8-8, C 0-2| SpaceBefore( + @124-126 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast index 7ee1e6b7b0..8666e0151f 100644 --- a/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast @@ -1,20 +1,20 @@ RecordUpdate { - update: |L 0-0, C 2-13| Var { + update: @2-13 Var { module_name: "Foo.Bar", ident: "baz", }, fields: [ - |L 0-0, C 16-20| RequiredValue( - |L 0-0, C 16-17| "x", + @16-20 RequiredValue( + @16-17 "x", [], - |L 0-0, C 19-20| Num( + @19-20 Num( "5", ), ), - |L 0-0, C 22-26| RequiredValue( - |L 0-0, C 22-23| "y", + @22-26 RequiredValue( + @22-23 "y", [], - |L 0-0, C 25-26| Num( + @25-26 Num( "0", ), ), diff --git a/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast index b4f4133f3b..1776ed2ff8 100644 --- a/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast @@ -1,28 +1,28 @@ Record( [ - |L 0-0, C 1-26| RequiredValue( - |L 0-0, C 1-2| "x", + @1-26 RequiredValue( + @1-2 "x", [], - |L 0-0, C 5-26| If( + @5-26 If( [ ( - |L 0-0, C 8-12| GlobalTag( + @8-12 GlobalTag( "True", ), - |L 0-0, C 18-19| Num( + @18-19 Num( "1", ), ), ], - |L 0-0, C 25-26| Num( + @25-26 Num( "2", ), ), ), - |L 0-0, C 28-32| RequiredValue( - |L 0-0, C 28-29| "y", + @28-32 RequiredValue( + @28-29 "y", [], - |L 0-0, C 31-32| Num( + @31-32 Num( "3", ), ), diff --git a/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast index ca8f338bbd..1c5fbbaa84 100644 --- a/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast @@ -1,10 +1,10 @@ Closure( [ - |L 0-0, C 1-2| Identifier( + @1-2 Identifier( "a", ), ], - |L 0-0, C 6-8| Num( + @6-8 Num( "42", ), ) diff --git a/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast index c3657bfca5..d04c130ed2 100644 --- a/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast @@ -1,10 +1,10 @@ Closure( [ - |L 0-0, C 1-2| Underscore( + @1-2 Underscore( "", ), ], - |L 0-0, C 6-8| Num( + @6-8 Num( "42", ), ) diff --git a/compiler/parse/tests/snapshots/pass/space_only_after_minus.expr.result-ast b/compiler/parse/tests/snapshots/pass/space_only_after_minus.expr.result-ast index 7ba8b91f84..45c8618b1c 100644 --- a/compiler/parse/tests/snapshots/pass/space_only_after_minus.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/space_only_after_minus.expr.result-ast @@ -1,14 +1,14 @@ BinOps( [ ( - |L 0-0, C 0-1| Var { + @0-1 Var { module_name: "", ident: "x", }, - |L 0-0, C 1-2| Minus, + @1-2 Minus, ), ], - |L 0-0, C 3-4| Var { + @3-4 Var { module_name: "", ident: "y", }, diff --git a/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast index 38acc0b739..231c1c41b1 100644 --- a/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast @@ -1,6 +1,6 @@ List( [ - |L 0-0, C 2-3| Num( + @2-3 Num( "1", ), ], diff --git a/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast b/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast index df2876e5a3..e03334f801 100644 --- a/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast +++ b/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast @@ -1,10 +1,10 @@ [ - |L 1-1, C 0-7| SpaceBefore( + @12-19 SpaceBefore( Body( - |L 1-1, C 0-3| Identifier( + @12-15 Identifier( "foo", ), - |L 1-1, C 6-7| Num( + @18-19 Num( "1", ), ), @@ -14,12 +14,12 @@ ), ], ), - |L 4-4, C 0-10| SpaceBefore( + @33-43 SpaceBefore( Body( - |L 4-4, C 0-3| Identifier( + @33-36 Identifier( "bar", ), - |L 4-4, C 6-10| Str( + @39-43 Str( PlainLine( "hi", ), @@ -33,13 +33,13 @@ ), ], ), - |L 5-5, C 0-13| SpaceAfter( + @44-57 SpaceAfter( SpaceBefore( Body( - |L 5-5, C 0-3| Identifier( + @44-47 Identifier( "baz", ), - |L 5-5, C 6-13| Str( + @50-57 Str( PlainLine( "stuff", ), diff --git a/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast index a0930ee9cf..2e4b2c87d2 100644 --- a/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast @@ -1,14 +1,14 @@ BinOps( [ ( - |L 0-0, C 0-1| Var { + @0-1 Var { module_name: "", ident: "x", }, - |L 0-0, C 2-3| Minus, + @2-3 Minus, ), ], - |L 0-0, C 4-5| Num( + @4-5 Num( "2", ), ) diff --git a/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast index f68168b626..849e5609fd 100644 --- a/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast @@ -1,13 +1,13 @@ BinOps( [ ( - |L 0-0, C 0-1| Num( + @0-1 Num( "1", ), - |L 0-0, C 3-4| Minus, + @3-4 Minus, ), ], - |L 0-0, C 7-8| Num( + @7-8 Num( "2", ), ) diff --git a/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast b/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast index 921fe3c6ad..93bc481021 100644 --- a/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast @@ -1,10 +1,10 @@ Closure( [ - |L 0-0, C 1-6| GlobalTag( + @1-6 GlobalTag( "Thing", ), ], - |L 0-0, C 10-12| Num( + @10-12 Num( "42", ), ) diff --git a/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast b/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast index 431a12166e..6b4d587eb8 100644 --- a/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast @@ -1,13 +1,13 @@ BinOps( [ ( - |L 0-0, C 0-2| Num( + @0-2 Num( "10", ), - |L 0-0, C 2-3| Star, + @2-3 Star, ), ], - |L 0-0, C 3-5| Num( + @3-5 Num( "11", ), ) diff --git a/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast index e40979f575..72f726931a 100644 --- a/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast @@ -1,16 +1,16 @@ Closure( [ - |L 0-0, C 1-2| Identifier( + @1-2 Identifier( "a", ), - |L 0-0, C 4-5| Identifier( + @4-5 Identifier( "b", ), - |L 0-0, C 7-8| Identifier( + @7-8 Identifier( "c", ), ], - |L 0-0, C 12-14| Num( + @12-14 Num( "42", ), ) diff --git a/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast index cba1cb7d83..84d9721826 100644 --- a/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast @@ -1,13 +1,13 @@ Closure( [ - |L 0-0, C 1-2| Identifier( + @1-2 Identifier( "a", ), - |L 0-0, C 4-5| Identifier( + @4-5 Identifier( "b", ), ], - |L 0-0, C 9-11| Num( + @9-11 Num( "42", ), ) diff --git a/compiler/parse/tests/snapshots/pass/two_backpassing.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_backpassing.expr.result-ast index d1fefd06d4..0bfd1dacf9 100644 --- a/compiler/parse/tests/snapshots/pass/two_backpassing.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/two_backpassing.expr.result-ast @@ -1,34 +1,34 @@ SpaceBefore( Backpassing( [ - |L 1-1, C 0-1| Identifier( + @18-19 Identifier( "x", ), ], - |L 1-1, C 5-14| ParensAround( + @23-32 ParensAround( Closure( [ - |L 1-1, C 7-8| Identifier( + @25-26 Identifier( "y", ), ], - |L 1-1, C 12-13| Var { + @30-31 Var { module_name: "", ident: "y", }, ), ), - |L 2-4, C 0-1| SpaceBefore( + @33-44 SpaceBefore( Backpassing( [ - |L 2-2, C 0-1| Identifier( + @33-34 Identifier( "z", ), ], - |L 2-2, C 5-7| Record( + @38-40 Record( [], ), - |L 4-4, C 0-1| SpaceBefore( + @43-44 SpaceBefore( Var { module_name: "", ident: "x", diff --git a/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast index bfda25dc05..7cab905c51 100644 --- a/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast @@ -1,12 +1,12 @@ When( - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 1-3| SpaceBefore( + @11-13 SpaceBefore( StrLiteral( PlainLine( "", @@ -17,14 +17,14 @@ When( ], ), ], - value: |L 1-1, C 7-8| Num( + value: @17-18 Num( "1", ), guard: None, }, WhenBranch { patterns: [ - |L 2-2, C 1-7| SpaceBefore( + @20-26 SpaceBefore( StrLiteral( PlainLine( "mise", @@ -35,7 +35,7 @@ When( ], ), ], - value: |L 2-2, C 11-12| Num( + value: @30-31 Num( "2", ), guard: None, diff --git a/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast index 4b7817ea4c..7ae7c0abd2 100644 --- a/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast @@ -1,20 +1,20 @@ SpaceBefore( Defs( [ - |L 1-1, C 0-5| Body( - |L 1-1, C 0-1| Identifier( + @18-23 Body( + @18-19 Identifier( "x", ), - |L 1-1, C 4-5| Num( + @22-23 Num( "5", ), ), - |L 2-2, C 0-5| SpaceBefore( + @24-29 SpaceBefore( Body( - |L 2-2, C 0-1| Identifier( + @24-25 Identifier( "y", ), - |L 2-2, C 4-5| Num( + @28-29 Num( "6", ), ), @@ -23,7 +23,7 @@ SpaceBefore( ], ), ], - |L 4-4, C 0-2| SpaceBefore( + @31-33 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast b/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast index 3408a90669..ca8a45f681 100644 --- a/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast @@ -1,33 +1,33 @@ Defs( [ - |L 0-0, C 0-30| Annotation( - |L 0-0, C 0-7| Identifier( + @0-30 Annotation( + @0-7 Identifier( "doStuff", ), - |L 0-0, C 20-30| Function( + @20-30 Function( [ - |L 0-0, C 10-16| Apply( + @10-16 Apply( "", "UserId", [], ), ], - |L 0-0, C 20-30| Apply( + @20-30 Apply( "", "Task", [ - |L 0-0, C 25-28| Apply( + @25-28 Apply( "", "Str", [], ), - |L 0-0, C 29-30| Inferred, + @29-30 Inferred, ], ), ), ), ], - |L 1-1, C 0-2| SpaceBefore( + @31-33 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/unary_negation.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_negation.expr.result-ast index 0b69481d27..ded94596fe 100644 --- a/compiler/parse/tests/snapshots/pass/unary_negation.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_negation.expr.result-ast @@ -1,7 +1,7 @@ UnaryOp( - |L 0-0, C 1-4| Var { + @1-4 Var { module_name: "", ident: "foo", }, - |L 0-0, C 0-1| Negate, + @0-1 Negate, ) diff --git a/compiler/parse/tests/snapshots/pass/unary_negation_access.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_negation_access.expr.result-ast index bac391b5bb..8a32af8c8a 100644 --- a/compiler/parse/tests/snapshots/pass/unary_negation_access.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_negation_access.expr.result-ast @@ -1,10 +1,10 @@ UnaryOp( - |L 0-0, C 1-11| Access( + @1-11 Access( Var { module_name: "", ident: "rec1", }, "field", ), - |L 0-0, C 0-1| Negate, + @0-1 Negate, ) diff --git a/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast index b584acfb8b..f42c07d6fd 100644 --- a/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast @@ -1,18 +1,18 @@ Apply( - |L 0-0, C 0-4| Var { + @0-4 Var { module_name: "", ident: "whee", }, [ - |L 0-0, C 6-8| Num( + @6-8 Num( "12", ), - |L 0-0, C 9-13| UnaryOp( - |L 0-0, C 10-13| Var { + @9-13 UnaryOp( + @10-13 Var { module_name: "", ident: "foo", }, - |L 0-0, C 9-10| Negate, + @9-10 Negate, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast index 995295897e..acb507d2b2 100644 --- a/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast @@ -1,15 +1,15 @@ UnaryOp( - |L 0-0, C 2-14| ParensAround( + @2-14 ParensAround( Apply( - |L 0-0, C 2-6| Var { + @2-6 Var { module_name: "", ident: "whee", }, [ - |L 0-0, C 8-10| Num( + @8-10 Num( "12", ), - |L 0-0, C 11-14| Var { + @11-14 Var { module_name: "", ident: "foo", }, @@ -17,5 +17,5 @@ UnaryOp( Space, ), ), - |L 0-0, C 0-1| Negate, + @0-1 Negate, ) diff --git a/compiler/parse/tests/snapshots/pass/unary_not.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_not.expr.result-ast index 1264450798..87170ac08a 100644 --- a/compiler/parse/tests/snapshots/pass/unary_not.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_not.expr.result-ast @@ -1,7 +1,7 @@ UnaryOp( - |L 0-0, C 1-5| Var { + @1-5 Var { module_name: "", ident: "blah", }, - |L 0-0, C 0-1| Not, + @0-1 Not, ) diff --git a/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast index 4d744df18b..b1f93ec376 100644 --- a/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast @@ -1,15 +1,15 @@ UnaryOp( - |L 0-0, C 2-14| ParensAround( + @2-14 ParensAround( Apply( - |L 0-0, C 2-6| Var { + @2-6 Var { module_name: "", ident: "whee", }, [ - |L 0-0, C 8-10| Num( + @8-10 Num( "12", ), - |L 0-0, C 11-14| Var { + @11-14 Var { module_name: "", ident: "foo", }, @@ -17,5 +17,5 @@ UnaryOp( Space, ), ), - |L 0-0, C 0-1| Not, + @0-1 Not, ) diff --git a/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast b/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast index afb4402f55..744f023e4f 100644 --- a/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast @@ -1,24 +1,24 @@ SpaceBefore( Backpassing( [ - |L 1-1, C 0-1| Underscore( + @18-19 Underscore( "", ), ], - |L 1-1, C 5-14| ParensAround( + @23-32 ParensAround( Closure( [ - |L 1-1, C 7-8| Identifier( + @25-26 Identifier( "y", ), ], - |L 1-1, C 12-13| Var { + @30-31 Var { module_name: "", ident: "y", }, ), ), - |L 3-3, C 0-1| SpaceBefore( + @34-35 SpaceBefore( Num( "4", ), diff --git a/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast b/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast index f3eedcea65..499046f993 100644 --- a/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast @@ -1,14 +1,14 @@ BinOps( [ ( - |L 0-0, C 0-1| Var { + @0-1 Var { module_name: "", ident: "x", }, - |L 0-0, C 1-2| Minus, + @1-2 Minus, ), ], - |L 0-0, C 2-3| Num( + @2-3 Num( "2", ), ) diff --git a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast index 83f3424244..b641b19efc 100644 --- a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast @@ -1,12 +1,12 @@ When( - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 4-5| SpaceBefore( + @14-15 SpaceBefore( Underscore( "", ), @@ -15,7 +15,7 @@ When( ], ), ], - value: |L 2-2, C 8-9| SpaceBefore( + value: @28-29 SpaceBefore( Num( "1", ), @@ -27,7 +27,7 @@ When( }, WhenBranch { patterns: [ - |L 4-4, C 4-5| SpaceBefore( + @35-36 SpaceBefore( Underscore( "", ), @@ -37,7 +37,7 @@ When( ], ), ], - value: |L 5-5, C 8-9| SpaceBefore( + value: @49-50 SpaceBefore( Num( "2", ), @@ -49,7 +49,7 @@ When( }, WhenBranch { patterns: [ - |L 7-7, C 4-6| SpaceBefore( + @56-58 SpaceBefore( GlobalTag( "Ok", ), @@ -59,7 +59,7 @@ When( ], ), ], - value: |L 8-8, C 8-9| SpaceBefore( + value: @71-72 SpaceBefore( Num( "3", ), diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast index 324e03f7ce..9192d1f1b6 100644 --- a/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast @@ -1,13 +1,13 @@ ParensAround( When( - |L 0-0, C 6-7| Var { + @6-7 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 4-6| SpaceBefore( + @15-17 SpaceBefore( GlobalTag( "Ok", ), @@ -16,7 +16,7 @@ ParensAround( ], ), ], - value: |L 2-2, C 8-9| SpaceBefore( + value: @29-30 SpaceBefore( Num( "3", ), diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast index b7fcacfb2d..6f74fb5e4d 100644 --- a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast @@ -1,14 +1,14 @@ ParensAround( SpaceAfter( When( - |L 0-0, C 6-7| Var { + @6-7 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 4-6| SpaceBefore( + @15-17 SpaceBefore( GlobalTag( "Ok", ), @@ -17,7 +17,7 @@ ParensAround( ], ), ], - value: |L 1-1, C 10-11| Num( + value: @21-22 Num( "3", ), guard: None, diff --git a/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast index 46a12593ae..62dd86481e 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast @@ -1,12 +1,12 @@ When( - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 1-7| SpaceBefore( + @11-17 SpaceBefore( StrLiteral( PlainLine( "blah", @@ -16,20 +16,20 @@ When( Newline, ], ), - |L 1-1, C 10-16| StrLiteral( + @20-26 StrLiteral( PlainLine( "blop", ), ), ], - value: |L 1-1, C 20-21| Num( + value: @30-31 Num( "1", ), guard: None, }, WhenBranch { patterns: [ - |L 2-2, C 1-6| SpaceBefore( + @33-38 SpaceBefore( StrLiteral( PlainLine( "foo", @@ -39,7 +39,7 @@ When( Newline, ], ), - |L 3-3, C 2-7| SpaceBefore( + @43-48 SpaceBefore( StrLiteral( PlainLine( "bar", @@ -50,7 +50,7 @@ When( ], ), ], - value: |L 3-3, C 11-12| Num( + value: @52-53 Num( "2", ), guard: None, diff --git a/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast index 6438516e8d..c01c55e1f9 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast @@ -1,12 +1,12 @@ When( - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 4-5| SpaceBefore( + @14-15 SpaceBefore( NumLiteral( "1", ), @@ -15,13 +15,13 @@ When( ], ), ], - value: |L 1-2, C 9-6| Apply( - |L 1-1, C 9-16| Var { + value: @19-33 Apply( + @19-26 Var { module_name: "Num", ident: "neg", }, [ - |L 2-2, C 5-6| SpaceBefore( + @32-33 SpaceBefore( Num( "2", ), @@ -36,7 +36,7 @@ When( }, WhenBranch { patterns: [ - |L 3-3, C 4-5| SpaceBefore( + @39-40 SpaceBefore( Underscore( "", ), @@ -45,7 +45,7 @@ When( ], ), ], - value: |L 3-3, C 9-10| Num( + value: @44-45 Num( "4", ), guard: None, diff --git a/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast index b1c09c7e0c..8c6a5e4e61 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast @@ -1,12 +1,12 @@ When( - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 1-2| SpaceBefore( + @11-12 SpaceBefore( NumLiteral( "1", ), @@ -15,14 +15,14 @@ When( ], ), ], - value: |L 1-1, C 6-7| Num( + value: @16-17 Num( "2", ), guard: None, }, WhenBranch { patterns: [ - |L 2-2, C 1-3| SpaceBefore( + @19-21 SpaceBefore( NumLiteral( "-3", ), @@ -31,7 +31,7 @@ When( ], ), ], - value: |L 2-2, C 7-8| Num( + value: @25-26 Num( "4", ), guard: None, diff --git a/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast index e1ed86ded5..8866aef903 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast @@ -1,12 +1,12 @@ When( - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 1-2| SpaceBefore( + @11-12 SpaceBefore( NumLiteral( "1", ), @@ -15,14 +15,14 @@ When( ], ), ], - value: |L 1-1, C 6-7| Num( + value: @16-17 Num( "2", ), guard: None, }, WhenBranch { patterns: [ - |L 2-2, C 1-2| SpaceBefore( + @19-20 SpaceBefore( NumLiteral( "3", ), @@ -31,7 +31,7 @@ When( ], ), ], - value: |L 2-2, C 6-7| Num( + value: @24-25 Num( "4", ), guard: None, diff --git a/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast index 174cc8abb2..cfb5251e17 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast @@ -1,15 +1,15 @@ When( - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 1-6| SpaceBefore( + @11-16 SpaceBefore( RecordDestructure( [ - |L 1-1, C 3-4| Identifier( + @13-14 Identifier( "y", ), ], @@ -19,20 +19,20 @@ When( ], ), ], - value: |L 1-1, C 10-11| Num( + value: @20-21 Num( "2", ), guard: None, }, WhenBranch { patterns: [ - |L 2-2, C 1-9| SpaceBefore( + @23-31 SpaceBefore( RecordDestructure( [ - |L 2-2, C 3-4| Identifier( + @25-26 Identifier( "z", ), - |L 2-2, C 6-7| Identifier( + @28-29 Identifier( "w", ), ], @@ -42,7 +42,7 @@ When( ], ), ], - value: |L 2-2, C 13-14| Num( + value: @35-36 Num( "4", ), guard: None, diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 387b36305a..ccbd0ee7c7 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -366,7 +366,7 @@ mod test_parse { assert_segments(r#""Hi, \u(123)!""#, |arena| { bumpalo::vec![in arena; Plaintext("Hi, "), - Unicode(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), "123")), + Unicode(Loc::new(Position::new(8, 0, 0), Position::new(11, 0, 0), "123")), Plaintext("!") ] }); @@ -376,7 +376,7 @@ mod test_parse { fn unicode_escape_in_front() { assert_segments(r#""\u(1234) is a unicode char""#, |arena| { bumpalo::vec![in arena; - Unicode(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), "1234")), + Unicode(Loc::new(Position::new(4, 0, 0), Position::new(8, 0, 0), "1234")), Plaintext(" is a unicode char") ] }); @@ -387,7 +387,7 @@ mod test_parse { assert_segments(r#""this is unicode: \u(1)""#, |arena| { bumpalo::vec![in arena; Plaintext("this is unicode: "), - Unicode(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), "1")) + Unicode(Loc::new(Position::new(21, 0, 0), Position::new(22, 0, 0), "1")) ] }); } @@ -396,11 +396,11 @@ mod test_parse { fn unicode_escape_multiple() { assert_segments(r#""\u(a1) this is \u(2Bcd) unicode \u(ef97)""#, |arena| { bumpalo::vec![in arena; - Unicode(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), "a1")), + Unicode(Loc::new(Position::new(4, 0, 0), Position::new(6, 0, 0), "a1")), Plaintext(" this is "), - Unicode(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), "2Bcd")), + Unicode(Loc::new(Position::new(19, 0, 0), Position::new(23, 0, 0), "2Bcd")), Plaintext(" unicode "), - Unicode(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), "ef97")) + Unicode(Loc::new(Position::new(36, 0, 0), Position::new(40, 0, 0), "ef97")) ] }); } @@ -417,7 +417,7 @@ mod test_parse { bumpalo::vec![in arena; Plaintext("Hi, "), - Interpolated(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), expr)), + Interpolated(Loc::new(Position::new(7, 0, 0), Position::new(11, 0, 0), expr)), Plaintext("!") ] }); @@ -432,7 +432,7 @@ mod test_parse { }); bumpalo::vec![in arena; - Interpolated(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), expr)), + Interpolated(Loc::new(Position::new(3, 0, 0), Position::new(7, 0, 0), expr)), Plaintext(", hi!") ] }); @@ -448,7 +448,7 @@ mod test_parse { bumpalo::vec![in arena; Plaintext("Hello "), - Interpolated(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), expr)) + Interpolated(Loc::new(Position::new(9, 0, 0), Position::new(13, 0, 0), expr)) ] }); } @@ -468,9 +468,9 @@ mod test_parse { bumpalo::vec![in arena; Plaintext("Hi, "), - Interpolated(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), expr1)), + Interpolated(Loc::new(Position::new(7, 0, 0), Position::new(11, 0, 0), expr1)), Plaintext("! How is "), - Interpolated(Loc::new(Position::new(0, 0, 0), Position::new(0, 0, 0), expr2)), + Interpolated(Loc::new(Position::new(23, 0, 0), Position::new(30, 0, 0), expr2)), Plaintext(" going?") ] }); diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index f5a093065a..73302733a3 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -98,13 +98,19 @@ impl fmt::Debug for Region { } } -#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Default)] +#[derive(Copy, Clone, Eq, PartialOrd, Ord, Hash, Default)] pub struct Position { pub offset: u32, line: u32, column: u16, } +impl PartialEq for Position { + fn eq(&self, other: &Self) -> bool { + self.offset == other.offset + } +} + impl Position { pub const fn zero() -> Position { Position { offset: 0, line: 0, column: 0 } From 8e1241adea9efcecc905f39105d0d92e10889ff6 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Thu, 23 Dec 2021 21:15:28 -0800 Subject: [PATCH 069/541] Remove line/column fields --- compiler/can/tests/test_can.rs | 32 +++++++++++++-------------- compiler/parse/src/state.rs | 15 ++----------- compiler/parse/tests/test_parse.rs | 22 +++++++++---------- compiler/region/src/all.rs | 35 +++++++----------------------- 4 files changed, 37 insertions(+), 67 deletions(-) diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index 7a17b80989..e53a93546a 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -944,12 +944,12 @@ mod test_can { let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![CycleEntry { symbol: interns.symbol(home, "x".into()), symbol_region: Region::new( - Position::new(0, 0, 0), - Position::new(1, 0, 0), + Position::new(0), + Position::new(1), ), expr_region: Region::new( - Position::new(4, 0, 0), - Position::new(5, 0, 0), + Position::new(4), + Position::new(5), ), }])); @@ -981,34 +981,34 @@ mod test_can { CycleEntry { symbol: interns.symbol(home, "x".into()), symbol_region: Region::new( - Position::new(0, 0, 0), - Position::new(1, 0, 0), + Position::new(0), + Position::new(1), ), expr_region: Region::new( - Position::new(4, 0, 0), - Position::new(5, 0, 0), + Position::new(4), + Position::new(5), ), }, CycleEntry { symbol: interns.symbol(home, "y".into()), symbol_region: Region::new( - Position::new(6, 0, 0), - Position::new(7, 0, 0), + Position::new(6), + Position::new(7), ), expr_region: Region::new( - Position::new(10, 0, 0), - Position::new(11, 0, 0), + Position::new(10), + Position::new(11), ), }, CycleEntry { symbol: interns.symbol(home, "z".into()), symbol_region: Region::new( - Position::new(12, 0, 0), - Position::new(13, 0, 0), + Position::new(12), + Position::new(13), ), expr_region: Region::new( - Position::new(16, 0, 0), - Position::new(17, 0, 0), + Position::new(16), + Position::new(17), ), }, ])); diff --git a/compiler/parse/src/state.rs b/compiler/parse/src/state.rs index 4945c2e6e5..95ac639bf5 100644 --- a/compiler/parse/src/state.rs +++ b/compiler/parse/src/state.rs @@ -45,10 +45,7 @@ impl<'a> State<'a> { /// Returns the current position pub const fn pos(&self) -> Position { - Position::new( - (self.input_len - self.bytes.len()) as u32, - self.xyzlcol.line, - self.xyzlcol.column) + Position::new((self.input_len - self.bytes.len()) as u32) } /// Returns whether the parser has reached the end of the input @@ -102,15 +99,7 @@ impl<'a> State<'a> { pub fn len_region(&self, length: u16) -> Region { Region::new( self.pos(), - Position::new( - self.pos().bump_column(length).offset, - self.xyzlcol.line, - self - .xyzlcol - .column - .checked_add(length) - .unwrap_or_else(|| panic!("len_region overflowed")), - ), + self.pos().bump_column(length), ) } diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index ccbd0ee7c7..6521defad5 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -366,7 +366,7 @@ mod test_parse { assert_segments(r#""Hi, \u(123)!""#, |arena| { bumpalo::vec![in arena; Plaintext("Hi, "), - Unicode(Loc::new(Position::new(8, 0, 0), Position::new(11, 0, 0), "123")), + Unicode(Loc::new(8, 11, "123")), Plaintext("!") ] }); @@ -376,7 +376,7 @@ mod test_parse { fn unicode_escape_in_front() { assert_segments(r#""\u(1234) is a unicode char""#, |arena| { bumpalo::vec![in arena; - Unicode(Loc::new(Position::new(4, 0, 0), Position::new(8, 0, 0), "1234")), + Unicode(Loc::new(4, 8, "1234")), Plaintext(" is a unicode char") ] }); @@ -387,7 +387,7 @@ mod test_parse { assert_segments(r#""this is unicode: \u(1)""#, |arena| { bumpalo::vec![in arena; Plaintext("this is unicode: "), - Unicode(Loc::new(Position::new(21, 0, 0), Position::new(22, 0, 0), "1")) + Unicode(Loc::new(21, 22, "1")) ] }); } @@ -396,11 +396,11 @@ mod test_parse { fn unicode_escape_multiple() { assert_segments(r#""\u(a1) this is \u(2Bcd) unicode \u(ef97)""#, |arena| { bumpalo::vec![in arena; - Unicode(Loc::new(Position::new(4, 0, 0), Position::new(6, 0, 0), "a1")), + Unicode(Loc::new(4, 6, "a1")), Plaintext(" this is "), - Unicode(Loc::new(Position::new(19, 0, 0), Position::new(23, 0, 0), "2Bcd")), + Unicode(Loc::new(19, 23, "2Bcd")), Plaintext(" unicode "), - Unicode(Loc::new(Position::new(36, 0, 0), Position::new(40, 0, 0), "ef97")) + Unicode(Loc::new(36, 40, "ef97")) ] }); } @@ -417,7 +417,7 @@ mod test_parse { bumpalo::vec![in arena; Plaintext("Hi, "), - Interpolated(Loc::new(Position::new(7, 0, 0), Position::new(11, 0, 0), expr)), + Interpolated(Loc::new(7, 11, expr)), Plaintext("!") ] }); @@ -432,7 +432,7 @@ mod test_parse { }); bumpalo::vec![in arena; - Interpolated(Loc::new(Position::new(3, 0, 0), Position::new(7, 0, 0), expr)), + Interpolated(Loc::new(3, 7, expr)), Plaintext(", hi!") ] }); @@ -448,7 +448,7 @@ mod test_parse { bumpalo::vec![in arena; Plaintext("Hello "), - Interpolated(Loc::new(Position::new(9, 0, 0), Position::new(13, 0, 0), expr)) + Interpolated(Loc::new(9, 13, expr)) ] }); } @@ -468,9 +468,9 @@ mod test_parse { bumpalo::vec![in arena; Plaintext("Hi, "), - Interpolated(Loc::new(Position::new(7, 0, 0), Position::new(11, 0, 0), expr1)), + Interpolated(Loc::new(7, 11, expr1)), Plaintext("! How is "), - Interpolated(Loc::new(Position::new(23, 0, 0), Position::new(30, 0, 0), expr2)), + Interpolated(Loc::new(23, 30, expr2)), Plaintext(" going?") ] }); diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index 73302733a3..339e0b5344 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -101,8 +101,6 @@ impl fmt::Debug for Region { #[derive(Copy, Clone, Eq, PartialOrd, Ord, Hash, Default)] pub struct Position { pub offset: u32, - line: u32, - column: u16, } impl PartialEq for Position { @@ -113,18 +111,16 @@ impl PartialEq for Position { impl Position { pub const fn zero() -> Position { - Position { offset: 0, line: 0, column: 0 } + Position { offset: 0 } } - pub const fn new(offset: u32, line: u32, column: u16) -> Position { - Position { offset, line, column } + pub const fn new(offset: u32) -> Position { + Position { offset } } #[must_use] pub const fn bump_column(self, count: u16) -> Self { Self { - line: self.line, - column: self.column + count, offset: self.offset + count as u32, } } @@ -132,8 +128,6 @@ impl Position { #[must_use] pub fn bump_invisible(self, count: u16) -> Self { Self { - line: self.line, - column: self.column, offset: self.offset + count as u32, } } @@ -141,8 +135,6 @@ impl Position { #[must_use] pub fn bump_newline(self) -> Self { Self { - line: self.line + 1, - column: 0, offset: self.offset + 1, } } @@ -151,8 +143,6 @@ impl Position { pub const fn sub(self, count: u16) -> Self { Self { offset: self.offset - count as u32, - line: self.line, - column: self.column - count, } } } @@ -305,8 +295,8 @@ pub struct Loc { } impl Loc { - pub fn new(start: Position, end: Position, value: T) -> Loc { - let region = Region::new(start, end); + pub fn new(start: u32, end: u32, value: T) -> Loc { + let region = Region::new(Position::new(start), Position::new(end)); Loc { region, value } } @@ -386,23 +376,14 @@ impl LineInfo { } pub fn convert_pos(&self, pos: Position) -> LineColumn { - let res = self.convert_offset(pos.offset); - // let expected = LineColumn { line: pos.line, column: pos.column }; - // assert_eq!(expected, res); - res + self.convert_offset(pos.offset) } pub fn convert_region(&self, region: Region) -> LineColumnRegion { - let res = LineColumnRegion { + LineColumnRegion { start: self.convert_pos(region.start()), end: self.convert_pos(region.end()), - }; - let expected = LineColumnRegion::new( - LineColumn { line: region.start.line, column: region.start.column }, - LineColumn { line: region.end.line, column: region.end.column }, - ); - assert_eq!(expected, res); - res + } } } From a13c474f6b038e438418ac528570c25ecfa300c5 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Thu, 23 Dec 2021 21:25:46 -0800 Subject: [PATCH 070/541] Remove line tracking in parser --- compiler/parse/src/blankspace.rs | 33 +++++++++++++++++++------------- compiler/parse/src/expr.rs | 16 ++++++++-------- compiler/parse/src/state.rs | 19 +++++++++++------- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/compiler/parse/src/blankspace.rs b/compiler/parse/src/blankspace.rs index 999ba4ca31..b0013d42f9 100644 --- a/compiler/parse/src/blankspace.rs +++ b/compiler/parse/src/blankspace.rs @@ -1,10 +1,10 @@ use crate::ast::CommentOrNewline; use crate::ast::Spaceable; use crate::parser::{self, and, backtrackable, BadInputError, Parser, Progress::*}; +use crate::state::JustColumn; use crate::state::State; use bumpalo::collections::vec::Vec; use bumpalo::Bump; -use roc_region::all::LineColumn; use roc_region::all::Loc; use roc_region::all::Position; @@ -194,7 +194,7 @@ where move |arena, mut state: State<'a>| { let comments_and_newlines = Vec::new_in(arena); - match eat_spaces(state.bytes(), state.xyzlcol, state.pos(), comments_and_newlines) { + match eat_spaces(state.bytes(), false, state.xyzlcol, state.pos(), comments_and_newlines) { HasTab(xyzlcol, pos) => { // there was a tab character let mut state = state; @@ -211,12 +211,13 @@ where } Good { xyzcol: pos, + multiline, bytes, comments_and_newlines, } => { if bytes == state.bytes() { Ok((NoProgress, &[] as &[_], state)) - } else if state.xyzlcol.line != pos.line { + } else if multiline { // we parsed at least one newline state.indent_column = pos.column; @@ -242,16 +243,18 @@ where enum SpaceState<'a> { Good { - xyzcol: LineColumn, + xyzcol: JustColumn, + multiline: bool, bytes: &'a [u8], comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, }, - HasTab(LineColumn, Position), + HasTab(JustColumn, Position), } fn eat_spaces<'a>( mut bytes: &'a [u8], - mut xyzlcol: LineColumn, + mut multiline: bool, + mut xyzlcol: JustColumn, mut pos: Position, mut comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, ) -> SpaceState<'a> { @@ -267,7 +270,7 @@ fn eat_spaces<'a>( b'\n' => { bytes = &bytes[1..]; pos = pos.bump_newline(); - xyzlcol.line += 1; + multiline = true; xyzlcol.column = 0; comments_and_newlines.push(CommentOrNewline::Newline); } @@ -281,7 +284,7 @@ fn eat_spaces<'a>( b'#' => { xyzlcol.column += 1; pos = pos.bump_column(1); - return eat_line_comment(&bytes[1..], xyzlcol, pos, comments_and_newlines); + return eat_line_comment(&bytes[1..], multiline, xyzlcol, pos, comments_and_newlines); } _ => break, } @@ -289,6 +292,7 @@ fn eat_spaces<'a>( Good { xyzcol: xyzlcol, + multiline, bytes, comments_and_newlines, } @@ -296,7 +300,8 @@ fn eat_spaces<'a>( fn eat_line_comment<'a>( mut bytes: &'a [u8], - mut xyzlcol: LineColumn, + mut multiline: bool, + mut xyzlcol: JustColumn, mut pos: Position, mut comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, ) -> SpaceState<'a> { @@ -318,9 +323,9 @@ fn eat_line_comment<'a>( pos = pos.bump_newline(); comments_and_newlines.push(CommentOrNewline::DocComment("")); - xyzlcol.line += 1; + multiline = true; xyzlcol.column = 0; - return eat_spaces(bytes, xyzlcol, pos, comments_and_newlines); + return eat_spaces(bytes, multiline, xyzlcol, pos, comments_and_newlines); } None => { // consume the second # @@ -330,6 +335,7 @@ fn eat_line_comment<'a>( return Good { xyzcol: xyzlcol, + multiline, bytes, comments_and_newlines, }; @@ -357,9 +363,9 @@ fn eat_line_comment<'a>( comments_and_newlines.push(CommentOrNewline::LineComment(comment)); } pos = pos.bump_newline(); - xyzlcol.line += 1; + multiline = true; xyzlcol.column = 0; - return eat_spaces(&bytes[1..], xyzlcol, pos, comments_and_newlines); + return eat_spaces(&bytes[1..], multiline, xyzlcol, pos, comments_and_newlines); } _ => { bytes = &bytes[1..]; @@ -381,6 +387,7 @@ fn eat_line_comment<'a>( Good { xyzcol: xyzlcol, + multiline, bytes, comments_and_newlines, } diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 80c04f3efd..87f267f1a3 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -11,12 +11,12 @@ use crate::parser::{ EPattern, ERecord, EString, EType, EWhen, Either, ParseResult, Parser, }; use crate::pattern::loc_closure_param; -use crate::state::State; +use crate::state::{State, JustColumn}; use crate::type_annotation; use bumpalo::collections::Vec; use bumpalo::Bump; use roc_module::called_via::{BinOp, CalledVia, UnaryOp}; -use roc_region::all::{Loc, Position, Region, LineColumn}; +use roc_region::all::{Loc, Position, Region}; use crate::parser::Progress::{self, *}; @@ -310,7 +310,7 @@ fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> { fn parse_expr_start<'a>( min_indent: u16, options: ExprParseOptions, - start: LineColumn, + start: JustColumn, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Loc>, EExpr<'a>> { @@ -331,7 +331,7 @@ fn parse_expr_start<'a>( fn parse_expr_operator_chain<'a>( min_indent: u16, options: ExprParseOptions, - start: LineColumn, + start: JustColumn, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { @@ -777,7 +777,7 @@ struct DefState<'a> { fn parse_defs_end<'a>( options: ExprParseOptions, - start: LineColumn, + start: JustColumn, mut def_state: DefState<'a>, arena: &'a Bump, state: State<'a>, @@ -892,7 +892,7 @@ fn parse_defs_end<'a>( fn parse_defs_expr<'a>( options: ExprParseOptions, - start: LineColumn, + start: JustColumn, def_state: DefState<'a>, arena: &'a Bump, state: State<'a>, @@ -933,7 +933,7 @@ fn parse_defs_expr<'a>( fn parse_expr_operator<'a>( min_indent: u16, options: ExprParseOptions, - start: LineColumn, + start: JustColumn, mut expr_state: ExprState<'a>, loc_op: Loc, arena: &'a Bump, @@ -1222,7 +1222,7 @@ fn parse_expr_operator<'a>( fn parse_expr_end<'a>( min_indent: u16, options: ExprParseOptions, - start: LineColumn, + start: JustColumn, mut expr_state: ExprState<'a>, arena: &'a Bump, state: State<'a>, diff --git a/compiler/parse/src/state.rs b/compiler/parse/src/state.rs index 95ac639bf5..5bf1adbd35 100644 --- a/compiler/parse/src/state.rs +++ b/compiler/parse/src/state.rs @@ -1,7 +1,7 @@ use crate::parser::Progress::*; use crate::parser::{BadInputError, Progress}; use bumpalo::Bump; -use roc_region::all::{Position, Region, LineColumn}; +use roc_region::all::{Position, Region}; use std::fmt; /// A position in a source file. @@ -15,19 +15,25 @@ pub struct State<'a> { input_len: usize, /// Current position within the input (line/column) - pub xyzlcol: LineColumn, + pub xyzlcol: JustColumn, /// Current indentation level, in columns /// (so no indent is col 1 - this saves an arithmetic operation.) pub indent_column: u16, } + +#[derive(Clone, Copy)] +pub struct JustColumn { + pub column: u16, +} + impl<'a> State<'a> { pub fn new(bytes: &'a [u8]) -> State<'a> { State { bytes, input_len: bytes.len(), - xyzlcol: LineColumn::default(), + xyzlcol: JustColumn { column: 0 }, indent_column: 0, } } @@ -80,8 +86,7 @@ impl<'a> State<'a> { Some(column_usize) if column_usize <= u16::MAX as usize => { Ok(State { bytes: &self.bytes[quantity..], - xyzlcol: LineColumn { - line: self.xyzlcol.line, + xyzlcol: JustColumn { column: column_usize as u16, }, // Once we hit a nonspace character, we are no longer indenting. @@ -125,8 +130,8 @@ impl<'a> fmt::Debug for State<'a> { write!( f, - "\n\t(line, col): ({}, {}),", - self.xyzlcol.line, self.xyzlcol.column + "\n\t(col): {},", + self.xyzlcol.column )?; write!(f, "\n\tindent_column: {}", self.indent_column)?; write!(f, "\n}}") From beb0629e05be684878eb56e151c35288026ccc84 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Thu, 23 Dec 2021 21:56:21 -0800 Subject: [PATCH 071/541] Track state in whitespace --- compiler/parse/src/blankspace.rs | 38 ++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/compiler/parse/src/blankspace.rs b/compiler/parse/src/blankspace.rs index b0013d42f9..64f9a21690 100644 --- a/compiler/parse/src/blankspace.rs +++ b/compiler/parse/src/blankspace.rs @@ -193,9 +193,9 @@ where move |arena, mut state: State<'a>| { let comments_and_newlines = Vec::new_in(arena); - - match eat_spaces(state.bytes(), false, state.xyzlcol, state.pos(), comments_and_newlines) { - HasTab(xyzlcol, pos) => { + let col =state.xyzlcol; + match eat_spaces(state.bytes(), state.clone(), false, col, state.pos(), comments_and_newlines) { + HasTab(xyzlcol, pos, new_state) => { // there was a tab character let mut state = state; state.xyzlcol = xyzlcol; @@ -211,10 +211,15 @@ where } Good { xyzcol: pos, + state: new_state, multiline, bytes, comments_and_newlines, } => { + assert_eq!( + std::str::from_utf8(new_state.bytes()).unwrap(), + std::str::from_utf8(bytes).unwrap()); + if bytes == state.bytes() { Ok((NoProgress, &[] as &[_], state)) } else if multiline { @@ -244,15 +249,17 @@ where enum SpaceState<'a> { Good { xyzcol: JustColumn, + state: State<'a>, multiline: bool, bytes: &'a [u8], comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, }, - HasTab(JustColumn, Position), + HasTab(JustColumn, Position, State<'a>), } fn eat_spaces<'a>( mut bytes: &'a [u8], + mut state: State<'a>, mut multiline: bool, mut xyzlcol: JustColumn, mut pos: Position, @@ -265,26 +272,30 @@ fn eat_spaces<'a>( b' ' => { pos = pos.bump_column(1); bytes = &bytes[1..]; + state = state.advance(1); xyzlcol.column += 1; } b'\n' => { bytes = &bytes[1..]; pos = pos.bump_newline(); + state = state.advance(1); multiline = true; xyzlcol.column = 0; comments_and_newlines.push(CommentOrNewline::Newline); } b'\r' => { bytes = &bytes[1..]; + state = state.advance(1); pos = pos.bump_invisible(1); } b'\t' => { - return HasTab(xyzlcol, pos); + return HasTab(xyzlcol, pos, state); } b'#' => { xyzlcol.column += 1; + state = state.advance(1); pos = pos.bump_column(1); - return eat_line_comment(&bytes[1..], multiline, xyzlcol, pos, comments_and_newlines); + return eat_line_comment(&bytes[1..], state, multiline, xyzlcol, pos, comments_and_newlines); } _ => break, } @@ -292,6 +303,7 @@ fn eat_spaces<'a>( Good { xyzcol: xyzlcol, + state, multiline, bytes, comments_and_newlines, @@ -300,6 +312,7 @@ fn eat_spaces<'a>( fn eat_line_comment<'a>( mut bytes: &'a [u8], + mut state: State<'a>, mut multiline: bool, mut xyzlcol: JustColumn, mut pos: Position, @@ -313,6 +326,7 @@ fn eat_line_comment<'a>( bytes = &bytes[2..]; xyzlcol.column += 2; pos = pos.bump_column(2); + state = state.advance(2); true } @@ -321,20 +335,23 @@ fn eat_line_comment<'a>( bytes = &bytes[2..]; pos = pos.bump_column(1); pos = pos.bump_newline(); + state = state.advance(2); comments_and_newlines.push(CommentOrNewline::DocComment("")); multiline = true; xyzlcol.column = 0; - return eat_spaces(bytes, multiline, xyzlcol, pos, comments_and_newlines); + return eat_spaces(bytes, state, multiline, xyzlcol, pos, comments_and_newlines); } None => { // consume the second # xyzlcol.column += 1; bytes = &bytes[1..]; + state = state.advance(1); // pos = pos.bump_column(1); return Good { xyzcol: xyzlcol, + state, multiline, bytes, comments_and_newlines, @@ -352,7 +369,7 @@ fn eat_line_comment<'a>( for c in bytes { match c { - b'\t' => return HasTab(xyzlcol, pos), + b'\t' => return HasTab(xyzlcol, pos, state), b'\n' => { let delta = (xyzlcol.column - initial_column) as usize; let comment = unsafe { std::str::from_utf8_unchecked(&initial[..delta]) }; @@ -363,12 +380,14 @@ fn eat_line_comment<'a>( comments_and_newlines.push(CommentOrNewline::LineComment(comment)); } pos = pos.bump_newline(); + state = state.advance(1); multiline = true; xyzlcol.column = 0; - return eat_spaces(&bytes[1..], multiline, xyzlcol, pos, comments_and_newlines); + return eat_spaces(&bytes[1..], state, multiline, xyzlcol, pos, comments_and_newlines); } _ => { bytes = &bytes[1..]; + state = state.advance(1); pos = pos.bump_column(1); xyzlcol.column += 1; } @@ -387,6 +406,7 @@ fn eat_line_comment<'a>( Good { xyzcol: xyzlcol, + state, multiline, bytes, comments_and_newlines, From 08a33aab1ba54929cd6f2da525b56cc856a94722 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Thu, 23 Dec 2021 21:59:48 -0800 Subject: [PATCH 072/541] Remove bytes tracking --- compiler/parse/src/blankspace.rs | 47 ++++++++++---------------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/compiler/parse/src/blankspace.rs b/compiler/parse/src/blankspace.rs index 64f9a21690..f701b9d374 100644 --- a/compiler/parse/src/blankspace.rs +++ b/compiler/parse/src/blankspace.rs @@ -193,11 +193,10 @@ where move |arena, mut state: State<'a>| { let comments_and_newlines = Vec::new_in(arena); - let col =state.xyzlcol; - match eat_spaces(state.bytes(), state.clone(), false, col, state.pos(), comments_and_newlines) { - HasTab(xyzlcol, pos, new_state) => { + let col = state.xyzlcol; + match eat_spaces(state.clone(), false, col, state.pos(), comments_and_newlines) { + HasTab(xyzlcol, pos, mut state) => { // there was a tab character - let mut state = state; state.xyzlcol = xyzlcol; // TODO: it _seems_ like if we're changing the line/column, we should also be // advancing the state by the corresponding number of bytes. @@ -213,14 +212,9 @@ where xyzcol: pos, state: new_state, multiline, - bytes, comments_and_newlines, } => { - assert_eq!( - std::str::from_utf8(new_state.bytes()).unwrap(), - std::str::from_utf8(bytes).unwrap()); - - if bytes == state.bytes() { + if new_state.bytes() == state.bytes() { Ok((NoProgress, &[] as &[_], state)) } else if multiline { // we parsed at least one newline @@ -229,7 +223,7 @@ where if pos.column >= min_indent { state.xyzlcol = pos; - state = state.advance(state.bytes().len() - bytes.len()); + state = state.advance(state.bytes().len() - new_state.bytes().len()); Ok((MadeProgress, comments_and_newlines.into_bump_slice(), state)) } else { @@ -237,7 +231,7 @@ where } } else { state.xyzlcol.column = pos.column; - state = state.advance(state.bytes().len() - bytes.len()); + state = state.advance(state.bytes().len() - new_state.bytes().len()); Ok((MadeProgress, comments_and_newlines.into_bump_slice(), state)) } @@ -251,14 +245,12 @@ enum SpaceState<'a> { xyzcol: JustColumn, state: State<'a>, multiline: bool, - bytes: &'a [u8], comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, }, HasTab(JustColumn, Position, State<'a>), } fn eat_spaces<'a>( - mut bytes: &'a [u8], mut state: State<'a>, mut multiline: bool, mut xyzlcol: JustColumn, @@ -267,16 +259,14 @@ fn eat_spaces<'a>( ) -> SpaceState<'a> { use SpaceState::*; - for c in bytes { + for c in state.bytes() { match c { b' ' => { pos = pos.bump_column(1); - bytes = &bytes[1..]; state = state.advance(1); xyzlcol.column += 1; } b'\n' => { - bytes = &bytes[1..]; pos = pos.bump_newline(); state = state.advance(1); multiline = true; @@ -284,7 +274,6 @@ fn eat_spaces<'a>( comments_and_newlines.push(CommentOrNewline::Newline); } b'\r' => { - bytes = &bytes[1..]; state = state.advance(1); pos = pos.bump_invisible(1); } @@ -295,7 +284,7 @@ fn eat_spaces<'a>( xyzlcol.column += 1; state = state.advance(1); pos = pos.bump_column(1); - return eat_line_comment(&bytes[1..], state, multiline, xyzlcol, pos, comments_and_newlines); + return eat_line_comment(state, multiline, xyzlcol, pos, comments_and_newlines); } _ => break, } @@ -305,13 +294,11 @@ fn eat_spaces<'a>( xyzcol: xyzlcol, state, multiline, - bytes, comments_and_newlines, } } fn eat_line_comment<'a>( - mut bytes: &'a [u8], mut state: State<'a>, mut multiline: bool, mut xyzlcol: JustColumn, @@ -320,10 +307,9 @@ fn eat_line_comment<'a>( ) -> SpaceState<'a> { use SpaceState::*; - let is_doc_comment = if let Some(b'#') = bytes.get(0) { - match bytes.get(1) { + let is_doc_comment = if let Some(b'#') = state.bytes().get(0) { + match state.bytes().get(1) { Some(b' ') => { - bytes = &bytes[2..]; xyzlcol.column += 2; pos = pos.bump_column(2); state = state.advance(2); @@ -332,7 +318,6 @@ fn eat_line_comment<'a>( } Some(b'\n') => { // consume the second # and the \n - bytes = &bytes[2..]; pos = pos.bump_column(1); pos = pos.bump_newline(); state = state.advance(2); @@ -340,12 +325,11 @@ fn eat_line_comment<'a>( comments_and_newlines.push(CommentOrNewline::DocComment("")); multiline = true; xyzlcol.column = 0; - return eat_spaces(bytes, state, multiline, xyzlcol, pos, comments_and_newlines); + return eat_spaces(state, multiline, xyzlcol, pos, comments_and_newlines); } None => { // consume the second # xyzlcol.column += 1; - bytes = &bytes[1..]; state = state.advance(1); // pos = pos.bump_column(1); @@ -353,7 +337,6 @@ fn eat_line_comment<'a>( xyzcol: xyzlcol, state, multiline, - bytes, comments_and_newlines, }; } @@ -364,10 +347,10 @@ fn eat_line_comment<'a>( false }; - let initial = bytes; + let initial = state.bytes(); let initial_column = xyzlcol.column; - for c in bytes { + for c in state.bytes() { match c { b'\t' => return HasTab(xyzlcol, pos, state), b'\n' => { @@ -383,10 +366,9 @@ fn eat_line_comment<'a>( state = state.advance(1); multiline = true; xyzlcol.column = 0; - return eat_spaces(&bytes[1..], state, multiline, xyzlcol, pos, comments_and_newlines); + return eat_spaces(state, multiline, xyzlcol, pos, comments_and_newlines); } _ => { - bytes = &bytes[1..]; state = state.advance(1); pos = pos.bump_column(1); xyzlcol.column += 1; @@ -408,7 +390,6 @@ fn eat_line_comment<'a>( xyzcol: xyzlcol, state, multiline, - bytes, comments_and_newlines, } } From 5ac3394a7325e7890fea325fbbe9ea37d5338e06 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Thu, 23 Dec 2021 22:01:48 -0800 Subject: [PATCH 073/541] Remove pos tracking --- compiler/parse/src/blankspace.rs | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/compiler/parse/src/blankspace.rs b/compiler/parse/src/blankspace.rs index f701b9d374..326773cffc 100644 --- a/compiler/parse/src/blankspace.rs +++ b/compiler/parse/src/blankspace.rs @@ -194,8 +194,8 @@ where move |arena, mut state: State<'a>| { let comments_and_newlines = Vec::new_in(arena); let col = state.xyzlcol; - match eat_spaces(state.clone(), false, col, state.pos(), comments_and_newlines) { - HasTab(xyzlcol, pos, mut state) => { + match eat_spaces(state.clone(), false, col, comments_and_newlines) { + HasTab(xyzlcol, mut state) => { // there was a tab character state.xyzlcol = xyzlcol; // TODO: it _seems_ like if we're changing the line/column, we should also be @@ -204,7 +204,7 @@ where // state = state.advance(); Err(( MadeProgress, - space_problem(BadInputError::HasTab, pos), + space_problem(BadInputError::HasTab, state.pos()), state, )) } @@ -247,14 +247,13 @@ enum SpaceState<'a> { multiline: bool, comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, }, - HasTab(JustColumn, Position, State<'a>), + HasTab(JustColumn, State<'a>), } fn eat_spaces<'a>( mut state: State<'a>, mut multiline: bool, mut xyzlcol: JustColumn, - mut pos: Position, mut comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, ) -> SpaceState<'a> { use SpaceState::*; @@ -262,12 +261,10 @@ fn eat_spaces<'a>( for c in state.bytes() { match c { b' ' => { - pos = pos.bump_column(1); state = state.advance(1); xyzlcol.column += 1; } b'\n' => { - pos = pos.bump_newline(); state = state.advance(1); multiline = true; xyzlcol.column = 0; @@ -275,16 +272,14 @@ fn eat_spaces<'a>( } b'\r' => { state = state.advance(1); - pos = pos.bump_invisible(1); } b'\t' => { - return HasTab(xyzlcol, pos, state); + return HasTab(xyzlcol, state); } b'#' => { xyzlcol.column += 1; state = state.advance(1); - pos = pos.bump_column(1); - return eat_line_comment(state, multiline, xyzlcol, pos, comments_and_newlines); + return eat_line_comment(state, multiline, xyzlcol, comments_and_newlines); } _ => break, } @@ -302,7 +297,6 @@ fn eat_line_comment<'a>( mut state: State<'a>, mut multiline: bool, mut xyzlcol: JustColumn, - mut pos: Position, mut comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, ) -> SpaceState<'a> { use SpaceState::*; @@ -311,21 +305,18 @@ fn eat_line_comment<'a>( match state.bytes().get(1) { Some(b' ') => { xyzlcol.column += 2; - pos = pos.bump_column(2); state = state.advance(2); true } Some(b'\n') => { // consume the second # and the \n - pos = pos.bump_column(1); - pos = pos.bump_newline(); state = state.advance(2); comments_and_newlines.push(CommentOrNewline::DocComment("")); multiline = true; xyzlcol.column = 0; - return eat_spaces(state, multiline, xyzlcol, pos, comments_and_newlines); + return eat_spaces(state, multiline, xyzlcol, comments_and_newlines); } None => { // consume the second # @@ -352,7 +343,7 @@ fn eat_line_comment<'a>( for c in state.bytes() { match c { - b'\t' => return HasTab(xyzlcol, pos, state), + b'\t' => return HasTab(xyzlcol, state), b'\n' => { let delta = (xyzlcol.column - initial_column) as usize; let comment = unsafe { std::str::from_utf8_unchecked(&initial[..delta]) }; @@ -362,15 +353,13 @@ fn eat_line_comment<'a>( } else { comments_and_newlines.push(CommentOrNewline::LineComment(comment)); } - pos = pos.bump_newline(); state = state.advance(1); multiline = true; xyzlcol.column = 0; - return eat_spaces(state, multiline, xyzlcol, pos, comments_and_newlines); + return eat_spaces(state, multiline, xyzlcol, comments_and_newlines); } _ => { state = state.advance(1); - pos = pos.bump_column(1); xyzlcol.column += 1; } } From 422cdea11298de72eb7bd53679123d190d55f195 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Thu, 23 Dec 2021 22:06:22 -0800 Subject: [PATCH 074/541] Track line start separately --- compiler/parse/src/blankspace.rs | 10 +++++++--- compiler/parse/src/state.rs | 10 ++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/compiler/parse/src/blankspace.rs b/compiler/parse/src/blankspace.rs index 326773cffc..0fde30d21d 100644 --- a/compiler/parse/src/blankspace.rs +++ b/compiler/parse/src/blankspace.rs @@ -265,13 +265,13 @@ fn eat_spaces<'a>( xyzlcol.column += 1; } b'\n' => { - state = state.advance(1); + state = state.advance_newline(); multiline = true; xyzlcol.column = 0; comments_and_newlines.push(CommentOrNewline::Newline); } b'\r' => { - state = state.advance(1); + state = state.advance_newline(); } b'\t' => { return HasTab(xyzlcol, state); @@ -353,11 +353,15 @@ fn eat_line_comment<'a>( } else { comments_and_newlines.push(CommentOrNewline::LineComment(comment)); } - state = state.advance(1); + state = state.advance_newline(); multiline = true; xyzlcol.column = 0; return eat_spaces(state, multiline, xyzlcol, comments_and_newlines); } + b'\r' => { + state = state.advance_newline(); + xyzlcol.column += 1; + } _ => { state = state.advance(1); xyzlcol.column += 1; diff --git a/compiler/parse/src/state.rs b/compiler/parse/src/state.rs index 5bf1adbd35..ce7db0fbec 100644 --- a/compiler/parse/src/state.rs +++ b/compiler/parse/src/state.rs @@ -14,6 +14,8 @@ pub struct State<'a> { /// Length of the original input in bytes input_len: usize, + line_start: Position, + /// Current position within the input (line/column) pub xyzlcol: JustColumn, @@ -33,6 +35,7 @@ impl<'a> State<'a> { State { bytes, input_len: bytes.len(), + line_start: Position::zero(), xyzlcol: JustColumn { column: 0 }, indent_column: 0, } @@ -49,6 +52,13 @@ impl<'a> State<'a> { state } + #[must_use] + pub fn advance_newline(&self) -> State<'a> { + let mut state = self.advance(1); + state.line_start = state.pos(); + state + } + /// Returns the current position pub const fn pos(&self) -> Position { Position::new((self.input_len - self.bytes.len()) as u32) From 2901549422cc8313dfff3dc1d6f69ff1cd155a23 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Fri, 24 Dec 2021 07:58:33 -0800 Subject: [PATCH 075/541] Assert new column calculation is correct --- compiler/parse/src/blankspace.rs | 18 ++++---- compiler/parse/src/expr.rs | 74 ++++++++++++++++---------------- compiler/parse/src/state.rs | 18 +++++++- 3 files changed, 64 insertions(+), 46 deletions(-) diff --git a/compiler/parse/src/blankspace.rs b/compiler/parse/src/blankspace.rs index 0fde30d21d..b7fb77742b 100644 --- a/compiler/parse/src/blankspace.rs +++ b/compiler/parse/src/blankspace.rs @@ -210,7 +210,7 @@ where } Good { xyzcol: pos, - state: new_state, + state: mut new_state, multiline, comments_and_newlines, } => { @@ -219,21 +219,23 @@ where } else if multiline { // we parsed at least one newline - state.indent_column = pos.column; + new_state.indent_column = pos.column; if pos.column >= min_indent { - state.xyzlcol = pos; - state = state.advance(state.bytes().len() - new_state.bytes().len()); + new_state.xyzlcol = pos; + // state = state.advance(state.bytes().len() - new_state.bytes().len()); + // assert_eq!(state.bytes().len(), new_state.bytes().len()); - Ok((MadeProgress, comments_and_newlines.into_bump_slice(), state)) + Ok((MadeProgress, comments_and_newlines.into_bump_slice(), new_state)) } else { Err((MadeProgress, indent_problem(state.pos()), state)) } } else { - state.xyzlcol.column = pos.column; - state = state.advance(state.bytes().len() - new_state.bytes().len()); + new_state.xyzlcol.column = pos.column; + // state = state.advance(state.bytes().len() - new_state.bytes().len()); + // assert_eq!(state.bytes().len(), new_state.bytes().len()); - Ok((MadeProgress, comments_and_newlines.into_bump_slice(), state)) + Ok((MadeProgress, comments_and_newlines.into_bump_slice(), new_state)) } } } diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 87f267f1a3..9f0967f46e 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -310,7 +310,7 @@ fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> { fn parse_expr_start<'a>( min_indent: u16, options: ExprParseOptions, - start: JustColumn, + start_column: u16, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Loc>, EExpr<'a>> { @@ -322,7 +322,7 @@ fn parse_expr_start<'a>( )), loc!(specialize(EExpr::Expect, expect_help(min_indent, options))), loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))), - loc!(move |a, s| parse_expr_operator_chain(min_indent, options, start, a, s)), + loc!(move |a, s| parse_expr_operator_chain(min_indent, options, start_column, a, s)), fail_expr_start_e() ] .parse(arena, state) @@ -331,7 +331,7 @@ fn parse_expr_start<'a>( fn parse_expr_operator_chain<'a>( min_indent: u16, options: ExprParseOptions, - start: JustColumn, + start_column: u16, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { @@ -353,7 +353,7 @@ fn parse_expr_operator_chain<'a>( end, }; - parse_expr_end(min_indent, options, start, expr_state, arena, state) + parse_expr_end(min_indent, options, start_column, expr_state, arena, state) } } } @@ -777,12 +777,12 @@ struct DefState<'a> { fn parse_defs_end<'a>( options: ExprParseOptions, - start: JustColumn, + start_column: u16, mut def_state: DefState<'a>, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, DefState<'a>, EExpr<'a>> { - let min_indent = start.column; + let min_indent = start_column; let initial = state.clone(); let state = match space0_e(min_indent, EExpr::Space, EExpr::IndentStart).parse(arena, state) { @@ -797,7 +797,7 @@ fn parse_defs_end<'a>( }; let start = state.pos(); - let xyzlcol = state.xyzlcol; + let column = state.column(); match space0_after_e( crate::pattern::loc_pattern_help(min_indent), @@ -833,7 +833,7 @@ fn parse_defs_end<'a>( loc_def_expr, ); - parse_defs_end(options, xyzlcol, def_state, arena, state) + parse_defs_end(options, column, def_state, arena, state) } } } @@ -860,7 +860,7 @@ fn parse_defs_end<'a>( loc_def_expr, ); - parse_defs_end(options, xyzlcol, def_state, arena, state) + parse_defs_end(options, column, def_state, arena, state) } Ok((_, BinOp::HasType, state)) => { let (_, ann_type, state) = specialize( @@ -882,7 +882,7 @@ fn parse_defs_end<'a>( ann_type, ); - parse_defs_end(options, xyzlcol, def_state, arena, state) + parse_defs_end(options, column, def_state, arena, state) } _ => Ok((MadeProgress, def_state, initial)), @@ -892,14 +892,14 @@ fn parse_defs_end<'a>( fn parse_defs_expr<'a>( options: ExprParseOptions, - start: JustColumn, + start_column: u16, def_state: DefState<'a>, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { - let min_indent = start.column; + let min_indent = start_column; - match parse_defs_end(options, start, def_state, arena, state) { + match parse_defs_end(options, start_column, def_state, arena, state) { Err(bad) => Err(bad), Ok((_, def_state, state)) => { // this is no def, because there is no `=` or `:`; parse as an expr @@ -933,7 +933,7 @@ fn parse_defs_expr<'a>( fn parse_expr_operator<'a>( min_indent: u16, options: ExprParseOptions, - start: JustColumn, + start_column: u16, mut expr_state: ExprState<'a>, loc_op: Loc, arena: &'a Bump, @@ -975,11 +975,11 @@ fn parse_expr_operator<'a>( expr_state.spaces_after = spaces; expr_state.end = new_end; - parse_expr_end(min_indent, options, start, expr_state, arena, state) + parse_expr_end(min_indent, options, start_column, expr_state, arena, state) } BinOp::Assignment => { let expr_region = expr_state.expr.region; - let indented_more = start.column + 1; + let indented_more = start_column + 1; let call = expr_state .validate_assignment_or_backpassing(arena, loc_op, EExpr::ElmStyleFunction) @@ -1020,11 +1020,11 @@ fn parse_expr_operator<'a>( spaces_after: &[], }; - parse_defs_expr(options, start, def_state, arena, state) + parse_defs_expr(options, start_column, def_state, arena, state) } BinOp::Backpassing => { let expr_region = expr_state.expr.region; - let indented_more = start.column + 1; + let indented_more = start_column + 1; let call = expr_state .validate_assignment_or_backpassing(arena, loc_op, |_, pos| { @@ -1074,7 +1074,7 @@ fn parse_expr_operator<'a>( } BinOp::HasType => { let expr_region = expr_state.expr.region; - let indented_more = start.column + 1; + let indented_more = start_column + 1; let (expr, arguments) = expr_state .validate_has_type(arena, loc_op) @@ -1168,7 +1168,7 @@ fn parse_expr_operator<'a>( spaces_after: &[], }; - parse_defs_expr(options, start, def_state, arena, state) + parse_defs_expr(options, start_column, def_state, arena, state) } _ => match loc_possibly_negative_or_negated_term(min_indent, options).parse(arena, state) { Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)), @@ -1208,7 +1208,7 @@ fn parse_expr_operator<'a>( expr_state.spaces_after = spaces; // TODO new start? - parse_expr_end(min_indent, options, start, expr_state, arena, state) + parse_expr_end(min_indent, options, start_column, expr_state, arena, state) } } } @@ -1222,7 +1222,7 @@ fn parse_expr_operator<'a>( fn parse_expr_end<'a>( min_indent: u16, options: ExprParseOptions, - start: JustColumn, + start_column: u16, mut expr_state: ExprState<'a>, arena: &'a Bump, state: State<'a>, @@ -1260,7 +1260,7 @@ fn parse_expr_end<'a>( expr_state.end = new_end; expr_state.spaces_after = new_spaces; - parse_expr_end(min_indent, options, start, expr_state, arena, state) + parse_expr_end(min_indent, options, start_column, expr_state, arena, state) } } } @@ -1273,7 +1273,7 @@ fn parse_expr_end<'a>( expr_state.consume_spaces(arena); expr_state.initial = before_op; parse_expr_operator( - min_indent, options, start, expr_state, loc_op, arena, state, + min_indent, options, start_column, expr_state, loc_op, arena, state, ) } Err((NoProgress, _, mut state)) => { @@ -1310,7 +1310,7 @@ fn parse_expr_end<'a>( match word2(b'<', b'-', EExpr::BackpassArrow).parse(arena, state) { Err((_, fail, state)) => Err((MadeProgress, fail, state)), Ok((_, _, state)) => { - let min_indent = start.column; + let min_indent = start_column; let parse_body = space0_before_e( move |a, s| parse_loc_expr(min_indent + 1, a, s), @@ -1391,8 +1391,8 @@ fn parse_loc_expr_with_options<'a>( arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Loc>, EExpr<'a>> { - let start = state.xyzlcol; - parse_expr_start(min_indent, options, start, arena, state) + let column = state.column(); + parse_expr_start(min_indent, options, column, arena, state) } /// If the given Expr would parse the same way as a valid Pattern, convert it. @@ -1539,17 +1539,17 @@ pub fn defs<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Loc>>, EExpr let (_, initial_space, state) = space0_e(min_indent, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?; - let xyzlcol = state.xyzlcol; + let start_column = state.column(); let options = ExprParseOptions { accept_multi_backpassing: false, check_for_arrow: true, }; - let (_, def_state, state) = parse_defs_end(options, xyzlcol, def_state, arena, state)?; + let (_, def_state, state) = parse_defs_end(options, start_column, def_state, arena, state)?; let (_, final_space, state) = - space0_e(xyzlcol.column, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?; + space0_e(start_column, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?; let mut output = Vec::with_capacity_in(def_state.defs.len(), arena); @@ -1851,12 +1851,12 @@ mod when { Err((NoProgress, fail, _)) => Err((NoProgress, fail, initial)), Ok((_progress, spaces, state)) => { match pattern_indent_level { - Some(wanted) if state.xyzlcol.column > wanted => { + Some(wanted) if state.column() > wanted => { // this branch is indented too much Err((NoProgress, EWhen::IndentPattern(state.pos()), initial)) } - Some(wanted) if state.xyzlcol.column < wanted => { - let indent = wanted - state.xyzlcol.column; + Some(wanted) if state.column() < wanted => { + let indent = wanted - state.column(); Err(( NoProgress, EWhen::PatternAlignment(indent, state.pos()), @@ -1868,7 +1868,7 @@ mod when { min_indent.max(pattern_indent_level.unwrap_or(min_indent)); // the region is not reliable for the indent column in the case of // parentheses around patterns - let pattern_indent_column = state.xyzlcol.column; + let pattern_indent_column = state.column(); let parser = sep_by1( word1(b'|', EWhen::Bar), @@ -1964,16 +1964,16 @@ fn expect_help<'a>( options: ExprParseOptions, ) -> impl Parser<'a, Expr<'a>, EExpect<'a>> { move |arena: &'a Bump, state: State<'a>| { - let start = state.xyzlcol; + let start_column = state.column(); let (_, _, state) = parser::keyword_e(keyword::EXPECT, EExpect::Expect).parse(arena, state)?; let (_, condition, state) = space0_before_e( specialize_ref(EExpect::Condition, move |arena, state| { - parse_loc_expr_with_options(start.column + 1, options, arena, state) + parse_loc_expr_with_options(start_column + 1, options, arena, state) }), - start.column + 1, + start_column + 1, EExpect::Space, EExpect::IndentCondition, ) diff --git a/compiler/parse/src/state.rs b/compiler/parse/src/state.rs index ce7db0fbec..a2df6ea67a 100644 --- a/compiler/parse/src/state.rs +++ b/compiler/parse/src/state.rs @@ -11,6 +11,8 @@ pub struct State<'a> { /// Beware: bytes[0] always points the the current byte the parser is examining. bytes: &'a [u8], + original_bytes: &'a [u8], + /// Length of the original input in bytes input_len: usize, @@ -34,6 +36,7 @@ impl<'a> State<'a> { pub fn new(bytes: &'a [u8]) -> State<'a> { State { bytes, + original_bytes: bytes, input_len: bytes.len(), line_start: Position::zero(), xyzlcol: JustColumn { column: 0 }, @@ -45,16 +48,29 @@ impl<'a> State<'a> { self.bytes } + pub fn column(&self) -> u16 { + assert_eq!( + self.xyzlcol.column as u32, + self.pos().offset - self.line_start.offset, + "between {:?} and {:?}", + std::str::from_utf8(&self.original_bytes[..self.pos().offset as usize]).unwrap(), + std::str::from_utf8(&self.original_bytes[self.pos().offset as usize..]).unwrap(), + ); + self.xyzlcol.column + } + #[must_use] pub fn advance(&self, offset: usize) -> State<'a> { let mut state = self.clone(); + // debug_assert!(!state.bytes[..offset].iter().any(|b| *b == b'\n')); state.bytes = &state.bytes[offset..]; state } #[must_use] pub fn advance_newline(&self) -> State<'a> { - let mut state = self.advance(1); + let mut state = self.clone(); + state.bytes = &state.bytes[1..]; state.line_start = state.pos(); state } From 70156b0a90e71756ada27cacf9c1235a9f19a128 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Fri, 24 Dec 2021 08:22:52 -0800 Subject: [PATCH 076/541] Simplify advancing --- compiler/can/src/expr.rs | 4 +- compiler/parse/src/blankspace.rs | 79 +++++++---------------- compiler/parse/src/expr.rs | 91 +++++++++++++-------------- compiler/parse/src/ident.rs | 75 +++++++++------------- compiler/parse/src/module.rs | 5 +- compiler/parse/src/number_literal.rs | 6 +- compiler/parse/src/parser.rs | 12 ++-- compiler/parse/src/pattern.rs | 20 +++--- compiler/parse/src/state.rs | 74 +++------------------- compiler/parse/src/string_literal.rs | 7 +-- compiler/parse/src/type_annotation.rs | 26 ++++---- compiler/parse/tests/test_parse.rs | 19 +----- compiler/region/src/all.rs | 6 +- reporting/src/error/canonicalize.rs | 12 ++-- reporting/src/error/parse.rs | 2 +- 15 files changed, 148 insertions(+), 290 deletions(-) diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 8b04496d60..846a064d5d 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -758,13 +758,13 @@ pub fn canonicalize_expr<'a>( let region1 = Region::new( *binop1_position, - binop1_position.bump_column(binop1.width()), + binop1_position.bump_column(binop1.width() as u32), ); let loc_binop1 = Loc::at(region1, *binop1); let region2 = Region::new( *binop2_position, - binop2_position.bump_column(binop2.width()), + binop2_position.bump_column(binop2.width() as u32), ); let loc_binop2 = Loc::at(region2, *binop2); diff --git a/compiler/parse/src/blankspace.rs b/compiler/parse/src/blankspace.rs index b7fb77742b..4be090bebb 100644 --- a/compiler/parse/src/blankspace.rs +++ b/compiler/parse/src/blankspace.rs @@ -1,7 +1,6 @@ use crate::ast::CommentOrNewline; use crate::ast::Spaceable; use crate::parser::{self, and, backtrackable, BadInputError, Parser, Progress::*}; -use crate::state::JustColumn; use crate::state::State; use bumpalo::collections::vec::Vec; use bumpalo::Bump; @@ -10,7 +9,7 @@ use roc_region::all::Position; pub fn space0_around_ee<'a, P, S, E>( parser: P, - min_indent: u16, + min_indent: u32, space_problem: fn(BadInputError, Position) -> E, indent_before_problem: fn(Position) -> E, indent_after_problem: fn(Position) -> E, @@ -36,7 +35,7 @@ where pub fn space0_before_optional_after<'a, P, S, E>( parser: P, - min_indent: u16, + min_indent: u32, space_problem: fn(BadInputError, Position) -> E, indent_before_problem: fn(Position) -> E, indent_after_problem: fn(Position) -> E, @@ -101,7 +100,7 @@ where pub fn space0_before_e<'a, P, S, E>( parser: P, - min_indent: u16, + min_indent: u32, space_problem: fn(BadInputError, Position) -> E, indent_problem: fn(Position) -> E, ) -> impl Parser<'a, Loc, E> @@ -128,7 +127,7 @@ where pub fn space0_after_e<'a, P, S, E>( parser: P, - min_indent: u16, + min_indent: u32, space_problem: fn(BadInputError, Position) -> E, indent_problem: fn(Position) -> E, ) -> impl Parser<'a, Loc, E> @@ -154,14 +153,14 @@ where } pub fn check_indent<'a, E>( - min_indent: u16, + min_indent: u32, indent_problem: fn(Position) -> E, ) -> impl Parser<'a, (), E> where E: 'a, { move |_, state: State<'a>| { - if state.xyzlcol.column >= min_indent { + if state.column() >= min_indent { Ok((NoProgress, (), state)) } else { Err((NoProgress, indent_problem(state.pos()), state)) @@ -170,7 +169,7 @@ where } pub fn space0_e<'a, E>( - min_indent: u16, + min_indent: u32, space_problem: fn(BadInputError, Position) -> E, indent_problem: fn(Position) -> E, ) -> impl Parser<'a, &'a [CommentOrNewline<'a>], E> @@ -182,7 +181,7 @@ where #[inline(always)] fn spaces_help_help<'a, E>( - min_indent: u16, + min_indent: u32, space_problem: fn(BadInputError, Position) -> E, indent_problem: fn(Position) -> E, ) -> impl Parser<'a, &'a [CommentOrNewline<'a>], E> @@ -191,17 +190,10 @@ where { use SpaceState::*; - move |arena, mut state: State<'a>| { + move |arena, state: State<'a>| { let comments_and_newlines = Vec::new_in(arena); - let col = state.xyzlcol; - match eat_spaces(state.clone(), false, col, comments_and_newlines) { - HasTab(xyzlcol, mut state) => { - // there was a tab character - state.xyzlcol = xyzlcol; - // TODO: it _seems_ like if we're changing the line/column, we should also be - // advancing the state by the corresponding number of bytes. - // Not doing this is likely a bug! - // state = state.advance(); + match eat_spaces(state.clone(), false, comments_and_newlines) { + HasTab(state) => { Err(( MadeProgress, space_problem(BadInputError::HasTab, state.pos()), @@ -209,7 +201,6 @@ where )) } Good { - xyzcol: pos, state: mut new_state, multiline, comments_and_newlines, @@ -219,22 +210,14 @@ where } else if multiline { // we parsed at least one newline - new_state.indent_column = pos.column; - - if pos.column >= min_indent { - new_state.xyzlcol = pos; - // state = state.advance(state.bytes().len() - new_state.bytes().len()); - // assert_eq!(state.bytes().len(), new_state.bytes().len()); + new_state.indent_column = new_state.column(); + if new_state.column() >= min_indent { Ok((MadeProgress, comments_and_newlines.into_bump_slice(), new_state)) } else { Err((MadeProgress, indent_problem(state.pos()), state)) } } else { - new_state.xyzlcol.column = pos.column; - // state = state.advance(state.bytes().len() - new_state.bytes().len()); - // assert_eq!(state.bytes().len(), new_state.bytes().len()); - Ok((MadeProgress, comments_and_newlines.into_bump_slice(), new_state)) } } @@ -244,18 +227,16 @@ where enum SpaceState<'a> { Good { - xyzcol: JustColumn, state: State<'a>, multiline: bool, comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, }, - HasTab(JustColumn, State<'a>), + HasTab(State<'a>), } fn eat_spaces<'a>( mut state: State<'a>, mut multiline: bool, - mut xyzlcol: JustColumn, mut comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, ) -> SpaceState<'a> { use SpaceState::*; @@ -264,31 +245,27 @@ fn eat_spaces<'a>( match c { b' ' => { state = state.advance(1); - xyzlcol.column += 1; } b'\n' => { state = state.advance_newline(); multiline = true; - xyzlcol.column = 0; comments_and_newlines.push(CommentOrNewline::Newline); } b'\r' => { state = state.advance_newline(); } b'\t' => { - return HasTab(xyzlcol, state); + return HasTab(state); } b'#' => { - xyzlcol.column += 1; state = state.advance(1); - return eat_line_comment(state, multiline, xyzlcol, comments_and_newlines); + return eat_line_comment(state, multiline, comments_and_newlines); } _ => break, } } Good { - xyzcol: xyzlcol, state, multiline, comments_and_newlines, @@ -298,7 +275,6 @@ fn eat_spaces<'a>( fn eat_line_comment<'a>( mut state: State<'a>, mut multiline: bool, - mut xyzlcol: JustColumn, mut comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, ) -> SpaceState<'a> { use SpaceState::*; @@ -306,28 +282,24 @@ fn eat_line_comment<'a>( let is_doc_comment = if let Some(b'#') = state.bytes().get(0) { match state.bytes().get(1) { Some(b' ') => { - xyzlcol.column += 2; state = state.advance(2); true } Some(b'\n') => { // consume the second # and the \n - state = state.advance(2); + state = state.advance(1); + state = state.advance_newline(); comments_and_newlines.push(CommentOrNewline::DocComment("")); multiline = true; - xyzlcol.column = 0; - return eat_spaces(state, multiline, xyzlcol, comments_and_newlines); + return eat_spaces(state, multiline, comments_and_newlines); } None => { // consume the second # - xyzlcol.column += 1; state = state.advance(1); - // pos = pos.bump_column(1); return Good { - xyzcol: xyzlcol, state, multiline, comments_and_newlines, @@ -341,13 +313,12 @@ fn eat_line_comment<'a>( }; let initial = state.bytes(); - let initial_column = xyzlcol.column; for c in state.bytes() { match c { - b'\t' => return HasTab(xyzlcol, state), + b'\t' => return HasTab(state), b'\n' => { - let delta = (xyzlcol.column - initial_column) as usize; + let delta = initial.len() - state.bytes().len(); let comment = unsafe { std::str::from_utf8_unchecked(&initial[..delta]) }; if is_doc_comment { @@ -357,22 +328,19 @@ fn eat_line_comment<'a>( } state = state.advance_newline(); multiline = true; - xyzlcol.column = 0; - return eat_spaces(state, multiline, xyzlcol, comments_and_newlines); + return eat_spaces(state, multiline, comments_and_newlines); } b'\r' => { state = state.advance_newline(); - xyzlcol.column += 1; } _ => { state = state.advance(1); - xyzlcol.column += 1; } } } // We made it to the end of the bytes. This means there's a comment without a trailing newline. - let delta = (xyzlcol.column - initial_column) as usize; + let delta = initial.len() - state.bytes().len(); let comment = unsafe { std::str::from_utf8_unchecked(&initial[..delta]) }; if is_doc_comment { @@ -382,7 +350,6 @@ fn eat_line_comment<'a>( } Good { - xyzcol: xyzlcol, state, multiline, comments_and_newlines, diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 9f0967f46e..f7b955f24a 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -11,7 +11,7 @@ use crate::parser::{ EPattern, ERecord, EString, EType, EWhen, Either, ParseResult, Parser, }; use crate::pattern::loc_closure_param; -use crate::state::{State, JustColumn}; +use crate::state::State; use crate::type_annotation; use bumpalo::collections::Vec; use bumpalo::Bump; @@ -31,7 +31,7 @@ fn expr_end<'a>() -> impl Parser<'a, (), EExpr<'a>> { } pub fn test_parse_expr<'a>( - min_indent: u16, + min_indent: u32, arena: &'a bumpalo::Bump, state: State<'a>, ) -> Result>, EExpr<'a>> { @@ -75,13 +75,13 @@ impl Default for ExprParseOptions { } } -pub fn expr_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EExpr<'a>> { +pub fn expr_help<'a>(min_indent: u32) -> impl Parser<'a, Expr<'a>, EExpr<'a>> { move |arena, state: State<'a>| { parse_loc_expr(min_indent, arena, state).map(|(a, b, c)| (a, b.value, c)) } } -fn loc_expr_in_parens_help<'a>(min_indent: u16) -> impl Parser<'a, Loc>, EInParens<'a>> { +fn loc_expr_in_parens_help<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EInParens<'a>> { move |arena, state| { let (_, loc_expr, state) = loc_expr_in_parens_help_help(min_indent).parse(arena, state)?; @@ -97,7 +97,7 @@ fn loc_expr_in_parens_help<'a>(min_indent: u16) -> impl Parser<'a, Loc> } fn loc_expr_in_parens_help_help<'a>( - min_indent: u16, + min_indent: u32, ) -> impl Parser<'a, Loc>, EInParens<'a>> { between!( word1(b'(', EInParens::Open), @@ -114,7 +114,7 @@ fn loc_expr_in_parens_help_help<'a>( ) } -fn loc_expr_in_parens_etc_help<'a>(min_indent: u16) -> impl Parser<'a, Loc>, EExpr<'a>> { +fn loc_expr_in_parens_etc_help<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EExpr<'a>> { move |arena, state: State<'a>| { let parser = loc!(and!( specialize(EExpr::InParens, loc_expr_in_parens_help(min_indent)), @@ -189,7 +189,7 @@ fn record_field_access<'a>() -> impl Parser<'a, &'a str, EExpr<'a>> { /// In some contexts we want to parse the `_` as an expression, so it can then be turned into a /// pattern later fn parse_loc_term_or_underscore<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, arena: &'a Bump, state: State<'a>, @@ -211,7 +211,7 @@ fn parse_loc_term_or_underscore<'a>( } fn parse_loc_term<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, arena: &'a Bump, state: State<'a>, @@ -249,7 +249,7 @@ fn underscore_expression<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> { } fn loc_possibly_negative_or_negated_term<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, ) -> impl Parser<'a, Loc>, EExpr<'a>> { one_of![ @@ -297,8 +297,7 @@ fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> { if state.bytes().starts_with(b"-") && !followed_by_whitespace { // the negate is only unary if it is not followed by whitespace - let mut state = state.advance(1); - state.xyzlcol.column += 1; + let state = state.advance(1); Ok((MadeProgress, (), state)) } else { // this is not a negated expression @@ -308,9 +307,9 @@ fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> { } fn parse_expr_start<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, - start_column: u16, + start_column: u32, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Loc>, EExpr<'a>> { @@ -329,9 +328,9 @@ fn parse_expr_start<'a>( } fn parse_expr_operator_chain<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, - start_column: u16, + start_column: u32, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { @@ -777,7 +776,7 @@ struct DefState<'a> { fn parse_defs_end<'a>( options: ExprParseOptions, - start_column: u16, + start_column: u32, mut def_state: DefState<'a>, arena: &'a Bump, state: State<'a>, @@ -892,7 +891,7 @@ fn parse_defs_end<'a>( fn parse_defs_expr<'a>( options: ExprParseOptions, - start_column: u16, + start_column: u32, def_state: DefState<'a>, arena: &'a Bump, state: State<'a>, @@ -931,9 +930,9 @@ fn parse_defs_expr<'a>( } fn parse_expr_operator<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, - start_column: u16, + start_column: u32, mut expr_state: ExprState<'a>, loc_op: Loc, arena: &'a Bump, @@ -1220,9 +1219,9 @@ fn parse_expr_operator<'a>( } fn parse_expr_end<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, - start_column: u16, + start_column: u32, mut expr_state: ExprState<'a>, arena: &'a Bump, state: State<'a>, @@ -1280,7 +1279,6 @@ fn parse_expr_end<'a>( // try multi-backpassing if options.accept_multi_backpassing && state.bytes().starts_with(b",") { state = state.advance(1); - state.xyzlcol.column += 1; let (_, mut patterns, state) = specialize_ref( EExpr::Pattern, @@ -1354,7 +1352,7 @@ fn parse_expr_end<'a>( } pub fn parse_loc_expr<'a>( - min_indent: u16, + min_indent: u32, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Loc>, EExpr<'a>> { @@ -1370,7 +1368,7 @@ pub fn parse_loc_expr<'a>( } pub fn parse_loc_expr_no_multi_backpassing<'a>( - min_indent: u16, + min_indent: u32, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Loc>, EExpr<'a>> { @@ -1386,7 +1384,7 @@ pub fn parse_loc_expr_no_multi_backpassing<'a>( } fn parse_loc_expr_with_options<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, arena: &'a Bump, state: State<'a>, @@ -1529,7 +1527,7 @@ fn assigned_expr_field_to_pattern_help<'a>( }) } -pub fn defs<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Loc>>, EExpr<'a>> { +pub fn defs<'a>(min_indent: u32) -> impl Parser<'a, Vec<'a, Loc>>, EExpr<'a>> { move |arena, state: State<'a>| { let def_state = DefState { defs: Vec::new_in(arena), @@ -1583,7 +1581,7 @@ pub fn defs<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Loc>>, EExpr // PARSER HELPERS fn closure_help<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, ) -> impl Parser<'a, Expr<'a>, ELambda<'a>> { map_with_arena!( @@ -1636,7 +1634,7 @@ mod when { /// Parser for when expressions. pub fn expr_help<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, ) -> impl Parser<'a, Expr<'a>, EWhen<'a>> { then( @@ -1680,7 +1678,7 @@ mod when { } /// Parsing when with indentation. - fn when_with_indent<'a>() -> impl Parser<'a, u16, EWhen<'a>> { + fn when_with_indent<'a>() -> impl Parser<'a, u32, EWhen<'a>> { move |arena, state: State<'a>| { parser::keyword_e(keyword::WHEN, EWhen::When) .parse(arena, state) @@ -1689,7 +1687,7 @@ mod when { } fn branches<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, ) -> impl Parser<'a, Vec<'a, &'a WhenBranch<'a>>, EWhen<'a>> { move |arena, state: State<'a>| { @@ -1776,10 +1774,10 @@ mod when { /// Parsing alternative patterns in when branches. fn branch_alternatives<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, - pattern_indent_level: Option, - ) -> impl Parser<'a, ((u16, Vec<'a, Loc>>), Option>>), EWhen<'a>> { + pattern_indent_level: Option, + ) -> impl Parser<'a, ((u32, Vec<'a, Loc>>), Option>>), EWhen<'a>> { let options = ExprParseOptions { check_for_arrow: false, ..options @@ -1809,7 +1807,7 @@ mod when { } fn branch_single_alternative<'a>( - min_indent: u16, + min_indent: u32, ) -> impl Parser<'a, Loc>, EWhen<'a>> { move |arena, state| { let (_, spaces, state) = @@ -1839,9 +1837,9 @@ mod when { } fn branch_alternatives_help<'a>( - min_indent: u16, - pattern_indent_level: Option, - ) -> impl Parser<'a, (u16, Vec<'a, Loc>>), EWhen<'a>> { + min_indent: u32, + pattern_indent_level: Option, + ) -> impl Parser<'a, (u32, Vec<'a, Loc>>), EWhen<'a>> { move |arena, state: State<'a>| { let initial = state.clone(); @@ -1905,7 +1903,7 @@ mod when { } /// Parsing the righthandside of a branch in a when conditional. - fn branch_result<'a>(indent: u16) -> impl Parser<'a, Loc>, EWhen<'a>> { + fn branch_result<'a>(indent: u32) -> impl Parser<'a, Loc>, EWhen<'a>> { skip_first!( word2(b'-', b'>', EWhen::Arrow), space0_before_e( @@ -1920,7 +1918,7 @@ mod when { } } -fn if_branch<'a>(min_indent: u16) -> impl Parser<'a, (Loc>, Loc>), EIf<'a>> { +fn if_branch<'a>(min_indent: u32) -> impl Parser<'a, (Loc>, Loc>), EIf<'a>> { move |arena, state| { // NOTE: only parse spaces before the expression let (_, cond, state) = space0_around_ee( @@ -1960,7 +1958,7 @@ fn if_branch<'a>(min_indent: u16) -> impl Parser<'a, (Loc>, Loc( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, ) -> impl Parser<'a, Expr<'a>, EExpect<'a>> { move |arena: &'a Bump, state: State<'a>| { @@ -1999,7 +1997,7 @@ fn expect_help<'a>( } fn if_expr_help<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, ) -> impl Parser<'a, Expr<'a>, EIf<'a>> { move |arena: &'a Bump, state| { @@ -2074,7 +2072,7 @@ fn assign_or_destructure_identifier<'a>() -> impl Parser<'a, Ident<'a>, EExpr<'a } #[allow(dead_code)] -fn with_indent<'a, E, T, P>(parser: P) -> impl Parser<'a, u16, E> +fn with_indent<'a, E, T, P>(parser: P) -> impl Parser<'a, u32, E> where P: Parser<'a, T, E>, E: 'a, @@ -2120,7 +2118,7 @@ fn ident_to_expr<'a>(arena: &'a Bump, src: Ident<'a>) -> Expr<'a> { } } -fn list_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EList<'a>> { +fn list_literal_help<'a>(min_indent: u32) -> impl Parser<'a, Expr<'a>, EList<'a>> { move |arena, state| { let (_, elements, state) = collection_trailing_sep_e!( word1(b'[', EList::Open), @@ -2146,7 +2144,7 @@ fn list_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EList<'a> } fn record_field_help<'a>( - min_indent: u16, + min_indent: u32, ) -> impl Parser<'a, AssignedField<'a, Expr<'a>>, ERecord<'a>> { use AssignedField::*; @@ -2210,7 +2208,7 @@ fn record_updateable_identifier<'a>() -> impl Parser<'a, Expr<'a>, ERecord<'a>> } fn record_help<'a>( - min_indent: u16, + min_indent: u32, ) -> impl Parser< 'a, ( @@ -2267,7 +2265,7 @@ fn record_help<'a>( ) } -fn record_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EExpr<'a>> { +fn record_literal_help<'a>(min_indent: u32) -> impl Parser<'a, Expr<'a>, EExpr<'a>> { then( loc!(specialize(EExpr::Record, record_help(min_indent))), move |arena, state, _, loc_record| { @@ -2375,7 +2373,6 @@ where macro_rules! good { ($op:expr, $width:expr) => {{ - state.xyzlcol.column += $width; state = state.advance($width); Ok((MadeProgress, $op, state)) diff --git a/compiler/parse/src/ident.rs b/compiler/parse/src/ident.rs index 3effc398db..773d86b4f5 100644 --- a/compiler/parse/src/ident.rs +++ b/compiler/parse/src/ident.rs @@ -68,10 +68,7 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, ()> { Err((NoProgress, (), state)) } else { let width = ident.len(); - match state.advance_without_indenting_ee(width, |_| ()) { - Ok(state) => Ok((MadeProgress, ident, state)), - Err(bad) => Err(bad), - } + Ok((MadeProgress, ident, state.advance(width))) } } } @@ -85,10 +82,7 @@ pub fn tag_name<'a>() -> impl Parser<'a, &'a str, ()> { Err(_) => Err((MadeProgress, (), state)), Ok(ident) => { let width = ident.len(); - match state.advance_without_indenting_ee(width, |_| ()) { - Ok(state) => Ok((MadeProgress, ident, state)), - Err(bad) => Err(bad), - } + Ok((MadeProgress, ident, state.advance(width))) } } } else { @@ -107,10 +101,7 @@ pub fn uppercase_ident<'a>() -> impl Parser<'a, &'a str, ()> { Err(progress) => Err((progress, (), state)), Ok(ident) => { let width = ident.len(); - match state.advance_without_indenting_ee(width, |_| ()) { - Ok(state) => Ok((MadeProgress, ident, state)), - Err(bad) => Err(bad), - } + Ok((MadeProgress, ident, state.advance(width))) } } } @@ -123,10 +114,7 @@ pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str, ()> { Err((MadeProgress, (), state)) } else { let width = ident.len(); - match state.advance_without_indenting_ee(width, |_| ()) { - Ok(state) => Ok((MadeProgress, ident, state)), - Err(bad) => Err(bad), - } + Ok((MadeProgress, ident, state.advance(width))) } } } @@ -134,9 +122,7 @@ pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str, ()> { macro_rules! advance_state { ($state:expr, $n:expr) => { - $state.advance_without_indenting_ee($n, |pos| { - BadIdent::Space(crate::parser::BadInputError::LineTooLong, pos) - }) + Ok($state.advance($n)) }; } @@ -177,9 +163,7 @@ fn malformed_identifier<'a>( let delta = initial_bytes.len() - state.bytes().len(); let parsed_str = unsafe { std::str::from_utf8_unchecked(&initial_bytes[..chomped + delta]) }; - state = state.advance_without_indenting_ee(chomped, |pos| { - EExpr::Space(crate::parser::BadInputError::LineTooLong, pos) - })?; + state = state.advance(chomped); Ok((MadeProgress, Ident::Malformed(parsed_str, problem), state)) } @@ -292,7 +276,7 @@ fn chomp_private_tag(buffer: &[u8], pos: Position) -> Result<&str, BadIdent> { let width = 1 + name.len(); if let Ok(('.', _)) = char::from_utf8_slice_start(&buffer[width..]) { - Err(BadIdent::BadPrivateTag(pos.bump_column(width as u16))) + Err(BadIdent::BadPrivateTag(pos.bump_column(width as u32))) } else { let value = unsafe { std::str::from_utf8_unchecked(&buffer[..width]) }; Ok(value) @@ -306,7 +290,7 @@ fn chomp_identifier_chain<'a>( arena: &'a Bump, buffer: &'a [u8], pos: Position, -) -> Result<(u16, Ident<'a>), (u16, BadIdent)> { +) -> Result<(u32, Ident<'a>), (u32, BadIdent)> { use encode_unicode::CharExt; let first_is_uppercase; @@ -318,7 +302,7 @@ fn chomp_identifier_chain<'a>( Ok(accessor) => { let bytes_parsed = 1 + accessor.len(); - return Ok((bytes_parsed as u16, Ident::AccessorFunction(accessor))); + return Ok((bytes_parsed as u32, Ident::AccessorFunction(accessor))); } Err(fail) => return Err((1, fail)), }, @@ -326,7 +310,7 @@ fn chomp_identifier_chain<'a>( Ok(tagname) => { let bytes_parsed = tagname.len(); - return Ok((bytes_parsed as u16, Ident::PrivateTag(tagname))); + return Ok((bytes_parsed as u32, Ident::PrivateTag(tagname))); } Err(fail) => return Err((1, fail)), }, @@ -381,19 +365,19 @@ fn chomp_identifier_chain<'a>( parts: parts.into_bump_slice(), }; - Ok((chomped as u16, ident)) + Ok((chomped as u32, ident)) } Err(0) if !module_name.is_empty() => Err(( - chomped as u16, - BadIdent::QualifiedTag(pos.bump_column(chomped as u16)), + chomped as u32, + BadIdent::QualifiedTag(pos.bump_column(chomped as u32)), )), Err(1) if parts.is_empty() => Err(( - chomped as u16 + 1, - BadIdent::WeirdDotQualified(pos.bump_column(chomped as u16 + 1)), + chomped as u32 + 1, + BadIdent::WeirdDotQualified(pos.bump_column(chomped as u32 + 1)), )), Err(width) => Err(( - chomped as u16 + width, - BadIdent::WeirdDotAccess(pos.bump_column(chomped as u16 + width)), + chomped as u32 + width, + BadIdent::WeirdDotAccess(pos.bump_column(chomped as u32 + width)), )), } } else if let Ok(('_', _)) = char::from_utf8_slice_start(&buffer[chomped..]) { @@ -401,13 +385,13 @@ fn chomp_identifier_chain<'a>( // but still parse them (and generate a malformed identifier) // to give good error messages for this case Err(( - chomped as u16 + 1, - BadIdent::Underscore(pos.bump_column(chomped as u16 + 1)), + chomped as u32 + 1, + BadIdent::Underscore(pos.bump_column(chomped as u32 + 1)), )) } else if first_is_uppercase { // just one segment, starting with an uppercase letter; that's a global tag let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; - Ok((chomped as u16, Ident::GlobalTag(value))) + Ok((chomped as u32, Ident::GlobalTag(value))) } else { // just one segment, starting with a lowercase letter; that's a normal identifier let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; @@ -415,11 +399,11 @@ fn chomp_identifier_chain<'a>( module_name: "", parts: arena.alloc([value]), }; - Ok((chomped as u16, ident)) + Ok((chomped as u32, ident)) } } -fn chomp_module_chain(buffer: &[u8]) -> Result { +fn chomp_module_chain(buffer: &[u8]) -> Result { let mut chomped = 0; while let Some(b'.') = buffer.get(chomped) { @@ -438,7 +422,7 @@ fn chomp_module_chain(buffer: &[u8]) -> Result { if chomped == 0 { Err(NoProgress) } else { - Ok(chomped as u16) + Ok(chomped as u32) } } @@ -446,10 +430,7 @@ pub fn concrete_type<'a>() -> impl Parser<'a, (&'a str, &'a str), ()> { move |_, state: State<'a>| match chomp_concrete_type(state.bytes()) { Err(progress) => Err((progress, (), state)), Ok((module_name, type_name, width)) => { - match state.advance_without_indenting_ee(width, |_| ()) { - Ok(state) => Ok((MadeProgress, (module_name, type_name), state)), - Err(bad) => Err(bad), - } + Ok((MadeProgress, (module_name, type_name), state.advance(width))) } } } @@ -489,7 +470,7 @@ fn chomp_concrete_type(buffer: &[u8]) -> Result<(&str, &str, usize), Progress> { } } -fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, &'a str>) -> Result { +fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, &'a str>) -> Result { let mut chomped = 0; while let Some(b'.') = buffer.get(chomped) { @@ -505,16 +486,16 @@ fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, &'a str>) -> Res chomped += name.len() + 1; } - Err(_) => return Err(chomped as u16 + 1), + Err(_) => return Err(chomped as u32 + 1), }, - None => return Err(chomped as u16 + 1), + None => return Err(chomped as u32 + 1), } } if chomped == 0 { Err(0) } else { - Ok(chomped as u16) + Ok(chomped as u32) } } diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index 95db104827..7ebe8c77b0 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -167,7 +167,6 @@ fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>, ()> { |_, mut state: State<'a>| match chomp_module_name(state.bytes()) { Ok(name) => { let width = name.len(); - state.xyzlcol.column += width as u16; state = state.advance(width); Ok((MadeProgress, ModuleName::new(name), state)) @@ -436,7 +435,7 @@ fn platform_requires<'a>() -> impl Parser<'a, PlatformRequires<'a>, ERequires<'a #[inline(always)] fn requires_rigids<'a>( - min_indent: u16, + min_indent: u32, ) -> impl Parser<'a, Collection<'a, Loc>>>, ERequires<'a>> { collection_trailing_sep_e!( word1(b'{', ERequires::ListStart), @@ -514,7 +513,7 @@ fn exposes_values<'a>() -> impl Parser< } fn spaces_around_keyword<'a, E>( - min_indent: u16, + min_indent: u32, keyword: &'static str, expectation: fn(Position) -> E, space_problem: fn(crate::parser::BadInputError, Position) -> E, diff --git a/compiler/parse/src/number_literal.rs b/compiler/parse/src/number_literal.rs index 38d503ccbb..b347dd884d 100644 --- a/compiler/parse/src/number_literal.rs +++ b/compiler/parse/src/number_literal.rs @@ -67,9 +67,7 @@ fn chomp_number_base<'a>( let string = unsafe { std::str::from_utf8_unchecked(&bytes[..chomped]) }; - let new = state.advance_without_indenting_ee(chomped + 2 + is_negative as usize, |_| { - ENumber::LineTooLong - })?; + let new = state.advance(chomped + 2 + is_negative as usize); Ok(( Progress::MadeProgress, @@ -103,7 +101,7 @@ fn chomp_number_dec<'a>( unsafe { std::str::from_utf8_unchecked(&state.bytes()[0..chomped + is_negative as usize]) }; let new = state - .advance_without_indenting_ee(chomped + is_negative as usize, |_| ENumber::LineTooLong)?; + .advance(chomped + is_negative as usize); Ok(( Progress::MadeProgress, diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index 568d73e1d9..666a5becd3 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -397,7 +397,7 @@ pub enum EWhen<'a> { IndentArrow(Position), IndentBranch(Position), IndentIfGuard(Position), - PatternAlignment(u16, Position), + PatternAlignment(u32, Position), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -730,12 +730,10 @@ where // to prevent treating `whence` or `iffy` as keywords match state.bytes().get(width) { Some(next) if *next == b' ' || *next == b'#' || *next == b'\n' => { - state.xyzlcol.column += width as u16; state = state.advance(width); Ok((MadeProgress, (), state)) } None => { - state.xyzlcol.column += width as u16; state = state.advance(width); Ok((MadeProgress, (), state)) } @@ -1289,8 +1287,7 @@ where move |_arena: &'a Bump, state: State<'a>| match state.bytes().get(0) { Some(x) if *x == word => { - let mut state = state.advance(1); - state.xyzlcol.column += 1; + let state = state.advance(1); Ok((MadeProgress, (), state)) } _ => Err((NoProgress, to_error(state.pos()), state)), @@ -1309,8 +1306,7 @@ where move |_arena: &'a Bump, state: State<'a>| { if state.bytes().starts_with(&needle) { - let mut state = state.advance(2); - state.xyzlcol.column += 2; + let state = state.advance(2); Ok((MadeProgress, (), state)) } else { Err((NoProgress, to_error(state.pos()), state)) @@ -1318,7 +1314,7 @@ where } } -pub fn check_indent<'a, TE, E>(min_indent: u16, to_problem: TE) -> impl Parser<'a, (), E> +pub fn check_indent<'a, TE, E>(min_indent: u32, to_problem: TE) -> impl Parser<'a, (), E> where TE: Fn(Position) -> E, E: 'a, diff --git a/compiler/parse/src/pattern.rs b/compiler/parse/src/pattern.rs index 2176d835fb..c76d115fc9 100644 --- a/compiler/parse/src/pattern.rs +++ b/compiler/parse/src/pattern.rs @@ -24,14 +24,14 @@ pub enum PatternType { WhenBranch, } -pub fn loc_closure_param<'a>(min_indent: u16) -> impl Parser<'a, Loc>, EPattern<'a>> { +pub fn loc_closure_param<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EPattern<'a>> { move |arena, state| parse_closure_param(arena, state, min_indent) } fn parse_closure_param<'a>( arena: &'a Bump, state: State<'a>, - min_indent: u16, + min_indent: u32, ) -> ParseResult<'a, Loc>, EPattern<'a>> { one_of!( // An ident is the most common param, e.g. \foo -> ... @@ -50,7 +50,7 @@ fn parse_closure_param<'a>( .parse(arena, state) } -pub fn loc_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Loc>, EPattern<'a>> { +pub fn loc_pattern_help<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EPattern<'a>> { one_of!( specialize(EPattern::PInParens, loc_pattern_in_parens_help(min_indent)), loc!(underscore_pattern_help()), @@ -65,12 +65,12 @@ pub fn loc_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Loc> } fn loc_tag_pattern_args_help<'a>( - min_indent: u16, + min_indent: u32, ) -> impl Parser<'a, Vec<'a, Loc>>, EPattern<'a>> { zero_or_more!(loc_tag_pattern_arg(min_indent)) } -fn loc_tag_pattern_arg<'a>(min_indent: u16) -> impl Parser<'a, Loc>, EPattern<'a>> { +fn loc_tag_pattern_arg<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EPattern<'a>> { // Don't parse operators, because they have a higher precedence than function application. // If we encounter one, we're done parsing function args! move |arena, state| { @@ -95,7 +95,7 @@ fn loc_tag_pattern_arg<'a>(min_indent: u16) -> impl Parser<'a, Loc>, } fn loc_parse_tag_pattern_arg<'a>( - min_indent: u16, + min_indent: u32, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Loc>, EPattern<'a>> { @@ -115,7 +115,7 @@ fn loc_parse_tag_pattern_arg<'a>( } fn loc_pattern_in_parens_help<'a>( - min_indent: u16, + min_indent: u32, ) -> impl Parser<'a, Loc>, PInParens<'a>> { between!( word1(b'(', PInParens::Open), @@ -162,7 +162,7 @@ fn string_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> { } fn loc_ident_pattern_help<'a>( - min_indent: u16, + min_indent: u32, can_have_arguments: bool, ) -> impl Parser<'a, Loc>, EPattern<'a>> { move |arena: &'a Bump, state: State<'a>| { @@ -310,7 +310,7 @@ fn lowercase_ident_pattern<'a>( } #[inline(always)] -fn record_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, PRecord<'a>> { +fn record_pattern_help<'a>(min_indent: u32) -> impl Parser<'a, Pattern<'a>, PRecord<'a>> { move |arena, state| { let (_, fields, state) = collection_trailing_sep_e!( // word1_check_indent!(b'{', PRecord::Open, min_indent, PRecord::IndentOpen), @@ -333,7 +333,7 @@ fn record_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, PRec } } -fn record_pattern_field<'a>(min_indent: u16) -> impl Parser<'a, Loc>, PRecord<'a>> { +fn record_pattern_field<'a>(min_indent: u32) -> impl Parser<'a, Loc>, PRecord<'a>> { use crate::parser::Either::*; move |arena, state: State<'a>| { diff --git a/compiler/parse/src/state.rs b/compiler/parse/src/state.rs index a2df6ea67a..49b65f890f 100644 --- a/compiler/parse/src/state.rs +++ b/compiler/parse/src/state.rs @@ -1,5 +1,4 @@ -use crate::parser::Progress::*; -use crate::parser::{BadInputError, Progress}; +use crate::parser::Progress; use bumpalo::Bump; use roc_region::all::{Position, Region}; use std::fmt; @@ -11,35 +10,23 @@ pub struct State<'a> { /// Beware: bytes[0] always points the the current byte the parser is examining. bytes: &'a [u8], - original_bytes: &'a [u8], - /// Length of the original input in bytes input_len: usize, + /// Position of the start of the current line line_start: Position, - /// Current position within the input (line/column) - pub xyzlcol: JustColumn, - /// Current indentation level, in columns /// (so no indent is col 1 - this saves an arithmetic operation.) - pub indent_column: u16, -} - - -#[derive(Clone, Copy)] -pub struct JustColumn { - pub column: u16, + pub indent_column: u32, } impl<'a> State<'a> { pub fn new(bytes: &'a [u8]) -> State<'a> { State { bytes, - original_bytes: bytes, input_len: bytes.len(), line_start: Position::zero(), - xyzlcol: JustColumn { column: 0 }, indent_column: 0, } } @@ -48,15 +35,8 @@ impl<'a> State<'a> { self.bytes } - pub fn column(&self) -> u16 { - assert_eq!( - self.xyzlcol.column as u32, - self.pos().offset - self.line_start.offset, - "between {:?} and {:?}", - std::str::from_utf8(&self.original_bytes[..self.pos().offset as usize]).unwrap(), - std::str::from_utf8(&self.original_bytes[self.pos().offset as usize..]).unwrap(), - ); - self.xyzlcol.column + pub fn column(&self) -> u32 { + self.pos().offset - self.line_start.offset } #[must_use] @@ -85,49 +65,11 @@ impl<'a> State<'a> { self.bytes.is_empty() } - /// Use advance_spaces to advance with indenting. - /// This assumes we are *not* advancing with spaces, or at least that - /// any spaces on the line were preceded by non-spaces - which would mean - /// they weren't eligible to indent anyway. - pub fn advance_without_indenting_e( - self, - quantity: usize, - to_error: TE, - ) -> Result - where - TE: Fn(BadInputError, Position) -> E, - { - self.advance_without_indenting_ee(quantity, |p| to_error(BadInputError::LineTooLong, p)) - } - - pub fn advance_without_indenting_ee( - self, - quantity: usize, - to_error: TE, - ) -> Result - where - TE: Fn(Position) -> E, - { - match (self.xyzlcol.column as usize).checked_add(quantity) { - Some(column_usize) if column_usize <= u16::MAX as usize => { - Ok(State { - bytes: &self.bytes[quantity..], - xyzlcol: JustColumn { - column: column_usize as u16, - }, - // Once we hit a nonspace character, we are no longer indenting. - ..self - }) - } - _ => Err((NoProgress, to_error(self.pos()), self)), - } - } - /// Returns a Region corresponding to the current state, but /// with the the end column advanced by the given amount. This is /// useful when parsing something "manually" (using input.chars()) /// and thus wanting a Region while not having access to loc(). - pub fn len_region(&self, length: u16) -> Region { + pub fn len_region(&self, length: u32) -> Region { Region::new( self.pos(), self.pos().bump_column(length), @@ -156,8 +98,8 @@ impl<'a> fmt::Debug for State<'a> { write!( f, - "\n\t(col): {},", - self.xyzlcol.column + "\n\t(offset): {:?},", + self.pos() )?; write!(f, "\n\tindent_column: {}", self.indent_column)?; write!(f, "\n}}") diff --git a/compiler/parse/src/string_literal.rs b/compiler/parse/src/string_literal.rs index c9a93ec371..cf2b4b5674 100644 --- a/compiler/parse/src/string_literal.rs +++ b/compiler/parse/src/string_literal.rs @@ -19,9 +19,7 @@ fn ascii_hex_digits<'a>() -> impl Parser<'a, &'a str, EString<'a>> { // We didn't find any hex digits! return Err((NoProgress, EString::CodePtEnd(state.pos()), state)); } else { - let state = state.advance_without_indenting_ee(buf.len(), |pos| { - EString::Space(BadInputError::LineTooLong, pos) - })?; + let state = state.advance(buf.len()); return Ok((MadeProgress, buf.into_bump_str(), state)); } @@ -33,8 +31,7 @@ fn ascii_hex_digits<'a>() -> impl Parser<'a, &'a str, EString<'a>> { macro_rules! advance_state { ($state:expr, $n:expr) => { - $state - .advance_without_indenting_ee($n, |pos| EString::Space(BadInputError::LineTooLong, pos)) + Ok($state.advance($n)) }; } diff --git a/compiler/parse/src/type_annotation.rs b/compiler/parse/src/type_annotation.rs index e6102eca08..a404f838c4 100644 --- a/compiler/parse/src/type_annotation.rs +++ b/compiler/parse/src/type_annotation.rs @@ -12,14 +12,14 @@ use bumpalo::Bump; use roc_region::all::{Loc, Position, Region}; pub fn located_help<'a>( - min_indent: u16, + min_indent: u32, is_trailing_comma_valid: bool, ) -> impl Parser<'a, Loc>, EType<'a>> { expression(min_indent, is_trailing_comma_valid) } #[inline(always)] -fn tag_union_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, ETypeTagUnion<'a>> { +fn tag_union_type<'a>(min_indent: u32) -> impl Parser<'a, TypeAnnotation<'a>, ETypeTagUnion<'a>> { move |arena, state| { let (_, tags, state) = collection_trailing_sep_e!( word1(b'[', ETypeTagUnion::Open), @@ -104,7 +104,7 @@ fn fail_type_start<'a, T: 'a>() -> impl Parser<'a, T, EType<'a>> { |_arena, state: State<'a>| Err((NoProgress, EType::TStart(state.pos()), state)) } -fn term<'a>(min_indent: u16) -> impl Parser<'a, Loc>, EType<'a>> { +fn term<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EType<'a>> { map_with_arena!( and!( one_of!( @@ -165,7 +165,7 @@ fn loc_inferred<'a>() -> impl Parser<'a, Loc>, EType<'a>> { }) } -fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Loc>, EType<'a>> { +fn loc_applied_arg<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EType<'a>> { use crate::ast::Spaceable; map_with_arena!( @@ -193,7 +193,7 @@ fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Loc( - min_indent: u16, + min_indent: u32, ) -> impl Parser<'a, Loc>, ETypeInParens<'a>> { between!( word1(b'(', ETypeInParens::Open), @@ -210,7 +210,7 @@ fn loc_type_in_parens<'a>( } #[inline(always)] -fn tag_type<'a>(min_indent: u16) -> impl Parser<'a, Tag<'a>, ETypeTagUnion<'a>> { +fn tag_type<'a>(min_indent: u32) -> impl Parser<'a, Tag<'a>, ETypeTagUnion<'a>> { move |arena, state: State<'a>| { let (_, name, state) = loc!(parse_tag_name(ETypeTagUnion::End)).parse(arena, state)?; @@ -245,7 +245,7 @@ where } fn record_type_field<'a>( - min_indent: u16, + min_indent: u32, ) -> impl Parser<'a, AssignedField<'a, TypeAnnotation<'a>>, ETypeRecord<'a>> { use crate::ident::lowercase_ident; use crate::parser::Either::*; @@ -323,7 +323,7 @@ fn record_type_field<'a>( } #[inline(always)] -fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, ETypeRecord<'a>> { +fn record_type<'a>(min_indent: u32) -> impl Parser<'a, TypeAnnotation<'a>, ETypeRecord<'a>> { use crate::type_annotation::TypeAnnotation::*; (move |arena, state| { @@ -352,7 +352,7 @@ fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, EType .trace("type_annotation:record_type") } -fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, EType<'a>> { +fn applied_type<'a>(min_indent: u32) -> impl Parser<'a, TypeAnnotation<'a>, EType<'a>> { map!( and!( specialize(EType::TApply, parse_concrete_type), @@ -379,13 +379,13 @@ fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, ETyp } fn loc_applied_args_e<'a>( - min_indent: u16, + min_indent: u32, ) -> impl Parser<'a, Vec<'a, Loc>>, EType<'a>> { zero_or_more!(loc_applied_arg(min_indent)) } fn expression<'a>( - min_indent: u16, + min_indent: u32, is_trailing_comma_valid: bool, ) -> impl Parser<'a, Loc>, EType<'a>> { (move |arena, state: State<'a>| { @@ -512,9 +512,7 @@ fn parse_concrete_type<'a>( let parsed_str = unsafe { std::str::from_utf8_unchecked(&initial_bytes[..chomped + delta]) }; - state = state.advance_without_indenting_ee(chomped, |pos| { - ETypeApply::Space(crate::parser::BadInputError::LineTooLong, pos) - })?; + state = state.advance(chomped); Ok((MadeProgress, TypeAnnotation::Malformed(parsed_str), state)) } diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 6521defad5..6276c73ead 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -23,7 +23,7 @@ mod test_parse { use roc_parse::parser::{Parser, SyntaxError}; use roc_parse::state::State; use roc_parse::test_helpers::parse_expr_with; - use roc_region::all::{Loc, Region, Position}; + use roc_region::all::{Loc, Region}; use roc_test_utils::assert_multiline_str_eq; use std::{f64, i64}; @@ -481,23 +481,6 @@ mod test_parse { assert_parsing_fails("", SyntaxError::Eof(Region::zero())); } - #[test] - fn first_line_too_long() { - let max_line_length = u16::MAX as usize; - - // the string literal "ZZZZZZZZZ" but with way more Zs - let too_long_str_body: String = (1..max_line_length) - .into_iter() - .map(|_| "Z".to_string()) - .collect(); - let too_long_str = format!("\"{}\"", too_long_str_body); - - // Make sure it's longer than our maximum line length - assert_eq!(too_long_str.len(), max_line_length + 1); - - assert_parsing_fails(&too_long_str, SyntaxError::LineTooLong(Position::zero())); - } - #[quickcheck] fn all_i64_values_parse(num: i64) { assert_parses_to(num.to_string().as_str(), Num(num.to_string().as_str())); diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index 339e0b5344..9496d5ae6c 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -119,14 +119,14 @@ impl Position { } #[must_use] - pub const fn bump_column(self, count: u16) -> Self { + pub const fn bump_column(self, count: u32) -> Self { Self { offset: self.offset + count as u32, } } #[must_use] - pub fn bump_invisible(self, count: u16) -> Self { + pub fn bump_invisible(self, count: u32) -> Self { Self { offset: self.offset + count as u32, } @@ -140,7 +140,7 @@ impl Position { } #[must_use] - pub const fn sub(self, count: u16) -> Self { + pub const fn sub(self, count: u32) -> Self { Self { offset: self.offset - count as u32, } diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index 3533777f65..317338c65a 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -711,9 +711,9 @@ fn to_bad_ident_pattern_report<'b>( #[derive(Debug)] enum BadIdentNext<'a> { - LowercaseAccess(u16), - UppercaseAccess(u16), - NumberAccess(u16), + LowercaseAccess(u32), + UppercaseAccess(u32), + NumberAccess(u32), Keyword(&'a str), DanglingDot, Other(Option), @@ -737,13 +737,13 @@ fn what_is_next<'a>(source_lines: &'a [&'a str], pos: LineColumn) -> BadIdentNex None => BadIdentNext::Other(None), Some('.') => match it.next() { Some(c) if c.is_lowercase() => { - BadIdentNext::LowercaseAccess(2 + till_whitespace(it) as u16) + BadIdentNext::LowercaseAccess(2 + till_whitespace(it) as u32) } Some(c) if c.is_uppercase() => { - BadIdentNext::UppercaseAccess(2 + till_whitespace(it) as u16) + BadIdentNext::UppercaseAccess(2 + till_whitespace(it) as u32) } Some(c) if c.is_ascii_digit() => { - BadIdentNext::NumberAccess(2 + till_whitespace(it) as u16) + BadIdentNext::NumberAccess(2 + till_whitespace(it) as u32) } _ => BadIdentNext::DanglingDot, }, diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index c40bd88ca4..0e0366b069 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -236,7 +236,7 @@ fn to_expr_report<'a>( EExpr::BadOperator(op, pos) => { let surroundings = Region::new(start, *pos); - let region = Region::new(*pos, pos.bump_column(op.len() as u16)); + let region = Region::new(*pos, pos.bump_column(op.len() as u32)); let suggestion = match *op { "|" => vec![ From cb8cf4459627abe19d742fd3e95db75621eec97b Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Fri, 24 Dec 2021 11:59:11 -0800 Subject: [PATCH 077/541] fmt --- compiler/can/tests/test_can.rs | 40 +--- compiler/load/src/file.rs | 10 +- compiler/parse/src/blankspace.rs | 24 +- compiler/parse/src/expr.rs | 13 +- compiler/parse/src/number_literal.rs | 3 +- compiler/parse/src/state.rs | 11 +- compiler/region/src/all.rs | 86 +++---- reporting/src/error/canonicalize.rs | 56 ++++- reporting/src/error/mono.rs | 7 +- reporting/src/error/parse.rs | 337 +++++++++++++++------------ reporting/src/error/type.rs | 17 +- reporting/tests/test_reporting.rs | 4 +- 12 files changed, 336 insertions(+), 272 deletions(-) diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index e53a93546a..2fa00c5f5e 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -943,14 +943,8 @@ mod test_can { let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![CycleEntry { symbol: interns.symbol(home, "x".into()), - symbol_region: Region::new( - Position::new(0), - Position::new(1), - ), - expr_region: Region::new( - Position::new(4), - Position::new(5), - ), + symbol_region: Region::new(Position::new(0), Position::new(1)), + expr_region: Region::new(Position::new(4), Position::new(5)), }])); assert_eq!(is_circular_def, true); @@ -980,36 +974,18 @@ mod test_can { let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![ CycleEntry { symbol: interns.symbol(home, "x".into()), - symbol_region: Region::new( - Position::new(0), - Position::new(1), - ), - expr_region: Region::new( - Position::new(4), - Position::new(5), - ), + symbol_region: Region::new(Position::new(0), Position::new(1)), + expr_region: Region::new(Position::new(4), Position::new(5)), }, CycleEntry { symbol: interns.symbol(home, "y".into()), - symbol_region: Region::new( - Position::new(6), - Position::new(7), - ), - expr_region: Region::new( - Position::new(10), - Position::new(11), - ), + symbol_region: Region::new(Position::new(6), Position::new(7)), + expr_region: Region::new(Position::new(10), Position::new(11)), }, CycleEntry { symbol: interns.symbol(home, "z".into()), - symbol_region: Region::new( - Position::new(12), - Position::new(13), - ), - expr_region: Region::new( - Position::new(16), - Position::new(17), - ), + symbol_region: Region::new(Position::new(12), Position::new(13)), + expr_region: Region::new(Position::new(16), Position::new(17)), }, ])); diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 1b7d720c45..1dda5322db 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -28,7 +28,7 @@ use roc_parse::header::PackageName; use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent}; use roc_parse::module::module_defs; use roc_parse::parser::{ParseProblem, Parser, SyntaxError}; -use roc_region::all::{Loc, Region, LineInfo}; +use roc_region::all::{LineInfo, Loc, Region}; use roc_solve::module::SolvedModule; use roc_solve::solve; use roc_types::solved_types::Solved; @@ -4334,7 +4334,13 @@ fn to_parse_problem_report<'a>( let lines = LineInfo::new(src); - let report = parse_problem(&alloc, &lines, problem.filename.clone(), starting_line, problem); + let report = parse_problem( + &alloc, + &lines, + problem.filename.clone(), + starting_line, + problem, + ); let mut buf = String::new(); let palette = DEFAULT_PALETTE; diff --git a/compiler/parse/src/blankspace.rs b/compiler/parse/src/blankspace.rs index 4be090bebb..2f91aa648b 100644 --- a/compiler/parse/src/blankspace.rs +++ b/compiler/parse/src/blankspace.rs @@ -193,13 +193,11 @@ where move |arena, state: State<'a>| { let comments_and_newlines = Vec::new_in(arena); match eat_spaces(state.clone(), false, comments_and_newlines) { - HasTab(state) => { - Err(( - MadeProgress, - space_problem(BadInputError::HasTab, state.pos()), - state, - )) - } + HasTab(state) => Err(( + MadeProgress, + space_problem(BadInputError::HasTab, state.pos()), + state, + )), Good { state: mut new_state, multiline, @@ -213,12 +211,20 @@ where new_state.indent_column = new_state.column(); if new_state.column() >= min_indent { - Ok((MadeProgress, comments_and_newlines.into_bump_slice(), new_state)) + Ok(( + MadeProgress, + comments_and_newlines.into_bump_slice(), + new_state, + )) } else { Err((MadeProgress, indent_problem(state.pos()), state)) } } else { - Ok((MadeProgress, comments_and_newlines.into_bump_slice(), new_state)) + Ok(( + MadeProgress, + comments_and_newlines.into_bump_slice(), + new_state, + )) } } } diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index f7b955f24a..47b68f680f 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -512,10 +512,7 @@ fn numeric_negate_expression<'a, T>( debug_assert_eq!(state.bytes().get(0), Some(&b'-')); // for overflow reasons, we must make the unary minus part of the number literal. let start = state.pos(); - let region = Region::new( - start, - expr.region.end(), - ); + let region = Region::new(start, expr.region.end()); let new_expr = match &expr.value { Expr::Num(string) => { @@ -1272,7 +1269,13 @@ fn parse_expr_end<'a>( expr_state.consume_spaces(arena); expr_state.initial = before_op; parse_expr_operator( - min_indent, options, start_column, expr_state, loc_op, arena, state, + min_indent, + options, + start_column, + expr_state, + loc_op, + arena, + state, ) } Err((NoProgress, _, mut state)) => { diff --git a/compiler/parse/src/number_literal.rs b/compiler/parse/src/number_literal.rs index b347dd884d..a4e8624684 100644 --- a/compiler/parse/src/number_literal.rs +++ b/compiler/parse/src/number_literal.rs @@ -100,8 +100,7 @@ fn chomp_number_dec<'a>( let string = unsafe { std::str::from_utf8_unchecked(&state.bytes()[0..chomped + is_negative as usize]) }; - let new = state - .advance(chomped + is_negative as usize); + let new = state.advance(chomped + is_negative as usize); Ok(( Progress::MadeProgress, diff --git a/compiler/parse/src/state.rs b/compiler/parse/src/state.rs index 49b65f890f..f55235f8f9 100644 --- a/compiler/parse/src/state.rs +++ b/compiler/parse/src/state.rs @@ -70,10 +70,7 @@ impl<'a> State<'a> { /// useful when parsing something "manually" (using input.chars()) /// and thus wanting a Region while not having access to loc(). pub fn len_region(&self, length: u32) -> Region { - Region::new( - self.pos(), - self.pos().bump_column(length), - ) + Region::new(self.pos(), self.pos().bump_column(length)) } /// Return a failing ParseResult for the given FailReason @@ -96,11 +93,7 @@ impl<'a> fmt::Debug for State<'a> { Err(_) => write!(f, "\n\tbytes: [invalid utf8] {:?}", self.bytes)?, } - write!( - f, - "\n\t(offset): {:?},", - self.pos() - )?; + write!(f, "\n\t(offset): {:?},", self.pos())?; write!(f, "\n\tindent_column: {}", self.indent_column)?; write!(f, "\n}}") } diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index 9496d5ae6c..ae0054896c 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -15,10 +15,7 @@ impl Region { } pub const fn new(start: Position, end: Position) -> Self { - Self { - start, - end, - } + Self { start, end } } pub fn contains(&self, other: &Self) -> bool { @@ -89,11 +86,7 @@ impl fmt::Debug for Region { // because it makes failed assertions much harder to read. write!(f, "…") } else { - write!( - f, - "@{}-{}", - self.start.offset, self.end.offset, - ) + write!(f, "@{}-{}", self.start.offset, self.end.offset,) } } } @@ -113,7 +106,7 @@ impl Position { pub const fn zero() -> Position { Position { offset: 0 } } - + pub const fn new(offset: u32) -> Position { Position { offset } } @@ -161,10 +154,7 @@ pub struct LineColumn { impl LineColumn { pub const fn zero() -> Self { - LineColumn { - line: 0, - column: 0, - } + LineColumn { line: 0, column: 0 } } #[must_use] @@ -184,10 +174,7 @@ pub struct LineColumnRegion { impl LineColumnRegion { pub const fn new(start: LineColumn, end: LineColumn) -> Self { - LineColumnRegion { - start, - end, - } + LineColumnRegion { start, end } } pub const fn zero() -> Self { @@ -203,7 +190,9 @@ impl LineColumnRegion { Greater => false, Equal => match self.end.line.cmp(&other.end.line) { Less => false, - Equal => self.start.column <= other.start.column && self.end.column >= other.end.column, + Equal => { + self.start.column <= other.start.column && self.end.column >= other.end.column + } Greater => self.start.column >= other.start.column, }, Less => match self.end.line.cmp(&other.end.line) { @@ -273,7 +262,11 @@ impl LineColumnRegion { impl fmt::Debug for LineColumnRegion { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.start.line == 0 && self.start.column == 0 && self.end.line == 0 && self.end.column == 0 { + if self.start.line == 0 + && self.start.column == 0 + && self.end.line == 0 + && self.end.column == 0 + { // In tests, it's super common to set all Located values to 0. // Also in tests, we don't want to bother printing the locations // because it makes failed assertions much harder to read. @@ -336,9 +329,7 @@ where fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let region = self.region; - if region.start == Position::zero() - && region.end == Position::zero() - { + if region.start == Position::zero() && region.end == Position::zero() { // In tests, it's super common to set all Located values to 0. // Also in tests, we don't want to bother printing the locations // because it makes failed assertions much harder to read. @@ -360,9 +351,7 @@ impl LineInfo { let mut line_offsets = Vec::new(); line_offsets.push(0); line_offsets.extend(src.match_indices('\n').map(|(offset, _)| offset as u32 + 1)); - LineInfo { - line_offsets, - } + LineInfo { line_offsets } } pub fn convert_offset(&self, offset: u32) -> LineColumn { @@ -372,7 +361,10 @@ impl LineInfo { Err(i) => i - 1, }; let column = offset - self.line_offsets[line]; - LineColumn { line: line as u32, column: column as u16 } + LineColumn { + line: line as u32, + column: column as u16, + } } pub fn convert_pos(&self, pos: Position) -> LineColumn { @@ -389,19 +381,14 @@ impl LineInfo { #[test] fn test_line_info() { - fn char_at_line<'a>(lines: &[&'a str], line_column: LineColumn) -> &'a str { let line = line_column.line as usize; - let line_text = if line < lines.len() { - lines[line] - } else { - "" - }; + let line_text = if line < lines.len() { lines[line] } else { "" }; let column = line_column.column as usize; if column == line_text.len() { "\n" } else { - &line_text[column .. column + 1] + &line_text[column..column + 1] } } @@ -416,19 +403,25 @@ fn test_line_info() { let info = LineInfo::new(&input); let mut last: Option = None; - + for offset in 0..=input.len() { let expected = if offset < input.len() { &input[offset..offset + 1] } else { "\n" // HACK! pretend there's an extra newline on the end, strictly so we can do the comparison }; - println!("checking {:?} {:?}, expecting {:?}", input, offset, expected); + println!( + "checking {:?} {:?}, expecting {:?}", + input, offset, expected + ); let line_column = info.convert_offset(offset as u32); - assert!(Some(line_column) > last, "{:?} > {:?}", Some(line_column), last); - assert_eq!( - expected, - char_at_line(lines, line_column)); + assert!( + Some(line_column) > last, + "{:?} > {:?}", + Some(line_column), + last + ); + assert_eq!(expected, char_at_line(lines, line_column)); last = Some(line_column); } @@ -441,20 +434,11 @@ fn test_line_info() { ) } - check_correctness(&[ - "", - "abc", - "def", - "", - "gi", - ]); + check_correctness(&["", "abc", "def", "", "gi"]); check_correctness(&[]); check_correctness(&["a"]); - check_correctness(&[ - "", - "", - ]); + check_correctness(&["", ""]); } diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index 317338c65a..dd64d2e612 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -2,7 +2,7 @@ use roc_collections::all::MutSet; use roc_module::ident::{Ident, Lowercase, ModuleName}; use roc_problem::can::PrecedenceProblem::BothNonAssociative; use roc_problem::can::{BadPattern, FloatErrorKind, IntErrorKind, Problem, RuntimeError}; -use roc_region::all::{Loc, Region, LineInfo, LineColumn, LineColumnRegion}; +use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Loc, Region}; use std::path::PathBuf; use crate::error::r#type::suggest; @@ -357,8 +357,9 @@ pub fn can_problem<'b>( alloc.reflow( "This annotation does not match the definition immediately following it:", ), - alloc.region(lines.convert_region( - Region::span_across(annotation_pattern, def_pattern))), + alloc.region( + lines.convert_region(Region::span_across(annotation_pattern, def_pattern)), + ), alloc.reflow("Is it a typo? If not, put either a newline or comment between them."), ]); @@ -454,7 +455,13 @@ fn to_invalid_optional_value_report<'b>( field_region: Region, record_region: Region, ) -> Report<'b> { - let doc = to_invalid_optional_value_report_help(alloc, lines, field_name, field_region, record_region); + let doc = to_invalid_optional_value_report_help( + alloc, + lines, + field_name, + field_region, + record_region, + ); Report { title: "BAD OPTIONAL VALUE".to_string(), @@ -477,7 +484,12 @@ fn to_invalid_optional_value_report_help<'b>( alloc.record_field(field_name), alloc.reflow(" field in an incorrect context!"), ]), - alloc.region_all_the_things(lines.convert_region(record_region), lines.convert_region(field_region), lines.convert_region(field_region), Annotation::Error), + alloc.region_all_the_things( + lines.convert_region(record_region), + lines.convert_region(field_region), + lines.convert_region(field_region), + Annotation::Error, + ), alloc.reflow(r"You can only use optional values in record destructuring, like:"), alloc .reflow(r"{ answer ? 42, otherField } = myRecord") @@ -561,7 +573,10 @@ fn to_bad_ident_expr_report<'b>( let region = Region::new(surroundings.start(), pos); alloc.stack(vec![ alloc.reflow("Underscores are not allowed in identifier names:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion( + lines.convert_region(surroundings), + lines.convert_region(region), + ), alloc.concat(vec![alloc.reflow( r"I recommend using camelCase, it is the standard in the Roc ecosystem.", )]), @@ -575,7 +590,10 @@ fn to_bad_ident_expr_report<'b>( let region = Region::new(pos, pos.bump_column(width)); alloc.stack(vec![ alloc.reflow("I am very confused by this field access:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion( + lines.convert_region(surroundings), + lines.convert_region(region), + ), alloc.concat(vec![ alloc.reflow(r"It looks like a record field access on a private tag.") ]), @@ -585,7 +603,10 @@ fn to_bad_ident_expr_report<'b>( let region = Region::new(pos, pos.bump_column(width)); alloc.stack(vec![ alloc.reflow("I am very confused by this expression:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion( + lines.convert_region(surroundings), + lines.convert_region(region), + ), alloc.concat(vec![ alloc.reflow( r"Looks like a private tag is treated like a module name. ", @@ -601,7 +622,10 @@ fn to_bad_ident_expr_report<'b>( Region::new(surroundings.start().bump_column(1), pos.bump_column(1)); alloc.stack(vec![ alloc.reflow("I am trying to parse a private tag here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion( + lines.convert_region(surroundings), + lines.convert_region(region), + ), alloc.concat(vec![ alloc.reflow(r"But after the "), alloc.keyword("@"), @@ -698,7 +722,10 @@ fn to_bad_ident_pattern_report<'b>( alloc.stack(vec![ alloc.reflow("I am trying to parse an identifier here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion( + lines.convert_region(surroundings), + lines.convert_region(region), + ), alloc.concat(vec![alloc.reflow( r"Underscores are not allowed in identifiers. Use camelCase instead!", )]), @@ -821,7 +848,14 @@ fn pretty_runtime_error<'b>( } RuntimeError::LookupNotInScope(loc_name, options) => { - doc = not_found(alloc, lines, loc_name.region, &loc_name.value, "value", options); + doc = not_found( + alloc, + lines, + loc_name.region, + &loc_name.value, + "value", + options, + ); title = UNRECOGNIZED_NAME; } RuntimeError::CircularDef(entries) => { diff --git a/reporting/src/error/mono.rs b/reporting/src/error/mono.rs index 3612ed7c57..a3df3d2338 100644 --- a/reporting/src/error/mono.rs +++ b/reporting/src/error/mono.rs @@ -1,6 +1,6 @@ use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; -use std::path::PathBuf; use roc_region::all::LineInfo; +use std::path::PathBuf; use ven_pretty::DocAllocator; pub fn mono_problem<'b>( @@ -98,7 +98,10 @@ pub fn mono_problem<'b>( alloc.string(index.ordinal()), alloc.reflow(" pattern is redundant:"), ]), - alloc.region_with_subregion(lines.convert_region(overall_region), lines.convert_region(branch_region)), + alloc.region_with_subregion( + lines.convert_region(overall_region), + lines.convert_region(branch_region), + ), alloc.reflow( "Any value of this shape will be handled by \ a previous pattern, so this one should be removed.", diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index 0e0366b069..5a5e815091 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -1,5 +1,5 @@ use roc_parse::parser::{ParseProblem, SyntaxError}; -use roc_region::all::{Position, Region, LineInfo, LineColumn, LineColumnRegion}; +use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Position, Region}; use std::path::PathBuf; use crate::report::{Report, RocDocAllocator, RocDocBuilder, Severity}; @@ -12,7 +12,13 @@ pub fn parse_problem<'a>( _starting_line: u32, parse_problem: ParseProblem>, ) -> Report<'a> { - to_syntax_report(alloc, lines, filename, &parse_problem.problem, parse_problem.pos) + to_syntax_report( + alloc, + lines, + filename, + &parse_problem.problem, + parse_problem.pos, + ) } fn note_for_record_type_indent<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> { @@ -137,7 +143,10 @@ fn to_syntax_report<'a>( } } SyntaxError::Eof(region) => { - let doc = alloc.stack(vec![alloc.reflow("End of Field"), alloc.region(lines.convert_region(*region))]); + let doc = alloc.stack(vec![ + alloc.reflow("End of Field"), + alloc.region(lines.convert_region(*region)), + ]); Report { filename, @@ -204,7 +213,9 @@ fn to_expr_report<'a>( match parse_problem { EExpr::If(if_, pos) => to_if_report(alloc, lines, filename, context, if_, *pos), EExpr::When(when, pos) => to_when_report(alloc, lines, filename, context, when, *pos), - EExpr::Lambda(lambda, pos) => to_lambda_report(alloc, lines, filename, context, lambda, *pos), + EExpr::Lambda(lambda, pos) => { + to_lambda_report(alloc, lines, filename, context, lambda, *pos) + } EExpr::List(list, pos) => to_list_report(alloc, lines, filename, context, list, *pos), EExpr::Str(string, pos) => to_str_report(alloc, lines, filename, context, string, *pos), EExpr::InParens(expr, pos) => { @@ -306,7 +317,10 @@ fn to_expr_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"This looks like an operator, but it's not one I recognize!"), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion( + lines.convert_region(surroundings), + lines.convert_region(region), + ), alloc.concat(suggestion), ]); @@ -452,9 +466,14 @@ fn to_expr_report<'a>( } } - EExpr::DefMissingFinalExpr2(expr, pos) => { - to_expr_report(alloc, lines, filename, Context::InDefFinalExpr(start), expr, *pos) - } + EExpr::DefMissingFinalExpr2(expr, pos) => to_expr_report( + alloc, + lines, + filename, + Context::InDefFinalExpr(start), + expr, + *pos, + ), EExpr::BadExprEnd(pos) => { let surroundings = Region::new(start, *pos); @@ -682,7 +701,9 @@ fn to_lambda_report<'a>( ELambda::Body(expr, pos) => { to_expr_report(alloc, lines, filename, Context::InDef(start), expr, pos) } - ELambda::Pattern(ref pattern, pos) => to_pattern_report(alloc, lines, filename, pattern, pos), + ELambda::Pattern(ref pattern, pos) => { + to_pattern_report(alloc, lines, filename, pattern, pos) + } ELambda::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), ELambda::IndentArrow(pos) => to_unfinished_lambda_report( @@ -792,7 +813,10 @@ fn to_str_report<'a>( alloc.reflow(r"I was partway through parsing a "), alloc.reflow(r" string literal, but I got stuck here:"), ]), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion( + lines.convert_region(surroundings), + lines.convert_region(region), + ), alloc.concat(vec![ alloc.reflow(r"This is not an escape sequence I recognize."), alloc.reflow(r" After a backslash, I am looking for one of these:"), @@ -1006,63 +1030,66 @@ fn to_list_report<'a>( pos, ), - EList::Open(pos) | EList::End(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { - Next::Other(Some(',')) => { - let surroundings = Region::new(start, pos); - let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); + EList::Open(pos) | EList::End(pos) => { + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { + Next::Other(Some(',')) => { + let surroundings = Region::new(start, pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ - alloc.reflow( - r"I am partway through started parsing a list, but I got stuck here:", - ), - alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ - alloc.reflow(r"I was expecting to see a list entry before this comma, "), - alloc.reflow(r"so try adding a list entry"), - alloc.reflow(r" and see if that helps?"), - ]), - ]); - Report { - filename, - doc, - title: "UNFINISHED LIST".to_string(), - severity: Severity::RuntimeError, - } - } - _ => { - let surroundings = Region::new(start, pos); - let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - - let doc = alloc.stack(vec![ - alloc.reflow( - r"I am partway through started parsing a list, but I got stuck here:", - ), - alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + let doc = alloc.stack(vec![ alloc.reflow( - r"I was expecting to see a closing square bracket before this, ", + r"I am partway through started parsing a list, but I got stuck here:", ), - alloc.reflow(r"so try adding a "), - alloc.parser_suggestion("]"), - alloc.reflow(r" and see if that helps?"), - ]), - alloc.concat(vec![ - alloc.note("When "), - alloc.reflow(r"I get stuck like this, "), - alloc.reflow(r"it usually means that there is a missing parenthesis "), - alloc.reflow(r"or bracket somewhere earlier. "), - alloc.reflow(r"It could also be a stray keyword or operator."), - ]), - ]); + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat(vec![ + alloc + .reflow(r"I was expecting to see a list entry before this comma, "), + alloc.reflow(r"so try adding a list entry"), + alloc.reflow(r" and see if that helps?"), + ]), + ]); + Report { + filename, + doc, + title: "UNFINISHED LIST".to_string(), + severity: Severity::RuntimeError, + } + } + _ => { + let surroundings = Region::new(start, pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - Report { - filename, - doc, - title: "UNFINISHED LIST".to_string(), - severity: Severity::RuntimeError, + let doc = alloc.stack(vec![ + alloc.reflow( + r"I am partway through started parsing a list, but I got stuck here:", + ), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat(vec![ + alloc.reflow( + r"I was expecting to see a closing square bracket before this, ", + ), + alloc.reflow(r"so try adding a "), + alloc.parser_suggestion("]"), + alloc.reflow(r" and see if that helps?"), + ]), + alloc.concat(vec![ + alloc.note("When "), + alloc.reflow(r"I get stuck like this, "), + alloc.reflow(r"it usually means that there is a missing parenthesis "), + alloc.reflow(r"or bracket somewhere earlier. "), + alloc.reflow(r"It could also be a stray keyword or operator."), + ]), + ]); + + Report { + filename, + doc, + title: "UNFINISHED LIST".to_string(), + severity: Severity::RuntimeError, + } } } - }, + } EList::IndentOpen(pos) | EList::IndentEnd(pos) => { let surroundings = Region::new(start, pos); @@ -1218,37 +1245,39 @@ fn to_when_report<'a>( use roc_parse::parser::EWhen; match *parse_problem { - EWhen::IfGuard(nested, pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { - Next::Token("->") => { - let surroundings = Region::new(start, pos); - let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); + EWhen::IfGuard(nested, pos) => { + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { + Next::Token("->") => { + let surroundings = Region::new(start, pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ - alloc.reflow( - r"I just started parsing an if guard, but there is no guard condition:", - ), - alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ - alloc.reflow("Try adding an expression before the arrow!") - ]), - ]); + let doc = alloc.stack(vec![ + alloc.reflow( + r"I just started parsing an if guard, but there is no guard condition:", + ), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat(vec![ + alloc.reflow("Try adding an expression before the arrow!") + ]), + ]); - Report { - filename, - doc, - title: "IF GUARD NO CONDITION".to_string(), - severity: Severity::RuntimeError, + Report { + filename, + doc, + title: "IF GUARD NO CONDITION".to_string(), + severity: Severity::RuntimeError, + } } + _ => to_expr_report( + alloc, + lines, + filename, + Context::InNode(Node::WhenIfGuard, start, Box::new(context)), + nested, + pos, + ), } - _ => to_expr_report( - alloc, - lines, - filename, - Context::InNode(Node::WhenIfGuard, start, Box::new(context)), - nested, - pos, - ), - }, + } EWhen::Arrow(pos) => { let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); @@ -1447,7 +1476,10 @@ fn to_unexpected_arrow_report<'a>( alloc.keyword("when"), alloc.reflow(r" expression right now, but this arrow is confusing me:"), ]), - alloc.region_with_subregion(lines.convert_region(surroundings), lines.convert_region(region)), + alloc.region_with_subregion( + lines.convert_region(surroundings), + lines.convert_region(region), + ), alloc.concat(vec![ alloc.reflow(r"It makes sense to see arrows around here, "), alloc.reflow(r"so I suspect it is something earlier."), @@ -1736,12 +1768,13 @@ fn to_precord_report<'a>( } } - PRecord::IndentEnd(pos) => match next_line_starts_with_close_curly(alloc.src_lines, lines.convert_pos(pos)) { - Some(curly_pos) => { - let surroundings = LineColumnRegion::new(lines.convert_pos(start), curly_pos); - let region = LineColumnRegion::from_pos(curly_pos); + PRecord::IndentEnd(pos) => { + match next_line_starts_with_close_curly(alloc.src_lines, lines.convert_pos(pos)) { + Some(curly_pos) => { + let surroundings = LineColumnRegion::new(lines.convert_pos(start), curly_pos); + let region = LineColumnRegion::from_pos(curly_pos); - let doc = alloc.stack(vec![ + let doc = alloc.stack(vec![ alloc.reflow( "I am partway through parsing a record pattern, but I got stuck here:", ), @@ -1751,39 +1784,40 @@ fn to_precord_report<'a>( ]), ]); - Report { - filename, - doc, - title: "NEED MORE INDENTATION".to_string(), - severity: Severity::RuntimeError, + Report { + filename, + doc, + title: "NEED MORE INDENTATION".to_string(), + severity: Severity::RuntimeError, + } + } + None => { + let surroundings = Region::new(start, pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); + + let doc = alloc.stack(vec![ + alloc.reflow( + r"I am partway through parsing a record pattern, but I got stuck here:", + ), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat(vec![ + alloc.reflow("I was expecting to see a closing curly "), + alloc.reflow("brace before this, so try adding a "), + alloc.parser_suggestion("}"), + alloc.reflow(" and see if that helps?"), + ]), + note_for_record_pattern_indent(alloc), + ]); + + Report { + filename, + doc, + title: "UNFINISHED RECORD PATTERN".to_string(), + severity: Severity::RuntimeError, + } } } - None => { - let surroundings = Region::new(start, pos); - let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - - let doc = alloc.stack(vec![ - alloc.reflow( - r"I am partway through parsing a record pattern, but I got stuck here:", - ), - alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ - alloc.reflow("I was expecting to see a closing curly "), - alloc.reflow("brace before this, so try adding a "), - alloc.parser_suggestion("}"), - alloc.reflow(" and see if that helps?"), - ]), - note_for_record_pattern_indent(alloc), - ]); - - Report { - filename, - doc, - title: "UNFINISHED RECORD PATTERN".to_string(), - severity: Severity::RuntimeError, - } - } - }, + } PRecord::IndentColon(_) => { unreachable!("because `{ foo }` is a valid field; the colon is not required") @@ -1947,31 +1981,37 @@ fn to_type_report<'a>( match parse_problem { EType::TRecord(record, pos) => to_trecord_report(alloc, lines, filename, record, *pos), - EType::TTagUnion(tag_union, pos) => to_ttag_union_report(alloc, lines, filename, tag_union, *pos), - EType::TInParens(tinparens, pos) => to_tinparens_report(alloc, lines, filename, tinparens, *pos), + EType::TTagUnion(tag_union, pos) => { + to_ttag_union_report(alloc, lines, filename, tag_union, *pos) + } + EType::TInParens(tinparens, pos) => { + to_tinparens_report(alloc, lines, filename, tinparens, *pos) + } EType::TApply(tapply, pos) => to_tapply_report(alloc, lines, filename, tapply, *pos), EType::TInlineAlias(talias, _) => to_talias_report(alloc, lines, filename, talias), - EType::TFunctionArgument(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(*pos)) { - Next::Other(Some(',')) => { - let surroundings = Region::new(start, *pos); - let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); + EType::TFunctionArgument(pos) => { + match what_is_next(alloc.src_lines, lines.convert_pos(*pos)) { + Next::Other(Some(',')) => { + let surroundings = Region::new(start, *pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a function argument type, but I encountered two commas in a row:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![alloc.reflow("Try removing one of them.")]), ]); - Report { - filename, - doc, - title: "DOUBLE COMMA".to_string(), - severity: Severity::RuntimeError, + Report { + filename, + doc, + title: "DOUBLE COMMA".to_string(), + severity: Severity::RuntimeError, + } } + _ => todo!(), } - _ => todo!(), - }, + } EType::TStart(pos) => { let surroundings = Region::new(start, *pos); @@ -2484,7 +2524,10 @@ fn to_ttag_union_report<'a>( } ETypeTagUnion::IndentEnd(pos) => { - match next_line_starts_with_close_square_bracket(alloc.src_lines, lines.convert_pos(pos)) { + match next_line_starts_with_close_square_bracket( + alloc.src_lines, + lines.convert_pos(pos), + ) { Some(curly_pos) => { let surroundings = LineColumnRegion::new(lines.convert_pos(start), curly_pos); let region = LineColumnRegion::from_pos(curly_pos); @@ -2944,15 +2987,21 @@ fn to_header_report<'a>( use roc_parse::parser::EHeader; match parse_problem { - EHeader::Provides(provides, pos) => to_provides_report(alloc, lines, filename, provides, *pos), + EHeader::Provides(provides, pos) => { + to_provides_report(alloc, lines, filename, provides, *pos) + } EHeader::Exposes(exposes, pos) => to_exposes_report(alloc, lines, filename, exposes, *pos), EHeader::Imports(imports, pos) => to_imports_report(alloc, lines, filename, imports, *pos), - EHeader::Requires(requires, pos) => to_requires_report(alloc, lines, filename, requires, *pos), + EHeader::Requires(requires, pos) => { + to_requires_report(alloc, lines, filename, requires, *pos) + } - EHeader::Packages(packages, pos) => to_packages_report(alloc, lines, filename, packages, *pos), + EHeader::Packages(packages, pos) => { + to_packages_report(alloc, lines, filename, packages, *pos) + } EHeader::Effects(effects, pos) => to_effects_report(alloc, lines, filename, effects, *pos), @@ -3590,6 +3639,6 @@ fn next_line_starts_with_char( fn to_keyword_region(pos: LineColumn, keyword: &str) -> LineColumnRegion { LineColumnRegion { start: pos, - end: pos.bump_column(keyword.len() as u16) + end: pos.bump_column(keyword.len() as u16), } } diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index 7e485699e0..2420c901d4 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -3,7 +3,7 @@ use roc_collections::all::{Index, MutSet, SendMap}; use roc_module::called_via::{BinOp, CalledVia}; use roc_module::ident::{Ident, IdentStr, Lowercase, TagName}; use roc_module::symbol::Symbol; -use roc_region::all::{Loc, Region, LineInfo}; +use roc_region::all::{LineInfo, Loc, Region}; use roc_solve::solve; use roc_types::pretty_print::{Parens, WILDCARD}; use roc_types::types::{Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt}; @@ -203,7 +203,10 @@ fn report_mismatch<'b>( further_details: Option>, ) -> Report<'b> { let snippet = if let Some(highlight) = opt_highlight { - alloc.region_with_subregion(lines.convert_region(highlight), lines.convert_region(region)) + alloc.region_with_subregion( + lines.convert_region(highlight), + lines.convert_region(region), + ) } else { alloc.region(lines.convert_region(region)) }; @@ -244,7 +247,10 @@ fn report_bad_type<'b>( further_details: RocDocBuilder<'b>, ) -> Report<'b> { let snippet = if let Some(highlight) = opt_highlight { - alloc.region_with_subregion(lines.convert_region(highlight), lines.convert_region(region)) + alloc.region_with_subregion( + lines.convert_region(highlight), + lines.convert_region(region), + ) } else { alloc.region(lines.convert_region(region)) }; @@ -428,7 +434,10 @@ fn to_expr_report<'b>( // for typed bodies, include the line(s) with the signature let joined = roc_region::all::Region::span_across(&ann_region, &expr_region); - alloc.region_with_subregion(lines.convert_region(joined), lines.convert_region(expr_region)) + alloc.region_with_subregion( + lines.convert_region(joined), + lines.convert_region(expr_region), + ) }, comparison, ]), diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index aabc59ec80..a5b74010c9 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -158,7 +158,9 @@ mod test_reporting { } for problem in type_problems { - if let Some(report) = type_problem(&alloc, &lines, filename.clone(), problem.clone()) { + if let Some(report) = + type_problem(&alloc, &lines, filename.clone(), problem.clone()) + { reports.push(report); } } From 5d94be2011a8795c31516d4431f8d0c71abee46a Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Fri, 24 Dec 2021 12:02:02 -0800 Subject: [PATCH 078/541] clippy --- compiler/region/src/all.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index ae0054896c..5418d8c132 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -91,17 +91,11 @@ impl fmt::Debug for Region { } } -#[derive(Copy, Clone, Eq, PartialOrd, Ord, Hash, Default)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct Position { pub offset: u32, } -impl PartialEq for Position { - fn eq(&self, other: &Self) -> bool { - self.offset == other.offset - } -} - impl Position { pub const fn zero() -> Position { Position { offset: 0 } @@ -348,8 +342,7 @@ pub struct LineInfo { impl LineInfo { pub fn new(src: &str) -> LineInfo { - let mut line_offsets = Vec::new(); - line_offsets.push(0); + let mut line_offsets = vec![0]; line_offsets.extend(src.match_indices('\n').map(|(offset, _)| offset as u32 + 1)); LineInfo { line_offsets } } From 9a92d51656d2efb586f6e144f86064e087f58542 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Fri, 24 Dec 2021 12:04:47 -0800 Subject: [PATCH 079/541] undo comment --- compiler/types/src/subs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index c656df1986..0f758d1f13 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -12,7 +12,7 @@ use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey}; static_assertions::assert_eq_size!([u8; 6 * 8], Descriptor); static_assertions::assert_eq_size!([u8; 4 * 8], Content); static_assertions::assert_eq_size!([u8; 3 * 8], FlatType); -// static_assertions::assert_eq_size!([u8; 6 * 8], Problem); +static_assertions::assert_eq_size!([u8; 6 * 8], Problem); static_assertions::assert_eq_size!([u8; 12], UnionTags); static_assertions::assert_eq_size!([u8; 2 * 8], RecordFields); From 5f7bec3ee87d56bb44750a08d41ea1488250df35 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Fri, 24 Dec 2021 16:19:16 -0800 Subject: [PATCH 080/541] Use assert_multiline_str_eq in repl_eval tests --- Cargo.lock | 1 + cli/Cargo.toml | 1 + cli/tests/repl_eval.rs | 14 ++++++-------- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1548053545..ea0b3e16c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3349,6 +3349,7 @@ dependencies = [ "roc_region", "roc_reporting", "roc_solve", + "roc_test_utils", "roc_types", "roc_unify", "rustyline", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index c01da3f76d..7b3d945425 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -83,6 +83,7 @@ wasmer-wasi = { version = "2.0.0", optional = true } wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] } wasmer-wasi = "2.0.0" pretty_assertions = "1.0.0" +roc_test_utils = { path = "../test_utils" } indoc = "1.0.3" serial_test = "0.5.1" tempfile = "3.2.0" diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index 7fed497b66..de5d12365c 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -1,20 +1,18 @@ -#[macro_use] -extern crate pretty_assertions; - #[macro_use] extern crate indoc; #[cfg(test)] mod repl_eval { use cli_utils::helpers; + use roc_test_utils::assert_multiline_str_eq; const ERROR_MESSAGE_START: char = '─'; fn expect_success(input: &str, expected: &str) { let out = helpers::repl_eval(input); - assert_eq!(&out.stderr, ""); - assert_eq!(&out.stdout, expected); + assert_multiline_str_eq!("", out.stderr.as_str()); + assert_multiline_str_eq!(expected, out.stdout.as_str()); assert!(out.status.success()); } @@ -25,12 +23,12 @@ mod repl_eval { // so skip till the header of the first error match out.stdout.find(ERROR_MESSAGE_START) { Some(index) => { - assert_eq!(&out.stderr, ""); - assert_eq!(&out.stdout[index..], expected); + assert_multiline_str_eq!("", out.stderr.as_str()); + assert_multiline_str_eq!(expected, &out.stdout[index..]); assert!(out.status.success()); } None => { - assert_eq!(&out.stderr, ""); + assert_multiline_str_eq!("", out.stderr.as_str()); assert!(out.status.success()); panic!( "I expected a failure, but there is no error message in stdout:\n\n{}", From 8e0b3bbacaadde7a8b2526d6a9e4b5ef2bda8961 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Sat, 25 Dec 2021 13:32:26 -0800 Subject: [PATCH 081/541] Fix repl_eval line attribution --- cli/src/repl/gen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index c16ba70895..9b04e29ceb 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -92,7 +92,7 @@ pub fn gen_and_eval<'a>( continue; } - let line_info = LineInfo::new(&src); + let line_info = LineInfo::new(&module_src); let src_lines: Vec<&str> = src.split('\n').collect(); let palette = DEFAULT_PALETTE; From e97e5c3bd95df515fad43f141126cd6e7d5eac06 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Tue, 28 Dec 2021 09:41:02 -0700 Subject: [PATCH 082/541] Fixup type annotations --- compiler/parse/src/type_annotation.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/parse/src/type_annotation.rs b/compiler/parse/src/type_annotation.rs index a404f838c4..c73aebab6c 100644 --- a/compiler/parse/src/type_annotation.rs +++ b/compiler/parse/src/type_annotation.rs @@ -69,7 +69,7 @@ fn check_type_alias( let name_start = annot.region.start(); let name_region = - Region::between(name_start, name_start.bump_column(tag_name.len() as u16)); + Region::between(name_start, name_start.bump_column(tag_name.len() as u32)); let header = AliasHeader { name: Loc::at(name_region, tag_name), @@ -85,7 +85,7 @@ fn check_type_alias( } } -fn parse_type_alias_after_as<'a>(min_indent: u16) -> impl Parser<'a, AliasHeader<'a>, EType<'a>> { +fn parse_type_alias_after_as<'a>(min_indent: u32) -> impl Parser<'a, AliasHeader<'a>, EType<'a>> { move |arena, state| { space0_before_e( term(min_indent), From 8d6eb178b173a5b03ffc3248f06ae99660104184 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Thu, 30 Dec 2021 18:12:26 -0700 Subject: [PATCH 083/541] Fix multiline pattern heuristic --- compiler/fmt/src/expr.rs | 13 +++++++++---- compiler/parse/src/ast.rs | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index bbbbbfd0fc..994d9d9796 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -6,7 +6,7 @@ use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT}; use crate::Buf; use roc_module::called_via::{self, BinOp}; use roc_parse::ast::{ - AssignedField, Base, Collection, CommentOrNewline, Expr, Pattern, WhenBranch, + AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces, Pattern, WhenBranch, }; use roc_parse::ast::{StrLiteral, StrSegment}; use roc_region::all::Loc; @@ -514,10 +514,15 @@ fn fmt_when<'a, 'buf>( let patterns = &branch.patterns; let expr = &branch.value; let (first_pattern, rest) = patterns.split_first().unwrap(); - let is_multiline = if rest.is_empty() { - false + let is_multiline = if let Some((last_pattern, inner_patterns)) = rest.split_last() { + !first_pattern.value.extract_spaces().after.is_empty() + || !last_pattern.value.extract_spaces().before.is_empty() + || inner_patterns.iter().any(|p| { + let spaces = p.value.extract_spaces(); + !spaces.before.is_empty() || !spaces.after.is_empty() + }) } else { - patterns.iter().any(|p| p.is_multiline()) + false }; fmt_pattern( diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index 9106623060..c8844357cd 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -856,6 +856,7 @@ macro_rules! impl_extract_spaces { } impl_extract_spaces!(Expr); +impl_extract_spaces!(Pattern); impl_extract_spaces!(Tag); impl_extract_spaces!(AssignedField); From 5c1084c45396c44e2cac3bb55045599ad4eaabcf Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Sat, 1 Jan 2022 14:40:03 -0700 Subject: [PATCH 084/541] Introduce SourceError to represent an error + original source --- ast/src/constrain.rs | 4 +-- cli/src/format.rs | 2 +- compiler/load/src/file.rs | 18 +++++----- compiler/parse/src/module.rs | 11 +++--- compiler/parse/src/parser.rs | 54 ++++++++++++++++++++++++++---- compiler/parse/src/state.rs | 31 ++++++++++------- compiler/parse/src/test_helpers.rs | 11 +++--- reporting/tests/helpers/mod.rs | 3 +- reporting/tests/test_reporting.rs | 10 +++--- 9 files changed, 96 insertions(+), 48 deletions(-) diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index a1fd0e6a2c..4df4193b85 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -1985,7 +1985,7 @@ pub mod test_constrain { ident::Lowercase, symbol::{IdentIds, Interns, ModuleIds, Symbol}, }; - use roc_parse::parser::SyntaxError; + use roc_parse::parser::{SyntaxError, SourceError}; use roc_region::all::Region; use roc_types::{ pretty_print::{content_to_string, name_all_type_vars}, @@ -2128,7 +2128,7 @@ pub mod test_constrain { env: &mut Env<'a>, scope: &mut Scope, region: Region, - ) -> Result<(Expr2, Output), SyntaxError<'a>> { + ) -> Result<(Expr2, Output), SourceError<'a, SyntaxError<'a>>> { match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)), Err(fail) => Err(fail), diff --git a/cli/src/format.rs b/cli/src/format.rs index 2bfdae99f7..bdb450ee8c 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -111,7 +111,7 @@ struct Ast<'a> { fn parse_all<'a>(arena: &'a Bump, src: &'a str) -> Result, SyntaxError<'a>> { let (module, state) = - module::parse_header(arena, State::new(src.as_bytes())).map_err(SyntaxError::Header)?; + module::parse_header(arena, State::new(src.as_bytes())).map_err(|e| SyntaxError::Header(e.problem))?; let (_, defs, _) = module_defs().parse(arena, state).map_err(|(_, e, _)| e)?; diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 1dda5322db..ea4800b84a 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -2352,7 +2352,7 @@ fn load_pkg_config<'a>( let parse_start = SystemTime::now(); let bytes = arena.alloc(bytes_vec); let parse_state = roc_parse::state::State::new(bytes); - let parsed = roc_parse::module::parse_header(arena, parse_state); + let parsed = roc_parse::module::parse_header(arena, parse_state.clone()); let parse_header_duration = parse_start.elapsed().unwrap(); // Insert the first entries for this module's timings @@ -2411,7 +2411,7 @@ fn load_pkg_config<'a>( Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg])) } Err(fail) => Err(LoadingProblem::ParsingFailed( - SyntaxError::Header(fail).into_parse_problem(filename, "", bytes), + fail.map_problem(SyntaxError::Header).into_parse_problem(filename), )), } } @@ -2518,7 +2518,7 @@ fn parse_header<'a>( ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> { let parse_start = SystemTime::now(); let parse_state = roc_parse::state::State::new(src_bytes); - let parsed = roc_parse::module::parse_header(arena, parse_state); + let parsed = roc_parse::module::parse_header(arena, parse_state.clone()); let parse_header_duration = parse_start.elapsed().unwrap(); // Insert the first entries for this module's timings @@ -2654,7 +2654,7 @@ fn parse_header<'a>( module_timing, )), Err(fail) => Err(LoadingProblem::ParsingFailed( - SyntaxError::Header(fail).into_parse_problem(filename, "", src_bytes), + fail.map_problem(SyntaxError::Header).into_parse_problem(filename), )), } } @@ -3705,11 +3705,10 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result, Loadi let parse_state = header.parse_state; let parsed_defs = match module_defs().parse(arena, parse_state) { Ok((_, success, _state)) => success, - Err((_, fail, _)) => { + Err((_, fail, state)) => { return Err(LoadingProblem::ParsingFailed(fail.into_parse_problem( header.module_path, - header.header_src, - source, + &state, ))); } }; @@ -4317,8 +4316,9 @@ fn to_parse_problem_report<'a>( // TODO this is not in fact safe let src = unsafe { from_utf8_unchecked(problem.bytes) }; - let mut src_lines: Vec<&str> = problem.prefix.lines().collect(); - src_lines.extend(src.lines().skip(1)); + let src_lines = src.lines().collect::>(); + // let mut src_lines: Vec<&str> = problem.prefix.lines().collect(); + // src_lines.extend(src.lines().skip(1)); let module_id = module_ids.get_or_insert(&"find module name somehow?".into()); diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index 7ebe8c77b0..625423a3b4 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -8,7 +8,7 @@ use crate::ident::{lowercase_ident, unqualified_ident, uppercase_ident}; use crate::parser::Progress::{self, *}; use crate::parser::{ backtrackable, specialize, word1, word2, EEffects, EExposes, EHeader, EImports, EPackages, - EProvides, ERequires, ETypedIdent, Parser, SyntaxError, + EProvides, ERequires, ETypedIdent, Parser, SyntaxError, SourceError, }; use crate::state::State; use crate::string_literal; @@ -39,10 +39,10 @@ pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Loc>>, SyntaxError<' pub fn parse_header<'a>( arena: &'a bumpalo::Bump, state: State<'a>, -) -> Result<(Module<'a>, State<'a>), EHeader<'a>> { +) -> Result<(Module<'a>, State<'a>), SourceError<'a, EHeader<'a>>> { match header().parse(arena, state) { Ok((_, module, state)) => Ok((module, state)), - Err((_, fail, _)) => Err(fail), + Err((_, fail, state)) => Err(SourceError::new(fail, &state)), } } @@ -167,6 +167,7 @@ fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>, ()> { |_, mut state: State<'a>| match chomp_module_name(state.bytes()) { Ok(name) => { let width = name.len(); + state.xyzlcol.column += width as u16; state = state.advance(width); Ok((MadeProgress, ModuleName::new(name), state)) @@ -435,7 +436,7 @@ fn platform_requires<'a>() -> impl Parser<'a, PlatformRequires<'a>, ERequires<'a #[inline(always)] fn requires_rigids<'a>( - min_indent: u32, + min_indent: u16, ) -> impl Parser<'a, Collection<'a, Loc>>>, ERequires<'a>> { collection_trailing_sep_e!( word1(b'{', ERequires::ListStart), @@ -513,7 +514,7 @@ fn exposes_values<'a>() -> impl Parser< } fn spaces_around_keyword<'a, E>( - min_indent: u32, + min_indent: u16, keyword: &'static str, expectation: fn(Position) -> E, space_problem: fn(crate::parser::BadInputError, Position) -> E, diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index 666a5becd3..235ba95972 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -225,19 +225,55 @@ pub fn bad_input_to_syntax_error<'a>(bad_input: BadInputError, pos: Position) -> } } -impl<'a> SyntaxError<'a> { +impl<'a, T> SourceError<'a, T> { + pub fn new( + problem: T, + state: &State<'a>, + ) -> Self { + Self { + problem, + bytes: state.original_bytes(), + } + } + + pub fn map_problem(self, f: impl FnOnce(T) -> E) -> SourceError<'a, E> { + SourceError { + problem: f(self.problem), + bytes: self.bytes, + } + } + pub fn into_parse_problem( self, filename: std::path::PathBuf, - prefix: &'a str, - bytes: &'a [u8], + ) -> ParseProblem<'a, T> { + ParseProblem { + pos: Position::default(), + problem: self.problem, + filename, + bytes: self.bytes, + } + } +} + +impl<'a> SyntaxError<'a> { + pub fn into_source_error(self, state: &State<'a>) -> SourceError<'a, SyntaxError<'a>> { + SourceError { + problem: self, + bytes: state.original_bytes(), + } + } + + pub fn into_parse_problem( + self, + filename: std::path::PathBuf, + state: &State<'a>, ) -> ParseProblem<'a, SyntaxError<'a>> { ParseProblem { pos: Position::default(), problem: self, filename, - bytes, - prefix, + bytes: state.original_bytes(), } } } @@ -561,14 +597,18 @@ pub enum ETypeInlineAlias { ArgumentNotLowercase(Position), } +#[derive(Debug)] +pub struct SourceError<'a, T> { + pub problem: T, + pub bytes: &'a [u8], +} + #[derive(Debug)] pub struct ParseProblem<'a, T> { pub pos: Position, pub problem: T, pub filename: std::path::PathBuf, pub bytes: &'a [u8], - /// prefix is usually the header (for parse problems in the body), or empty - pub prefix: &'a str, } pub trait Parser<'a, Output, Error> { diff --git a/compiler/parse/src/state.rs b/compiler/parse/src/state.rs index f55235f8f9..59af7f21dc 100644 --- a/compiler/parse/src/state.rs +++ b/compiler/parse/src/state.rs @@ -7,11 +7,12 @@ use std::fmt; #[derive(Clone)] pub struct State<'a> { /// The raw input bytes from the file. - /// Beware: bytes[0] always points the the current byte the parser is examining. - bytes: &'a [u8], + /// Beware: original_bytes[0] always points the the start of the file. + /// Use bytes()[0] to access the current byte the parser is inspecting + original_bytes: &'a [u8], - /// Length of the original input in bytes - input_len: usize, + /// Offset in original_bytes that the parser is currently inspecting + offset: usize, /// Position of the start of the current line line_start: Position, @@ -24,15 +25,19 @@ pub struct State<'a> { impl<'a> State<'a> { pub fn new(bytes: &'a [u8]) -> State<'a> { State { - bytes, - input_len: bytes.len(), + original_bytes: bytes, + offset: 0, line_start: Position::zero(), indent_column: 0, } } + pub fn original_bytes(&self) -> &'a [u8] { + self.original_bytes + } + pub fn bytes(&self) -> &'a [u8] { - self.bytes + &self.original_bytes[self.offset..] } pub fn column(&self) -> u32 { @@ -43,26 +48,26 @@ impl<'a> State<'a> { pub fn advance(&self, offset: usize) -> State<'a> { let mut state = self.clone(); // debug_assert!(!state.bytes[..offset].iter().any(|b| *b == b'\n')); - state.bytes = &state.bytes[offset..]; + state.offset += offset; state } #[must_use] pub fn advance_newline(&self) -> State<'a> { let mut state = self.clone(); - state.bytes = &state.bytes[1..]; + state.offset += 1; state.line_start = state.pos(); state } /// Returns the current position pub const fn pos(&self) -> Position { - Position::new((self.input_len - self.bytes.len()) as u32) + Position::new(self.offset as u32) } /// Returns whether the parser has reached the end of the input pub const fn has_reached_end(&self) -> bool { - self.bytes.is_empty() + self.offset == self.original_bytes.len() } /// Returns a Region corresponding to the current state, but @@ -88,9 +93,9 @@ impl<'a> fmt::Debug for State<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "State {{")?; - match std::str::from_utf8(self.bytes) { + match std::str::from_utf8(self.bytes()) { Ok(string) => write!(f, "\n\tbytes: [utf8] {:?}", string)?, - Err(_) => write!(f, "\n\tbytes: [invalid utf8] {:?}", self.bytes)?, + Err(_) => write!(f, "\n\tbytes: [invalid utf8] {:?}", self.bytes())?, } write!(f, "\n\t(offset): {:?},", self.pos())?; diff --git a/compiler/parse/src/test_helpers.rs b/compiler/parse/src/test_helpers.rs index 6f3a94b4d4..960f976efc 100644 --- a/compiler/parse/src/test_helpers.rs +++ b/compiler/parse/src/test_helpers.rs @@ -2,6 +2,7 @@ use crate::ast; use crate::module::module_defs; // use crate::module::module_defs; use crate::parser::Parser; +use crate::parser::SourceError; use crate::parser::SyntaxError; use crate::state::State; use bumpalo::collections::Vec as BumpVec; @@ -12,19 +13,21 @@ pub fn parse_expr_with<'a>( arena: &'a Bump, input: &'a str, ) -> Result, SyntaxError<'a>> { - parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) + parse_loc_with(arena, input) + .map(|loc_expr| loc_expr.value) + .map_err(|e| e.problem) } #[allow(dead_code)] pub fn parse_loc_with<'a>( arena: &'a Bump, input: &'a str, -) -> Result>, SyntaxError<'a>> { +) -> Result>, SourceError<'a, SyntaxError<'a>>> { let state = State::new(input.trim().as_bytes()); - match crate::expr::test_parse_expr(0, arena, state) { + match crate::expr::test_parse_expr(0, arena, state.clone()) { Ok(loc_expr) => Ok(loc_expr), - Err(fail) => Err(SyntaxError::Expr(fail)), + Err(fail) => Err(SyntaxError::Expr(fail).into_source_error(&state)), } } diff --git a/reporting/tests/helpers/mod.rs b/reporting/tests/helpers/mod.rs index cd6cf8e80c..388c5b4ad3 100644 --- a/reporting/tests/helpers/mod.rs +++ b/reporting/tests/helpers/mod.rs @@ -11,6 +11,7 @@ use roc_collections::all::{ImMap, MutMap, SendSet}; use roc_constrain::expr::constrain_expr; use roc_constrain::module::{constrain_imported_values, Import}; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; +use roc_parse::parser::{SyntaxError, SourceError}; use roc_problem::can::Problem; use roc_region::all::Loc; use roc_solve::solve; @@ -102,7 +103,7 @@ pub struct CanExprOut { #[derive(Debug)] pub struct ParseErrOut<'a> { - pub fail: roc_parse::parser::SyntaxError<'a>, + pub fail: SourceError<'a, SyntaxError<'a>>, pub home: ModuleId, pub interns: Interns, } diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index a5b74010c9..eb2a823f3e 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -142,7 +142,7 @@ mod test_reporting { let alloc = RocDocAllocator::new(&src_lines, home, &interns); - let problem = fail.into_parse_problem(filename.clone(), "", src.as_bytes()); + let problem = fail.into_parse_problem(filename.clone()); let doc = parse_problem(&alloc, &lines, filename, 0, problem); callback(doc.pretty(&alloc).append(alloc.line()), buf) @@ -207,11 +207,9 @@ mod test_reporting { let alloc = RocDocAllocator::new(&src_lines, home, &interns); use roc_parse::parser::SyntaxError; - let problem = SyntaxError::Header(fail).into_parse_problem( - filename.clone(), - "", - src.as_bytes(), - ); + let problem = fail + .map_problem(SyntaxError::Header) + .into_parse_problem(filename.clone()); let doc = parse_problem(&alloc, &lines, filename, 0, problem); callback(doc.pretty(&alloc).append(alloc.line()), buf) From f969c7c6d0303d8673fc5942944767acc25140f0 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Sat, 1 Jan 2022 14:45:24 -0700 Subject: [PATCH 085/541] Fixup rebase errors --- compiler/parse/src/module.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index 625423a3b4..c224555760 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -167,7 +167,6 @@ fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>, ()> { |_, mut state: State<'a>| match chomp_module_name(state.bytes()) { Ok(name) => { let width = name.len(); - state.xyzlcol.column += width as u16; state = state.advance(width); Ok((MadeProgress, ModuleName::new(name), state)) @@ -436,7 +435,7 @@ fn platform_requires<'a>() -> impl Parser<'a, PlatformRequires<'a>, ERequires<'a #[inline(always)] fn requires_rigids<'a>( - min_indent: u16, + min_indent: u32, ) -> impl Parser<'a, Collection<'a, Loc>>>, ERequires<'a>> { collection_trailing_sep_e!( word1(b'{', ERequires::ListStart), @@ -514,7 +513,7 @@ fn exposes_values<'a>() -> impl Parser< } fn spaces_around_keyword<'a, E>( - min_indent: u16, + min_indent: u32, keyword: &'static str, expectation: fn(Position) -> E, space_problem: fn(crate::parser::BadInputError, Position) -> E, From 8092f31a295e3939484670daa209fc8f65c605e4 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Sat, 1 Jan 2022 15:30:17 -0700 Subject: [PATCH 086/541] fmt --- ast/src/constrain.rs | 2 +- cli/src/format.rs | 4 ++-- compiler/load/src/file.rs | 13 +++++++------ compiler/parse/src/module.rs | 2 +- compiler/parse/src/parser.rs | 12 +++--------- reporting/tests/helpers/mod.rs | 2 +- 6 files changed, 15 insertions(+), 20 deletions(-) diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index 4df4193b85..5ea08e4fcb 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -1985,7 +1985,7 @@ pub mod test_constrain { ident::Lowercase, symbol::{IdentIds, Interns, ModuleIds, Symbol}, }; - use roc_parse::parser::{SyntaxError, SourceError}; + use roc_parse::parser::{SourceError, SyntaxError}; use roc_region::all::Region; use roc_types::{ pretty_print::{content_to_string, name_all_type_vars}, diff --git a/cli/src/format.rs b/cli/src/format.rs index bdb450ee8c..13f3edc71f 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -110,8 +110,8 @@ struct Ast<'a> { } fn parse_all<'a>(arena: &'a Bump, src: &'a str) -> Result, SyntaxError<'a>> { - let (module, state) = - module::parse_header(arena, State::new(src.as_bytes())).map_err(|e| SyntaxError::Header(e.problem))?; + let (module, state) = module::parse_header(arena, State::new(src.as_bytes())) + .map_err(|e| SyntaxError::Header(e.problem))?; let (_, defs, _) = module_defs().parse(arena, state).map_err(|(_, e, _)| e)?; diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index ea4800b84a..d07a4d388d 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -2411,7 +2411,8 @@ fn load_pkg_config<'a>( Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg])) } Err(fail) => Err(LoadingProblem::ParsingFailed( - fail.map_problem(SyntaxError::Header).into_parse_problem(filename), + fail.map_problem(SyntaxError::Header) + .into_parse_problem(filename), )), } } @@ -2654,7 +2655,8 @@ fn parse_header<'a>( module_timing, )), Err(fail) => Err(LoadingProblem::ParsingFailed( - fail.map_problem(SyntaxError::Header).into_parse_problem(filename), + fail.map_problem(SyntaxError::Header) + .into_parse_problem(filename), )), } } @@ -3706,10 +3708,9 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result, Loadi let parsed_defs = match module_defs().parse(arena, parse_state) { Ok((_, success, _state)) => success, Err((_, fail, state)) => { - return Err(LoadingProblem::ParsingFailed(fail.into_parse_problem( - header.module_path, - &state, - ))); + return Err(LoadingProblem::ParsingFailed( + fail.into_parse_problem(header.module_path, &state), + )); } }; diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index c224555760..4f9532e5a4 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -8,7 +8,7 @@ use crate::ident::{lowercase_ident, unqualified_ident, uppercase_ident}; use crate::parser::Progress::{self, *}; use crate::parser::{ backtrackable, specialize, word1, word2, EEffects, EExposes, EHeader, EImports, EPackages, - EProvides, ERequires, ETypedIdent, Parser, SyntaxError, SourceError, + EProvides, ERequires, ETypedIdent, Parser, SourceError, SyntaxError, }; use crate::state::State; use crate::string_literal; diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index 235ba95972..cb648faab3 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -226,10 +226,7 @@ pub fn bad_input_to_syntax_error<'a>(bad_input: BadInputError, pos: Position) -> } impl<'a, T> SourceError<'a, T> { - pub fn new( - problem: T, - state: &State<'a>, - ) -> Self { + pub fn new(problem: T, state: &State<'a>) -> Self { Self { problem, bytes: state.original_bytes(), @@ -242,11 +239,8 @@ impl<'a, T> SourceError<'a, T> { bytes: self.bytes, } } - - pub fn into_parse_problem( - self, - filename: std::path::PathBuf, - ) -> ParseProblem<'a, T> { + + pub fn into_parse_problem(self, filename: std::path::PathBuf) -> ParseProblem<'a, T> { ParseProblem { pos: Position::default(), problem: self.problem, diff --git a/reporting/tests/helpers/mod.rs b/reporting/tests/helpers/mod.rs index 388c5b4ad3..544c440164 100644 --- a/reporting/tests/helpers/mod.rs +++ b/reporting/tests/helpers/mod.rs @@ -11,7 +11,7 @@ use roc_collections::all::{ImMap, MutMap, SendSet}; use roc_constrain::expr::constrain_expr; use roc_constrain::module::{constrain_imported_values, Import}; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; -use roc_parse::parser::{SyntaxError, SourceError}; +use roc_parse::parser::{SourceError, SyntaxError}; use roc_problem::can::Problem; use roc_region::all::Loc; use roc_solve::solve; From 9557c234cbcdb756bf95ab6b3fb138949fb03c71 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Sat, 1 Jan 2022 18:20:57 -0800 Subject: [PATCH 087/541] Update recently added tests --- .../destructure_tag_assignment.expr.result-ast | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast b/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast index 58acb2a0fd..be7a2a54bf 100644 --- a/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast @@ -1,22 +1,22 @@ Defs( [ - |L 0-0, C 0-36| Body( - |L 0-0, C 0-5| Apply( - |L 0-0, C 0-5| GlobalTag( + @0-36 Body( + @0-5 Apply( + @0-5 GlobalTag( "Email", ), [ - |L 0-0, C 6-9| Identifier( + @6-9 Identifier( "str", ), ], ), - |L 0-0, C 12-36| Apply( - |L 0-0, C 12-17| GlobalTag( + @12-36 Apply( + @12-17 GlobalTag( "Email", ), [ - |L 0-0, C 18-36| Str( + @18-36 Str( PlainLine( "blah@example.com", ), @@ -26,7 +26,7 @@ Defs( ), ), ], - |L 1-1, C 0-3| SpaceBefore( + @37-40 SpaceBefore( Var { module_name: "", ident: "str", From 2553cae5dec0c3b88d2c9335e4cff56150f2d831 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Sat, 1 Jan 2022 19:06:19 -0800 Subject: [PATCH 088/541] Fix region size check --- Cargo.lock | 3 +++ compiler/region/Cargo.toml | 3 +++ compiler/region/src/all.rs | 7 ++----- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea0b3e16c8..fb77fdb6cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3653,6 +3653,9 @@ dependencies = [ [[package]] name = "roc_region" version = "0.1.0" +dependencies = [ + "static_assertions", +] [[package]] name = "roc_reporting" diff --git a/compiler/region/Cargo.toml b/compiler/region/Cargo.toml index 4ca1e3d4e3..ecea4be54a 100644 --- a/compiler/region/Cargo.toml +++ b/compiler/region/Cargo.toml @@ -4,3 +4,6 @@ version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" edition = "2018" + +[dependencies] +static_assertions = "1.1.0" diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index 5418d8c132..1ce888667a 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -72,11 +72,8 @@ impl Region { } } -#[test] -fn region_size() { - // Region is used all over the place. Avoid increasing its size! - assert_eq!(std::mem::size_of::(), 12); -} +// Region is used all over the place. Avoid increasing its size! +static_assertions::assert_eq_size!([u8; 8], Region); impl fmt::Debug for Region { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { From a85fdda07a92d1e0dde5ecf209f6499315e1336a Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 2 Jan 2022 12:38:41 +0000 Subject: [PATCH 089/541] Fix misalignment bug in wasm tests (copy/paste error) --- compiler/test_gen/src/helpers/wasm32_test_result.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/test_gen/src/helpers/wasm32_test_result.rs b/compiler/test_gen/src/helpers/wasm32_test_result.rs index 498c086dde..316ef31bde 100644 --- a/compiler/test_gen/src/helpers/wasm32_test_result.rs +++ b/compiler/test_gen/src/helpers/wasm32_test_result.rs @@ -114,7 +114,7 @@ wasm_test_result_primitive!(u64, i64_store, Align::Bytes8); wasm_test_result_primitive!(i64, i64_store, Align::Bytes8); wasm_test_result_primitive!(usize, i32_store, Align::Bytes4); -wasm_test_result_primitive!(f32, f32_store, Align::Bytes8); +wasm_test_result_primitive!(f32, f32_store, Align::Bytes4); wasm_test_result_primitive!(f64, f64_store, Align::Bytes8); wasm_test_result_stack_memory!(u128); From e1c0acb6509e6fd38c7f42549d3f2e62d3318209 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Sun, 2 Jan 2022 09:12:54 -0800 Subject: [PATCH 090/541] Fix region formatting test issues --- compiler/test_gen/src/gen_primitives.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index d339961dc5..999c2fe91d 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -1972,7 +1972,7 @@ fn hof_conditional() { #[test] #[cfg(any(feature = "gen-llvm"))] #[should_panic( - expected = "Roc failed with message: \"Shadowing { original_region: |L 3-3, C 4-5|, shadow: |L 6-6, C 8-9| Ident" + expected = "Roc failed with message: \"Shadowing { original_region: @57-58, shadow: @90-91 Ident" )] fn pattern_shadowing() { assert_evals_to!( @@ -2448,9 +2448,7 @@ fn backpassing_result() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -#[should_panic( - expected = "Shadowing { original_region: |L 3-3, C 4-5|, shadow: |L 5-5, C 6-7| Ident" -)] +#[should_panic(expected = "Shadowing { original_region: @57-58, shadow: @74-75 Ident")] fn function_malformed_pattern() { assert_evals_to!( indoc!( From 9b24a4ed9b1c9165f8e6843b215e6ebea787244b Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Sun, 2 Jan 2022 09:41:41 -0800 Subject: [PATCH 091/541] Fix dev backend problem reporter --- compiler/test_gen/src/helpers/dev.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/test_gen/src/helpers/dev.rs b/compiler/test_gen/src/helpers/dev.rs index 522dc6ed51..68b1189936 100644 --- a/compiler/test_gen/src/helpers/dev.rs +++ b/compiler/test_gen/src/helpers/dev.rs @@ -3,6 +3,7 @@ use roc_build::link::{link, LinkType}; use roc_builtins::bitcode; use roc_can::builtins::builtin_defs_map; use roc_collections::all::MutMap; +use roc_region::all::LineInfo; use tempfile::tempdir; #[allow(unused_imports)] @@ -124,6 +125,7 @@ pub fn helper( continue; } + let line_info = LineInfo::new(&src); let src_lines: Vec<&str> = src.split('\n').collect(); let palette = DEFAULT_PALETTE; @@ -139,7 +141,7 @@ pub fn helper( continue; } _ => { - let report = can_problem(&alloc, module_path.clone(), problem); + let report = can_problem(&alloc, &line_info, module_path.clone(), problem); let mut buf = String::new(); report.render_color_terminal(&mut buf, &alloc, &palette); @@ -150,7 +152,7 @@ pub fn helper( } for problem in type_problems { - if let Some(report) = type_problem(&alloc, module_path.clone(), problem) { + if let Some(report) = type_problem(&alloc, &line_info, module_path.clone(), problem) { let mut buf = String::new(); report.render_color_terminal(&mut buf, &alloc, &palette); @@ -160,7 +162,7 @@ pub fn helper( } for problem in mono_problems { - let report = mono_problem(&alloc, module_path.clone(), problem); + let report = mono_problem(&alloc, &line_info, module_path.clone(), problem); let mut buf = String::new(); report.render_color_terminal(&mut buf, &alloc, &palette); From a6e2f1152e4fac50530ebd1b1ba4452196229ba0 Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Sun, 2 Jan 2022 13:29:08 -0600 Subject: [PATCH 092/541] The tests run, but they don't work --- compiler/builtins/bitcode/src/utils.zig | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 60978c4a37..51f54a6801 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -223,7 +223,8 @@ const Failure = struct{ start_col: u16, end_col: u16, }; -threadlocal var failures = [_]Failure{ }; +threadlocal var failures: [*]Failure = undefined; +threadlocal var failure_length: usize = 0; threadlocal var failure_capacity: usize = 0; pub fn expectFailed( @@ -239,13 +240,15 @@ pub fn expectFailed( .end_col = end_col }; - if (failures.len >= failure_capacity) { + if (failure_length >= failure_capacity) { failure_capacity += 4096; - failures.ptr = roc_alloc(failure_capacity, @alignOf(Failure)); + const raw_pointer = roc_alloc(failure_capacity, @alignOf(Failure)); + const aligned_pointer = @alignCast(@alignOf(Failure), raw_pointer); + failures = @ptrCast([*]Failure, aligned_pointer); } - failures[failures.len] = new_failure; - failures.len += 1; + failures[failure_length] = new_failure; + failure_length += 1; } pub fn getExpectFailures() [_]Failure { @@ -318,9 +321,9 @@ test "increfC, static data" { } test "expectFailure does something"{ - try std.testing.expectEqual(failures.len, 0); + try std.testing.expectEqual(failure_length, 0); expectFailed(1, 2, 3, 4); - try std.testing.expectEqual(failures.len, 1); + try std.testing.expectEqual(failure_length, 1); const what_it_should_look_like = Failure{ .start_line = 1, .end_line = 2, From 39f6bde62c878eb0e1a22d5b6ac0a47c4c9004d1 Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Sun, 2 Jan 2022 14:03:10 -0600 Subject: [PATCH 093/541] Install a temporary memory allocation solution to make the test work --- compiler/builtins/bitcode/src/utils.zig | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 51f54a6801..ec3ce388c2 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -251,10 +251,15 @@ pub fn expectFailed( failure_length += 1; } -pub fn getExpectFailures() [_]Failure { +pub fn getExpectFailures() [*]Failure { return failures; } +pub fn deinitFailures() void { + roc_dealloc(failures, @alignOf(Failure)); + failure_length = 0; +} + pub fn unsafeReallocate( source_ptr: [*]u8, alignment: u32, @@ -321,6 +326,11 @@ test "increfC, static data" { } test "expectFailure does something"{ + //TODO: Fix whatever is causing this to error out + // defer deinitFailures(); + // For now we're doing this instead: + defer std.testing.allocator.destroy(@ptrCast(*[4096]u8, failures)); + try std.testing.expectEqual(failure_length, 0); expectFailed(1, 2, 3, 4); try std.testing.expectEqual(failure_length, 1); From e95c5f9a9d055f6a6c04d0d80081c1414bbecca8 Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Sun, 2 Jan 2022 14:08:25 -0600 Subject: [PATCH 094/541] Include a public API for the failure collection --- compiler/builtins/bitcode/src/utils.zig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index ec3ce388c2..d4d802f38c 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -251,8 +251,8 @@ pub fn expectFailed( failure_length += 1; } -pub fn getExpectFailures() [*]Failure { - return failures; +pub fn getExpectFailures() []Failure { + return failures[0..failure_length]; } pub fn deinitFailures() void { @@ -330,15 +330,15 @@ test "expectFailure does something"{ // defer deinitFailures(); // For now we're doing this instead: defer std.testing.allocator.destroy(@ptrCast(*[4096]u8, failures)); - - try std.testing.expectEqual(failure_length, 0); + + try std.testing.expectEqual(getExpectFailures().len, 0); expectFailed(1, 2, 3, 4); - try std.testing.expectEqual(failure_length, 1); + try std.testing.expectEqual(getExpectFailures().len, 1); const what_it_should_look_like = Failure{ .start_line = 1, .end_line = 2, .start_col = 3, .end_col = 4 }; - try std.testing.expectEqual(failures[0], what_it_should_look_like); + try std.testing.expectEqual(getExpectFailures()[0], what_it_should_look_like); } \ No newline at end of file From bf582b812050af2acfcad11fb307aaa5436a5cf7 Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Sun, 2 Jan 2022 14:55:56 -0600 Subject: [PATCH 095/541] Add C implementations for testing functions in zig --- compiler/builtins/bitcode/src/main.zig | 5 +++-- compiler/builtins/bitcode/src/utils.zig | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 23bcfbef61..92d9e28c72 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -146,8 +146,9 @@ comptime { exportUtilsFn(utils.increfC, "incref"); exportUtilsFn(utils.decrefC, "decref"); exportUtilsFn(utils.decrefCheckNullC, "decref_check_null"); - exportUtilsFn(utils.expectFailed, "expect_failed"); - exportUtilsFn(utils.getExpectFailures, "get_expect_failures"); + exportUtilsFn(utils.expectFailedC, "expect_failed"); + exportUtilsFn(utils.getExpectFailuresC, "get_expect_failures"); + exportUtilsFn(utils.deinitFailuresC, "deinit_failures"); @export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak }); } diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index d4d802f38c..ab92e1a101 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -251,15 +251,35 @@ pub fn expectFailed( failure_length += 1; } +pub fn expectFailedC ( + start_line: u32, + end_line: u32, + start_col: u16, + end_col: u16, +) callconv(.C) void { + return @call(.{ .modifier = always_inline }, expectFailed, .{ start_line, end_line, start_col, end_col }); +} + + pub fn getExpectFailures() []Failure { return failures[0..failure_length]; } +pub fn getExpectFailuresC() callconv(.C) *c_void { + var bytes = @ptrCast(*c_void, failures); + + return bytes; +} + pub fn deinitFailures() void { roc_dealloc(failures, @alignOf(Failure)); failure_length = 0; } +pub fn deinitFailuresC() callconv(.C) void { + return @call(.{ .modifier = always_inline }, deinitFailures, .{}); +} + pub fn unsafeReallocate( source_ptr: [*]u8, alignment: u32, From e324366ecf511ff4c2b4e45b4e0b411ebb775219 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 2 Jan 2022 22:16:51 -0500 Subject: [PATCH 096/541] zig fmt --- compiler/builtins/bitcode/src/utils.zig | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index ab92e1a101..62a16cc40a 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -217,7 +217,7 @@ pub fn allocateWithRefcount( } } -const Failure = struct{ +const Failure = struct { start_line: u32, end_line: u32, start_col: u16, @@ -233,12 +233,7 @@ pub fn expectFailed( start_col: u16, end_col: u16, ) void { - const new_failure = Failure{ - .start_line = start_line, - .end_line = end_line, - .start_col = start_col, - .end_col = end_col - }; + const new_failure = Failure{ .start_line = start_line, .end_line = end_line, .start_col = start_col, .end_col = end_col }; if (failure_length >= failure_capacity) { failure_capacity += 4096; @@ -251,7 +246,7 @@ pub fn expectFailed( failure_length += 1; } -pub fn expectFailedC ( +pub fn expectFailedC( start_line: u32, end_line: u32, start_col: u16, @@ -260,7 +255,6 @@ pub fn expectFailedC ( return @call(.{ .modifier = always_inline }, expectFailed, .{ start_line, end_line, start_col, end_col }); } - pub fn getExpectFailures() []Failure { return failures[0..failure_length]; } @@ -345,20 +339,15 @@ test "increfC, static data" { try std.testing.expectEqual(mock_rc, REFCOUNT_MAX_ISIZE); } -test "expectFailure does something"{ +test "expectFailure does something" { //TODO: Fix whatever is causing this to error out // defer deinitFailures(); // For now we're doing this instead: - defer std.testing.allocator.destroy(@ptrCast(*[4096]u8, failures)); + defer std.testing.allocator.destroy(@ptrCast(*[4096]u8, failures)); try std.testing.expectEqual(getExpectFailures().len, 0); expectFailed(1, 2, 3, 4); try std.testing.expectEqual(getExpectFailures().len, 1); - const what_it_should_look_like = Failure{ - .start_line = 1, - .end_line = 2, - .start_col = 3, - .end_col = 4 - }; + const what_it_should_look_like = Failure{ .start_line = 1, .end_line = 2, .start_col = 3, .end_col = 4 }; try std.testing.expectEqual(getExpectFailures()[0], what_it_should_look_like); -} \ No newline at end of file +} From 8c267f937b1c34d02028d14952ca7769497be443 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 2 Jan 2022 22:37:51 -0500 Subject: [PATCH 097/541] Use std.testing.free over destroy --- compiler/builtins/bitcode/src/utils.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 62a16cc40a..7d62f3b3c8 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -43,7 +43,7 @@ fn testing_roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, _: u32) fn testing_roc_dealloc(c_ptr: *c_void, _: u32) callconv(.C) void { const ptr = @ptrCast([*]u8, @alignCast(16, c_ptr)); - std.testing.allocator.destroy(ptr); + std.testing.allocator.free(ptr); } fn testing_roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { From afca1b9ee5d9a9ddacb1da6aaa1816af5e896d07 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 2 Jan 2022 22:40:17 -0500 Subject: [PATCH 098/541] Revert "Use std.testing.free over destroy" This reverts commit 7ba074353028907450c0eaad0da5cc32c4f38df8. --- compiler/builtins/bitcode/src/utils.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 7d62f3b3c8..62a16cc40a 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -43,7 +43,7 @@ fn testing_roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, _: u32) fn testing_roc_dealloc(c_ptr: *c_void, _: u32) callconv(.C) void { const ptr = @ptrCast([*]u8, @alignCast(16, c_ptr)); - std.testing.allocator.free(ptr); + std.testing.allocator.destroy(ptr); } fn testing_roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { From bfdf0bf9162aaa6639b9ddb86a8f848ffe5e617d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 2 Jan 2022 22:41:31 -0500 Subject: [PATCH 099/541] Use roc_realloc in expect failures --- compiler/builtins/bitcode/src/utils.zig | 35 ++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 62a16cc40a..89696539bd 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -18,6 +18,9 @@ extern fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void; // Signals to the host that the program has panicked extern fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void; +// should work just like libc memcpy (we can't assume libc is present) +extern fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; + comptime { const builtin = @import("builtin"); // During tetsts, use the testing allocators to satisfy these functions. @@ -235,11 +238,35 @@ pub fn expectFailed( ) void { const new_failure = Failure{ .start_line = start_line, .end_line = end_line, .start_col = start_col, .end_col = end_col }; + // If we don't have enough capacity to add a failure, allocate a new failures pointer. if (failure_length >= failure_capacity) { - failure_capacity += 4096; - const raw_pointer = roc_alloc(failure_capacity, @alignOf(Failure)); - const aligned_pointer = @alignCast(@alignOf(Failure), raw_pointer); - failures = @ptrCast([*]Failure, aligned_pointer); + if (failure_capacity > 0) { + // We already had previous failures allocated, so try to realloc in order + // to grow the size in-place without having to memcpy bytes over. + const old_pointer = failures; + const old_bytes = failure_capacity * @sizeOf(Failure); + + failure_capacity *= 2; + + const new_bytes = failure_capacity * @sizeOf(Failure); + const raw_pointer = roc_realloc(failures, new_bytes, old_bytes, @alignOf(Failure)); + + failures = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_pointer)); + + // If realloc wasn't able to expand in-place (that is, it returned a different pointer), + // then copy the data into the new pointer and dealloc the old one. + if (failures != old_pointer) { + roc_memcpy(@ptrCast([*]u8, failures), @ptrCast([*]u8, old_pointer), old_bytes); + roc_dealloc(old_pointer, @alignOf(Failure)); + } + } else { + // We've never had any failures before, so allocate the failures for the first time. + failure_capacity = 10; + + const raw_pointer = roc_alloc(failure_capacity * @sizeOf(Failure), @alignOf(Failure)); + + failures = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_pointer)); + } } failures[failure_length] = new_failure; From ac67eaf837c28493a62da8125fa2ff3b40276abe Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 2 Jan 2022 22:42:01 -0500 Subject: [PATCH 100/541] Use deinitFailures in expectFailure test --- compiler/builtins/bitcode/src/utils.zig | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 89696539bd..5a30527678 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -367,10 +367,7 @@ test "increfC, static data" { } test "expectFailure does something" { - //TODO: Fix whatever is causing this to error out - // defer deinitFailures(); - // For now we're doing this instead: - defer std.testing.allocator.destroy(@ptrCast(*[4096]u8, failures)); + defer deinitFailures(); try std.testing.expectEqual(getExpectFailures().len, 0); expectFailed(1, 2, 3, 4); From 74cca25aecb6becc8dccd46053b32345eafc3acd Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 2 Jan 2022 22:53:35 -0500 Subject: [PATCH 101/541] Use global mutex over threadlocal --- compiler/builtins/bitcode/src/utils.zig | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 5a30527678..09861e4e22 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -226,9 +226,13 @@ const Failure = struct { start_col: u16, end_col: u16, }; -threadlocal var failures: [*]Failure = undefined; -threadlocal var failure_length: usize = 0; -threadlocal var failure_capacity: usize = 0; + +// BEGIN FAILURES GLOBALS /////////////////// +var failures_mutex = std.Thread.Mutex{}; +var failures: [*]Failure = undefined; +var failure_length: usize = 0; +var failure_capacity: usize = 0; +// END FAILURES GLOBALS ///////////////////// pub fn expectFailed( start_line: u32, @@ -238,6 +242,21 @@ pub fn expectFailed( ) void { const new_failure = Failure{ .start_line = start_line, .end_line = end_line, .start_col = start_col, .end_col = end_col }; + // Lock the failures mutex before reading from any of the failures globals, + // and then release the lock once we're done modifying things. + + // TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d + // to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig + // + // ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9: + // + // failures_mutex.lock(); + // defer failures_mutex.release(); + // + // 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆 + const held = failures_mutex.acquire(); + defer held.release(); + // If we don't have enough capacity to add a failure, allocate a new failures pointer. if (failure_length >= failure_capacity) { if (failure_capacity > 0) { From 922d8e57c7fab4771fcf4fbeea41982457f4f3aa Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Sun, 2 Jan 2022 14:56:30 -0600 Subject: [PATCH 102/541] trying to see if we have access to our testing modules elsewhere --- compiler/gen_llvm/src/llvm/build.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 685d517742..a5284f4c55 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -6038,6 +6038,7 @@ fn run_low_level<'a, 'ctx, 'env>( match env.ptr_bytes { 8 => { + let test = env.module.get_function("get_expect_failed").unwrap(); let fn_ptr_type = context .void_type() .fn_type(&[], false) From 650c29de3cbfe58be8382cff9ac0f9a3d4bd2064 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Sun, 2 Jan 2022 21:50:42 -0800 Subject: [PATCH 103/541] Change LineColumn::column to u32 to avoid overflow, and remove LineTooLong error --- compiler/parse/src/parser.rs | 6 +----- compiler/region/src/all.rs | 8 ++++---- reporting/src/error/parse.rs | 4 ++-- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index cb648faab3..21dad70f2b 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -49,7 +49,6 @@ pub enum SyntaxError<'a> { Unexpected(Region), OutdentedTooFar, ConditionFailed, - LineTooLong(Position), TooManyLines, Eof(Region), InvalidPattern, @@ -208,18 +207,16 @@ pub enum EImports { pub enum BadInputError { HasTab, /// - LineTooLong, TooManyLines, /// /// BadUtf8, } -pub fn bad_input_to_syntax_error<'a>(bad_input: BadInputError, pos: Position) -> SyntaxError<'a> { +pub fn bad_input_to_syntax_error<'a>(bad_input: BadInputError) -> SyntaxError<'a> { use crate::parser::BadInputError::*; match bad_input { HasTab => SyntaxError::NotYetImplemented("call error on tabs".to_string()), - LineTooLong => SyntaxError::LineTooLong(pos), TooManyLines => SyntaxError::TooManyLines, BadUtf8 => SyntaxError::BadUtf8, } @@ -323,7 +320,6 @@ pub enum EExpr<'a> { #[derive(Debug, Clone, PartialEq, Eq)] pub enum ENumber { End, - LineTooLong, } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index 1ce888667a..1481e4a735 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -140,7 +140,7 @@ impl Debug for Position { #[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Default)] pub struct LineColumn { pub line: u32, - pub column: u16, + pub column: u32, } impl LineColumn { @@ -149,7 +149,7 @@ impl LineColumn { } #[must_use] - pub const fn bump_column(self, count: u16) -> Self { + pub const fn bump_column(self, count: u32) -> Self { Self { line: self.line, column: self.column + count, @@ -353,7 +353,7 @@ impl LineInfo { let column = offset - self.line_offsets[line]; LineColumn { line: line as u32, - column: column as u16, + column: column as u32, } } @@ -419,7 +419,7 @@ fn test_line_info() { info.convert_offset(input.len() as u32), LineColumn { line: lines.len().saturating_sub(1) as u32, - column: lines.last().map(|l| l.len()).unwrap_or(0) as u16, + column: lines.last().map(|l| l.len()).unwrap_or(0) as u32, } ) } diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index 5a5e815091..01d4d47270 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -3628,7 +3628,7 @@ fn next_line_starts_with_char( match spaces_dropped.chars().next() { Some(c) if c == character => Some(LineColumn { line: pos.line + 1, - column: (line.len() - spaces_dropped.len()) as u16, + column: (line.len() - spaces_dropped.len()) as u32, }), _ => None, } @@ -3639,6 +3639,6 @@ fn next_line_starts_with_char( fn to_keyword_region(pos: LineColumn, keyword: &str) -> LineColumnRegion { LineColumnRegion { start: pos, - end: pos.bump_column(keyword.len() as u16), + end: pos.bump_column(keyword.len() as u32), } } From b9665a8f0c5601cc17847c9c1873a420ce8a6e4b Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 3 Jan 2022 15:51:31 +0100 Subject: [PATCH 104/541] fixed precedence comment --- cli_utils/Cargo.lock | 154 +++++++++++++++++++++++++----- compiler/module/src/called_via.rs | 3 +- 2 files changed, 133 insertions(+), 24 deletions(-) diff --git a/cli_utils/Cargo.lock b/cli_utils/Cargo.lock index 852c98d3fb..c9b9c20929 100644 --- a/cli_utils/Cargo.lock +++ b/cli_utils/Cargo.lock @@ -386,6 +386,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "clipboard-win" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8340083d28acb43451166543b98c838299b7e0863621be53a338adceea0ed" +dependencies = [ + "error-code", + "str-buf", + "winapi", +] + [[package]] name = "cocoa" version = "0.24.0" @@ -480,7 +491,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4423d79fed83ebd9ab81ec21fa97144300a961782158287dc9bf7eddac37ff0b" dependencies = [ - "clipboard-win", + "clipboard-win 3.1.1", "objc", "objc-foundation", "objc_id", @@ -808,9 +819,9 @@ dependencies = [ [[package]] name = "dirs-next" -version = "1.0.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf36e65a80337bea855cd4ef9b8401ffce06a7baedf2e85ec467b1ac3f6e82b6" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ "cfg-if 1.0.0", "dirs-sys-next", @@ -898,6 +909,12 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "env_logger" version = "0.9.0" @@ -911,12 +928,33 @@ dependencies = [ "termcolor", ] +[[package]] +name = "error-code" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5115567ac25674e0043e472be13d14e537f37ea8aa4bdc4aef0c89add1db1ff" +dependencies = [ + "libc", + "str-buf", +] + [[package]] name = "fake-simd" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fd-lock" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16910e685088843d53132b04e0f10a571fdb193224fc589685b3ba1ce4cb03d" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "windows-sys", +] + [[package]] name = "find-crate" version = "0.6.3" @@ -1691,16 +1729,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d" [[package]] -name = "nix" -version = "0.17.0" +name = "nibble_vec" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" dependencies = [ - "bitflags", - "cc", - "cfg-if 0.1.10", - "libc", - "void", + "smallvec", ] [[package]] @@ -1740,6 +1774,19 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nix" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset", +] + [[package]] name = "nom" version = "7.1.0" @@ -2246,6 +2293,16 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.8.4" @@ -2789,6 +2846,9 @@ dependencies = [ [[package]] name = "roc_region" version = "0.1.0" +dependencies = [ + "static_assertions", +] [[package]] name = "roc_reporting" @@ -2887,16 +2947,21 @@ dependencies = [ [[package]] name = "rustyline" -version = "6.2.0" -source = "git+https://github.com/rtfeldman/rustyline?tag=prompt-fix#a6b8a20d2bf5c3793d7367848be2f4afec2f0d99" +version = "9.1.1" +source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc" dependencies = [ - "cfg-if 0.1.10", + "bitflags", + "cfg-if 1.0.0", + "clipboard-win 4.2.2", "dirs-next", + "fd-lock", "libc", "log", "memchr", - "nix 0.17.0", + "nix 0.23.1", + "radix_trie", "scopeguard", + "smallvec", "unicode-segmentation", "unicode-width", "utf8parse", @@ -2905,8 +2970,8 @@ dependencies = [ [[package]] name = "rustyline-derive" -version = "0.3.1" -source = "git+https://github.com/rtfeldman/rustyline?tag=prompt-fix#a6b8a20d2bf5c3793d7367848be2f4afec2f0d99" +version = "0.6.0" +source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc" dependencies = [ "quote", "syn", @@ -3175,6 +3240,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "str-buf" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a" + [[package]] name = "strip-ansi-escapes" version = "0.1.1" @@ -3418,12 +3489,6 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - [[package]] name = "vte" version = "0.10.1" @@ -3816,6 +3881,49 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2" + +[[package]] +name = "windows_i686_gnu" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a" + +[[package]] +name = "windows_i686_msvc" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f" + [[package]] name = "winit" version = "0.25.0" diff --git a/compiler/module/src/called_via.rs b/compiler/module/src/called_via.rs index a53b20ab08..62d1ecbe3f 100644 --- a/compiler/module/src/called_via.rs +++ b/compiler/module/src/called_via.rs @@ -45,10 +45,11 @@ pub enum BinOp { GreaterThanOrEq, And, Or, - Pizza, // lowest precedence + Pizza, Assignment, HasType, Backpassing, + // lowest precedence } impl BinOp { From 7044e95df80e4716d398276e9ca3d795007b0431 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 3 Jan 2022 16:18:53 +0100 Subject: [PATCH 105/541] fix imported function thunk --- compiler/mono/src/ir.rs | 43 +++++++++++++++++++++++++-- examples/benchmarks/Issue2279.roc | 4 ++- examples/benchmarks/Issue2279Help.roc | 4 ++- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 66e28453eb..8356aef776 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -4017,7 +4017,8 @@ pub fn with_hole<'a>( // if it's in there, it's a call by name, otherwise it's a call by pointer let is_known = |key| { // a proc in this module, or an imported symbol - procs.partial_procs.contains_key(key) || env.is_imported_symbol(key) + procs.partial_procs.contains_key(key) + || (env.is_imported_symbol(key) && !procs.is_imported_module_thunk(key)) }; match loc_expr.value { @@ -4072,8 +4073,44 @@ pub fn with_hole<'a>( LocalFunction(_) => { unreachable!("if this was known to be a function, we would not be here") } - Imported(_) => { - unreachable!("an imported value is never an anonymous function") + Imported(thunk_name) => { + debug_assert!(procs.is_imported_module_thunk(thunk_name)); + + add_needed_external(procs, env, fn_var, thunk_name); + + let function_symbol = env.unique_symbol(); + + match full_layout { + RawFunctionLayout::Function( + arg_layouts, + lambda_set, + ret_layout, + ) => { + let closure_data_symbol = function_symbol; + + result = match_on_lambda_set( + env, + lambda_set, + closure_data_symbol, + arg_symbols, + arg_layouts, + ret_layout, + assigned, + hole, + ); + + result = force_thunk( + env, + thunk_name, + Layout::LambdaSet(lambda_set), + function_symbol, + env.arena.alloc(result), + ); + } + RawFunctionLayout::ZeroArgumentThunk(_) => { + unreachable!("calling a non-closure layout") + } + } } Value(function_symbol) => match full_layout { RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout) => { diff --git a/examples/benchmarks/Issue2279.roc b/examples/benchmarks/Issue2279.roc index 68c0046589..2adffc6c8b 100644 --- a/examples/benchmarks/Issue2279.roc +++ b/examples/benchmarks/Issue2279.roc @@ -4,6 +4,8 @@ app "issue2279" provides [ main ] to pf main = - text = Issue2279Help.text + t1 = Issue2279Help.asText 42 + t2 = Issue2279Help.text + text = if True then t1 else t2 Task.putLine text diff --git a/examples/benchmarks/Issue2279Help.roc b/examples/benchmarks/Issue2279Help.roc index 6e7332bbeb..15fb04cf0f 100644 --- a/examples/benchmarks/Issue2279Help.roc +++ b/examples/benchmarks/Issue2279Help.roc @@ -1,5 +1,7 @@ interface Issue2279Help - exposes [ text ] + exposes [ text, asText ] imports [] text = "Hello, world!" + +asText = Num.toStr From fc4cea9f75074495a7194c41287184ace4106237 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 3 Jan 2022 16:50:43 +0100 Subject: [PATCH 106/541] don't throw away a jump --- compiler/mono/src/ir.rs | 7 ++++++- examples/benchmarks/Issue2279.roc | 8 +++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 8356aef776..3a5c368d04 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -2909,8 +2909,13 @@ fn specialize_naked_symbol<'a>( std::vec::Vec::new(), layout_cache, assigned, - env.arena.alloc(Stmt::Ret(assigned)), + env.arena.alloc(match hole { + Stmt::Jump(id, _) => Stmt::Jump(*id, env.arena.alloc([assigned])), + Stmt::Ret(_) => Stmt::Ret(assigned), + _ => unreachable!(), + }), ); + return result; } } diff --git a/examples/benchmarks/Issue2279.roc b/examples/benchmarks/Issue2279.roc index 2adffc6c8b..0b85cdd904 100644 --- a/examples/benchmarks/Issue2279.roc +++ b/examples/benchmarks/Issue2279.roc @@ -4,8 +4,10 @@ app "issue2279" provides [ main ] to pf main = - t1 = Issue2279Help.asText 42 - t2 = Issue2279Help.text - text = if True then t1 else t2 + text = + if True then + Issue2279Help.text + else + Issue2279Help.asText 42 Task.putLine text From d2f95bde63245a37933217431929f94ad0f63d72 Mon Sep 17 00:00:00 2001 From: rvcas Date: Mon, 3 Jan 2022 18:15:46 -0500 Subject: [PATCH 107/541] chore(StrToNum): layout is always a Struct, we don't need the Union case --- compiler/gen_llvm/src/llvm/build.rs | 1 - compiler/gen_wasm/src/low_level.rs | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index be49f729e9..01012f3137 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -5322,7 +5322,6 @@ fn run_low_level<'a, 'ctx, 'env>( let (string, _string_layout) = load_symbol_and_layout(scope, &args[0]); let number_layout = match layout { - Layout::Union(UnionLayout::NonRecursive(tags)) => tags[1][0], Layout::Struct(fields) => fields[0], // TODO: why is it sometimes a struct? _ => unreachable!(), }; diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index c1baade7a3..77b631aa1d 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -1,7 +1,7 @@ use roc_builtins::bitcode::{self, FloatWidth}; use roc_module::low_level::{LowLevel, LowLevel::*}; use roc_module::symbol::Symbol; -use roc_mono::layout::{Builtin, Layout, UnionLayout}; +use roc_mono::layout::{Builtin, Layout}; use roc_reporting::internal_error; use crate::layout::{StackMemoryFormat::*, WasmLayout}; @@ -50,8 +50,7 @@ pub fn dispatch_low_level<'a>( StrCountGraphemes => return BuiltinCall(bitcode::STR_COUNT_GRAPEHEME_CLUSTERS), StrToNum => { let number_layout = match mono_layout { - Layout::Union(UnionLayout::NonRecursive(tags)) => tags[1][0], - Layout::Struct(fields) => fields[0], // TODO: why is it sometimes a struct? + Layout::Struct(fields) => fields[0], _ => internal_error!("Unexpected mono layout {:?} for StrToNum", mono_layout), }; // match on the return layout to figure out which zig builtin we need From f22f96843e2a2314e0a4f508ff335e4680df4501 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Mon, 3 Jan 2022 19:52:33 -0800 Subject: [PATCH 108/541] Refactor ParseProblem * Remove the `pos` field, which was always being assigned Position::default() * Remove one use of this `pos`, by removing the never-used SyntaxError::ConditionFailed variant * Adjust the other use to do what was probably intended - which is to say, pointing to the beginning of the def with the error * Rename to FileError, reuse `SourceError` as an inner field, to avoid duplicating the `bytes` --- compiler/load/src/file.rs | 16 +++---- compiler/parse/src/module.rs | 9 ++-- compiler/parse/src/parser.rs | 44 +++++++++++-------- compiler/parse/src/test_helpers.rs | 3 +- .../type_argument_no_arrow.expr.result-ast | 2 +- .../fail/type_double_comma.expr.result-ast | 2 +- docs/src/lib.rs | 4 +- reporting/src/error/parse.rs | 35 +++------------ reporting/tests/test_reporting.rs | 4 +- 9 files changed, 53 insertions(+), 66 deletions(-) diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index d07a4d388d..489f8a84f9 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -27,7 +27,7 @@ use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation}; use roc_parse::header::PackageName; use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent}; use roc_parse::module::module_defs; -use roc_parse::parser::{ParseProblem, Parser, SyntaxError}; +use roc_parse::parser::{FileError, Parser, SyntaxError}; use roc_region::all::{LineInfo, Loc, Region}; use roc_solve::module::SolvedModule; use roc_solve::solve; @@ -843,7 +843,7 @@ enum Msg<'a> { exposed_to_host: MutMap, }, - FailedToParse(ParseProblem<'a, SyntaxError<'a>>), + FailedToParse(FileError<'a, SyntaxError<'a>>), FailedToReadFile { filename: PathBuf, error: io::ErrorKind, @@ -1035,7 +1035,7 @@ pub enum LoadingProblem<'a> { filename: PathBuf, error: io::ErrorKind, }, - ParsingFailed(ParseProblem<'a, SyntaxError<'a>>), + ParsingFailed(FileError<'a, SyntaxError<'a>>), UnexpectedHeader(String), MsgChannelDied, @@ -2412,7 +2412,7 @@ fn load_pkg_config<'a>( } Err(fail) => Err(LoadingProblem::ParsingFailed( fail.map_problem(SyntaxError::Header) - .into_parse_problem(filename), + .into_file_error(filename), )), } } @@ -2656,7 +2656,7 @@ fn parse_header<'a>( )), Err(fail) => Err(LoadingProblem::ParsingFailed( fail.map_problem(SyntaxError::Header) - .into_parse_problem(filename), + .into_file_error(filename), )), } } @@ -3709,7 +3709,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result, Loadi Ok((_, success, _state)) => success, Err((_, fail, state)) => { return Err(LoadingProblem::ParsingFailed( - fail.into_parse_problem(header.module_path, &state), + fail.into_file_error(header.module_path, &state), )); } }; @@ -4309,14 +4309,14 @@ fn to_file_problem_report(filename: &Path, error: io::ErrorKind) -> String { } fn to_parse_problem_report<'a>( - problem: ParseProblem<'a, SyntaxError<'a>>, + problem: FileError<'a, SyntaxError<'a>>, mut module_ids: ModuleIds, all_ident_ids: MutMap, ) -> String { use roc_reporting::report::{parse_problem, RocDocAllocator, DEFAULT_PALETTE}; // TODO this is not in fact safe - let src = unsafe { from_utf8_unchecked(problem.bytes) }; + let src = unsafe { from_utf8_unchecked(problem.problem.bytes) }; let src_lines = src.lines().collect::>(); // let mut src_lines: Vec<&str> = problem.prefix.lines().collect(); // src_lines.extend(src.lines().skip(1)); diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index 4f9532e5a4..7b5f3206fa 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -7,8 +7,8 @@ use crate::header::{ use crate::ident::{lowercase_ident, unqualified_ident, uppercase_ident}; use crate::parser::Progress::{self, *}; use crate::parser::{ - backtrackable, specialize, word1, word2, EEffects, EExposes, EHeader, EImports, EPackages, - EProvides, ERequires, ETypedIdent, Parser, SourceError, SyntaxError, + backtrackable, specialize, specialize_region, word1, word2, EEffects, EExposes, EHeader, + EImports, EPackages, EProvides, ERequires, ETypedIdent, Parser, SourceError, SyntaxError, }; use crate::state::State; use crate::string_literal; @@ -31,7 +31,10 @@ pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Loc>>, SyntaxError<' // force that we parse until the end of the input let min_indent = 0; skip_second!( - specialize(|e, _| SyntaxError::Expr(e), crate::expr::defs(min_indent),), + specialize_region( + |e, r| SyntaxError::Expr(e, r.start()), + crate::expr::defs(min_indent), + ), end_of_file() ) } diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index 21dad70f2b..73f2bbd38c 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -48,7 +48,6 @@ impl Progress { pub enum SyntaxError<'a> { Unexpected(Region), OutdentedTooFar, - ConditionFailed, TooManyLines, Eof(Region), InvalidPattern, @@ -59,7 +58,7 @@ pub enum SyntaxError<'a> { Todo, Type(EType<'a>), Pattern(EPattern<'a>), - Expr(EExpr<'a>), + Expr(EExpr<'a>, Position), Header(EHeader<'a>), Space(BadInputError), NotEndOfFile(Position), @@ -237,12 +236,10 @@ impl<'a, T> SourceError<'a, T> { } } - pub fn into_parse_problem(self, filename: std::path::PathBuf) -> ParseProblem<'a, T> { - ParseProblem { - pos: Position::default(), - problem: self.problem, + pub fn into_file_error(self, filename: std::path::PathBuf) -> FileError<'a, T> { + FileError { + problem: self, filename, - bytes: self.bytes, } } } @@ -255,17 +252,12 @@ impl<'a> SyntaxError<'a> { } } - pub fn into_parse_problem( + pub fn into_file_error( self, filename: std::path::PathBuf, state: &State<'a>, - ) -> ParseProblem<'a, SyntaxError<'a>> { - ParseProblem { - pos: Position::default(), - problem: self, - filename, - bytes: state.original_bytes(), - } + ) -> FileError<'a, SyntaxError<'a>> { + self.into_source_error(state).into_file_error(filename) } } @@ -594,11 +586,9 @@ pub struct SourceError<'a, T> { } #[derive(Debug)] -pub struct ParseProblem<'a, T> { - pub pos: Position, - pub problem: T, +pub struct FileError<'a, T> { + pub problem: SourceError<'a, T>, pub filename: std::path::PathBuf, - pub bytes: &'a [u8], } pub trait Parser<'a, Output, Error> { @@ -1295,6 +1285,22 @@ where } } +/// Like `specialize`, except the error function receives a Region representing the begin/end of the error +pub fn specialize_region<'a, F, P, T, X, Y>(map_error: F, parser: P) -> impl Parser<'a, T, Y> +where + F: Fn(X, Region) -> Y, + P: Parser<'a, T, X>, + Y: 'a, +{ + move |a, s: State<'a>| { + let start = s.pos(); + match parser.parse(a, s) { + Ok(t) => Ok(t), + Err((p, error, s)) => Err((p, map_error(error, Region::new(start, s.pos())), s)), + } + } +} + pub fn specialize_ref<'a, F, P, T, X, Y>(map_error: F, parser: P) -> impl Parser<'a, T, Y> where F: Fn(&'a X, Position) -> Y, diff --git a/compiler/parse/src/test_helpers.rs b/compiler/parse/src/test_helpers.rs index 960f976efc..77c165dde9 100644 --- a/compiler/parse/src/test_helpers.rs +++ b/compiler/parse/src/test_helpers.rs @@ -8,6 +8,7 @@ use crate::state::State; use bumpalo::collections::Vec as BumpVec; use bumpalo::Bump; use roc_region::all::Loc; +use roc_region::all::Position; pub fn parse_expr_with<'a>( arena: &'a Bump, @@ -27,7 +28,7 @@ pub fn parse_loc_with<'a>( match crate::expr::test_parse_expr(0, arena, state.clone()) { Ok(loc_expr) => Ok(loc_expr), - Err(fail) => Err(SyntaxError::Expr(fail).into_source_error(&state)), + Err(fail) => Err(SyntaxError::Expr(fail, Position::default()).into_source_error(&state)), } } diff --git a/compiler/parse/tests/snapshots/fail/type_argument_no_arrow.expr.result-ast b/compiler/parse/tests/snapshots/fail/type_argument_no_arrow.expr.result-ast index 2601637ebe..ded80c1d02 100644 --- a/compiler/parse/tests/snapshots/fail/type_argument_no_arrow.expr.result-ast +++ b/compiler/parse/tests/snapshots/fail/type_argument_no_arrow.expr.result-ast @@ -1 +1 @@ -Expr(Type(TIndentEnd(@12), @4)) \ No newline at end of file +Expr(Type(TIndentEnd(@12), @4), @0) \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/fail/type_double_comma.expr.result-ast b/compiler/parse/tests/snapshots/fail/type_double_comma.expr.result-ast index c020db9200..41d89171c2 100644 --- a/compiler/parse/tests/snapshots/fail/type_double_comma.expr.result-ast +++ b/compiler/parse/tests/snapshots/fail/type_double_comma.expr.result-ast @@ -1 +1 @@ -Expr(Type(TFunctionArgument(@8), @4)) \ No newline at end of file +Expr(Type(TFunctionArgument(@8), @4), @0) \ No newline at end of file diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 305e7c45b8..35eaa93a13 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -16,7 +16,7 @@ use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; use roc_parse::ident::{parse_ident, Ident}; use roc_parse::parser::SyntaxError; use roc_parse::state::State; -use roc_region::all::Region; +use roc_region::all::{Position, Region}; use std::fs; use std::path::{Path, PathBuf}; @@ -128,7 +128,7 @@ pub fn syntax_highlight_expr<'a>( Ok(buf.to_string()) } - Err(fail) => Err(SyntaxError::Expr(fail)), + Err(fail) => Err(SyntaxError::Expr(fail, Position::default())), } } diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index 01d4d47270..27129d659e 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -1,4 +1,4 @@ -use roc_parse::parser::{ParseProblem, SyntaxError}; +use roc_parse::parser::{FileError, SyntaxError}; use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Position, Region}; use std::path::PathBuf; @@ -10,15 +10,9 @@ pub fn parse_problem<'a>( lines: &LineInfo, filename: PathBuf, _starting_line: u32, - parse_problem: ParseProblem>, + parse_problem: FileError>, ) -> Report<'a> { - to_syntax_report( - alloc, - lines, - filename, - &parse_problem.problem, - parse_problem.pos, - ) + to_syntax_report(alloc, lines, filename, &parse_problem.problem.problem) } fn note_for_record_type_indent<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> { @@ -68,7 +62,6 @@ fn to_syntax_report<'a>( lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::SyntaxError<'a>, - start: Position, ) -> Report<'a> { use SyntaxError::*; @@ -79,22 +72,7 @@ fn to_syntax_report<'a>( severity: Severity::RuntimeError, }; - let region = Region::from_pos(start); - match parse_problem { - SyntaxError::ConditionFailed => { - let doc = alloc.stack(vec![ - alloc.reflow("A condition failed:"), - alloc.region(lines.convert_region(region)), - ]); - - Report { - filename, - doc, - title: "PARSE PROBLEM".to_string(), - severity: Severity::RuntimeError, - } - } SyntaxError::ArgumentsBeforeEquals(region) => { let doc = alloc.stack(vec![ alloc.reflow("Unexpected tokens in front of the `=` symbol:"), @@ -126,12 +104,11 @@ fn to_syntax_report<'a>( report(doc) } NotEndOfFile(pos) => { - let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I expected to reach the end of the file, but got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.region(region), alloc.concat(vec![alloc.reflow("no hints")]), ]); @@ -167,11 +144,11 @@ fn to_syntax_report<'a>( } Type(typ) => to_type_report(alloc, lines, filename, typ, Position::default()), Pattern(pat) => to_pattern_report(alloc, lines, filename, pat, Position::default()), - Expr(expr) => to_expr_report( + Expr(expr, start) => to_expr_report( alloc, lines, filename, - Context::InDef(start), + Context::InDef(*start), expr, Position::default(), ), diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index eb2a823f3e..d2dae2c026 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -142,7 +142,7 @@ mod test_reporting { let alloc = RocDocAllocator::new(&src_lines, home, &interns); - let problem = fail.into_parse_problem(filename.clone()); + let problem = fail.into_file_error(filename.clone()); let doc = parse_problem(&alloc, &lines, filename, 0, problem); callback(doc.pretty(&alloc).append(alloc.line()), buf) @@ -209,7 +209,7 @@ mod test_reporting { use roc_parse::parser::SyntaxError; let problem = fail .map_problem(SyntaxError::Header) - .into_parse_problem(filename.clone()); + .into_file_error(filename.clone()); let doc = parse_problem(&alloc, &lines, filename, 0, problem); callback(doc.pretty(&alloc).append(alloc.line()), buf) From 352c7979eb5c1333477206d3cff088b9dffcdea4 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Mon, 3 Jan 2022 20:06:23 -0800 Subject: [PATCH 109/541] Re-enable debug_assert in State::advance --- compiler/parse/src/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/parse/src/state.rs b/compiler/parse/src/state.rs index 59af7f21dc..685dba35e8 100644 --- a/compiler/parse/src/state.rs +++ b/compiler/parse/src/state.rs @@ -47,7 +47,7 @@ impl<'a> State<'a> { #[must_use] pub fn advance(&self, offset: usize) -> State<'a> { let mut state = self.clone(); - // debug_assert!(!state.bytes[..offset].iter().any(|b| *b == b'\n')); + debug_assert!(!state.bytes()[..offset].iter().any(|b| *b == b'\n')); state.offset += offset; state } From d9cfa2b2a50b60564ebc07953e9e5cae46ec246a Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Mon, 3 Jan 2022 20:06:31 -0800 Subject: [PATCH 110/541] Remove unused State::fail method --- compiler/parse/src/state.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/compiler/parse/src/state.rs b/compiler/parse/src/state.rs index 685dba35e8..faf0132655 100644 --- a/compiler/parse/src/state.rs +++ b/compiler/parse/src/state.rs @@ -1,5 +1,3 @@ -use crate::parser::Progress; -use bumpalo::Bump; use roc_region::all::{Position, Region}; use std::fmt; @@ -77,16 +75,6 @@ impl<'a> State<'a> { pub fn len_region(&self, length: u32) -> Region { Region::new(self.pos(), self.pos().bump_column(length)) } - - /// Return a failing ParseResult for the given FailReason - pub fn fail( - self, - _arena: &'a Bump, - progress: Progress, - reason: X, - ) -> Result<(Progress, T, Self), (Progress, X, Self)> { - Err((progress, reason, self)) - } } impl<'a> fmt::Debug for State<'a> { From a3c6bfce43cceeb7674be2f7da5e9223bc85ed23 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Mon, 3 Jan 2022 20:08:39 -0800 Subject: [PATCH 111/541] Mark infer_union_def_position as a test (followup to #2305) --- compiler/solve/tests/solve_expr.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 78982c509b..7ada9982f1 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -4923,6 +4923,7 @@ mod solve_expr { ) } + #[test] fn infer_union_def_position() { infer_eq_without_problem( indoc!( From e236703c42da0cbf65f3862ae3ba96d8a523113d Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Mon, 3 Jan 2022 20:16:38 -0800 Subject: [PATCH 112/541] Add examples/benchmarks/issue2279 to gitignore --- examples/benchmarks/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/benchmarks/.gitignore b/examples/benchmarks/.gitignore index a9ac429d87..b42405dacb 100644 --- a/examples/benchmarks/.gitignore +++ b/examples/benchmarks/.gitignore @@ -12,6 +12,7 @@ closure cfold rbtree-insert rbtree-del +issue2279 rbtree-ck test-astar test-base64 From 5d7b4018b795fee0047665b6e2a458d9ca919145 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 2 Jan 2022 13:58:36 +0000 Subject: [PATCH 113/541] Generate refcounting helper procedures for tag unions --- compiler/mono/src/code_gen_help/mod.rs | 6 + compiler/mono/src/code_gen_help/refcount.rs | 249 +++++++++++++++++++- compiler/test_gen/src/gen_refcount.rs | 47 ++++ 3 files changed, 296 insertions(+), 6 deletions(-) diff --git a/compiler/mono/src/code_gen_help/mod.rs b/compiler/mono/src/code_gen_help/mod.rs index 65dfc811a6..b708d3324e 100644 --- a/compiler/mono/src/code_gen_help/mod.rs +++ b/compiler/mono/src/code_gen_help/mod.rs @@ -32,6 +32,12 @@ enum HelperOp { Eq, } +impl HelperOp { + fn is_decref(&self) -> bool { + matches!(self, Self::DecRef(_)) + } +} + #[derive(Debug)] struct Specialization<'a> { op: HelperOp, diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index 4a27376f25..c95fb55f10 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -1,3 +1,4 @@ +use bumpalo::collections::vec::Vec; use roc_builtins::bitcode::IntWidth; use roc_module::low_level::{LowLevel, LowLevel::*}; use roc_module::symbol::{IdentIds, Symbol}; @@ -6,7 +7,7 @@ use crate::code_gen_help::let_lowlevel; use crate::ir::{ BranchInfo, Call, CallType, Expr, JoinPointId, Literal, ModifyRc, Param, Stmt, UpdateModeId, }; -use crate::layout::{Builtin, Layout, UnionLayout}; +use crate::layout::{Builtin, Layout, TagIdIntType, UnionLayout}; use super::{CodeGenHelp, Context, HelperOp}; @@ -112,9 +113,12 @@ pub fn refcount_generic<'a>( Layout::Struct(field_layouts) => { refcount_struct(root, ident_ids, ctx, field_layouts, structure) } - Layout::Union(_) => rc_todo(), - Layout::LambdaSet(_) => { - unreachable!("Refcounting on LambdaSet is invalid. Should be a Union at runtime.") + Layout::Union(union_layout) => { + refcount_tag_union(root, ident_ids, ctx, union_layout, structure) + } + Layout::LambdaSet(lambda_set) => { + let runtime_layout = lambda_set.runtime_representation(); + refcount_generic(root, ident_ids, ctx, runtime_layout, structure) } Layout::RecursivePointer => rc_todo(), } @@ -124,12 +128,32 @@ pub fn refcount_generic<'a>( // In the short term, it helps us to skip refcounting and let it leak, so we can make // progress incrementally. Kept in sync with generate_procs using assertions. pub fn is_rc_implemented_yet(layout: &Layout) -> bool { + use UnionLayout::*; + match layout { Layout::Builtin(Builtin::Dict(..) | Builtin::Set(_)) => false, Layout::Builtin(Builtin::List(elem_layout)) => is_rc_implemented_yet(elem_layout), Layout::Builtin(_) => true, Layout::Struct(fields) => fields.iter().all(is_rc_implemented_yet), - _ => false, + Layout::Union(union_layout) => match union_layout { + NonRecursive(tags) => tags + .iter() + .all(|fields| fields.iter().all(is_rc_implemented_yet)), + Recursive(tags) => tags + .iter() + .all(|fields| fields.iter().all(is_rc_implemented_yet)), + NonNullableUnwrapped(fields) => fields.iter().all(is_rc_implemented_yet), + NullableWrapped { other_tags, .. } => other_tags + .iter() + .all(|fields| fields.iter().all(is_rc_implemented_yet)), + NullableUnwrapped { other_fields, .. } => { + other_fields.iter().all(is_rc_implemented_yet) + } + }, + Layout::LambdaSet(lambda_set) => { + is_rc_implemented_yet(&lambda_set.runtime_representation()) + } + Layout::RecursivePointer => true, } } @@ -417,7 +441,7 @@ fn refcount_list<'a>( let rc_ptr = root.create_symbol(ident_ids, "rc_ptr"); let alignment = layout.alignment_bytes(root.ptr_size); - let modify_elems = if elem_layout.is_refcounted() && !matches!(ctx.op, HelperOp::DecRef(_)) { + let modify_elems = if elem_layout.is_refcounted() && !ctx.op.is_decref() { refcount_list_elems( root, ident_ids, @@ -667,3 +691,216 @@ fn refcount_struct<'a>( stmt } + +fn refcount_tag_union<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + union_layout: UnionLayout<'a>, + structure: Symbol, +) -> Stmt<'a> { + use UnionLayout::*; + + let parent_rec_ptr_layout = ctx.recursive_union; + if !matches!(union_layout, NonRecursive(_)) { + ctx.recursive_union = Some(union_layout); + } + + let body = match union_layout { + NonRecursive(tags) => { + refcount_tag_union_help(root, ident_ids, ctx, union_layout, tags, None, structure) + } + + Recursive(tags) => { + refcount_tag_union_help(root, ident_ids, ctx, union_layout, tags, None, structure) + } + + NonNullableUnwrapped(field_layouts) => { + let tags = root.arena.alloc([field_layouts]); + refcount_tag_union_help(root, ident_ids, ctx, union_layout, tags, None, structure) + } + + NullableWrapped { + other_tags: tags, + nullable_id, + } => { + let null_id = Some(nullable_id); + refcount_tag_union_help(root, ident_ids, ctx, union_layout, tags, null_id, structure) + } + + NullableUnwrapped { + other_fields, + nullable_id, + } => { + let null_id = Some(nullable_id as TagIdIntType); + let tags = root.arena.alloc([other_fields]); + refcount_tag_union_help(root, ident_ids, ctx, union_layout, tags, null_id, structure) + } + }; + + ctx.recursive_union = parent_rec_ptr_layout; + + body +} + +fn refcount_tag_union_help<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + union_layout: UnionLayout<'a>, + tag_layouts: &'a [&'a [Layout<'a>]], + null_id: Option, + structure: Symbol, +) -> Stmt<'a> { + let is_non_recursive = matches!(union_layout, UnionLayout::NonRecursive(_)); + /* TODO: tail recursion + let tailrec_loop = JoinPointId(root.create_symbol(ident_ids, "tailrec_loop")); + let structure = if is_non_recursive { + initial_structure + } else { + // current value in the tail-recursive loop + root.create_symbol(ident_ids, "structure") + }; + */ + let tag_id_layout = union_layout.tag_id_layout(); + + let tag_id_sym = root.create_symbol(ident_ids, "tag_id"); + let tag_id_stmt = |next| { + Stmt::Let( + tag_id_sym, + Expr::GetTagId { + structure, + union_layout, + }, + tag_id_layout, + next, + ) + }; + + let modify_fields_stmt = if ctx.op.is_decref() { + rc_return_stmt(root, ident_ids, ctx) + } else { + let mut tag_branches = Vec::with_capacity_in(tag_layouts.len(), root.arena); + + let mut tag_id: TagIdIntType = 0; + for field_layouts in tag_layouts.iter() { + if let Some(id) = null_id { + if tag_id == id { + tag_id += 1; + } + } + + let fields_stmt = refcount_tag_fields( + root, + ident_ids, + ctx, + union_layout, + field_layouts, + structure, + tag_id as TagIdIntType, + ); + + tag_branches.push((tag_id as u64, BranchInfo::None, fields_stmt)); + + tag_id += 1; + } + + let default_stmt: Stmt<'a> = tag_branches.pop().unwrap().2; + + Stmt::Switch { + cond_symbol: tag_id_sym, + cond_layout: tag_id_layout, + branches: tag_branches.into_bump_slice(), + default_branch: (BranchInfo::None, root.arena.alloc(default_stmt)), + ret_layout: LAYOUT_UNIT, + } + }; + + let rc_structure_stmt = if is_non_recursive { + modify_fields_stmt + } else { + let rc_ptr = root.create_symbol(ident_ids, "rc_ptr"); + + let alignment = Layout::Union(union_layout).alignment_bytes(root.ptr_size); + let modify_structure_stmt = modify_refcount( + root, + ident_ids, + ctx, + rc_ptr, + alignment, + root.arena.alloc(modify_fields_stmt), + ); + + let rc_ptr_stmt = rc_ptr_from_data_ptr( + root, + ident_ids, + structure, + rc_ptr, + root.arena.alloc(modify_structure_stmt), + ); + + if let Some(id) = null_id { + let null_branch = ( + id as u64, + BranchInfo::None, + rc_return_stmt(root, ident_ids, ctx), + ); + Stmt::Switch { + cond_symbol: tag_id_sym, + cond_layout: tag_id_layout, + branches: root.arena.alloc([null_branch]), + default_branch: (BranchInfo::None, root.arena.alloc(rc_ptr_stmt)), + ret_layout: LAYOUT_UNIT, + } + } else { + rc_ptr_stmt + } + }; + + tag_id_stmt(root.arena.alloc( + // + rc_structure_stmt, + )) +} + +fn refcount_tag_fields<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + union_layout: UnionLayout<'a>, + field_layouts: &'a [Layout<'a>], + structure: Symbol, + tag_id: TagIdIntType, +) -> Stmt<'a> { + let mut stmt = rc_return_stmt(root, ident_ids, ctx); + + for (i, field_layout) in field_layouts.iter().enumerate().rev() { + if field_layout.contains_refcounted() { + let field_val = root.create_symbol(ident_ids, &format!("field_{}_{}", tag_id, i)); + let field_val_expr = Expr::UnionAtIndex { + union_layout, + tag_id, + index: i as u64, + structure, + }; + let field_val_stmt = |next| Stmt::Let(field_val, field_val_expr, *field_layout, next); + + let mod_unit = root.create_symbol(ident_ids, &format!("mod_field_{}_{}", tag_id, i)); + let mod_args = refcount_args(root, ctx, field_val); + let mod_expr = root + .call_specialized_op(ident_ids, ctx, *field_layout, mod_args) + .unwrap(); + let mod_stmt = |next| Stmt::Let(mod_unit, mod_expr, LAYOUT_UNIT, next); + + stmt = field_val_stmt(root.arena.alloc( + // + mod_stmt(root.arena.alloc( + // + stmt, + )), + )) + } + } + + stmt +} diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs index cb795d6603..dd223a08ea 100644 --- a/compiler/test_gen/src/gen_refcount.rs +++ b/compiler/test_gen/src/gen_refcount.rs @@ -150,3 +150,50 @@ fn struct_dealloc() { &[0] // s ); } + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn union_nonrec_inc() { + assert_refcounts!( + indoc!( + r#" + TwoOrNone a: [ Two a a, None ] + + s = Str.concat "A long enough string " "to be heap-allocated" + + two : TwoOrNone Str + two = Two s s + + [two, two] + "# + ), + RocList<([RocStr; 2], i64)>, + &[ + 4, // s + 1 // result list + ] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn union_nonrec_dec() { + assert_refcounts!( + indoc!( + r#" + TwoOrNone a: [ Two a a, None ] + + s = Str.concat "A long enough string " "to be heap-allocated" + + two : TwoOrNone Str + two = Two s s + + when two is + Two x _ -> x + None -> "" + "# + ), + RocStr, + &[1] // s + ); +} From e0d0e1884d4af3469a0ad3183c200bdae240fd49 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 2 Jan 2022 15:47:52 +0000 Subject: [PATCH 114/541] Check number of allocations in refcount tests --- compiler/test_gen/src/helpers/wasm.rs | 26 +++++++++---- .../test_gen/src/helpers/wasm_test_platform.c | 37 +++++++++++-------- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 47973ea05d..1cf56cbf2c 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -303,28 +303,38 @@ where let memory = instance.exports.get_memory(MEMORY_NAME).unwrap(); + let expected_len = num_refcounts as i32; let init_refcount_test = instance.exports.get_function("init_refcount_test").unwrap(); - let init_result = init_refcount_test.call(&[wasmer::Value::I32(num_refcounts as i32)]); - let refcount_array_addr = match init_result { + let init_result = init_refcount_test.call(&[wasmer::Value::I32(expected_len)]); + let refcount_vector_addr = match init_result { Err(e) => return Err(format!("{:?}", e)), Ok(result) => match result[0] { wasmer::Value::I32(a) => a, _ => panic!(), }, }; - // An array of refcount pointers - let refcount_ptr_array: WasmPtr, wasmer::Array> = - WasmPtr::new(refcount_array_addr as u32); - let refcount_ptrs: &[Cell>] = refcount_ptr_array - .deref(memory, 0, num_refcounts as u32) - .unwrap(); + // Run the test let test_wrapper = instance.exports.get_function(TEST_WRAPPER_NAME).unwrap(); match test_wrapper.call(&[]) { Err(e) => return Err(format!("{:?}", e)), Ok(_) => {} } + // Check we got the right number of refcounts + let refcount_vector_len: WasmPtr = WasmPtr::new(refcount_vector_addr as u32); + let actual_len = refcount_vector_len.deref(memory).unwrap().get(); + if actual_len != expected_len { + panic!("Expected {} refcounts but got {}", expected_len, actual_len); + } + + // Read the actual refcount values + let refcount_ptr_array: WasmPtr, wasmer::Array> = + WasmPtr::new(4 + refcount_vector_addr as u32); + let refcount_ptrs: &[Cell>] = refcount_ptr_array + .deref(memory, 0, num_refcounts as u32) + .unwrap(); + let mut refcounts = Vec::with_capacity(num_refcounts); for i in 0..num_refcounts { let rc_ptr = refcount_ptrs[i].get(); diff --git a/compiler/test_gen/src/helpers/wasm_test_platform.c b/compiler/test_gen/src/helpers/wasm_test_platform.c index 5ed3093c03..017ecc113d 100644 --- a/compiler/test_gen/src/helpers/wasm_test_platform.c +++ b/compiler/test_gen/src/helpers/wasm_test_platform.c @@ -3,21 +3,26 @@ // Makes test runs take 50% longer, due to linking #define ENABLE_PRINTF 0 +typedef struct { + size_t length; + size_t* elements[]; // flexible array member +} Vector; + // Globals for refcount testing -size_t **rc_pointers; // array of pointers to refcount values -size_t rc_pointers_len; -size_t rc_pointers_index; +Vector *rc_pointers; +size_t rc_pointers_capacity; // The rust test passes us the max number of allocations it expects to make, // and we tell it where we're going to write the refcount pointers. // It won't actually read that memory until later, when the test is done. -size_t **init_refcount_test(size_t max_allocs) +Vector *init_refcount_test(size_t capacity) { - rc_pointers = malloc(max_allocs * sizeof(size_t *)); - rc_pointers_len = max_allocs; - rc_pointers_index = 0; - for (size_t i = 0; i < max_allocs; ++i) - rc_pointers[i] = NULL; + rc_pointers_capacity = capacity; + + rc_pointers = malloc((1 + capacity) * sizeof(size_t *)); + rc_pointers->length = 0; + for (size_t i = 0; i < capacity; ++i) + rc_pointers->elements[i] = NULL; return rc_pointers; } @@ -51,11 +56,11 @@ void *roc_alloc(size_t size, unsigned int alignment) if (rc_pointers) { ASSERT(alignment >= sizeof(size_t)); - ASSERT(rc_pointers_index < rc_pointers_len); + ASSERT(rc_pointers->length < rc_pointers_capacity); size_t *rc_ptr = alloc_ptr_to_rc_ptr(allocated, alignment); - rc_pointers[rc_pointers_index] = rc_ptr; - rc_pointers_index++; + rc_pointers->elements[rc_pointers->length] = rc_ptr; + rc_pointers->length++; } #if ENABLE_PRINTF @@ -94,15 +99,15 @@ void roc_dealloc(void *ptr, unsigned int alignment) // Then even if malloc reuses the space, everything still works size_t *rc_ptr = alloc_ptr_to_rc_ptr(ptr, alignment); int i = 0; - for (; i < rc_pointers_index; ++i) + for (; i < rc_pointers->length; ++i) { - if (rc_pointers[i] == rc_ptr) + if (rc_pointers->elements[i] == rc_ptr) { - rc_pointers[i] = NULL; + rc_pointers->elements[i] = NULL; break; } } - int was_found = i < rc_pointers_index; + int was_found = i < rc_pointers->length; ASSERT(was_found); } From 94dea1df9ff481d30ac48334bf75846afa3b9724 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 2 Jan 2022 23:41:04 +0000 Subject: [PATCH 115/541] Add a refcount test for recursive unions --- compiler/test_gen/src/gen_refcount.rs | 31 +++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs index dd223a08ea..2c82756e8c 100644 --- a/compiler/test_gen/src/gen_refcount.rs +++ b/compiler/test_gen/src/gen_refcount.rs @@ -153,7 +153,7 @@ fn struct_dealloc() { #[test] #[cfg(any(feature = "gen-wasm"))] -fn union_nonrec_inc() { +fn union_nonrecursive_inc() { assert_refcounts!( indoc!( r#" @@ -177,7 +177,7 @@ fn union_nonrec_inc() { #[test] #[cfg(any(feature = "gen-wasm"))] -fn union_nonrec_dec() { +fn union_nonrecursive_dec() { assert_refcounts!( indoc!( r#" @@ -197,3 +197,30 @@ fn union_nonrec_dec() { &[1] // s ); } + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn union_recursive_inc() { + assert_refcounts!( + indoc!( + r#" + Expr : [ Sym Str, Add Expr Expr ] + + s = Str.concat "heap_allocated_" "symbol_name" + + e : Expr + e = Add (Sym s) (Sym s) + + [e, e] + "# + ), + RocStr, + &[ + 4, // s + 1, // Sym + 1, // Sym + 2, // Add + 1 // list + ] + ); +} From 5e642c880ca7ed12f49d2359d3839a506517d216 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 3 Jan 2022 15:08:29 +0000 Subject: [PATCH 116/541] Mask out union tag ID from pointer when calculating refcount address --- compiler/mono/src/code_gen_help/mod.rs | 2 +- compiler/mono/src/code_gen_help/refcount.rs | 91 +++++++++++++++------ compiler/test_gen/src/gen_refcount.rs | 45 ++++++++-- 3 files changed, 108 insertions(+), 30 deletions(-) diff --git a/compiler/mono/src/code_gen_help/mod.rs b/compiler/mono/src/code_gen_help/mod.rs index b708d3324e..154984b383 100644 --- a/compiler/mono/src/code_gen_help/mod.rs +++ b/compiler/mono/src/code_gen_help/mod.rs @@ -35,7 +35,7 @@ enum HelperOp { impl HelperOp { fn is_decref(&self) -> bool { matches!(self, Self::DecRef(_)) - } + } } #[derive(Debug)] diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index c95fb55f10..943bb07463 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -189,6 +189,7 @@ pub fn rc_ptr_from_data_ptr<'a>( ident_ids: &mut IdentIds, structure: Symbol, rc_ptr_sym: Symbol, + mask_lower_bits: bool, following: &'a Stmt<'a>, ) -> Stmt<'a> { // Typecast the structure pointer to an integer @@ -203,6 +204,21 @@ pub fn rc_ptr_from_data_ptr<'a>( }); let addr_stmt = |next| Stmt::Let(addr_sym, addr_expr, root.layout_isize, next); + // Mask for lower bits (for tag union id) + let mask_sym = root.create_symbol(ident_ids, "mask"); + let mask_expr = Expr::Literal(Literal::Int(-(root.ptr_size as i128))); + let mask_stmt = |next| Stmt::Let(mask_sym, mask_expr, root.layout_isize, next); + + let masked_sym = root.create_symbol(ident_ids, "masked"); + let and_expr = Expr::Call(Call { + call_type: CallType::LowLevel { + op: LowLevel::And, + update_mode: UpdateModeId::BACKEND_DUMMY, + }, + arguments: root.arena.alloc([addr_sym, mask_sym]), + }); + let and_stmt = |next| Stmt::Let(masked_sym, and_expr, root.layout_isize, next); + // Pointer size constant let ptr_size_sym = root.create_symbol(ident_ids, "ptr_size"); let ptr_size_expr = Expr::Literal(Literal::Int(root.ptr_size as i128)); @@ -210,38 +226,67 @@ pub fn rc_ptr_from_data_ptr<'a>( // Refcount address let rc_addr_sym = root.create_symbol(ident_ids, "rc_addr"); - let rc_addr_expr = Expr::Call(Call { + let sub_expr = Expr::Call(Call { call_type: CallType::LowLevel { op: LowLevel::NumSub, update_mode: UpdateModeId::BACKEND_DUMMY, }, - arguments: root.arena.alloc([addr_sym, ptr_size_sym]), + arguments: root.arena.alloc([ + if mask_lower_bits { + masked_sym + } else { + addr_sym + }, + ptr_size_sym, + ]), }); - let rc_addr_stmt = |next| Stmt::Let(rc_addr_sym, rc_addr_expr, root.layout_isize, next); + let sub_stmt = |next| Stmt::Let(rc_addr_sym, sub_expr, root.layout_isize, next); // Typecast the refcount address from integer to pointer - let rc_ptr_expr = Expr::Call(Call { + let cast_expr = Expr::Call(Call { call_type: CallType::LowLevel { op: LowLevel::PtrCast, update_mode: UpdateModeId::BACKEND_DUMMY, }, arguments: root.arena.alloc([rc_addr_sym]), }); - let rc_ptr_stmt = |next| Stmt::Let(rc_ptr_sym, rc_ptr_expr, LAYOUT_PTR, next); + let cast_stmt = |next| Stmt::Let(rc_ptr_sym, cast_expr, LAYOUT_PTR, next); - addr_stmt(root.arena.alloc( - // - ptr_size_stmt(root.arena.alloc( + if mask_lower_bits { + addr_stmt(root.arena.alloc( // - rc_addr_stmt(root.arena.alloc( + mask_stmt(root.arena.alloc( // - rc_ptr_stmt(root.arena.alloc( + and_stmt(root.arena.alloc( // - following, + ptr_size_stmt(root.arena.alloc( + // + sub_stmt(root.arena.alloc( + // + cast_stmt(root.arena.alloc( + // + following, + )), + )), + )), )), )), - )), - )) + )) + } else { + addr_stmt(root.arena.alloc( + // + ptr_size_stmt(root.arena.alloc( + // + sub_stmt(root.arena.alloc( + // + cast_stmt(root.arena.alloc( + // + following, + )), + )), + )), + )) + } } fn modify_refcount<'a>( @@ -356,6 +401,7 @@ fn refcount_str<'a>( ident_ids, elements, rc_ptr, + false, root.arena.alloc( // mod_rc_stmt, @@ -467,7 +513,14 @@ fn refcount_list<'a>( let modify_list_and_elems = elements_stmt(arena.alloc( // - rc_ptr_from_data_ptr(root, ident_ids, elements, rc_ptr, arena.alloc(modify_list)), + rc_ptr_from_data_ptr( + root, + ident_ids, + elements, + rc_ptr, + false, + arena.alloc(modify_list), + ), )); // @@ -753,15 +806,6 @@ fn refcount_tag_union_help<'a>( structure: Symbol, ) -> Stmt<'a> { let is_non_recursive = matches!(union_layout, UnionLayout::NonRecursive(_)); - /* TODO: tail recursion - let tailrec_loop = JoinPointId(root.create_symbol(ident_ids, "tailrec_loop")); - let structure = if is_non_recursive { - initial_structure - } else { - // current value in the tail-recursive loop - root.create_symbol(ident_ids, "structure") - }; - */ let tag_id_layout = union_layout.tag_id_layout(); let tag_id_sym = root.create_symbol(ident_ids, "tag_id"); @@ -836,6 +880,7 @@ fn refcount_tag_union_help<'a>( ident_ids, structure, rc_ptr, + union_layout.stores_tag_id_in_pointer(root.ptr_size), root.arena.alloc(modify_structure_stmt), ); diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs index 2c82756e8c..490c1b7cb0 100644 --- a/compiler/test_gen/src/gen_refcount.rs +++ b/compiler/test_gen/src/gen_refcount.rs @@ -206,21 +206,54 @@ fn union_recursive_inc() { r#" Expr : [ Sym Str, Add Expr Expr ] - s = Str.concat "heap_allocated_" "symbol_name" + s = Str.concat "heap_allocated" "_symbol_name" + + x : Expr + x = Sym s e : Expr - e = Add (Sym s) (Sym s) + e = Add x x [e, e] "# ), - RocStr, + // test_wrapper receives a List, doesn't matter kind of elements it points to + RocList, &[ 4, // s - 1, // Sym - 1, // Sym - 2, // Add + 4, // sym + 2, // e 1 // list ] ); } + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn union_recursive_dec() { + assert_refcounts!( + indoc!( + r#" + Expr : [ Sym Str, Add Expr Expr ] + + s = Str.concat "heap_allocated" "_symbol_name" + + x : Expr + x = Sym s + + e : Expr + e = Add x x + + when e is + Add y _ -> y + Sym _ -> e + "# + ), + &RocStr, + &[ + 1, // s + 1, // sym + 0 // e + ] + ); +} From ad95ea4a3b72c6c9ef15221324437292c0d767a2 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 4 Jan 2022 16:52:45 +0000 Subject: [PATCH 117/541] Remove a debug assert --- compiler/mono/src/code_gen_help/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/mono/src/code_gen_help/mod.rs b/compiler/mono/src/code_gen_help/mod.rs index 154984b383..9eac857d50 100644 --- a/compiler/mono/src/code_gen_help/mod.rs +++ b/compiler/mono/src/code_gen_help/mod.rs @@ -180,7 +180,7 @@ impl<'a> CodeGenHelp<'a> { ) -> Option> { use HelperOp::*; - debug_assert!(self.debug_recursion_depth < 10); + // debug_assert!(self.debug_recursion_depth < 100); self.debug_recursion_depth += 1; let layout = if matches!(called_layout, Layout::RecursivePointer) { From 456bda08952e8a7445c159fe7455c13bc5d9baa1 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 4 Jan 2022 20:00:42 +0000 Subject: [PATCH 118/541] Prevent confusion between separate instances of RecursivePointer --- compiler/mono/src/code_gen_help/mod.rs | 52 ++++++++++++++++++++++++++ compiler/test_gen/src/gen_compare.rs | 42 +++++++++++++++++++++ compiler/test_gen/src/gen_refcount.rs | 40 ++++++++++++++++++++ 3 files changed, 134 insertions(+) diff --git a/compiler/mono/src/code_gen_help/mod.rs b/compiler/mono/src/code_gen_help/mod.rs index 9eac857d50..d76c30a579 100644 --- a/compiler/mono/src/code_gen_help/mod.rs +++ b/compiler/mono/src/code_gen_help/mod.rs @@ -231,6 +231,8 @@ impl<'a> CodeGenHelp<'a> { ) -> Symbol { use HelperOp::*; + let layout = self.replace_rec_ptr(ctx, layout); + let found = self .specializations .iter() @@ -329,6 +331,56 @@ impl<'a> CodeGenHelp<'a> { let ident_id = ident_ids.add(Ident::from(debug_name)); Symbol::new(self.home, ident_id) } + + // When creating or looking up Specializations, we need to replace RecursivePointer + // with the particular Union layout it represents at this point in the tree. + // For example if a program uses `RoseTree a : [ Tree a (List (RoseTree a)) ]` + // then it could have both `RoseTree I64` and `RoseTree Str`. In this case it + // needs *two* specializations for `List(RecursivePointer)`, not just one. + fn replace_rec_ptr(&self, ctx: &Context<'a>, layout: Layout<'a>) -> Layout<'a> { + match layout { + Layout::Builtin(Builtin::Dict(k, v)) => Layout::Builtin(Builtin::Dict( + self.arena.alloc(self.replace_rec_ptr(ctx, *k)), + self.arena.alloc(self.replace_rec_ptr(ctx, *v)), + )), + + Layout::Builtin(Builtin::Set(k)) => Layout::Builtin(Builtin::Set( + self.arena.alloc(self.replace_rec_ptr(ctx, *k)), + )), + + Layout::Builtin(Builtin::List(v)) => Layout::Builtin(Builtin::List( + self.arena.alloc(self.replace_rec_ptr(ctx, *v)), + )), + + Layout::Builtin(_) => layout, + + Layout::Struct(fields) => { + let new_fields_iter = fields.iter().map(|f| self.replace_rec_ptr(ctx, *f)); + Layout::Struct(self.arena.alloc_slice_fill_iter(new_fields_iter)) + } + + Layout::Union(UnionLayout::NonRecursive(tags)) => { + let mut new_tags = Vec::with_capacity_in(tags.len(), self.arena); + for fields in tags { + let mut new_fields = Vec::with_capacity_in(fields.len(), self.arena); + for field in fields.iter() { + new_fields.push(self.replace_rec_ptr(ctx, *field)) + } + new_tags.push(new_fields.into_bump_slice()); + } + Layout::Union(UnionLayout::NonRecursive(new_tags.into_bump_slice())) + } + + Layout::Union(_) => layout, + + Layout::LambdaSet(lambda_set) => { + self.replace_rec_ptr(ctx, lambda_set.runtime_representation()) + } + + // This line is the whole point of the function + Layout::RecursivePointer => Layout::Union(ctx.recursive_union.unwrap()), + } + } } fn let_lowlevel<'a>( diff --git a/compiler/test_gen/src/gen_compare.rs b/compiler/test_gen/src/gen_compare.rs index 65fe0a252e..38610b8cb2 100644 --- a/compiler/test_gen/src/gen_compare.rs +++ b/compiler/test_gen/src/gen_compare.rs @@ -498,6 +498,48 @@ fn eq_rosetree() { ); } +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn eq_different_rosetrees() { + // Requires two different equality procedures for `List (Rose I64)` and `List (Rose Str)` + // even though both appear in the mono Layout as `List(RecursivePointer)` + assert_evals_to!( + indoc!( + r#" + Rose a : [ Rose a (List (Rose a)) ] + + a1 : Rose I64 + a1 = Rose 999 [] + a2 : Rose I64 + a2 = Rose 0 [a1] + + b1 : Rose I64 + b1 = Rose 999 [] + b2 : Rose I64 + b2 = Rose 0 [b1] + + ab = a2 == b2 + + c1 : Rose Str + c1 = Rose "hello" [] + c2 : Rose Str + c2 = Rose "" [c1] + + d1 : Rose Str + d1 = Rose "hello" [] + d2 : Rose Str + d2 = Rose "" [d1] + + cd = c2 == d2 + + ab && cd + "# + ), + true, + bool + ); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[ignore] diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs index 490c1b7cb0..9f8d787236 100644 --- a/compiler/test_gen/src/gen_refcount.rs +++ b/compiler/test_gen/src/gen_refcount.rs @@ -257,3 +257,43 @@ fn union_recursive_dec() { ] ); } + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn refcount_different_rosetrees_inc() { + // Requires two different equality procedures for `List (Rose I64)` and `List (Rose Str)` + // even though both appear in the mono Layout as `List(RecursivePointer)` + assert_refcounts!( + indoc!( + r#" + Rose a : [ Rose a (List (Rose a)) ] + + s = Str.concat "A long enough string " "to be heap-allocated" + + i1 : Rose I64 + i1 = Rose 999 [] + + s1 : Rose Str + s1 = Rose s [] + + i2 : Rose I64 + i2 = Rose 0 [i1, i1, i1] + + s2 : Rose Str + s2 = Rose "" [s1, s1] + + Tuple i2 s2 + "# + ), + (usize, usize), + &[ + 2, // s + 3, // i1 + 2, // s1 + 1, // [i1, i1] + 1, // i2 + 1, // [s1, s1] + 1 // s2 + ] + ); +} From d9cc3c5692bdf0738174e075c28684e7634a8fc0 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 5 Jan 2022 12:22:26 +0000 Subject: [PATCH 119/541] Modify refcount of contents *before* structure to prevent use-after-free --- compiler/mono/src/code_gen_help/refcount.rs | 303 ++++++++++++-------- compiler/test_gen/src/gen_compare.rs | 1 + compiler/test_gen/src/gen_refcount.rs | 41 +++ 3 files changed, 233 insertions(+), 112 deletions(-) diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index 943bb07463..755648c2ce 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -68,8 +68,9 @@ pub fn refcount_stmt<'a>( refcount_stmt(root, ident_ids, ctx, layout, modify, following) } - // Struct is stack-only, so DecRef is a no-op + // Struct and non-recursive Unions are stack-only, so DecRef is a no-op Layout::Struct(_) => following, + Layout::Union(UnionLayout::NonRecursive(_)) => following, // Inline the refcounting code instead of making a function. Don't iterate fields, // and replace any return statements with jumps to the `following` statement. @@ -114,7 +115,7 @@ pub fn refcount_generic<'a>( refcount_struct(root, ident_ids, ctx, field_layouts, structure) } Layout::Union(union_layout) => { - refcount_tag_union(root, ident_ids, ctx, union_layout, structure) + refcount_union(root, ident_ids, ctx, union_layout, structure) } Layout::LambdaSet(lambda_set) => { let runtime_layout = lambda_set.runtime_representation(); @@ -482,12 +483,32 @@ fn refcount_list<'a>( // // modify refcount of the list and its elements + // (elements first, to avoid use-after-free for Dec) // let rc_ptr = root.create_symbol(ident_ids, "rc_ptr"); let alignment = layout.alignment_bytes(root.ptr_size); - let modify_elems = if elem_layout.is_refcounted() && !ctx.op.is_decref() { + let ret_stmt = rc_return_stmt(root, ident_ids, ctx); + let modify_list = modify_refcount( + root, + ident_ids, + ctx, + rc_ptr, + alignment, + arena.alloc(ret_stmt), + ); + + let get_rc_and_modify_list = rc_ptr_from_data_ptr( + root, + ident_ids, + elements, + rc_ptr, + false, + arena.alloc(modify_list), + ); + + let modify_elems_and_list = if elem_layout.is_refcounted() && !ctx.op.is_decref() { refcount_list_elems( root, ident_ids, @@ -497,43 +518,31 @@ fn refcount_list<'a>( box_union_layout, len, elements, + get_rc_and_modify_list, ) } else { - rc_return_stmt(root, ident_ids, ctx) + get_rc_and_modify_list }; - let modify_list = modify_refcount( - root, - ident_ids, - ctx, - rc_ptr, - alignment, - arena.alloc(modify_elems), - ); - - let modify_list_and_elems = elements_stmt(arena.alloc( - // - rc_ptr_from_data_ptr( - root, - ident_ids, - elements, - rc_ptr, - false, - arena.alloc(modify_list), - ), - )); - // // Do nothing if the list is empty // + let non_empty_branch = root.arena.alloc( + // + elements_stmt(root.arena.alloc( + // + modify_elems_and_list, + )), + ); + let if_stmt = Stmt::Switch { cond_symbol: is_empty, cond_layout: LAYOUT_BOOL, branches: root .arena .alloc([(1, BranchInfo::None, rc_return_stmt(root, ident_ids, ctx))]), - default_branch: (BranchInfo::None, root.arena.alloc(modify_list_and_elems)), + default_branch: (BranchInfo::None, non_empty_branch), ret_layout: LAYOUT_UNIT, }; @@ -559,6 +568,7 @@ fn refcount_list_elems<'a>( box_union_layout: UnionLayout<'a>, length: Symbol, elements: Symbol, + following: Stmt<'a>, ) -> Stmt<'a> { use LowLevel::*; let layout_isize = root.layout_isize; @@ -573,9 +583,9 @@ fn refcount_list_elems<'a>( // // let size = literal int - let size = root.create_symbol(ident_ids, "size"); - let size_expr = Expr::Literal(Literal::Int(elem_layout.stack_size(root.ptr_size) as i128)); - let size_stmt = |next| Stmt::Let(size, size_expr, layout_isize, next); + let elem_size = root.create_symbol(ident_ids, "elem_size"); + let elem_size_expr = Expr::Literal(Literal::Int(elem_layout.stack_size(root.ptr_size) as i128)); + let elem_size_stmt = |next| Stmt::Let(elem_size, elem_size_expr, layout_isize, next); // let list_size = len * size let list_size = root.create_symbol(ident_ids, "list_size"); @@ -585,7 +595,7 @@ fn refcount_list_elems<'a>( layout_isize, list_size, NumMul, - &[length, size], + &[length, elem_size], next, ) }; @@ -641,8 +651,16 @@ fn refcount_list_elems<'a>( // Next loop iteration // let next_addr = root.create_symbol(ident_ids, "next_addr"); - let next_addr_stmt = - |next| let_lowlevel(arena, layout_isize, next_addr, NumAdd, &[addr, size], next); + let next_addr_stmt = |next| { + let_lowlevel( + arena, + layout_isize, + next_addr, + NumAdd, + &[addr, elem_size], + next, + ) + }; // // Control flow @@ -655,9 +673,7 @@ fn refcount_list_elems<'a>( cond_symbol: is_end, cond_layout: LAYOUT_BOOL, ret_layout, - branches: root - .arena - .alloc([(1, BranchInfo::None, rc_return_stmt(root, ident_ids, ctx))]), + branches: root.arena.alloc([(1, BranchInfo::None, following)]), default_branch: ( BranchInfo::None, arena.alloc(box_stmt(arena.alloc( @@ -693,7 +709,7 @@ fn refcount_list_elems<'a>( start_stmt(arena.alloc( // - size_stmt(arena.alloc( + elem_size_stmt(arena.alloc( // list_size_stmt(arena.alloc( // @@ -745,32 +761,30 @@ fn refcount_struct<'a>( stmt } -fn refcount_tag_union<'a>( +fn refcount_union<'a>( root: &mut CodeGenHelp<'a>, ident_ids: &mut IdentIds, ctx: &mut Context<'a>, - union_layout: UnionLayout<'a>, + union: UnionLayout<'a>, structure: Symbol, ) -> Stmt<'a> { use UnionLayout::*; let parent_rec_ptr_layout = ctx.recursive_union; - if !matches!(union_layout, NonRecursive(_)) { - ctx.recursive_union = Some(union_layout); + if !matches!(union, NonRecursive(_)) { + ctx.recursive_union = Some(union); } - let body = match union_layout { - NonRecursive(tags) => { - refcount_tag_union_help(root, ident_ids, ctx, union_layout, tags, None, structure) - } + let body = match union { + NonRecursive(tags) => refcount_union_nonrec(root, ident_ids, ctx, union, tags, structure), Recursive(tags) => { - refcount_tag_union_help(root, ident_ids, ctx, union_layout, tags, None, structure) + refcount_tag_union_rec(root, ident_ids, ctx, union, tags, None, structure) } NonNullableUnwrapped(field_layouts) => { let tags = root.arena.alloc([field_layouts]); - refcount_tag_union_help(root, ident_ids, ctx, union_layout, tags, None, structure) + refcount_tag_union_rec(root, ident_ids, ctx, union, tags, None, structure) } NullableWrapped { @@ -778,7 +792,7 @@ fn refcount_tag_union<'a>( nullable_id, } => { let null_id = Some(nullable_id); - refcount_tag_union_help(root, ident_ids, ctx, union_layout, tags, null_id, structure) + refcount_tag_union_rec(root, ident_ids, ctx, union, tags, null_id, structure) } NullableUnwrapped { @@ -787,7 +801,7 @@ fn refcount_tag_union<'a>( } => { let null_id = Some(nullable_id as TagIdIntType); let tags = root.arena.alloc([other_fields]); - refcount_tag_union_help(root, ident_ids, ctx, union_layout, tags, null_id, structure) + refcount_tag_union_rec(root, ident_ids, ctx, union, tags, null_id, structure) } }; @@ -796,16 +810,14 @@ fn refcount_tag_union<'a>( body } -fn refcount_tag_union_help<'a>( +fn refcount_union_nonrec<'a>( root: &mut CodeGenHelp<'a>, ident_ids: &mut IdentIds, ctx: &mut Context<'a>, union_layout: UnionLayout<'a>, tag_layouts: &'a [&'a [Layout<'a>]], - null_id: Option, structure: Symbol, ) -> Stmt<'a> { - let is_non_recursive = matches!(union_layout, UnionLayout::NonRecursive(_)); let tag_id_layout = union_layout.tag_id_layout(); let tag_id_sym = root.create_symbol(ident_ids, "tag_id"); @@ -821,93 +833,159 @@ fn refcount_tag_union_help<'a>( ) }; - let modify_fields_stmt = if ctx.op.is_decref() { - rc_return_stmt(root, ident_ids, ctx) - } else { - let mut tag_branches = Vec::with_capacity_in(tag_layouts.len(), root.arena); + let continuation = rc_return_stmt(root, ident_ids, ctx); - let mut tag_id: TagIdIntType = 0; - for field_layouts in tag_layouts.iter() { - if let Some(id) = null_id { - if tag_id == id { - tag_id += 1; - } + let switch_stmt = refcount_union_contents( + root, + ident_ids, + ctx, + union_layout, + tag_layouts, + None, + structure, + tag_id_sym, + tag_id_layout, + continuation, + ); + + tag_id_stmt(root.arena.alloc( + // + switch_stmt, + )) +} + +#[allow(clippy::too_many_arguments)] +fn refcount_union_contents<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + union_layout: UnionLayout<'a>, + tag_layouts: &'a [&'a [Layout<'a>]], + null_id: Option, + structure: Symbol, + tag_id_sym: Symbol, + tag_id_layout: Layout<'a>, + modify_union_stmt: Stmt<'a>, +) -> Stmt<'a> { + let mut tag_branches = Vec::with_capacity_in(tag_layouts.len() + 1, root.arena); + + if let Some(id) = null_id { + let ret = rc_return_stmt(root, ident_ids, ctx); + tag_branches.push((id as u64, BranchInfo::None, ret)); + } + + let mut tag_id: TagIdIntType = 0; + for field_layouts in tag_layouts.iter() { + match null_id { + Some(id) if id == tag_id => { + tag_id += 1; } + _ => {} + } - let fields_stmt = refcount_tag_fields( - root, - ident_ids, - ctx, - union_layout, - field_layouts, + let fields_stmt = refcount_tag_fields( + root, + ident_ids, + ctx, + union_layout, + field_layouts, + structure, + tag_id, + modify_union_stmt.clone(), // TODO: Use a jump, this is a bit bloated + ); + + tag_branches.push((tag_id as u64, BranchInfo::None, fields_stmt)); + + tag_id += 1; + } + + let default_stmt: Stmt<'a> = tag_branches.pop().unwrap().2; + + Stmt::Switch { + cond_symbol: tag_id_sym, + cond_layout: tag_id_layout, + branches: tag_branches.into_bump_slice(), + default_branch: (BranchInfo::None, root.arena.alloc(default_stmt)), + ret_layout: LAYOUT_UNIT, + } +} + +fn refcount_tag_union_rec<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + union_layout: UnionLayout<'a>, + tag_layouts: &'a [&'a [Layout<'a>]], + null_id: Option, + structure: Symbol, +) -> Stmt<'a> { + let tag_id_layout = union_layout.tag_id_layout(); + + let tag_id_sym = root.create_symbol(ident_ids, "tag_id"); + let tag_id_stmt = |next| { + Stmt::Let( + tag_id_sym, + Expr::GetTagId { structure, - tag_id as TagIdIntType, - ); - - tag_branches.push((tag_id as u64, BranchInfo::None, fields_stmt)); - - tag_id += 1; - } - - let default_stmt: Stmt<'a> = tag_branches.pop().unwrap().2; - - Stmt::Switch { - cond_symbol: tag_id_sym, - cond_layout: tag_id_layout, - branches: tag_branches.into_bump_slice(), - default_branch: (BranchInfo::None, root.arena.alloc(default_stmt)), - ret_layout: LAYOUT_UNIT, - } + union_layout, + }, + tag_id_layout, + next, + ) }; - let rc_structure_stmt = if is_non_recursive { - modify_fields_stmt - } else { + let rc_structure_stmt = { let rc_ptr = root.create_symbol(ident_ids, "rc_ptr"); let alignment = Layout::Union(union_layout).alignment_bytes(root.ptr_size); + let ret_stmt = rc_return_stmt(root, ident_ids, ctx); let modify_structure_stmt = modify_refcount( root, ident_ids, ctx, rc_ptr, alignment, - root.arena.alloc(modify_fields_stmt), + root.arena.alloc(ret_stmt), ); - let rc_ptr_stmt = rc_ptr_from_data_ptr( + rc_ptr_from_data_ptr( root, ident_ids, structure, rc_ptr, union_layout.stores_tag_id_in_pointer(root.ptr_size), root.arena.alloc(modify_structure_stmt), - ); - - if let Some(id) = null_id { - let null_branch = ( - id as u64, - BranchInfo::None, - rc_return_stmt(root, ident_ids, ctx), - ); - Stmt::Switch { - cond_symbol: tag_id_sym, - cond_layout: tag_id_layout, - branches: root.arena.alloc([null_branch]), - default_branch: (BranchInfo::None, root.arena.alloc(rc_ptr_stmt)), - ret_layout: LAYOUT_UNIT, - } - } else { - rc_ptr_stmt - } + ) }; - tag_id_stmt(root.arena.alloc( - // - rc_structure_stmt, - )) + let rc_contents_then_structure = if ctx.op.is_decref() { + rc_structure_stmt + } else { + refcount_union_contents( + root, + ident_ids, + ctx, + union_layout, + tag_layouts, + null_id, + structure, + tag_id_sym, + tag_id_layout, + rc_structure_stmt, + ) + }; + + if ctx.op.is_decref() && null_id.is_none() { + rc_contents_then_structure + } else { + tag_id_stmt(root.arena.alloc( + // + rc_contents_then_structure, + )) + } } +#[allow(clippy::too_many_arguments)] fn refcount_tag_fields<'a>( root: &mut CodeGenHelp<'a>, ident_ids: &mut IdentIds, @@ -916,8 +994,9 @@ fn refcount_tag_fields<'a>( field_layouts: &'a [Layout<'a>], structure: Symbol, tag_id: TagIdIntType, + following: Stmt<'a>, ) -> Stmt<'a> { - let mut stmt = rc_return_stmt(root, ident_ids, ctx); + let mut stmt = following; for (i, field_layout) in field_layouts.iter().enumerate().rev() { if field_layout.contains_refcounted() { diff --git a/compiler/test_gen/src/gen_compare.rs b/compiler/test_gen/src/gen_compare.rs index 38610b8cb2..42476f45dd 100644 --- a/compiler/test_gen/src/gen_compare.rs +++ b/compiler/test_gen/src/gen_compare.rs @@ -374,6 +374,7 @@ fn eq_linked_list_false() { #[test] #[cfg(any(feature = "gen-wasm"))] +#[ignore] // TODO: tail-call elimination for refcounting fn eq_linked_list_long() { assert_evals_to!( indoc!( diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs index 9f8d787236..7746689a9d 100644 --- a/compiler/test_gen/src/gen_refcount.rs +++ b/compiler/test_gen/src/gen_refcount.rs @@ -297,3 +297,44 @@ fn refcount_different_rosetrees_inc() { ] ); } + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn refcount_different_rosetrees_dec() { + // Requires two different equality procedures for `List (Rose I64)` and `List (Rose Str)` + // even though both appear in the mono Layout as `List(RecursivePointer)` + assert_refcounts!( + indoc!( + r#" + Rose a : [ Rose a (List (Rose a)) ] + + s = Str.concat "A long enough string " "to be heap-allocated" + + i1 : Rose I64 + i1 = Rose 999 [] + + s1 : Rose Str + s1 = Rose s [] + + i2 : Rose I64 + i2 = Rose 0 [i1, i1] + + s2 : Rose Str + s2 = Rose "" [s1, s1] + + when (Tuple i2 s2) is + Tuple (Rose x _) _ -> x + "# + ), + i64, + &[ + 0, // s + 0, // i1 + 0, // s1 + 0, // [i1, i1] + 0, // i2 + 0, // [s1, s1] + 0, // s2 + ] + ); +} From 040b8ce43066b53e87121e7b620a3bb123fb8ed9 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 6 Jan 2022 09:27:37 +0000 Subject: [PATCH 120/541] Reduce code size for Union refcount procedures --- compiler/mono/src/code_gen_help/refcount.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index 755648c2ce..338c8ad445 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -867,6 +867,7 @@ fn refcount_union_contents<'a>( tag_id_layout: Layout<'a>, modify_union_stmt: Stmt<'a>, ) -> Stmt<'a> { + let jp_modify_union = JoinPointId(root.create_symbol(ident_ids, "jp_modify_union")); let mut tag_branches = Vec::with_capacity_in(tag_layouts.len() + 1, root.arena); if let Some(id) = null_id { @@ -883,6 +884,10 @@ fn refcount_union_contents<'a>( _ => {} } + // After refcounting the fields, jump to modify the union itself + // (Order is important, to avoid use-after-free for Dec) + let following = Stmt::Jump(jp_modify_union, &[]); + let fields_stmt = refcount_tag_fields( root, ident_ids, @@ -891,7 +896,7 @@ fn refcount_union_contents<'a>( field_layouts, structure, tag_id, - modify_union_stmt.clone(), // TODO: Use a jump, this is a bit bloated + following, ); tag_branches.push((tag_id as u64, BranchInfo::None, fields_stmt)); @@ -901,12 +906,19 @@ fn refcount_union_contents<'a>( let default_stmt: Stmt<'a> = tag_branches.pop().unwrap().2; - Stmt::Switch { + let tag_id_switch = Stmt::Switch { cond_symbol: tag_id_sym, cond_layout: tag_id_layout, branches: tag_branches.into_bump_slice(), default_branch: (BranchInfo::None, root.arena.alloc(default_stmt)), ret_layout: LAYOUT_UNIT, + }; + + Stmt::Join { + id: jp_modify_union, + parameters: &[], + body: root.arena.alloc(modify_union_stmt), + remainder: root.arena.alloc(tag_id_switch), } } From 854106972cbf27666e3392a3d659a3f693777db0 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 6 Jan 2022 10:22:03 +0000 Subject: [PATCH 121/541] Nicer assertions in C test platform --- .../test_gen/src/helpers/wasm_test_platform.c | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/compiler/test_gen/src/helpers/wasm_test_platform.c b/compiler/test_gen/src/helpers/wasm_test_platform.c index 017ecc113d..b01543d0de 100644 --- a/compiler/test_gen/src/helpers/wasm_test_platform.c +++ b/compiler/test_gen/src/helpers/wasm_test_platform.c @@ -3,9 +3,10 @@ // Makes test runs take 50% longer, due to linking #define ENABLE_PRINTF 0 -typedef struct { +typedef struct +{ size_t length; - size_t* elements[]; // flexible array member + size_t *elements[]; // flexible array member } Vector; // Globals for refcount testing @@ -28,15 +29,15 @@ Vector *init_refcount_test(size_t capacity) } #if ENABLE_PRINTF -#define ASSERT(x) \ - if (!(x)) \ - { \ - printf("FAILED: " #x "\n"); \ - abort(); \ +#define ASSERT(condition, format, ...) \ + if (!(condition)) \ + { \ + printf("ASSERT FAILED: " #format "\n", __VA_ARGS__); \ + abort(); \ } #else -#define ASSERT(x) \ - if (!(x)) \ +#define ASSERT(condition, format, ...) \ + if (!(condition)) \ abort(); #endif @@ -55,8 +56,9 @@ void *roc_alloc(size_t size, unsigned int alignment) if (rc_pointers) { - ASSERT(alignment >= sizeof(size_t)); - ASSERT(rc_pointers->length < rc_pointers_capacity); + ASSERT(alignment >= sizeof(size_t), "alignment %zd != %zd", alignment, sizeof(size_t)); + size_t num_alloc = rc_pointers->length + 1; + ASSERT(num_alloc <= rc_pointers_capacity, "Too many allocations %zd > %zd", num_alloc, rc_pointers_capacity); size_t *rc_ptr = alloc_ptr_to_rc_ptr(allocated, alignment); rc_pointers->elements[rc_pointers->length] = rc_ptr; @@ -108,7 +110,7 @@ void roc_dealloc(void *ptr, unsigned int alignment) } } int was_found = i < rc_pointers->length; - ASSERT(was_found); + ASSERT(was_found, "RC pointer not found %p", rc_ptr); } #if ENABLE_PRINTF From bfec2555015a4a9ff2e1f9d8b909ff54f4bfa864 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 6 Jan 2022 10:22:23 +0000 Subject: [PATCH 122/541] More refcount tests --- compiler/test_gen/src/gen_refcount.rs | 82 +++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 12 deletions(-) diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs index 7746689a9d..5c2420c6d2 100644 --- a/compiler/test_gen/src/gen_refcount.rs +++ b/compiler/test_gen/src/gen_refcount.rs @@ -7,6 +7,10 @@ use indoc::indoc; #[allow(unused_imports)] use roc_std::{RocList, RocStr}; +// A "good enough" representation of a pointer for these tests, because +// we ignore the return value. As long as it's the right stack size, it's fine. +type Pointer = usize; + #[test] #[cfg(any(feature = "gen-wasm"))] fn str_inc() { @@ -154,6 +158,8 @@ fn struct_dealloc() { #[test] #[cfg(any(feature = "gen-wasm"))] fn union_nonrecursive_inc() { + type TwoStr = (RocStr, RocStr, i64); + assert_refcounts!( indoc!( r#" @@ -164,14 +170,14 @@ fn union_nonrecursive_inc() { two : TwoOrNone Str two = Two s s - [two, two] + four : TwoOrNone (TwoOrNone Str) + four = Two two two + + four "# ), - RocList<([RocStr; 2], i64)>, - &[ - 4, // s - 1 // result list - ] + (TwoStr, TwoStr, i64), + &[4] ); } @@ -214,16 +220,14 @@ fn union_recursive_inc() { e : Expr e = Add x x - [e, e] + Pair e e "# ), - // test_wrapper receives a List, doesn't matter kind of elements it points to - RocList, + (Pointer, Pointer), &[ 4, // s 4, // sym 2, // e - 1 // list ] ); } @@ -249,7 +253,7 @@ fn union_recursive_dec() { Sym _ -> e "# ), - &RocStr, + Pointer, &[ 1, // s 1, // sym @@ -285,7 +289,7 @@ fn refcount_different_rosetrees_inc() { Tuple i2 s2 "# ), - (usize, usize), + (Pointer, Pointer), &[ 2, // s 3, // i1 @@ -338,3 +342,57 @@ fn refcount_different_rosetrees_dec() { ] ); } + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn union_linked_list_inc() { + assert_refcounts!( + indoc!( + r#" + LinkedList a : [ Nil, Cons a (LinkedList a) ] + + s = Str.concat "A long enough string " "to be heap-allocated" + + linked : LinkedList Str + linked = Cons s (Cons s (Cons s Nil)) + + Tuple linked linked + "# + ), + (Pointer, Pointer), + &[ + 6, // s + 2, // Cons + 2, // Cons + 2, // Cons + ] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn union_linked_list_dec() { + assert_refcounts!( + indoc!( + r#" + LinkedList a : [ Nil, Cons a (LinkedList a) ] + + s = Str.concat "A long enough string " "to be heap-allocated" + + linked : LinkedList Str + linked = Cons s (Cons s (Cons s Nil)) + + when linked is + Cons x _ -> x + Nil -> "" + "# + ), + RocStr, + &[ + 1, // s + 0, // Cons + 0, // Cons + 0, // Cons + ] + ); +} From 8ebdc8ea7f6a52016dcd43371d670334f89a9296 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 6 Jan 2022 10:47:57 +0000 Subject: [PATCH 123/541] Tests for linked list tail recursion in helper procs --- compiler/test_gen/src/gen_compare.rs | 1 - compiler/test_gen/src/gen_refcount.rs | 32 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/compiler/test_gen/src/gen_compare.rs b/compiler/test_gen/src/gen_compare.rs index 42476f45dd..38610b8cb2 100644 --- a/compiler/test_gen/src/gen_compare.rs +++ b/compiler/test_gen/src/gen_compare.rs @@ -374,7 +374,6 @@ fn eq_linked_list_false() { #[test] #[cfg(any(feature = "gen-wasm"))] -#[ignore] // TODO: tail-call elimination for refcounting fn eq_linked_list_long() { assert_evals_to!( indoc!( diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs index 5c2420c6d2..d817c63fe7 100644 --- a/compiler/test_gen/src/gen_refcount.rs +++ b/compiler/test_gen/src/gen_refcount.rs @@ -396,3 +396,35 @@ fn union_linked_list_dec() { ] ); } + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn union_linked_list_long_dec() { + assert_refcounts!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + LinkedList a : [ Nil, Cons a (LinkedList a) ] + + prependOnes = \n, tail -> + if n == 0 then + tail + else + prependOnes (n-1) (Cons 1 tail) + + main = + n = 1_000 + + linked : LinkedList I64 + linked = prependOnes n Nil + + when linked is + Cons x _ -> x + Nil -> -1 + "# + ), + i64, + &[0; 1_000] + ); +} From 71a5013ed4e72784030c4deb4626f2eaa22f2a03 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 2 Jan 2022 23:41:33 +0000 Subject: [PATCH 124/541] Add an HTML file for debugging wasm --- .../test_gen/src/helpers/debug-wasm-test.html | 309 ++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 compiler/test_gen/src/helpers/debug-wasm-test.html diff --git a/compiler/test_gen/src/helpers/debug-wasm-test.html b/compiler/test_gen/src/helpers/debug-wasm-test.html new file mode 100644 index 0000000000..ca3ecd8a2b --- /dev/null +++ b/compiler/test_gen/src/helpers/debug-wasm-test.html @@ -0,0 +1,309 @@ + + + + + +
+

Debug Wasm tests in the browser!

+

+ You can step through the generated code instruction-by-instruction, and + examine memory contents +

+

Steps

+
    +
  • + In gen_wasm/src/lib.rs, set + DEBUG_LOG_SETTINGS.keep_test_binary = true +
  • +
  • Run cargo test-gen-wasm -- my_test --nocapture
  • +
  • + Look for the path written to the console for + final.wasm and select it in the file picker below +
  • +
  • + Open the browser DevTools
    + Control+Shift+I or Command+Option+I or F12 +
  • +
  • + Click one of the buttons below, depending on what kind of test it is. +
    + + Only one of them will work. The other will probably crash or + something. + +
  • +
  • + The debugger should pause just before entering the first Wasm call. + Step into a couple of Wasm calls until you reach your test code in + $#UserApp_main_1 +
  • +
  • + Chrome DevTools now has a Memory Inspector panel! In the debugger, + find Module -> memories -> $memory. Right click and + select "Reveal in Memory Inspector" +
  • +
+ +
+
+ + +
+
+
+ + +
+
+
+ + + From 20b4f4ae6fc537da560a294ae46e721b66ba7447 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 6 Jan 2022 11:20:24 -0500 Subject: [PATCH 125/541] Remove platform-specific packages from README Turns out there's a more promising design that doesn't require this! --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index ea09ac6ec2..c53721ef89 100644 --- a/README.md +++ b/README.md @@ -71,8 +71,6 @@ The core Roc language and standard library include no I/O operations, which give * A VR or [Arduino](https://www.arduino.cc/) platform can expose uncommon I/O operations supported by that hardware, while omitting common I/O operations that are unsupported (such as reading keyboard input from a terminal that doesn't exist). * A high-performance Web server written in Rust can be a Roc platform where all I/O operations are implemented in terms of Streams or Observables rather than a more traditional asynchronous abstraction like Futures or Promises. This would mean all code in that platform's ecosystem would be necessarily built on a common streaming abstraction. -Each Roc platform gets its own separate package repository, with packages built on top of the API that platform exposes. This means each platform has its own ecosystem where everything is built on top of the same shared set of platform-specific primitives. - ## Project Goals Roc is in relatively early stages of development. It's currently possible to build both platforms and applications (see the [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) folder for some examples that aren't particularly organized at the moment), although [documentation](https://github.com/rtfeldman/roc/tree/trunk/compiler/builtins/docs) is in even earlier stages than the compiler itself. From 5560ecb63ee541a4d56109e614fd396a05dfb11b Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 6 Jan 2022 22:42:39 +0000 Subject: [PATCH 126/541] Implement tail recursion for union refcounting procs --- compiler/mono/src/code_gen_help/mod.rs | 42 ++++ compiler/mono/src/code_gen_help/refcount.rs | 237 +++++++++++++++++++- compiler/test_gen/src/gen_refcount.rs | 4 +- 3 files changed, 276 insertions(+), 7 deletions(-) diff --git a/compiler/mono/src/code_gen_help/mod.rs b/compiler/mono/src/code_gen_help/mod.rs index d76c30a579..61c4c32c9e 100644 --- a/compiler/mono/src/code_gen_help/mod.rs +++ b/compiler/mono/src/code_gen_help/mod.rs @@ -381,6 +381,48 @@ impl<'a> CodeGenHelp<'a> { Layout::RecursivePointer => Layout::Union(ctx.recursive_union.unwrap()), } } + + fn union_tail_recursion_fields( + &self, + union: UnionLayout<'a>, + ) -> (bool, Vec<'a, Option>) { + use UnionLayout::*; + match union { + NonRecursive(_) => return (false, bumpalo::vec![in self.arena]), + + Recursive(tags) => self.union_tail_recursion_fields_help(tags), + + NonNullableUnwrapped(field_layouts) => { + self.union_tail_recursion_fields_help(&[field_layouts]) + } + + NullableWrapped { + other_tags: tags, .. + } => self.union_tail_recursion_fields_help(tags), + + NullableUnwrapped { other_fields, .. } => { + self.union_tail_recursion_fields_help(&[other_fields]) + } + } + } + + fn union_tail_recursion_fields_help( + &self, + tags: &[&'a [Layout<'a>]], + ) -> (bool, Vec<'a, Option>) { + let mut can_use_tailrec = false; + let mut tailrec_indices = Vec::with_capacity_in(tags.len(), self.arena); + + for fields in tags.iter() { + let found_index = fields + .iter() + .position(|f| matches!(f, Layout::RecursivePointer)); + tailrec_indices.push(found_index); + can_use_tailrec |= found_index.is_some(); + } + + (can_use_tailrec, tailrec_indices) + } } fn let_lowlevel<'a>( diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index 338c8ad445..2bc4aeff92 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -779,12 +779,21 @@ fn refcount_union<'a>( NonRecursive(tags) => refcount_union_nonrec(root, ident_ids, ctx, union, tags, structure), Recursive(tags) => { - refcount_tag_union_rec(root, ident_ids, ctx, union, tags, None, structure) + let (is_tailrec, tail_idx) = root.union_tail_recursion_fields(union); + if is_tailrec && !ctx.op.is_decref() { + refcount_union_tailrec(root, ident_ids, ctx, union, tags, None, tail_idx, structure) + } else { + refcount_union_rec(root, ident_ids, ctx, union, tags, None, structure) + } } NonNullableUnwrapped(field_layouts) => { + // We don't do tail recursion on NonNullableUnwrapped. + // Its RecursionPointer is always nested inside a List, Option, or other sub-layout, since + // a direct RecursionPointer is only possible if there's at least one non-recursive variant. + // This nesting makes it harder to do tail recursion, so we just don't. let tags = root.arena.alloc([field_layouts]); - refcount_tag_union_rec(root, ident_ids, ctx, union, tags, None, structure) + refcount_union_rec(root, ident_ids, ctx, union, tags, None, structure) } NullableWrapped { @@ -792,7 +801,14 @@ fn refcount_union<'a>( nullable_id, } => { let null_id = Some(nullable_id); - refcount_tag_union_rec(root, ident_ids, ctx, union, tags, null_id, structure) + let (is_tailrec, tail_idx) = root.union_tail_recursion_fields(union); + if is_tailrec && !ctx.op.is_decref() { + refcount_union_tailrec( + root, ident_ids, ctx, union, tags, null_id, tail_idx, structure, + ) + } else { + refcount_union_rec(root, ident_ids, ctx, union, tags, null_id, structure) + } } NullableUnwrapped { @@ -801,7 +817,14 @@ fn refcount_union<'a>( } => { let null_id = Some(nullable_id as TagIdIntType); let tags = root.arena.alloc([other_fields]); - refcount_tag_union_rec(root, ident_ids, ctx, union, tags, null_id, structure) + let (is_tailrec, tail_idx) = root.union_tail_recursion_fields(union); + if is_tailrec && !ctx.op.is_decref() { + refcount_union_tailrec( + root, ident_ids, ctx, union, tags, null_id, tail_idx, structure, + ) + } else { + refcount_union_rec(root, ident_ids, ctx, union, tags, null_id, structure) + } } }; @@ -922,7 +945,7 @@ fn refcount_union_contents<'a>( } } -fn refcount_tag_union_rec<'a>( +fn refcount_union_rec<'a>( root: &mut CodeGenHelp<'a>, ident_ids: &mut IdentIds, ctx: &mut Context<'a>, @@ -997,6 +1020,210 @@ fn refcount_tag_union_rec<'a>( } } +// Refcount a recursive union using tail-call elimination to limit stack growth +#[allow(clippy::too_many_arguments)] +fn refcount_union_tailrec<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + union_layout: UnionLayout<'a>, + tag_layouts: &'a [&'a [Layout<'a>]], + null_id: Option, + tailrec_indices: Vec<'a, Option>, + initial_structure: Symbol, +) -> Stmt<'a> { + let tailrec_loop = JoinPointId(root.create_symbol(ident_ids, "tailrec_loop")); + let current = root.create_symbol(ident_ids, "current"); + let next_ptr = root.create_symbol(ident_ids, "next_ptr"); + let layout = Layout::Union(union_layout); + + let tag_id_layout = union_layout.tag_id_layout(); + + let tag_id_sym = root.create_symbol(ident_ids, "tag_id"); + let tag_id_stmt = |next| { + Stmt::Let( + tag_id_sym, + Expr::GetTagId { + structure: current, + union_layout, + }, + tag_id_layout, + next, + ) + }; + + // Do refcounting on the structure itself + // In the control flow, this comes *after* refcounting the fields + // It receives a `next` parameter to pass through to the outer joinpoint + let rc_structure_stmt = { + let rc_ptr = root.create_symbol(ident_ids, "rc_ptr"); + let next_addr = root.create_symbol(ident_ids, "next_addr"); + + let exit_stmt = rc_return_stmt(root, ident_ids, ctx); + let jump_to_loop = Stmt::Jump(tailrec_loop, root.arena.alloc([next_ptr])); + + let loop_or_exit = Stmt::Switch { + cond_symbol: next_addr, + cond_layout: root.layout_isize, + branches: root.arena.alloc([(0, BranchInfo::None, exit_stmt)]), + default_branch: (BranchInfo::None, root.arena.alloc(jump_to_loop)), + ret_layout: LAYOUT_UNIT, + }; + let loop_or_exit_based_on_next_addr = { + let_lowlevel( + root.arena, + root.layout_isize, + next_addr, + PtrCast, + &[next_ptr], + root.arena.alloc(loop_or_exit), + ) + }; + + let alignment = layout.alignment_bytes(root.ptr_size); + let modify_structure_stmt = modify_refcount( + root, + ident_ids, + ctx, + rc_ptr, + alignment, + root.arena.alloc(loop_or_exit_based_on_next_addr), + ); + + rc_ptr_from_data_ptr( + root, + ident_ids, + current, + rc_ptr, + union_layout.stores_tag_id_in_pointer(root.ptr_size), + root.arena.alloc(modify_structure_stmt), + ) + }; + + let rc_contents_then_structure = { + let jp_modify_union = JoinPointId(root.create_symbol(ident_ids, "jp_modify_union")); + let mut tag_branches = Vec::with_capacity_in(tag_layouts.len() + 1, root.arena); + + // If this is null, there is no refcount, no `next`, no fields. Just return. + if let Some(id) = null_id { + let ret = rc_return_stmt(root, ident_ids, ctx); + tag_branches.push((id as u64, BranchInfo::None, ret)); + } + + let mut tag_id: TagIdIntType = 0; + for (field_layouts, opt_tailrec_index) in tag_layouts.iter().zip(tailrec_indices) { + match null_id { + Some(id) if id == tag_id => { + tag_id += 1; + } + _ => {} + } + + // After refcounting the fields, jump to modify the union itself. + // The loop param is a pointer to the next union. It gets passed through two jumps. + let (non_tailrec_fields, jump_to_modify_union) = + if let Some(tailrec_index) = opt_tailrec_index { + let mut filtered = Vec::with_capacity_in(field_layouts.len() - 1, root.arena); + let mut tail_stmt = None; + for (i, field) in field_layouts.iter().enumerate() { + if i != tailrec_index { + filtered.push(*field); + } else { + let field_val = + root.create_symbol(ident_ids, &format!("field_{}_{}", tag_id, i)); + let field_val_expr = Expr::UnionAtIndex { + union_layout, + tag_id, + index: i as u64, + structure: current, + }; + let jump_params = root.arena.alloc([field_val]); + let jump = root.arena.alloc(Stmt::Jump(jp_modify_union, jump_params)); + tail_stmt = Some(Stmt::Let(field_val, field_val_expr, *field, jump)); + } + } + + (filtered.into_bump_slice(), tail_stmt.unwrap()) + } else { + let zero = root.create_symbol(ident_ids, "zero"); + let zero_expr = Expr::Literal(Literal::Int(0)); + let zero_stmt = |next| Stmt::Let(zero, zero_expr, root.layout_isize, next); + + let null = root.create_symbol(ident_ids, "null"); + let null_stmt = + |next| let_lowlevel(root.arena, layout, null, PtrCast, &[zero], next); + + let tail_stmt = zero_stmt(root.arena.alloc( + // + null_stmt(root.arena.alloc( + // + Stmt::Jump(jp_modify_union, root.arena.alloc([null])), + )), + )); + + (*field_layouts, tail_stmt) + }; + + let fields_stmt = refcount_tag_fields( + root, + ident_ids, + ctx, + union_layout, + non_tailrec_fields, + current, + tag_id, + jump_to_modify_union, + ); + + tag_branches.push((tag_id as u64, BranchInfo::None, fields_stmt)); + + tag_id += 1; + } + + let default_stmt: Stmt<'a> = tag_branches.pop().unwrap().2; + + let tag_id_switch = Stmt::Switch { + cond_symbol: tag_id_sym, + cond_layout: tag_id_layout, + branches: tag_branches.into_bump_slice(), + default_branch: (BranchInfo::None, root.arena.alloc(default_stmt)), + ret_layout: LAYOUT_UNIT, + }; + + let jp_param = Param { + symbol: next_ptr, + borrow: true, + layout, + }; + + Stmt::Join { + id: jp_modify_union, + parameters: root.arena.alloc([jp_param]), + body: root.arena.alloc(rc_structure_stmt), + remainder: root.arena.alloc(tag_id_switch), + } + }; + + let loop_body = tag_id_stmt(root.arena.alloc( + // + rc_contents_then_structure, + )); + + let loop_init = Stmt::Jump(tailrec_loop, root.arena.alloc([initial_structure])); + let loop_param = Param { + symbol: current, + borrow: true, + layout: Layout::Union(union_layout), + }; + + Stmt::Join { + id: tailrec_loop, + parameters: root.arena.alloc([loop_param]), + body: root.arena.alloc(loop_body), + remainder: root.arena.alloc(loop_init), + } +} + #[allow(clippy::too_many_arguments)] fn refcount_tag_fields<'a>( root: &mut CodeGenHelp<'a>, diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs index d817c63fe7..87dbe52856 100644 --- a/compiler/test_gen/src/gen_refcount.rs +++ b/compiler/test_gen/src/gen_refcount.rs @@ -265,7 +265,7 @@ fn union_recursive_dec() { #[test] #[cfg(any(feature = "gen-wasm"))] fn refcount_different_rosetrees_inc() { - // Requires two different equality procedures for `List (Rose I64)` and `List (Rose Str)` + // Requires two different Inc procedures for `List (Rose I64)` and `List (Rose Str)` // even though both appear in the mono Layout as `List(RecursivePointer)` assert_refcounts!( indoc!( @@ -305,7 +305,7 @@ fn refcount_different_rosetrees_inc() { #[test] #[cfg(any(feature = "gen-wasm"))] fn refcount_different_rosetrees_dec() { - // Requires two different equality procedures for `List (Rose I64)` and `List (Rose Str)` + // Requires two different Dec procedures for `List (Rose I64)` and `List (Rose Str)` // even though both appear in the mono Layout as `List(RecursivePointer)` assert_refcounts!( indoc!( From 85efcbccdda856ad58cf9b26700ef281aac17ddc Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 6 Jan 2022 23:08:11 -0600 Subject: [PATCH 127/541] Support functions in structures Closes #2313 --- cli/src/repl/eval.rs | 45 ++++++++++++++++++++++-------------------- cli/tests/repl_eval.rs | 33 +++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 7404fe53d3..920f0de630 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -219,6 +219,11 @@ fn tag_id_from_recursive_ptr( } } +const OPAQUE_FUNCTION: Expr = Expr::Var { + module_name: "", + ident: "", +}; + fn jit_to_ast_help<'a>( env: &Env<'a, 'a>, lib: Library, @@ -259,6 +264,7 @@ fn jit_to_ast_help<'a>( I64 => helper!(i64), I128 => helper!(i128), }; + dbg!(&result); Ok(result) } @@ -335,7 +341,7 @@ fn jit_to_ast_help<'a>( } Content::Structure(FlatType::Func(_, _, _)) => { // a function with a struct as the closure environment - Err(ToAstProblem::FunctionLayout) + Ok(OPAQUE_FUNCTION) } other => { unreachable!( @@ -385,13 +391,7 @@ fn jit_to_ast_help<'a>( Layout::RecursivePointer => { unreachable!("RecursivePointers can only be inside structures") } - Layout::LambdaSet(lambda_set) => jit_to_ast_help( - env, - lib, - main_fn_name, - &lambda_set.runtime_representation(), - content, - ), + Layout::LambdaSet(_) => Ok(OPAQUE_FUNCTION), }; result.map(|e| apply_newtypes(env, newtype_tags, e)) } @@ -435,15 +435,17 @@ fn ptr_to_ast<'a>( let (newtype_tags, content) = unroll_newtypes(env, content); let content = unroll_aliases(env, content); - let expr = match layout { - Layout::Builtin(Builtin::Bool) => { + let expr = match (content, layout) { + (Content::Structure(FlatType::Func(_, _, _)), _) + | (_, Layout::LambdaSet(_)) => OPAQUE_FUNCTION, + (_, Layout::Builtin(Builtin::Bool)) => { // TODO: bits are not as expected here. // num is always false at the moment. let num = unsafe { *(ptr as *const bool) }; bool_to_ast(env, num, content) } - Layout::Builtin(Builtin::Int(int_width)) => { + (_, Layout::Builtin(Builtin::Int(int_width))) => { use IntWidth::*; match int_width { @@ -459,7 +461,7 @@ fn ptr_to_ast<'a>( I128 => helper!(i128), } } - Layout::Builtin(Builtin::Float(float_width)) => { + (_, Layout::Builtin(Builtin::Float(float_width))) => { use FloatWidth::*; match float_width { @@ -468,19 +470,19 @@ fn ptr_to_ast<'a>( F128 => todo!("F128 not implemented"), } } - Layout::Builtin(Builtin::List(elem_layout)) => { + (_, Layout::Builtin(Builtin::List(elem_layout))) => { // Turn the (ptr, len) wrapper struct into actual ptr and len values. let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) }; let ptr = unsafe { *(ptr as *const *const u8) }; list_to_ast(env, ptr, len, elem_layout, content) } - Layout::Builtin(Builtin::Str) => { + (_, Layout::Builtin(Builtin::Str)) => { let arena_str = unsafe { *(ptr as *const &'static str) }; str_to_ast(env.arena, arena_str) } - Layout::Struct(field_layouts) => match content { + (_, Layout::Struct(field_layouts)) => match content { Content::Structure(FlatType::Record(fields, _)) => { struct_to_ast(env, ptr, field_layouts, *fields) } @@ -504,7 +506,7 @@ fn ptr_to_ast<'a>( ); } }, - Layout::RecursivePointer => { + (_, Layout::RecursivePointer) => { match (content, when_recursive) { (Content::RecursionVar { structure, @@ -516,7 +518,7 @@ fn ptr_to_ast<'a>( other => unreachable!("Something had a RecursivePointer layout, but instead of being a RecursionVar and having a known recursive layout, I found {:?}", other), } } - Layout::Union(UnionLayout::NonRecursive(union_layouts)) => { + (_, Layout::Union(UnionLayout::NonRecursive(union_layouts))) => { let union_layout = UnionLayout::NonRecursive(union_layouts); let tags = match content { @@ -552,7 +554,7 @@ fn ptr_to_ast<'a>( WhenRecursive::Unreachable, ) } - Layout::Union(union_layout @ UnionLayout::Recursive(union_layouts)) => { + (_, Layout::Union(union_layout @ UnionLayout::Recursive(union_layouts))) => { let (rec_var, tags) = match content { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), _ => unreachable!("any other content would have a different layout"), @@ -581,7 +583,7 @@ fn ptr_to_ast<'a>( when_recursive, ) } - Layout::Union(UnionLayout::NonNullableUnwrapped(_)) => { + (_, Layout::Union(UnionLayout::NonNullableUnwrapped(_))) => { let (rec_var, tags) = match unroll_recursion_var(env, content) { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), @@ -608,7 +610,7 @@ fn ptr_to_ast<'a>( when_recursive, ) } - Layout::Union(UnionLayout::NullableUnwrapped { .. }) => { + (_, Layout::Union(UnionLayout::NullableUnwrapped { .. })) => { let (rec_var, tags) = match unroll_recursion_var(env, content) { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), @@ -641,7 +643,7 @@ fn ptr_to_ast<'a>( ) } } - Layout::Union(union_layout @ UnionLayout::NullableWrapped { .. }) => { + (_, Layout::Union(union_layout @ UnionLayout::NullableWrapped { .. })) => { let (rec_var, tags) = match unroll_recursion_var(env, content) { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), @@ -848,6 +850,7 @@ fn struct_to_ast<'a>( let var = field.into_inner(); let content = subs.get_content_without_compacting(var); + let loc_expr = &*arena.alloc(Loc { value: ptr_to_ast( env, diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index de5d12365c..434ee8ac31 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -811,6 +811,39 @@ mod repl_eval { ) } + #[test] + fn function_in_list() { + expect_success( + r#"[\x -> x + 1, \s -> s * 2]"#, + r#"[ , ] : List (Num a -> Num a)"#, + ) + } + + #[test] + fn function_in_record() { + expect_success( + r#"{ n: 1, adder: \x -> x + 1 }"#, + r#"{ adder: , n: } : { adder : Num a -> Num a, n : Num * }"#, + ) + } + + #[test] + #[ignore = "TODO"] + fn function_in_unwrapped_record() { + expect_success( + r#"{ adder: \x -> x + 1 }"#, + r#"{ adder: } : { adder : Num a -> Num a }"#, + ) + } + + #[test] + fn function_in_tag() { + expect_success( + r#"Adder (\x -> x + 1)"#, + r#"Adder : [ Adder (Num a -> Num a) ]*"#, + ) + } + // #[test] // fn parse_problem() { // // can't find something that won't parse currently From 4efc5010859671513d2c9923fa0f05f036d87144 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 6 Jan 2022 09:50:57 -0500 Subject: [PATCH 128/541] Expand tutorial to include open/closed records/unions --- TUTORIAL.md | 445 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 436 insertions(+), 9 deletions(-) diff --git a/TUTORIAL.md b/TUTORIAL.md index d02ae8a929..4bc33b9f8f 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -291,7 +291,8 @@ Records are not objects; they don't have methods or inheritance, they just store We create the record when we write `{ birds: 5, iguanas: 7 }`. This defines a record with two *fields* - namely, the `birds` field and the `iguanas` field - and then assigns the number `5` to the `birds` field and the number `7` to the -`iguanas` field. +`iguanas` field. Order doesn't matter with record fields; we could have also specified +`iguanas` first and `birds` second, and Roc would consider it the exact same record. When we write `counts.birds`, it accesses the `birds` field of the `counts` record, and when we write `counts.iguanas` it accesses the `iguanas` field. When we use `==` @@ -545,6 +546,11 @@ This makes two changes to our earlier `stoplightColor` / `stoplightStr` example. Any tag can be given a payload like this. A payload doesn't have to be a string; we could also have said (for example) `Custom { r: 40, g: 60, b: 80 }` to specify an RGB color instead of a string. Then in our `when` we could have written `Custom record ->` and then after the `->` used `record.r`, `record.g`, and `record.b` to access the `40`, `60`, `80` values. We could also have written `Custom { r, g, b } ->` to *destructure* the record, and then accessed these `r`, `g`, and `b` defs after the `->` instead. +We refer to whatever comes before a `->` in a `when` expression as a *pattern* - so for example, in the +`Custom description -> description` branch, `Custom description` would be a pattern. In programming, using +patterns in branching conditionals like `when` is known as [pattern matching](https://en.wikipedia.org/wiki/Pattern_matching). You may hear people say things like "let's pattern match on `Custom` here" as a way to +suggest making a `when` branch that begins with something like `Custom description ->`. + ## Lists Another thing we can do in Roc is to make a *list* of values. Here's an example: @@ -654,6 +660,24 @@ Instead, we're using a `when` to tell when we've got a string or a number, and t We could take this as far as we like, adding more different tags (e.g. `BoolElem True`) and then adding more branches to the `when` to handle them appropriately. +### Using tags as functions + +Let's say I want to apply a tag to a bunch of elements in a list. For example: + +```elm +List.map [ "a", "b", "c", ] \str -> Foo str +``` + +This is a perfectly reasonable way to write it, but I can also write it like this: + +```elm +List.map [ "a", "b", "c", ] Foo +``` + +These two versions compile to the same thing. As a convenience, Roc lets you specify +a tag name where a function is expected; when you do this, the compiler infers that you +want a function which uses all of its arguments as the payload to the given tag. + ### `List.any` and `List.all` There are several functions that work like `List.map` - they walk through each element of a list and do @@ -1011,14 +1035,6 @@ its argument without modifying it in any way. This is known as [the identity fun [ This part of the tutorial has not been written yet. Coming soon! ] -### Open and closed records - -[ This part of the tutorial has not been written yet. Coming soon! ] - -### Open and closed tag unions - -[ This part of the tutorial has not been written yet. Coming soon! ] - ## Interface modules [ This part of the tutorial has not been written yet. Coming soon! ] @@ -1330,6 +1346,417 @@ Some important things to note about backpassing and `await`: * Backpassing syntax does not need to be used with `await` in particular. It can be used with any function. * Roc's compiler treats functions defined with backpassing exactly the same way as functions defined the other way. The only difference between `\text ->` and `text <-` is how they look, so feel free to use whichever looks nicer to you! +# Appendix: Advanced Concepts + +Here are some concepts you likely won't need as a beginner, but may want to know about eventually. +This is listed as an appendix rather than the main tutorial, to emphasize that it's totally fine +to stop reading here and go build things! + +## Open Records and Closed Records + +Let's say I write a function which takes a record with a `firstName` +and `lastName` field, and puts them together with a space in between: + +```swift +fullName = \user -> + "\(user.firstName) \(user.lastName)" +``` + +I can pass this function a record that has more fields than just +`firstName` and `lastName`, as long as it has *at least* both of those fields +(and both of them are strings). So any of these calls would work: + +* `fullName { firstName: "Sam", lastName: "Sample" }` +* `fullName { firstName: "Sam", lastName: "Sample", email: "blah@example.com" }` +* `fullName { age: 5, firstName: "Sam", things: 3, lastName: "Sample", role: Admin }` + +This `user` argument is an *open record* - that is, a description of a minimum set of fields +on a record, and their types. When a function takes an open record as an argument, +it's okay if you pass it a record with more fields than just the ones specified. + +In contrast, a *closed record* is one that requires an exact set of fields (and their types), +with no additional fields accepted. + +If we add a type annotation to this `fullName` function, we can choose to have it accept either +an open record or a closed record: + +```coffee +# Closed record +fullName : { firstName : Str, lastName : Str } -> Str +fullName = \user - > + "\(user.firstName) \(user.lastName)" +``` + +```coffee +# Open record (because of the `*`) +fullName : { firstName : Str, lastName : Str }* -> Str +fullName = \user - > + "\(user.firstName) \(user.lastName)" +``` + +The `*` in the type `{ firstName : Str, lastName : Str }*` is what makes it an open record type. +This `*` is the *wildcard type* we saw earlier with empty lists. (An empty list has the type `List *`, +in contrast to something like `List Str` which is a list of strings.) + +This is because record types can optionally end in a type variable. If that variable is a `*`, then +it's an open record. If the type variable is missing, then it's a closed record. You can also specify +a closed type variable by putting a `{}` there (so for example, `{ email : Str }{}` is another way to write +`{ email : Str }`). In practice, it's basically always written without the `{}` on the end, but later on +we'll see a situation where putting types other than `*` in that spot can be useful. + +## Constrained Records + +The type variable can also be a named type variable, like so: + +```coffee +addHttps : { url : Str }a -> { url : Str }a +addHttps = \record -> + { record & url: "https://\(record.url)" } +``` + +This function uses *constrained records* in its type. The annotation is saying: +* This function takes a record which has at least a `url` field, and possibly others +* That `url` field has the type `Str` +* It returns a record of exactly the same type as the one it was given + +So if we give this function a record with five fields, it will return a record with those +same five fields. The only requirement is that one of those fields must be `url : Str`. + +In practice, constrained records appear in type annotations much less often than open or closed records do. + +Here's when you can typically expect to encounter these three flavors of type variables in records: + +- *Open records* are what the compiler infers when you use a record as an argument, or when destructuring it (for example, `{ x, y } =`). +- *Closed records* are what the compiler infers when you create a new record (for example, `{ x: 5, y: 6 }`) +- *Constrained records* are what the compiler infers when you do a record update (for example, `{ user & email: newEmail }`) + +Of note, you can pass a closed record to a function that accepts a smaller open record, but not the reverse. +So a function `{ a : Str, b : Bool }* -> Str` can accept an `{ a : Str, b : Bool, c : Bool }` record, +but a function `{ a : Str, b : Bool, c : Bool } -> Str` would not accept an `{ a : Str, b : Bool }*` record. + +This is because if a function accepts `{ a : Str, b : Bool, c : Bool }`, that means it might access the `c` +field of that record. So if you passed it a record that was not guaranteed to have all three of those fields +present (such as an `{ a : Str, b : Bool }*` record, which only guarantees that the fields `a` and `b` are present), +the function might try to access a `c` field at runtime that did not exist! + +## Type Variables in Record Annotations + +You can add type annotations to make record types less flexible than what the compiler infers, but not more +flexible. For example, you can use an annotation to tell the compiler to treat a record as closed when it would +be inferred as open (or constrained), but you can't use an annotation to make a record open when it would be +inferred as closed. + +If you like, you can always annotate your functions as accepting open records. However, in practice this may not +always be the nicest choice. For example, let's say you have a `User` type alias, like so: + +```coffee +User : + { + email : Str, + firstName : Str, + lastName : Str, + } +``` + +This defines `User` to be a closed record, which in practice is the most common way records named `User` +tend to be defined. + +If you want to have a function take a `User`, you might write its type like so: + +```elm +isValid : User -> Bool +``` + +If you want to have a function return a `User`, you might write its type like so: + +```elm +userFromEmail : Str -> User +``` + +A function which takes a user and returns a user might look like this: + +```elm +capitalizeNames : User -> User +``` + +This is a perfectly reasonable way to write all of these functions. However, I +might decide that I really want the `isValid` function to take an open record - +that is, a record with *at least* the fields of this `User` record, but possibly others as well. + +Since open records have a type variable, in order to do this I'd need to add a +type variable to the `User` type alias: + +```coffee +User a : + { + email : Str, + firstName : Str, + lastName : Str, + }a +``` + +I can still write the same three functions, but now their types need to look different. + +This is what the first one would look like: + +```elm +isValid : User * -> Bool +``` + +Here, the `User *` type alias substitutes `*` for the type variable `a` in the type alias, +which takes it from `{ email : Str, … }a` to `{ email : Str, … }*`. Now I can pass it any +record that has at least the fields in `User`, and possibly others as well, which was my goal. + +```elm +userFromEmail : Str -> User {} +``` + +Here, the `User {}` type alias substitutes `{}` for the type variable `a` in the type alias, +which takes it from `{ email : Str, … }a` to `{ email : Str, … }{}`. As noted earlier, +this is another way to specify a closed record: putting a `{}` after it, in the same place that +you'd find a `*` in an open record. + +> **Aside:** This works because you can form new record types by replacing the type variable with +> other record types. For example, `{ a : Str, b : Str }` can also be written `{ a : Str }{ b : Str }`. +> You can chain these more than once, e.g. `{ a : Str }{ b : Str }{ c : Str, d : Str }`. +> This is more useful when used with type annotations; for example, `{ a : Str, b : Str }User` describes +> a closed record consisting of all the fields in the closed record `User`, plus `a : Str` and `b : Str`. + +This function still returns the same record as it always did, it just needs to be annotated as +`User {}` now instead of just `User`, becuase the `User` type alias has a variable in it that must be +specified. + +The third function might need to use a named type variable: + +```elm +capitalizeNames : User a -> User a +``` + +If this function does a record update on the given user, and returns that - for example, if its +definition were `capitalizeNames = \user -> { user & email: "blah" }` - then it needs to use the +same named type variable for both the argument and return value. + +However, if returns a new `User` that it created from scratch, then its type could instead be: + +```elm +capitalizeNames : User * -> User {} +``` + +This says that it takes a record with at least the fields specified in the `User` type alias, +and possibly others...and then returns a record with exactly the fields specified in the `User` +type alias, and no others. + +These three examples illustrate why it's relatively uncommon to use open records for type aliases: +it makes a lot of types need to incorporate a type variable that otherwise they could omit, +all so that `isValid` can be given something that has not only the fields `User` has, but +some others as well. (In the case of a `User` record in particular, it may be that the extra +fields were included due to a mistake rather than on purpose, and accepting an open record could +prevent the compiler from raising an error that would have revealed the mistake.) + +That said, this is a useful technique to know about if you want to (for example) make a record +type that accumulates more and more fields as it progresses through a series of operations. + +## Open and Closed Tag Unions + +Just like how Roc has open records and closed records, it also has open and closed tag unions. + +The *open tag union* (or *open union* for short) `[ Foo Str, Bar Bool ]*` represents a tag that might +be `Foo Str` and might be `Bar Bool`, but might also be some other tag whose type isn't known at compile time. + +Because an open union represents possibilities that are impossible to know ahead of time, any `when` I use on a +`[ Foo Str, Bar Bool ]*` value must include a catch-all `_ ->` branch. Otherwise, if one of those +unknown tags were to come up, the `when` would not know what to do with it! For example: + +```coffee +example : [ Foo Str, Bar Bool ]* -> Bool +example = \tag -> + when tag is + Foo str -> Str.isEmpty str + Bar bool -> bool + _ -> False +``` + +In contrast, a *closed tag union* (or *closed union*) like `[ Foo Str, Bar Bool ]` (without the `*`) +represents an exhaustive set of possible tags. If I use a `when` on one of these, I can match on `Foo` +only and then on `Bar` only, with no need for a catch-all branch. For example: + +```coffee +example : [ Foo Str, Bar Bool ] -> Bool +example = \tag -> + when tag is + Foo str -> Str.isEmpty str + Bar bool -> bool +``` + +If we were to remove the type annotations from the previous two code examples, Roc would infer the same +types for them anyway. + +It would infer `tag : [ Foo Str, Bar Bool ]` for the latter example because the `when tag is` expression +only includes a `Foo Str` branch and a `Bar Bool` branch, and nothing else. Since the `when` doesn't handle +any other possibilities, these two tags must be the only possible ones the `tag` argument could be. + +It would infer `tag : [ Foo Str, Bar Bool ]*` for the former example because the `when tag is` expression +includes a `Foo Str` branch and a `Bar Bool` branch - meaning we know about at least those two specific +possibilities - but also a `_ ->` branch, indicating that there may be other tags we don't know about. Since +the `when` is flexible enough to handle all possible tags, `tag` gets inferred as an open union. + +Putting these together, whether a tag union is inferred to be open or closed depends on which possibilities +the implementation actually handles. + +> **Aside:** As with open and closed records, we can use type annotations to make tag union types less flexible +> than what would be inferred. If we added a `_ ->` branch to the second example above, the compiler would still +> accept `example : [ Foo Str, Bar Bool ] -> Bool` as the type annotation, even though the catch-all branch +> would permit the more flexible `example : [ Foo Str, Bar Bool ]* -> Bool` annotation instead. + +## Combining Open Unions + +When we make a new record, it's inferred to be a closed record. For example, in `foo { a: "hi" }`, +the type of `{ a: "hi" }` is inferred to be `{ a : Str }`. In contrast, when we make a new tag, it's inferred +to be an open union. So in `foo (Bar "hi")`, the type of `Bar "hi"` is inferred to be `[ Bar Str ]*`. + +This is because open unions can accumulate additional tags based on how they're used in the program, +whereas closed unions cannot. For example, let's look at this conditional: + +```elm +if x > 5 then + "foo" +else + 7 +``` + +This will be a type mismatch because the two branches have incompatible types. Strings and numbers are not +type-compatible! Now let's look at another example: + +```elm +if x > 5 then + Ok "foo" +else + Err "bar" +``` + +This shouldn't be a type mismatch, because we can see that the two branches are compatible; they are both +tags that could easily coexist in the same tag union. But if the compiler inferred the type of `Ok "foo"` to be +the closed union `[ Ok Str ]`, and likewise for `Err "bar"` and `[ Err Str ]`, then this would have to be +a type mismatch - because those two closed unions are incompatible. + +Instead, the compiler infers `Ok "foo"` to be the open union `[ Ok Str ]*`, and `Err "bar"` to be the open +union `[ Err Str ]*`. Then, when using them together in this conditional, the inferred type of the conditional +becomes `[ Ok Str, Err Str ]*` - that is, the combination of the unions in each of its branches. (Branches in +a `when` work the same way with open unions.) + +Earlier we saw how a function which accepts an open union must account for more possibilities, by including +catch-all `_ ->` patterns in its `when` expressions. So *accepting* an open union means you have more requirements. +In contrast, when you already *have* a value which is an open union, you have fewer requirements. A value +which is an open union (like `Ok "foo"`, which has the type `[ Ok Str ]*`) can be provided to anything that's +expecting a tag union (no matter whether it's open or closed), as long as the expected tag union includes at least +the tags in the open union you're providing. + +So if I have an `[ Ok Str ]*` value, I can pass it functions with any of these types (among others): + +* `[ Ok Str ]* -> Bool` +* `[ Ok Str ] -> Bool` +* `[ Ok Str, Err Bool ]* -> Bool` +* `[ Ok Str, Err Bool ] -> Bool` +* `[ Ok Str, Err Bool, Whatever ]* -> Bool` +* `[ Ok Str, Err Bool, Whatever ] -> Bool` +* `Result Str Bool -> Bool` +* `[ Err Bool, Whatever ]* -> Bool` + +That last one works because a function accepting an open union can accept any unrecognized tag, including +`Ok Str` - even though it is not mentioned as one of the tags in `[ Err Bool, Whatever ]*`! Remember, when +a function accepts an open tag union, any `when` branches on that union must include a catch-all `_ ->` branch, +which is the branch that will end up handling the `Ok Str` value we pass in. + +However, I could not pass an `[ Ok Str ]*` to a function with a *closed* tag union argument that did not +mention `Ok Str` as one of its tags. So if I tried to pass `[ Ok Str ]*` to a function with the type +`[ Err Bool, Whatever ] -> Str`, I would get a type mismatch - because a `when` in that function could +be handling the `Err Bool` possibility and the `Whatever` possibility, and since it would not necessarily have +a catch-all `_ ->` branch, it might not know what to do with an `Ok Str` if it received one. + +> **Note:** It wouldn't be accurate to say that a function which accepts an open union handles +> "all possible tags." For example, if I have a function `[ Ok Str ]* -> Bool` and I pass it +> `Ok 5`, that will still be a type mismatch. If you think about it, a `when` in that function might +> have the branch `Ok str ->` which assumes there's a string inside that `Ok`, and if `Ok 5` type-checked, +> then that assumption would be false and things would break! +> +> So `[ Ok Str ]*` is more restrictive than `[]*`. It's basically saying "this may or may not be an `Ok` tag, +> but if it is an `Ok` tag, then it's guaranteed to have a payload of exactly `Str`." + +In summary, here's a way to think about the difference between open unions in a value you have, compared to a value you're accepting: + +* If you *have* a closed union, that means it has all the tags it ever will, and can't accumulate more. +* If you *have* an open union, that means it can accumulate more tags through conditional branches. +* If you *accept* a closed union, that means you only have to handle the possibilities listed in the union. +* If you *accept* an open union, that means you have to handle the possibility that it has a tag you can't know about. + +## Type Variables in Tag Unions + +Earlier we saw these two examples, one with an open tag union and the other with a closed one: + +```coffee +example : [ Foo Str, Bar Bool ]* -> Bool +example = \tag -> + when tag is + Foo str -> Str.isEmpty str + Bar bool -> bool + _ -> False +``` + +```coffee +example : [ Foo Str, Bar Bool ] -> Bool +example = \tag -> + when tag is + Foo str -> Str.isEmpty str + Bar bool -> bool +``` + +Similarly to how there are open records with a `*`, closed records with nothing, +and constrained records with a named type variable, we can also have *constrained tag unions* +with a named type variable. Here's an example: + +```coffee +example : [ Foo Str, Bar Bool ]a -> [ Foo Str, Bar Bool ]a +example = \tag -> + when tag is + Foo str -> Bar (Str.isEmpty str) + Bar _ -> Bar False + other -> other +``` + +This type says that the `example` function will take either a `Foo Str` tag, or a `Bar Bool` tag, +or possibly another tag we don't know about at compile time - and it also says that the function's +return type is the same as the type of its argument. + +So if we give this function a `[ Foo Str, Bar Bool, Baz (List Str) ]` argument, then it will be guaranteed +to return a `[ Foo Str, Bar Bool, Baz (List Str) ]` value. This is more constrained than a function that +returned `[ Foo Str, Bar Bool ]*` because that would say it could return *any* other tag (in addition to +the `Foo Str` and `Bar Bool` we already know about). + +If we removed the type annotation from `example` above, Roc's compiler would infer the same type anyway. +This may be surprising if you look closely at the body of the function, because: + +* The return type includes `Foo Str`, but no branch explicitly returns `Foo`. Couldn't the return type be `[ Bar Bool ]a` instead? +* The argument type includes `Bar Bool` even though we never look at `Bar`'s payload. Couldn't the argument type be inferred to be `Bar *` instead of `Bar Bool`, since we never look at it? + +The reason it has this type is the `other -> other` branch. Take a look at that branch, and ask this question: +"What is the type of `other`?" There has to be exactly one answer! It can't be the case that `other` has one +type before the `->` and another type after it; whenever you see a named value in Roc, it is guaranteed to have +the same type everywhere it appears in that scope. + +For this reason, any time you see a function that only runs a `when` on its only argument, and that `when` +includes a branch like `x -> x` or `other -> other`, the function's argument type and reurn type must necessarily +be equivalent. + +> **Note:** Just like with records, you can also replace the type variable in tag union types with a concrete type. +> For example, `[ Foo Str ][ Bar Bool ][ Baz (List Str) ]` is equivalent to `[ Foo Str, Bar Bool, Baz (List Str) ]`. +> +> Also just like with records, you can use this to compose tag union type aliases. For example, you can write +> `NetworkError : [ Timeout, Disconnected ]` and then `Problem : [ InvalidInput, UnknownFormat ]NetworkError` + +## Phantom Types + +[ This part of the tutorial has not been written yet. Coming soon! ] + ## Operator Desugaring Table Here are various Roc expressions involving operators, and what they desugar to. From 9cd27b72d7db069e0b70ee48ccb461295cf8cc85 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 6 Jan 2022 22:13:44 -0500 Subject: [PATCH 129/541] Fix some links in Num docs --- compiler/builtins/docs/Num.roc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index 4c012e99b3..75573db684 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -389,17 +389,17 @@ Nat : Int [ @Natural ] ## | ` (over 340 undecillion) 0` | #U128 | 16 Bytes | ## | ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | | ## -## Roc also has one variable-size integer type: #Nat. The size of #Nat is equal +## Roc also has one variable-size integer type: [Nat]. The size of [Nat] is equal ## to the size of a memory address, which varies by system. For example, when -## compiling for a 64-bit system, #Nat is the same as #U64. When compiling for a -## 32-bit system, it's the same as #U32. +## compiling for a 64-bit system, [Nat] is the same as [U64]. When compiling for a +## 32-bit system, it's the same as [U32]. ## -## A common use for #Nat is to store the length ("len" for short) of a -## collection like #List, #Set, or #Map. 64-bit systems can represent longer +## A common use for [Nat] is to store the length ("len" for short) of a +## collection like a [List]. 64-bit systems can represent longer ## lists in memory than 32-bit systems can, which is why the length of a list -## is represented as a #Nat in Roc. +## is represented as a [Nat] in Roc. ## -## If any operation would result in an #Int that is either too big +## If any operation would result in an [Int] that is either too big ## or too small to fit in that range (e.g. calling `Int.maxI32 + 1`), ## then the operation will *overflow*. When an overflow occurs, the program will crash. ## From febb9c974a4b00fa81a010a2d1758d5cd552f421 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 6 Jan 2022 22:13:57 -0500 Subject: [PATCH 130/541] Add numeric types to tutorial --- TUTORIAL.md | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/TUTORIAL.md b/TUTORIAL.md index 4bc33b9f8f..39d48143c8 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -1031,9 +1031,99 @@ of the type annotation, or even the function's implementation! The only way to h Similarly, the only way to have a function whose type is `a -> a` is if the function's implementation returns its argument without modifying it in any way. This is known as [the identity function](https://en.wikipedia.org/wiki/Identity_function). -### Numeric types +## Numeric types -[ This part of the tutorial has not been written yet. Coming soon! ] +Roc has different numeric types that each have different tradeoffs. +They can all be broken down into two categories: [fractions](https://en.wikipedia.org/wiki/Fraction), +and [integers](https://en.wikipedia.org/wiki/Integer). In Roc we call these `Frac` and `Int` for short. + +### Integers + +Roc's integer types have two important characteristics: their *size* and their [*signedness*](https://en.wikipedia.org/wiki/Signedness). +Together, these two characteristics determine the range of numbers the integer type can represent. + +For example, the Roc type `U8` can represent the numbers 0 through 255, whereas the `I16` type can represent +the numbers -32768 through 32767. You can actually infer these ranges from their names (`U8` and `I16`) alone! + +The `U` in `U8` indicates that it's *unsigned*, meaning that it can't have a minus [sign](https://en.wikipedia.org/wiki/Sign_(mathematics)), and therefore can't be negative. The fact that it's unsigned tells us immediately that +its lowest value is zero. The 8 in `U8` means it is 8 [bits](https://en.wikipedia.org/wiki/Bit) in size, which +means it has room to represent 2⁸ (which is equal to 256) different numbers. Since one of those 256 different numbers +is 0, we can look at `U8` and know that it goes from `0` (since it's unsigned) to `255` (2⁸ - 1, since it's 8 bits). + +If we change `U8` to `I8`, making it a *signed* 8-bit integer, the range changes. Because it's still 8 bits, it still +has room to represent 2⁸ (that is, 256) different numbers. However, now in addition to one of those 256 numbers +being zero, about half of rest will be negative, and the others positive. So instead of ranging from, say -255 +to 255 (which, counting zero, would represent 511 different numbers; too many to fit in 8 bits!) an `I8` value +ranges from -128 to 127. + +Notice that the negative extreme is `-128` versus `127` (not `128`) on the positive side. That's because of +needing room for zero; the slot for zero is taken from the positive range because zero doesn't have a minus sign. +So in general, you can find the lowest signed number by taking its total range (256 different numbers in the case +of an 8-bit integer) and dividing it in half (half of 256 is 128, so -128 is `I8`'s lowest number). To find the +highest number, take the positive version of the lowest number (so, convert `-128` to `128`) and then subtract 1 +to make room for zero (so, `128` becomes `127`; `I8` ranges from -128 to 127). + +Following this pattern, the 16 in `I16` means that it's a signed 16 bit integer. +That tells us it has room to represent 2¹⁶ (which is equal to 65536) different numbers. Half of 65536 is 32768, +so the lowest `I16` would be -32768, and the highest would be 32767. Knowing that, we can also quickly tell that +the lowest `U16` would be zero (since it always is for unsigned integers), and the higeest `U16` would be 65536. + +Choosing a size depends on your performance needs and the range of numbers you want to represent. Consider: + +* Larger integer sizes can represent a wider range of numbers. If you absolutely need to represent numbers in a certain range, make sure to pick an integer size that can hold them! +* Smaller integer sizes take up less memory. These savings rarely matters in variables and function arguments, but the sizes of integers that you use in data structures can add up. This can also affect whether those data structures fit in [cache lines](https://en.wikipedia.org/wiki/CPU_cache#Cache_performance), which can easily be a performance bottleneck. +* Certain processors work faster on some numeric sizes than others. There isn't even a general rule like "larger numeric sizes run slower" (or the reverse, for that matter) that applies to all processors. In fact, if the CPU is taking too long to run numeric calculations, you may find a performance improvement by experimenting with numeric sizes that are larger than otherwise necessary. However, in practice, doing this typically degrades overall performance, so be careful to measure properly! + +Here are the different fixed-size integer types that Roc supports: + +| Range | Type | Size | +|--------------------------------------------------------|-------|----------| +| ` -128` | #I8 | 1 Byte | +| ` 127` | | | +|--------------------------------------------------------|-------|----------| +| ` 0` | #U8 | 1 Byte | +| ` 255` | | | +|--------------------------------------------------------|-------|----------| +| ` -32_768` | #I16 | 2 Bytes | +| ` 32_767` | | | +|--------------------------------------------------------|-------|----------| +| ` 0` | #U16 | 2 Bytes | +| ` 65_535` | | | +|--------------------------------------------------------|-------|----------| +| ` -2_147_483_648` | #I32 | 4 Bytes | +| ` 2_147_483_647` | | | +|--------------------------------------------------------|-------|----------| +| ` 0` | #U32 | 4 Bytes | +| ` (over 4 billion) 4_294_967_295` | | | +|--------------------------------------------------------|-------|----------| +| ` -9_223_372_036_854_775_808` | #I64 | 8 Bytes | +| ` 9_223_372_036_854_775_807` | | | +|--------------------------------------------------------|-------|----------| +| ` 0` | #U64 | 8 Bytes | +| ` (over 18 quintillion) 18_446_744_073_709_551_615` | | | +|--------------------------------------------------------|-------|----------| +| `-170_141_183_460_469_231_731_687_303_715_884_105_728` | #I128 | 16 Bytes | +| ` 170_141_183_460_469_231_731_687_303_715_884_105_727` | | | +|--------------------------------------------------------|-------|----------| +| ` (over 340 undecillion) 0` | #U128 | 16 Bytes | +| ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | | + +Roc also has one variable-size integer type: [Nat]. The size of [Nat] is equal +to the size of a memory address, which varies by system. For example, when +compiling for a 64-bit system, [Nat] is the same as [U64]. When compiling for a +32-bit system, it's the same as [U32]. + +A common use for [Nat] is to store the length ("len" for short) of a +collection like a [List]. 64-bit systems can represent longer +lists in memory than 32-bit systems can, which is why the length of a list +is represented as a [Nat] in Roc. + +If any operation would result in an [Int] that is either too big +or too small to fit in that range (e.g. calling `Int.maxI32 + 1`), +then the operation will *overflow*. When an overflow occurs, the program will crash. + +As such, it's very important to design your code not to exceed these bounds! +If you need to do math outside these bounds, consider using a larger numeric size. ## Interface modules From 46d36ed942eea6f4ab6ced226e7fd1c735b1a8bc Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 6 Jan 2022 22:18:07 -0500 Subject: [PATCH 131/541] Fix tutorial table formatting --- TUTORIAL.md | 43 ++++++++++------------------------ compiler/builtins/docs/Num.roc | 20 ++++++++-------- 2 files changed, 22 insertions(+), 41 deletions(-) diff --git a/TUTORIAL.md b/TUTORIAL.md index 39d48143c8..35aa531559 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -1076,37 +1076,18 @@ Choosing a size depends on your performance needs and the range of numbers you w Here are the different fixed-size integer types that Roc supports: -| Range | Type | Size | -|--------------------------------------------------------|-------|----------| -| ` -128` | #I8 | 1 Byte | -| ` 127` | | | -|--------------------------------------------------------|-------|----------| -| ` 0` | #U8 | 1 Byte | -| ` 255` | | | -|--------------------------------------------------------|-------|----------| -| ` -32_768` | #I16 | 2 Bytes | -| ` 32_767` | | | -|--------------------------------------------------------|-------|----------| -| ` 0` | #U16 | 2 Bytes | -| ` 65_535` | | | -|--------------------------------------------------------|-------|----------| -| ` -2_147_483_648` | #I32 | 4 Bytes | -| ` 2_147_483_647` | | | -|--------------------------------------------------------|-------|----------| -| ` 0` | #U32 | 4 Bytes | -| ` (over 4 billion) 4_294_967_295` | | | -|--------------------------------------------------------|-------|----------| -| ` -9_223_372_036_854_775_808` | #I64 | 8 Bytes | -| ` 9_223_372_036_854_775_807` | | | -|--------------------------------------------------------|-------|----------| -| ` 0` | #U64 | 8 Bytes | -| ` (over 18 quintillion) 18_446_744_073_709_551_615` | | | -|--------------------------------------------------------|-------|----------| -| `-170_141_183_460_469_231_731_687_303_715_884_105_728` | #I128 | 16 Bytes | -| ` 170_141_183_460_469_231_731_687_303_715_884_105_727` | | | -|--------------------------------------------------------|-------|----------| -| ` (over 340 undecillion) 0` | #U128 | 16 Bytes | -| ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | | +| Range | Type | Size | +| -----------------------------------------------------------: | :---- | :------- | +| `-128`
`127` | `I8` | 1 Byte | +| `0`
`255` | `U8` | 1 Byte | +| `-32_768`
`32_767` | `I16` | 2 Bytes | +| `0`
`65_535` | `U16` | 2 Bytes | +| `-2_147_483_648`
`2_147_483_647` | `I32` | 4 Bytes | +| `0`
(over 4 billion) `4_294_967_295` | `U32` | 4 Bytes | +| `-9_223_372_036_854_775_808`
`9_223_372_036_854_775_807` | `I64` | 8 Bytes | +| `0`
(over 18 quintillion) `18_446_744_073_709_551_615` | `U64` | 8 Bytes | +| `-170_141_183_460_469_231_731_687_303_715_884_105_728`
`170_141_183_460_469_231_731_687_303_715_884_105_727` | `I128` | 16 Bytes | +| `0`
(over 340 undecillion) `340_282_366_920_938_463_463_374_607_431_768_211_455` | `U128` | 16 Bytes | Roc also has one variable-size integer type: [Nat]. The size of [Nat] is equal to the size of a memory address, which varies by system. For example, when diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index 75573db684..b3c49daf95 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -359,34 +359,34 @@ Nat : Int [ @Natural ] ## ## | Range | Type | Size | ## |--------------------------------------------------------|-------|----------| -## | ` -128` | #I8 | 1 Byte | +## | ` -128` | [I8] | 1 Byte | ## | ` 127` | | | ## |--------------------------------------------------------|-------|----------| -## | ` 0` | #U8 | 1 Byte | +## | ` 0` | [U8] | 1 Byte | ## | ` 255` | | | ## |--------------------------------------------------------|-------|----------| -## | ` -32_768` | #I16 | 2 Bytes | +## | ` -32_768` | [I16] | 2 Bytes | ## | ` 32_767` | | | ## |--------------------------------------------------------|-------|----------| -## | ` 0` | #U16 | 2 Bytes | +## | ` 0` | [U16] | 2 Bytes | ## | ` 65_535` | | | ## |--------------------------------------------------------|-------|----------| -## | ` -2_147_483_648` | #I32 | 4 Bytes | +## | ` -2_147_483_648` | [I32] | 4 Bytes | ## | ` 2_147_483_647` | | | ## |--------------------------------------------------------|-------|----------| -## | ` 0` | #U32 | 4 Bytes | +## | ` 0` | [U32] | 4 Bytes | ## | ` (over 4 billion) 4_294_967_295` | | | ## |--------------------------------------------------------|-------|----------| -## | ` -9_223_372_036_854_775_808` | #I64 | 8 Bytes | +## | ` -9_223_372_036_854_775_808` | [I64] | 8 Bytes | ## | ` 9_223_372_036_854_775_807` | | | ## |--------------------------------------------------------|-------|----------| -## | ` 0` | #U64 | 8 Bytes | +## | ` 0` | [U64] | 8 Bytes | ## | ` (over 18 quintillion) 18_446_744_073_709_551_615` | | | ## |--------------------------------------------------------|-------|----------| -## | `-170_141_183_460_469_231_731_687_303_715_884_105_728` | #I128 | 16 Bytes | +## | `-170_141_183_460_469_231_731_687_303_715_884_105_728` | [I128]| 16 Bytes | ## | ` 170_141_183_460_469_231_731_687_303_715_884_105_727` | | | ## |--------------------------------------------------------|-------|----------| -## | ` (over 340 undecillion) 0` | #U128 | 16 Bytes | +## | ` (over 340 undecillion) 0` | [U128]| 16 Bytes | ## | ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | | ## ## Roc also has one variable-size integer type: [Nat]. The size of [Nat] is equal From 6fd9736886cddae1e01452a6b45851c3a2839c03 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 7 Jan 2022 19:46:10 -0500 Subject: [PATCH 132/541] Update numeric types --- TUTORIAL.md | 142 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 129 insertions(+), 13 deletions(-) diff --git a/TUTORIAL.md b/TUTORIAL.md index 35aa531559..af9be56e83 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -1089,22 +1089,138 @@ Here are the different fixed-size integer types that Roc supports: | `-170_141_183_460_469_231_731_687_303_715_884_105_728`
`170_141_183_460_469_231_731_687_303_715_884_105_727` | `I128` | 16 Bytes | | `0`
(over 340 undecillion) `340_282_366_920_938_463_463_374_607_431_768_211_455` | `U128` | 16 Bytes | -Roc also has one variable-size integer type: [Nat]. The size of [Nat] is equal -to the size of a memory address, which varies by system. For example, when -compiling for a 64-bit system, [Nat] is the same as [U64]. When compiling for a -32-bit system, it's the same as [U32]. +Roc also has one variable-size integer type: `Nat` (short for "natural number"). +The size of `Nat` is equal to the size of a memory address, which varies by system. +For example, when compiling for a 64-bit system, `Nat` works the same way as `U64`. +When compiling for a 32-bit system, it works the same way as `U32`. Most popular +computing devices today are 64-bit, so `Nat` is usually the same as `U64`, but +Web Assembly is typically 32-bit - so when running a Roc program built for Web Assembly, +`Nat` will work like a `U32` in that program. -A common use for [Nat] is to store the length ("len" for short) of a -collection like a [List]. 64-bit systems can represent longer -lists in memory than 32-bit systems can, which is why the length of a list -is represented as a [Nat] in Roc. +A common use for `Nat` is to store the length of a collection like a `List`; +there's a function `List.len : List * -> Nat` which returns the length of the given list. +64-bit systems can represent longer lists in memory than 32-bit systems can, +which is why the length of a list is represented as a `Nat`. -If any operation would result in an [Int] that is either too big -or too small to fit in that range (e.g. calling `Int.maxI32 + 1`), -then the operation will *overflow*. When an overflow occurs, the program will crash. +If any operation would result in an integer that is either too big +or too small to fit in that range (e.g. calling `Int.maxI32 + 1`, which adds 1 to +the highest possible 32-bit integer), then the operation will *overflow*. +When an overflow occurs, the program will crash. + +As such, it's very important to design your integer operations not to exceed these bounds! + +### Fractions + +Roc has three fractional types: + +* `F32`, a 32-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) +* `F64`, a 32-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) +* `Dec`, a 128-bit decimal [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) + +All of these are different from integers in that they can represent numbers with fractional components, +such as 1.5 and -0.123. + +## [Dec] is the best default choice for representing base-10 decimal numbers +## like currency, because it is base-10 under the hood. In contrast, +## [F64] and [F32] are base-2 under the hood, which can lead to decimal +## precision loss even when doing addition and subtraction. For example, when +## using [F64], running 0.1 + 0.2 returns 0.3000000000000000444089209850062616169452667236328125, +## whereas when using [Dec], 0.1 + 0.2 returns 0.3. +## +## Under the hood, a [Dec] is an [I128], and operations on it perform +## [base-10 fixed-point arithmetic](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) +## with 18 decimal places of precision. +## +## This means a [Dec] can represent whole numbers up to slightly over 170 +## quintillion, along with 18 decimal places. (To be precise, it can store +## numbers betwween `-170_141_183_460_469_231_731.687303715884105728` +## and `170_141_183_460_469_231_731.687303715884105727`.) Why 18 +## decimal places? It's the highest number of decimal places where you can still +## convert any [U64] to a [Dec] without losing information. +## +## There are some use cases where [F64] and [F32] can be better choices than [Dec] +## despite their precision issues. For example, in graphical applications they +## can be a better choice for representing coordinates because they take up +## less memory, certain relevant calculations run faster (see performance +## details, below), and decimal precision loss isn't as big a concern when +## dealing with screen coordinates as it is when dealing with currency. +## +## ## Performance +## +## [Dec] typically takes slightly less time than [F64] to perform addition and +## subtraction, but 10-20 times longer to perform multiplication and division. +## [sqrt] and trigonometry are massively slower with [Dec] than with [F64]. +Dec : Float [ @Decimal128 ] + +## A fixed-size number with a fractional component. +## +## Roc fractions come in two flavors: fixed-point base-10 and floating-point base-2. +## +## * [Dec] is a 128-bit [fixed-point](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) base-10 number. It's a great default choice, especially when precision is important - for example when representing currency. With [Dec], 0.1 + 0.2 returns 0.3. +## * [F64] and [F32] are [floating-point](https://en.wikipedia.org/wiki/Floating-point_arithmetic) base-2 numbers. They sacrifice precision for lower memory usage and improved performance on some operations. This makes them a good fit for representing graphical coordinates. With [F64], 0.1 + 0.2 returns 0.3000000000000000444089209850062616169452667236328125. +## +## If you don't specify a type, Roc will default to using [Dec] because it's +## the least error-prone overall. For example, suppose you write this: +## +## wasItPrecise = 0.1 + 0.2 == 0.3 +## +## The value of `wasItPrecise` here will be `True`, because Roc uses [Dec] +## by default when there are no types specified. +## +## In contrast, suppose we use `f32` or `f64` for one of these numbers: +## +## wasItPrecise = 0.1f64 + 0.2 == 0.3 +## +## Here, `wasItPrecise` will be `False` because the entire calculation will have +## been done in a base-2 floating point calculation, which causes noticeable +## precision loss in this case. +## +## The floating-point numbers ([F32] and [F64]) also have three values which +## are not ordinary [finite numbers](https://en.wikipedia.org/wiki/Finite_number). +## They are: +## * ∞ ([infinity](https://en.wikipedia.org/wiki/Infinity)) +## * -∞ (negative infinity) +## * *NaN* ([not a number](https://en.wikipedia.org/wiki/NaN)) +## +## These values are different from ordinary numbers in that they only occur +## when a floating-point calculation encounters an error. For example: +## * Dividing a positive [F64] by `0.0` returns ∞. +## * Dividing a negative [F64] by `0.0` returns -∞. +## * Dividing a [F64] of `0.0` by `0.0` returns [*NaN*](Num.isNaN). +## +## These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) +## floating point standard. Because almost all modern processors are built to +## this standard, deviating from these rules has a significant performance +## cost! Since the most common reason to choose [F64] or [F32] over [Dec] is +## access to hardware-accelerated performance, Roc follows these rules exactly. +## +## There's no literal syntax for these error values, but you can check to see if +## you ended up with one of them by using [isNaN], [isFinite], and [isInfinite]. +## Whenever a function in this module could return one of these values, that +## possibility is noted in the function's documentation. +## +## ## Performance Notes +## +## On typical modern CPUs, performance is similar between [Dec], [F64], and [F32] +## for addition and subtraction. For example, [F32] and [F64] do addition using +## a single CPU floating-point addition instruction, which typically takes a +## few clock cycles to complete. In contrast, [Dec] does addition using a few +## CPU integer arithmetic instructions, each of which typically takes only one +## clock cycle to complete. Exact numbers will vary by CPU, but they should be +## similar overall. +## +## [Dec] is significantly slower for multiplication and division. It not only +## needs to do more arithmetic instructions than [F32] and [F64] do, but also +## those instructions typically take more clock cycles to complete. +## +## With [Num.sqrt] and trigonometry functions like [Num.cos], there is +## an even bigger performance difference. [F32] and [F64] can do these in a +## single instruction, whereas [Dec] needs entire custom procedures - which use +## loops and conditionals. If you need to do performance-critical trigonometry +## or square roots, either [F64] or [F32] is probably a better choice than the +## usual default choice of [Dec], despite the precision problems they bring. +Float a : Num [ @Fraction a ] -As such, it's very important to design your code not to exceed these bounds! -If you need to do math outside these bounds, consider using a larger numeric size. ## Interface modules From 97cbba2eb6873900da7e367eb579f1d35f399c92 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 7 Jan 2022 20:50:10 -0500 Subject: [PATCH 133/541] Add Num, Int, Frac sections --- TUTORIAL.md | 159 +++++++++++++++++++--------------------------------- 1 file changed, 58 insertions(+), 101 deletions(-) diff --git a/TUTORIAL.md b/TUTORIAL.md index af9be56e83..4a7b3a3c61 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -1114,113 +1114,70 @@ As such, it's very important to design your integer operations not to exceed the Roc has three fractional types: * `F32`, a 32-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) -* `F64`, a 32-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) +* `F64`, a 64-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) * `Dec`, a 128-bit decimal [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) -All of these are different from integers in that they can represent numbers with fractional components, +These are different from integers in that they can represent numbers with fractional components, such as 1.5 and -0.123. -## [Dec] is the best default choice for representing base-10 decimal numbers -## like currency, because it is base-10 under the hood. In contrast, -## [F64] and [F32] are base-2 under the hood, which can lead to decimal -## precision loss even when doing addition and subtraction. For example, when -## using [F64], running 0.1 + 0.2 returns 0.3000000000000000444089209850062616169452667236328125, -## whereas when using [Dec], 0.1 + 0.2 returns 0.3. -## -## Under the hood, a [Dec] is an [I128], and operations on it perform -## [base-10 fixed-point arithmetic](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) -## with 18 decimal places of precision. -## -## This means a [Dec] can represent whole numbers up to slightly over 170 -## quintillion, along with 18 decimal places. (To be precise, it can store -## numbers betwween `-170_141_183_460_469_231_731.687303715884105728` -## and `170_141_183_460_469_231_731.687303715884105727`.) Why 18 -## decimal places? It's the highest number of decimal places where you can still -## convert any [U64] to a [Dec] without losing information. -## -## There are some use cases where [F64] and [F32] can be better choices than [Dec] -## despite their precision issues. For example, in graphical applications they -## can be a better choice for representing coordinates because they take up -## less memory, certain relevant calculations run faster (see performance -## details, below), and decimal precision loss isn't as big a concern when -## dealing with screen coordinates as it is when dealing with currency. -## -## ## Performance -## -## [Dec] typically takes slightly less time than [F64] to perform addition and -## subtraction, but 10-20 times longer to perform multiplication and division. -## [sqrt] and trigonometry are massively slower with [Dec] than with [F64]. -Dec : Float [ @Decimal128 ] +`Dec` is the best default choice for representing base-10 decimal numbers +like currency, because it is base-10 under the hood. In contrast, +`F64` and `F32` are base-2 under the hood, which can lead to decimal +precision loss even when doing addition and subtraction. For example, when +using `F64`, running 0.1 + 0.2 returns 0.3000000000000000444089209850062616169452667236328125, +whereas when using `Dec`, 0.1 + 0.2 returns 0.3. -## A fixed-size number with a fractional component. -## -## Roc fractions come in two flavors: fixed-point base-10 and floating-point base-2. -## -## * [Dec] is a 128-bit [fixed-point](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) base-10 number. It's a great default choice, especially when precision is important - for example when representing currency. With [Dec], 0.1 + 0.2 returns 0.3. -## * [F64] and [F32] are [floating-point](https://en.wikipedia.org/wiki/Floating-point_arithmetic) base-2 numbers. They sacrifice precision for lower memory usage and improved performance on some operations. This makes them a good fit for representing graphical coordinates. With [F64], 0.1 + 0.2 returns 0.3000000000000000444089209850062616169452667236328125. -## -## If you don't specify a type, Roc will default to using [Dec] because it's -## the least error-prone overall. For example, suppose you write this: -## -## wasItPrecise = 0.1 + 0.2 == 0.3 -## -## The value of `wasItPrecise` here will be `True`, because Roc uses [Dec] -## by default when there are no types specified. -## -## In contrast, suppose we use `f32` or `f64` for one of these numbers: -## -## wasItPrecise = 0.1f64 + 0.2 == 0.3 -## -## Here, `wasItPrecise` will be `False` because the entire calculation will have -## been done in a base-2 floating point calculation, which causes noticeable -## precision loss in this case. -## -## The floating-point numbers ([F32] and [F64]) also have three values which -## are not ordinary [finite numbers](https://en.wikipedia.org/wiki/Finite_number). -## They are: -## * ∞ ([infinity](https://en.wikipedia.org/wiki/Infinity)) -## * -∞ (negative infinity) -## * *NaN* ([not a number](https://en.wikipedia.org/wiki/NaN)) -## -## These values are different from ordinary numbers in that they only occur -## when a floating-point calculation encounters an error. For example: -## * Dividing a positive [F64] by `0.0` returns ∞. -## * Dividing a negative [F64] by `0.0` returns -∞. -## * Dividing a [F64] of `0.0` by `0.0` returns [*NaN*](Num.isNaN). -## -## These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) -## floating point standard. Because almost all modern processors are built to -## this standard, deviating from these rules has a significant performance -## cost! Since the most common reason to choose [F64] or [F32] over [Dec] is -## access to hardware-accelerated performance, Roc follows these rules exactly. -## -## There's no literal syntax for these error values, but you can check to see if -## you ended up with one of them by using [isNaN], [isFinite], and [isInfinite]. -## Whenever a function in this module could return one of these values, that -## possibility is noted in the function's documentation. -## -## ## Performance Notes -## -## On typical modern CPUs, performance is similar between [Dec], [F64], and [F32] -## for addition and subtraction. For example, [F32] and [F64] do addition using -## a single CPU floating-point addition instruction, which typically takes a -## few clock cycles to complete. In contrast, [Dec] does addition using a few -## CPU integer arithmetic instructions, each of which typically takes only one -## clock cycle to complete. Exact numbers will vary by CPU, but they should be -## similar overall. -## -## [Dec] is significantly slower for multiplication and division. It not only -## needs to do more arithmetic instructions than [F32] and [F64] do, but also -## those instructions typically take more clock cycles to complete. -## -## With [Num.sqrt] and trigonometry functions like [Num.cos], there is -## an even bigger performance difference. [F32] and [F64] can do these in a -## single instruction, whereas [Dec] needs entire custom procedures - which use -## loops and conditionals. If you need to do performance-critical trigonometry -## or square roots, either [F64] or [F32] is probably a better choice than the -## usual default choice of [Dec], despite the precision problems they bring. -Float a : Num [ @Fraction a ] +`F32` and `F64` have direct hardware support on common processors today. There is no hardware support +for fixed-point decimals, so under the hood, a `Dec` is an `I128`; operations on it perform +[base-10 fixed-point arithmetic](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) +with 18 decimal places of precision. +This means a `Dec` can represent whole numbers up to slightly over 170 +quintillion, along with 18 decimal places. (To be precise, it can store +numbers betwween `-170_141_183_460_469_231_731.687303715884105728` +and `170_141_183_460_469_231_731.687303715884105727`.) Why 18 +decimal places? It's the highest number of decimal places where you can still +convert any `U64] to a `Dec` without losing information. + +While the fixed-point `Dec` has a fixed range, the floating-point `F32` and `F64` do not. +Instead, outside of a certain range they start to lose precision instead of immediately overflowing +the way integers and `Dec` do. `F64` can represent [between 15 and 17 significant digits](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) before losing precision, whereas `F32` can only represent [between 6 and 9](https://en.wikipedia.org/wiki/Single-precision_floating-point_format#IEEE_754_single-precision_binary_floating-point_format:_binary32). + +There are some use cases where `F64` and `F32` can be better choices than `Dec` +despite their precision drawbacks. For example, in graphical applications they +can be a better choice for representing coordinates because they take up less memory, +various relevant calculations run faster, and decimal precision loss isn't as big a concern +when dealing with screen coordinates as it is when dealing with something like currency. + +### Num, Int, and Frac + +Some operations work on specific numeric types - such as `I64` or `Dec` - but operations support +multiple numeric types. For example, the `Num.abs` function works on any number, since you can +take the [absolute value](https://en.wikipedia.org/wiki/Absolute_value) of integers and fractions alike. +Its type is: + +```elm +abs : Num a -> Num a +``` + +This type says `abs` takes a number and then returns a number of the same type. That's because the +`Num` type is compatible with both integers and fractions. + +There's also an `Int` type which is only compatible with integers, and a `Frac` type which is only +compatible with fractions. For example: + +```elm +Num.xor : Int a, Int a -> Int a +``` + +```elm +Num.cos : Frac a -> Frac a +``` + +When you write a number literal in Roc, it has the type `Num *`. So you could call `Num.xor 1 1` +and also `Num.cos 1` and have them all work as expected; the number literal `1` has the type +`Num *`, which is compatible with the more constrained types `Int` and `Frac`. For the same reason, +you can pass number literals to functions expecting even more constrained types, like `I32` or `F64`. ## Interface modules From 2b5862067bdf71bd96c2e6a6a0790ccf24b05220 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 7 Jan 2022 22:20:53 -0500 Subject: [PATCH 134/541] fix typo --- TUTORIAL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TUTORIAL.md b/TUTORIAL.md index 4a7b3a3c61..40cd335ec4 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -1667,7 +1667,7 @@ you'd find a `*` in an open record. > a closed record consisting of all the fields in the closed record `User`, plus `a : Str` and `b : Str`. This function still returns the same record as it always did, it just needs to be annotated as -`User {}` now instead of just `User`, becuase the `User` type alias has a variable in it that must be +`User {}` now instead of just `User`, because the `User` type alias has a variable in it that must be specified. The third function might need to use a named type variable: From c910359fc1486d690117adefe6d490c1c82d52f3 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sat, 8 Jan 2022 14:42:54 +0100 Subject: [PATCH 135/541] typo --- TUTORIAL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TUTORIAL.md b/TUTORIAL.md index 40cd335ec4..0604fdf9ae 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -1888,7 +1888,7 @@ type before the `->` and another type after it; whenever you see a named value i the same type everywhere it appears in that scope. For this reason, any time you see a function that only runs a `when` on its only argument, and that `when` -includes a branch like `x -> x` or `other -> other`, the function's argument type and reurn type must necessarily +includes a branch like `x -> x` or `other -> other`, the function's argument type and return type must necessarily be equivalent. > **Note:** Just like with records, you can also replace the type variable in tag union types with a concrete type. From fd474528ebc77dc33836b24a890051618478e427 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sat, 8 Jan 2022 15:11:27 -0500 Subject: [PATCH 136/541] Unroll newtypes that are records --- cli/src/repl/eval.rs | 198 +++++++++++++---------------------------- cli/tests/repl_eval.rs | 9 +- 2 files changed, 71 insertions(+), 136 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 920f0de630..28c553d6ce 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -69,14 +69,25 @@ pub unsafe fn jit_to_ast<'a>( } } -// Unrolls tag unions that are newtypes (i.e. are singleton variants with one type argument). -// This is sometimes important in synchronizing `Content`s with `Layout`s, since `Layout`s will -// always unwrap newtypes and use the content of the underlying type. +enum NewtypeKind<'a> { + Tag(&'a TagName), + RecordField(&'a str), +} + +/// Unrolls types that are newtypes. These include +/// - Singleton tags with one type argument (e.g. `Container Str`) +/// - Records with exactly one field (e.g. `{ number: Nat }`) +/// +/// This is important in synchronizing `Content`s with `Layout`s, since `Layout`s will +/// always unwrap newtypes and use the content of the underlying type. +/// +/// The returned list of newtype containers is ordered by increasing depth. As an example, +/// `A ({b : C 123})` will have the unrolled list `[Tag(A), RecordField(b), Tag(C)]`. fn unroll_newtypes<'a>( env: &Env<'a, 'a>, mut content: &'a Content, -) -> (Vec<'a, &'a TagName>, &'a Content) { - let mut newtype_tags = Vec::with_capacity_in(1, env.arena); +) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) { + let mut newtype_containers = Vec::with_capacity_in(1, env.arena); loop { match content { Content::Structure(FlatType::TagUnion(tags, _)) @@ -86,26 +97,50 @@ fn unroll_newtypes<'a>( .unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION) .next() .unwrap(); - newtype_tags.push(tag_name); + newtype_containers.push(NewtypeKind::Tag(tag_name)); let var = vars[0]; content = env.subs.get_content_without_compacting(var); } - _ => return (newtype_tags, content), + Content::Structure(FlatType::Record(fields, _)) if fields.len() == 1 => { + let (label, field) = fields + .sorted_iterator(env.subs, Variable::EMPTY_RECORD) + .next() + .unwrap(); + newtype_containers.push(NewtypeKind::RecordField( + env.arena.alloc_str(label.as_str()), + )); + let field_var = *field.as_inner(); + content = env.subs.get_content_without_compacting(field_var); + } + _ => return (newtype_containers, content), } } } fn apply_newtypes<'a>( env: &Env<'a, '_>, - newtype_tags: Vec<'a, &'a TagName>, + newtype_containers: Vec<'a, NewtypeKind<'a>>, mut expr: Expr<'a>, ) -> Expr<'a> { - for tag_name in newtype_tags.into_iter().rev() { - let tag_expr = tag_name_to_expr(env, tag_name); - let loc_tag_expr = &*env.arena.alloc(Loc::at_zero(tag_expr)); - let loc_arg_expr = &*env.arena.alloc(Loc::at_zero(expr)); - let loc_arg_exprs = env.arena.alloc_slice_copy(&[loc_arg_expr]); - expr = Expr::Apply(loc_tag_expr, loc_arg_exprs, CalledVia::Space); + let arena = env.arena; + // Reverse order of what we receieve from `unroll_newtypes` since we want the deepest + // container applied first. + for container in newtype_containers.into_iter().rev() { + match container { + NewtypeKind::Tag(tag_name) => { + let tag_expr = tag_name_to_expr(env, tag_name); + let loc_tag_expr = &*arena.alloc(Loc::at_zero(tag_expr)); + let loc_arg_expr = &*arena.alloc(Loc::at_zero(expr)); + let loc_arg_exprs = arena.alloc_slice_copy(&[loc_arg_expr]); + expr = Expr::Apply(loc_tag_expr, loc_arg_exprs, CalledVia::Space); + } + NewtypeKind::RecordField(field_name) => { + let label = Loc::at_zero(*arena.alloc(field_name)); + let field_val = arena.alloc(Loc::at_zero(expr)); + let field = Loc::at_zero(AssignedField::RequiredValue(label, &[], field_val)); + expr = Expr::Record(Collection::with_items(&*arena.alloc([field]))) + } + } } expr } @@ -231,7 +266,7 @@ fn jit_to_ast_help<'a>( layout: &Layout<'a>, content: &'a Content, ) -> Result, ToAstProblem> { - let (newtype_tags, content) = unroll_newtypes(env, content); + let (newtype_containers, content) = unroll_newtypes(env, content); let content = unroll_aliases(env, content); let result = match layout { Layout::Builtin(Builtin::Bool) => Ok(run_jit_function!(lib, main_fn_name, bool, |num| { @@ -264,7 +299,6 @@ fn jit_to_ast_help<'a>( I64 => helper!(i64), I128 => helper!(i128), }; - dbg!(&result); Ok(result) } @@ -393,7 +427,7 @@ fn jit_to_ast_help<'a>( } Layout::LambdaSet(_) => Ok(OPAQUE_FUNCTION), }; - result.map(|e| apply_newtypes(env, newtype_tags, e)) + result.map(|e| apply_newtypes(env, newtype_containers, e)) } fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> { @@ -433,7 +467,7 @@ fn ptr_to_ast<'a>( }}; } - let (newtype_tags, content) = unroll_newtypes(env, content); + let (newtype_containers, content) = unroll_newtypes(env, content); let content = unroll_aliases(env, content); let expr = match (content, layout) { (Content::Structure(FlatType::Func(_, _, _)), _) @@ -686,7 +720,7 @@ fn ptr_to_ast<'a>( ); } }; - apply_newtypes(env, newtype_tags, expr) + apply_newtypes(env, newtype_containers, expr) } fn list_to_ast<'a>( @@ -720,18 +754,17 @@ fn list_to_ast<'a>( for index in 0..len { let offset_bytes = index * elem_size; let elem_ptr = unsafe { ptr.add(offset_bytes) }; - let loc_expr = &*arena.alloc(Loc { - value: ptr_to_ast( - env, - elem_ptr, - elem_layout, - WhenRecursive::Unreachable, - elem_content, - ), - region: Region::zero(), - }); + let (newtype_containers, elem_content) = unroll_newtypes(env, elem_content); + let expr = ptr_to_ast( + env, + elem_ptr, + elem_layout, + WhenRecursive::Unreachable, + elem_content, + ); + let expr = Loc::at_zero(apply_newtypes(env, newtype_containers, expr)); - output.push(loc_expr); + output.push(&*arena.alloc(expr)); } let output = output.into_bump_slice(); @@ -922,40 +955,6 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a match content { Structure(flat_type) => { match flat_type { - FlatType::Record(fields, _) => { - debug_assert_eq!(fields.len(), 1); - - let (label, field) = fields - .sorted_iterator(env.subs, Variable::EMPTY_RECORD) - .next() - .unwrap(); - - let loc_label = Loc { - value: &*arena.alloc_str(label.as_str()), - region: Region::zero(), - }; - - let assigned_field = { - // We may be multiple levels deep in nested tag unions - // and/or records (e.g. { a: { b: { c: True } } }), - // so we need to do this recursively on the field type. - let field_var = *field.as_inner(); - let field_content = env.subs.get_content_without_compacting(field_var); - let loc_expr = Loc { - value: bool_to_ast(env, value, field_content), - region: Region::zero(), - }; - - AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr)) - }; - - let loc_assigned_field = Loc { - value: assigned_field, - region: Region::zero(), - }; - - Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field]))) - } FlatType::TagUnion(tags, _) if tags.len() == 1 => { let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); @@ -1034,40 +1033,6 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> match content { Structure(flat_type) => { match flat_type { - FlatType::Record(fields, _) => { - debug_assert_eq!(fields.len(), 1); - - let (label, field) = fields - .sorted_iterator(env.subs, Variable::EMPTY_RECORD) - .next() - .unwrap(); - - let loc_label = Loc { - value: &*arena.alloc_str(label.as_str()), - region: Region::zero(), - }; - - let assigned_field = { - // We may be multiple levels deep in nested tag unions - // and/or records (e.g. { a: { b: { c: True } } }), - // so we need to do this recursively on the field type. - let field_var = *field.as_inner(); - let field_content = env.subs.get_content_without_compacting(field_var); - let loc_expr = Loc { - value: byte_to_ast(env, value, field_content), - region: Region::zero(), - }; - - AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr)) - }; - - let loc_assigned_field = Loc { - value: assigned_field, - region: Region::zero(), - }; - - Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field]))) - } FlatType::TagUnion(tags, _) if tags.len() == 1 => { let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); @@ -1150,43 +1115,6 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E Structure(flat_type) => { match flat_type { FlatType::Apply(Symbol::NUM_NUM, _) => num_expr, - FlatType::Record(fields, _) => { - // This was a single-field record that got unwrapped at runtime. - // Even if it was an i64 at runtime, we still need to report - // it as a record with the correct field name! - // Its type signature will tell us that. - debug_assert_eq!(fields.len(), 1); - - let (label, field) = fields - .sorted_iterator(env.subs, Variable::EMPTY_RECORD) - .next() - .unwrap(); - - let loc_label = Loc { - value: &*arena.alloc_str(label.as_str()), - region: Region::zero(), - }; - - let assigned_field = { - // We may be multiple levels deep in nested tag unions - // and/or records (e.g. { a: { b: { c: 5 } } }), - // so we need to do this recursively on the field type. - let field_var = *field.as_inner(); - let field_content = env.subs.get_content_without_compacting(field_var); - let loc_expr = Loc { - value: num_to_ast(env, num_expr, field_content), - region: Region::zero(), - }; - - AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr)) - }; - let loc_assigned_field = Loc { - value: assigned_field, - region: Region::zero(), - }; - - Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field]))) - } FlatType::TagUnion(tags, _) => { // This was a single-tag union that got unwrapped at runtime. debug_assert_eq!(tags.len(), 1); diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index 434ee8ac31..a33bc81d26 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -828,7 +828,6 @@ mod repl_eval { } #[test] - #[ignore = "TODO"] fn function_in_unwrapped_record() { expect_success( r#"{ adder: \x -> x + 1 }"#, @@ -844,6 +843,14 @@ mod repl_eval { ) } + #[test] + fn newtype_of_record_of_tag_of_record_of_tag() { + expect_success( + r#"A {b: C {d: 1}}"#, + r#"A { b: C { d: 1 } } : [ A { b : [ C { d : Num * } ]* } ]*"#, + ) + } + // #[test] // fn parse_problem() { // // can't find something that won't parse currently From 605cad1114518dc449baa462c350ed16e948a584 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sat, 8 Jan 2022 15:24:17 -0500 Subject: [PATCH 137/541] Reify commented-out tests --- cli/tests/repl_eval.rs | 63 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index a33bc81d26..b4d83b61d1 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -851,13 +851,58 @@ mod repl_eval { ) } - // #[test] - // fn parse_problem() { - // // can't find something that won't parse currently - // } - // - // #[test] - // fn mono_problem() { - // // can't produce a mono error (non-exhaustive pattern) yet - // } + #[test] + fn parse_problem() { + expect_failure( + "add m n = m + n", + indoc!( + r#" + ── ARGUMENTS BEFORE EQUALS ───────────────────────────────────────────────────── + + I am partway through parsing a definition, but I got stuck here: + + 1│ app "app" provides [ replOutput ] to "./platform" + 2│ + 3│ replOutput = + 4│ add m n = m + n + ^^^ + + Looks like you are trying to define a function. In roc, functions are + always written as a lambda, like increment = \n -> n + 1. + "# + ), + ); + } + + #[test] + fn mono_problem() { + expect_failure( + r#" + t : [A, B, C] + t = A + + when t is + A -> "a" + "#, + indoc!( + r#" + ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + + This when does not cover all the possibilities: + + 7│> when t is + 8│> A -> "a" + + Other possibilities include: + + B + C + + I would have to crash if I saw one of those! Add branches for them! + + + Enter an expression, or :help, or :exit/:q."# + ), + ); + } } From 19d8f08e632089c057d29dd50737bd3682573de4 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 8 Jan 2022 22:05:08 -0500 Subject: [PATCH 138/541] Explain multiple payload values for tags --- TUTORIAL.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TUTORIAL.md b/TUTORIAL.md index 0604fdf9ae..8800cdb6ae 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -546,6 +546,10 @@ This makes two changes to our earlier `stoplightColor` / `stoplightStr` example. Any tag can be given a payload like this. A payload doesn't have to be a string; we could also have said (for example) `Custom { r: 40, g: 60, b: 80 }` to specify an RGB color instead of a string. Then in our `when` we could have written `Custom record ->` and then after the `->` used `record.r`, `record.g`, and `record.b` to access the `40`, `60`, `80` values. We could also have written `Custom { r, g, b } ->` to *destructure* the record, and then accessed these `r`, `g`, and `b` defs after the `->` instead. +A tag can also have a payload with more than one value. Instead of `Custom { r: 40, g: 60, b: 80 }` we could +write `Custom 40 60 80`. If we did that, then instead of destructuring a record with `Custom { r, g, b } ->` +inside a `when`, we would write `Custom r g b ->` to destructure the values directly out of the payload. + We refer to whatever comes before a `->` in a `when` expression as a *pattern* - so for example, in the `Custom description -> description` branch, `Custom description` would be a pattern. In programming, using patterns in branching conditionals like `when` is known as [pattern matching](https://en.wikipedia.org/wiki/Pattern_matching). You may hear people say things like "let's pattern match on `Custom` here" as a way to From 737460a86fb457fc85cb8aa3eac34e398619e07d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 8 Jan 2022 22:23:36 -0500 Subject: [PATCH 139/541] Clarify record type variables --- TUTORIAL.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/TUTORIAL.md b/TUTORIAL.md index 8800cdb6ae..d98df23528 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -1546,11 +1546,17 @@ The `*` in the type `{ firstName : Str, lastName : Str }*` is what makes it an o This `*` is the *wildcard type* we saw earlier with empty lists. (An empty list has the type `List *`, in contrast to something like `List Str` which is a list of strings.) -This is because record types can optionally end in a type variable. If that variable is a `*`, then +This is because record types can optionally end in a type variable. Just like how we can have `List *` +or `List a -> List a`, we can also have `{ first : Str, last : Str }*` or +`{ first : Str, last : Str }a -> { first: Str, last : Str }a`. The differences are that in `List a`, +the type variable is required and appears with a space after `List`; in a record, the type variable +is optional, and appears (with no space) immediately after `}`. + +If the type variable in a record type is a `*` (such as in `{ first : Str, last : Str }*`), then it's an open record. If the type variable is missing, then it's a closed record. You can also specify -a closed type variable by putting a `{}` there (so for example, `{ email : Str }{}` is another way to write -`{ email : Str }`). In practice, it's basically always written without the `{}` on the end, but later on -we'll see a situation where putting types other than `*` in that spot can be useful. +a closed record by putting a `{}` as the type variable (so for example, `{ email : Str }{}` is another way to write +`{ email : Str }`). In practice, closed records are basically always written without the `{}` on the end, +but later on we'll see a situation where putting types other than `*` in that spot can be useful. ## Constrained Records @@ -1631,7 +1637,8 @@ This is a perfectly reasonable way to write all of these functions. However, I might decide that I really want the `isValid` function to take an open record - that is, a record with *at least* the fields of this `User` record, but possibly others as well. -Since open records have a type variable, in order to do this I'd need to add a +Since open records have a type variable (like `*` in `{ email : Str }` or `a` in +`{ email : Str }a -> { email : Str }a`), in order to do this I'd need to add a type variable to the `User` type alias: ```coffee @@ -1643,8 +1650,10 @@ User a : }a ``` -I can still write the same three functions, but now their types need to look different. +Notice that the `a` type variable appears not only in `User a` but also in `}a` at the end of the +record type! +Using `User a` type alias, I can still write the same three functions, but now their types need to look different. This is what the first one would look like: ```elm From 48d9c0c66a7623f7d008419ead5b546ca7c43383 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Sat, 8 Jan 2022 20:33:28 -0700 Subject: [PATCH 140/541] Fix potential typo --- TUTORIAL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TUTORIAL.md b/TUTORIAL.md index d98df23528..783a81f48a 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -1637,7 +1637,7 @@ This is a perfectly reasonable way to write all of these functions. However, I might decide that I really want the `isValid` function to take an open record - that is, a record with *at least* the fields of this `User` record, but possibly others as well. -Since open records have a type variable (like `*` in `{ email : Str }` or `a` in +Since open records have a type variable (like `*` in `{ email : Str }*` or `a` in `{ email : Str }a -> { email : Str }a`), in order to do this I'd need to add a type variable to the `User` type alias: From 9bbf98eb8e376ebd05c954d625511bdb717b2a53 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 9 Jan 2022 13:11:44 -0500 Subject: [PATCH 141/541] Explain field accessors in the tutorial --- TUTORIAL.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/TUTORIAL.md b/TUTORIAL.md index 783a81f48a..fb6a1a52ba 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -329,6 +329,21 @@ values - including other records, or even functions! { birds: 4, nestedRecord: { someFunction: (\arg -> arg + 1), name: "Sam" } } ``` +### Record shorthands + +Roc has a couple of shorthands you can use to express some record-related operations more concisely. + +Instead of writing `\record -> record.x` we can write `.x` and it will evaluate to the same thing: +a function that takes a record and returns its `x` field. You can do this with any field you want. +For example: + +```elm +returnFoo = .foo + +returnFoo { foo: "hi!", bar: "blah" } +# returns "hi!" +``` + Whenever we're setting a field to be a def that has the same name as the field - for example, `{ x: x }` - we can shorten it to just writing the name of the def alone - for example, `{ x }`. We can do this with as many fields as we like, e.g. From 7a6c6b675cace99922920bb8cacbc7428c98fb10 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 7 Jan 2022 19:46:21 +0000 Subject: [PATCH 142/541] Wasm: Store type signatures in serialized form --- compiler/gen_wasm/src/wasm_module/sections.rs | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index f67f51a1bd..b831ee3eff 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -108,35 +108,54 @@ impl<'a> Serialize for Signature<'a> { #[derive(Debug)] pub struct TypeSection<'a> { /// Private. See WasmModule::add_function_signature - signatures: Vec<'a, Signature<'a>>, + arena: &'a Bump, + bytes: Vec<'a, u8>, + offsets: Vec<'a, usize>, } impl<'a> TypeSection<'a> { pub fn new(arena: &'a Bump, capacity: usize) -> Self { TypeSection { - signatures: Vec::with_capacity_in(capacity, arena), + arena, + bytes: Vec::with_capacity_in(capacity * 4, arena), + offsets: Vec::with_capacity_in(capacity, arena), } } /// Find a matching signature or insert a new one. Return the index. pub fn insert(&mut self, signature: Signature<'a>) -> u32 { - // Using linear search because we need to preserve indices stored in - // the Function section. (Also for practical sizes it's fast) - let maybe_index = self.signatures.iter().position(|s| *s == signature); - match maybe_index { - Some(index) => index as u32, - None => { - let index = self.signatures.len(); - self.signatures.push(signature); - index as u32 + let mut sig_bytes = Vec::with_capacity_in(signature.param_types.len() + 4, self.arena); + signature.serialize(&mut sig_bytes); + + let sig_len = sig_bytes.len(); + let bytes_len = self.bytes.len(); + + for (i, offset) in self.offsets.iter().enumerate() { + let end = offset + sig_len; + if end > bytes_len { + break; + } + if &self.bytes[*offset..end] == sig_bytes.as_slice() { + return i as u32; } } + + let sig_id = self.offsets.len(); + self.offsets.push(bytes_len); + self.bytes.extend_from_slice(&sig_bytes); + + sig_id as u32 } } impl<'a> Serialize for TypeSection<'a> { fn serialize(&self, buffer: &mut T) { - serialize_vector_section(buffer, SectionId::Type, &self.signatures); + if !self.bytes.is_empty() { + let header_indices = write_section_header(buffer, SectionId::Type); + buffer.encode_u32(self.offsets.len() as u32); + buffer.append_slice(&self.bytes); + update_section_size(buffer, header_indices); + } } } From 743e14148c33ae7fccec9bd9e46a4e034244e0df Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 7 Jan 2022 20:01:35 +0000 Subject: [PATCH 143/541] Wasm: Store Function section as bytes and a count --- compiler/gen_wasm/src/wasm_module/sections.rs | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index b831ee3eff..07707e3bb8 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -84,6 +84,21 @@ fn serialize_vector_section( } } +/// Serialize a section that is stored as bytes and a count +fn serialize_bytes_section( + buffer: &mut B, + section_id: SectionId, + count: u32, + bytes: &[u8], +) { + if !bytes.is_empty() { + let header_indices = write_section_header(buffer, section_id); + buffer.encode_u32(count); + buffer.append_slice(bytes); + update_section_size(buffer, header_indices); + } +} + /******************************************************************* * * Type section @@ -150,12 +165,12 @@ impl<'a> TypeSection<'a> { impl<'a> Serialize for TypeSection<'a> { fn serialize(&self, buffer: &mut T) { - if !self.bytes.is_empty() { - let header_indices = write_section_header(buffer, SectionId::Type); - buffer.encode_u32(self.offsets.len() as u32); - buffer.append_slice(&self.bytes); - update_section_size(buffer, header_indices); - } + serialize_bytes_section( + buffer, + SectionId::Type, + self.offsets.len() as u32, + &self.bytes, + ); } } @@ -260,19 +275,26 @@ impl<'a> Serialize for ImportSection<'a> { #[derive(Debug)] pub struct FunctionSection<'a> { - pub signature_indices: Vec<'a, u32>, + pub count: u32, + pub bytes: Vec<'a, u8>, } impl<'a> FunctionSection<'a> { pub fn new(arena: &'a Bump, capacity: usize) -> Self { FunctionSection { - signature_indices: Vec::with_capacity_in(capacity, arena), + count: 0, + bytes: Vec::with_capacity_in(capacity, arena), } } + + fn add_sig(&mut self, sig_id: u32) { + self.bytes.encode_u32(sig_id); + self.count += 1; + } } impl<'a> Serialize for FunctionSection<'a> { fn serialize(&self, buffer: &mut T) { - serialize_vector_section(buffer, SectionId::Function, &self.signature_indices); + serialize_bytes_section(buffer, SectionId::Function, self.count, &self.bytes); } } @@ -608,7 +630,7 @@ impl<'a> WasmModule<'a> { /// Create entries in the Type and Function sections for a function signature pub fn add_function_signature(&mut self, signature: Signature<'a>) { let index = self.types.insert(signature); - self.function.signature_indices.push(index); + self.function.add_sig(index); } /// Serialize the module to bytes From b8f51fb1bb2a8c699d5f3c4caa7cbc7998f602f7 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 7 Jan 2022 20:22:32 +0000 Subject: [PATCH 144/541] Wasm: add preloaded_bytes and preloaded_count to Code section --- compiler/gen_wasm/src/backend.rs | 2 ++ compiler/gen_wasm/src/wasm_module/sections.rs | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index f2dd748291..f57fa05780 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -126,6 +126,8 @@ impl<'a> WasmBackend<'a> { start: (), element: (), code: CodeSection { + preloaded_count: 0, + preloaded_bytes: Vec::with_capacity_in(0, arena), code_builders: Vec::with_capacity_in(num_procs, arena), }, data: DataSection { diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 07707e3bb8..66f74d6c27 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -489,6 +489,8 @@ impl<'a> Serialize for ExportSection<'a> { #[derive(Debug)] pub struct CodeSection<'a> { + pub preloaded_count: u32, + pub preloaded_bytes: Vec<'a, u8>, pub code_builders: Vec<'a, CodeBuilder<'a>>, } @@ -500,7 +502,9 @@ impl<'a> CodeSection<'a> { relocations: &mut Vec<'a, RelocationEntry>, ) -> usize { let header_indices = write_section_header(buffer, SectionId::Code); - buffer.encode_u32(self.code_builders.len() as u32); + buffer.encode_u32(self.preloaded_count + self.code_builders.len() as u32); + + buffer.append_slice(&self.preloaded_bytes); for code_builder in self.code_builders.iter() { code_builder.serialize_with_relocs(buffer, relocations, header_indices.body_index); From 265171ad17c39efa8babf25d6bc3ca8f174d49d7 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 7 Jan 2022 11:26:38 +0000 Subject: [PATCH 145/541] Wasm: Make sure string constants are placed at a properly aligned offset --- compiler/gen_wasm/src/backend.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index f57fa05780..e391840d4b 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -30,8 +30,9 @@ use crate::wasm_module::{ LinkingSubSection, LocalId, Signature, SymInfo, ValueType, }; use crate::{ - copy_memory, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, DEBUG_LOG_SETTINGS, - MEMORY_NAME, PTR_SIZE, PTR_TYPE, STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME, + copy_memory, round_up_to_alignment, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, + DEBUG_LOG_SETTINGS, MEMORY_NAME, PTR_SIZE, PTR_TYPE, STACK_POINTER_GLOBAL_ID, + STACK_POINTER_NAME, }; /// The memory address where the constants data will be loaded during module instantiation. @@ -1465,11 +1466,15 @@ impl<'a> WasmBackend<'a> { None => { let const_segment_bytes = &mut self.module.data.segments[CONST_SEGMENT_INDEX].init; - // Store the string in the data section - // Prefix it with a special refcount value (treated as "infinity") - // The string's `elements` field points at the data after the refcount + // Pad the existing data segment to make sure the refcount and string are aligned + let aligned_len = round_up_to_alignment!(const_segment_bytes.len(), 4usize); + const_segment_bytes.resize(aligned_len, 0); + + // Prefix the string with "infinite" refcount let refcount_max_bytes: [u8; 4] = (REFCOUNT_MAX as i32).to_le_bytes(); const_segment_bytes.extend_from_slice(&refcount_max_bytes); + + // Add the string bytes to the data segment let elements_offset = const_segment_bytes.len() as u32; let elements_addr = elements_offset + CONST_SEGMENT_BASE_ADDR; const_segment_bytes.extend_from_slice(string.as_bytes()); From 6db7dbed1c41b08daab8e8839a2e2a2d95385e14 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 7 Jan 2022 21:47:27 +0000 Subject: [PATCH 146/541] Wasm: Store DataSection as bytes and segment count --- compiler/gen_wasm/src/backend.rs | 96 +++++++------------ compiler/gen_wasm/src/wasm_module/sections.rs | 29 +++++- 2 files changed, 56 insertions(+), 69 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index e391840d4b..118191dc73 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -40,9 +40,6 @@ use crate::{ /// Follow Emscripten's example by leaving 1kB unused (though 4 bytes would probably do!) const CONST_SEGMENT_BASE_ADDR: u32 = 1024; -/// Index of the data segment where we store constants -const CONST_SEGMENT_INDEX: usize = 0; - pub struct WasmBackend<'a> { env: &'a Env<'a>, interns: &'a mut Interns, @@ -50,7 +47,7 @@ pub struct WasmBackend<'a> { // Module-level data module: WasmModule<'a>, layout_ids: LayoutIds<'a>, - constant_sym_index_map: MutMap<&'a str, usize>, + next_constant_addr: u32, builtin_sym_index_map: MutMap<&'a str, usize>, proc_symbols: Vec<'a, (Symbol, u32)>, linker_symbols: Vec<'a, SymInfo>, @@ -107,13 +104,6 @@ impl<'a> WasmBackend<'a> { name: STACK_POINTER_NAME.to_string(), })); - let const_segment = DataSegment { - mode: DataMode::Active { - offset: ConstExpr::I32(CONST_SEGMENT_BASE_ADDR as i32), - }, - init: Vec::with_capacity_in(64, arena), - }; - let module = WasmModule { types: TypeSection::new(arena, num_procs), import: ImportSection::new(arena), @@ -131,9 +121,7 @@ impl<'a> WasmBackend<'a> { preloaded_bytes: Vec::with_capacity_in(0, arena), code_builders: Vec::with_capacity_in(num_procs, arena), }, - data: DataSection { - segments: bumpalo::vec![in arena; const_segment], - }, + data: DataSection::new(arena), linking: LinkingSection::new(arena), relocations: RelocationSection::new(arena, "reloc.CODE"), }; @@ -146,7 +134,7 @@ impl<'a> WasmBackend<'a> { module, layout_ids, - constant_sym_index_map: MutMap::default(), + next_constant_addr: CONST_SEGMENT_BASE_ADDR, builtin_sym_index_map: MutMap::default(), proc_symbols, linker_symbols, @@ -1445,61 +1433,41 @@ impl<'a> WasmBackend<'a> { sym: Symbol, layout: &Layout<'a>, ) -> (u32, u32) { - match self.constant_sym_index_map.get(string) { - Some(linker_sym_index) => { - // We've seen this string before. The linker metadata has a reference - // to its offset in the constants data segment. - let syminfo = &self.linker_symbols[*linker_sym_index]; - match syminfo { - SymInfo::Data(DataSymbol::Defined { segment_offset, .. }) => { - let elements_addr = *segment_offset + CONST_SEGMENT_BASE_ADDR; - (*linker_sym_index as u32, elements_addr) - } - _ => internal_error!( - "Compiler bug: Invalid linker symbol info for string {:?}:\n{:?}", - string, - syminfo - ), - } - } + // Place the segment at a 4-byte aligned offset + let segment_addr = round_up_to_alignment!(self.next_constant_addr, PTR_SIZE); + let elements_addr = segment_addr + PTR_SIZE; + let length_with_refcount = 4 + string.len(); + self.next_constant_addr = segment_addr + length_with_refcount as u32; - None => { - let const_segment_bytes = &mut self.module.data.segments[CONST_SEGMENT_INDEX].init; + let mut segment = DataSegment { + mode: DataMode::active_at(segment_addr), + init: Vec::with_capacity_in(length_with_refcount, self.env.arena), + }; - // Pad the existing data segment to make sure the refcount and string are aligned - let aligned_len = round_up_to_alignment!(const_segment_bytes.len(), 4usize); - const_segment_bytes.resize(aligned_len, 0); + // Prefix the string bytes with "infinite" refcount + let refcount_max_bytes: [u8; 4] = (REFCOUNT_MAX as i32).to_le_bytes(); + segment.init.extend_from_slice(&refcount_max_bytes); + segment.init.extend_from_slice(string.as_bytes()); - // Prefix the string with "infinite" refcount - let refcount_max_bytes: [u8; 4] = (REFCOUNT_MAX as i32).to_le_bytes(); - const_segment_bytes.extend_from_slice(&refcount_max_bytes); + let segment_index = self.module.data.append_segment(segment); - // Add the string bytes to the data segment - let elements_offset = const_segment_bytes.len() as u32; - let elements_addr = elements_offset + CONST_SEGMENT_BASE_ADDR; - const_segment_bytes.extend_from_slice(string.as_bytes()); + // Generate linker info + let name = self + .layout_ids + .get(sym, layout) + .to_symbol_string(sym, self.interns); + let linker_symbol = SymInfo::Data(DataSymbol::Defined { + flags: 0, + name, + segment_index, + segment_offset: 4, + size: string.len() as u32, + }); - // Generate linker info - // Just pick the symbol name from the first usage - let name = self - .layout_ids - .get(sym, layout) - .to_symbol_string(sym, self.interns); - let linker_symbol = SymInfo::Data(DataSymbol::Defined { - flags: 0, - name, - segment_index: CONST_SEGMENT_INDEX as u32, - segment_offset: elements_offset, - size: string.len() as u32, - }); + let linker_sym_index = self.linker_symbols.len(); + self.linker_symbols.push(linker_symbol); - let linker_sym_index = self.linker_symbols.len(); - self.constant_sym_index_map.insert(string, linker_sym_index); - self.linker_symbols.push(linker_symbol); - - (linker_sym_index as u32, elements_addr) - } - } + (linker_sym_index as u32, elements_addr) } fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) { diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 66f74d6c27..391e887c33 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -530,6 +530,14 @@ pub enum DataMode { Passive, } +impl DataMode { + pub fn active_at(offset: u32) -> Self { + DataMode::Active { + offset: ConstExpr::I32(offset as i32), + } + } +} + #[derive(Debug)] pub struct DataSegment<'a> { pub mode: DataMode, @@ -554,19 +562,30 @@ impl Serialize for DataSegment<'_> { #[derive(Debug)] pub struct DataSection<'a> { - pub segments: Vec<'a, DataSegment<'a>>, + segment_count: u32, + bytes: Vec<'a, u8>, } impl<'a> DataSection<'a> { - fn is_empty(&self) -> bool { - self.segments.is_empty() || self.segments.iter().all(|seg| seg.init.is_empty()) + pub fn new(arena: &'a Bump) -> Self { + DataSection { + segment_count: 0, + bytes: bumpalo::vec![in arena], + } + } + + pub fn append_segment(&mut self, segment: DataSegment<'a>) -> u32 { + let index = self.segment_count; + self.segment_count += 1; + segment.serialize(&mut self.bytes); + index } } impl Serialize for DataSection<'_> { fn serialize(&self, buffer: &mut T) { - if !self.is_empty() { - serialize_vector_section(buffer, SectionId::Data, &self.segments); + if !self.bytes.is_empty() { + serialize_bytes_section(buffer, SectionId::Data, self.segment_count, &self.bytes); } } } From 8cf82ae1b342b2fcd4389a9910541fec667afa7f Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 7 Jan 2022 23:30:39 +0000 Subject: [PATCH 147/541] Wasm: Delete old output files before compiling new ones --- compiler/test_gen/src/helpers/wasm.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 47973ea05d..a5c018bf16 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -156,6 +156,8 @@ fn run_linker( let wasm_build_dir: &Path = if let Some(src_hash) = build_dir_hash { debug_dir = format!("/tmp/roc/gen_wasm/{:016x}", src_hash); + std::fs::remove_file(format!("{}/app.o", debug_dir)).unwrap_or_else(|_| {}); + std::fs::remove_file(format!("{}/final.wasm", debug_dir)).unwrap_or_else(|_| {}); std::fs::create_dir_all(&debug_dir).unwrap(); println!( "Debug commands:\n\twasm-objdump -dx {}/app.o\n\twasm-objdump -dx {}/final.wasm", From 738434329eadec33aca8aa28651f64143f681ce8 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 7 Jan 2022 22:36:53 +0000 Subject: [PATCH 148/541] Wasm: refactor the model of the Linking section --- compiler/gen_wasm/src/backend.rs | 40 +++++----- compiler/gen_wasm/src/lib.rs | 2 +- compiler/gen_wasm/src/wasm_module/linking.rs | 79 +++++++++---------- compiler/gen_wasm/src/wasm_module/mod.rs | 2 +- compiler/gen_wasm/src/wasm_module/sections.rs | 6 +- .../src/helpers/wasm32_test_result.rs | 6 +- 6 files changed, 64 insertions(+), 71 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 118191dc73..854531f78a 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -26,8 +26,8 @@ use crate::wasm_module::sections::{ Import, ImportDesc, ImportSection, MemorySection, TypeSection, WasmModule, }; use crate::wasm_module::{ - code_builder, CodeBuilder, ConstExpr, Export, ExportType, Global, GlobalType, - LinkingSubSection, LocalId, Signature, SymInfo, ValueType, + code_builder, CodeBuilder, ConstExpr, Export, ExportType, Global, GlobalType, LocalId, + Signature, SymInfo, ValueType, }; use crate::{ copy_memory, round_up_to_alignment, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, @@ -50,7 +50,6 @@ pub struct WasmBackend<'a> { next_constant_addr: u32, builtin_sym_index_map: MutMap<&'a str, usize>, proc_symbols: Vec<'a, (Symbol, u32)>, - linker_symbols: Vec<'a, SymInfo>, helper_proc_gen: CodeGenHelp<'a>, // Function-level data @@ -103,6 +102,8 @@ impl<'a> WasmBackend<'a> { index: STACK_POINTER_GLOBAL_ID, name: STACK_POINTER_NAME.to_string(), })); + let mut linking = LinkingSection::new(arena); + linking.symbol_table = linker_symbols; let module = WasmModule { types: TypeSection::new(arena, num_procs), @@ -122,7 +123,7 @@ impl<'a> WasmBackend<'a> { code_builders: Vec::with_capacity_in(num_procs, arena), }, data: DataSection::new(arena), - linking: LinkingSection::new(arena), + linking, relocations: RelocationSection::new(arena, "reloc.CODE"), }; @@ -137,7 +138,6 @@ impl<'a> WasmBackend<'a> { next_constant_addr: CONST_SEGMENT_BASE_ADDR, builtin_sym_index_map: MutMap::default(), proc_symbols, - linker_symbols, helper_proc_gen, // Function-level data @@ -157,7 +157,7 @@ impl<'a> WasmBackend<'a> { fn register_helper_proc(&mut self, new_proc_info: (Symbol, ProcLayout<'a>)) { let (new_proc_sym, new_proc_layout) = new_proc_info; let wasm_fn_index = self.proc_symbols.len() as u32; - let linker_sym_index = self.linker_symbols.len() as u32; + let linker_sym_index = self.module.linking.symbol_table.len() as u32; let name = self .layout_ids @@ -165,17 +165,15 @@ impl<'a> WasmBackend<'a> { .to_symbol_string(new_proc_sym, self.interns); self.proc_symbols.push((new_proc_sym, linker_sym_index)); - self.linker_symbols - .push(SymInfo::Function(WasmObjectSymbol::Defined { - flags: 0, - index: wasm_fn_index, - name, - })); + let linker_symbol = SymInfo::Function(WasmObjectSymbol::Defined { + flags: 0, + index: wasm_fn_index, + name, + }); + self.module.linking.symbol_table.push(linker_symbol); } - pub fn finalize_module(mut self) -> WasmModule<'a> { - let symbol_table = LinkingSubSection::SymbolTable(self.linker_symbols); - self.module.linking.subsections.push(symbol_table); + pub fn into_module(self) -> WasmModule<'a> { self.module } @@ -1464,8 +1462,8 @@ impl<'a> WasmBackend<'a> { size: string.len() as u32, }); - let linker_sym_index = self.linker_symbols.len(); - self.linker_symbols.push(linker_symbol); + let linker_sym_index = self.module.linking.symbol_table.len(); + self.module.linking.symbol_table.push(linker_symbol); (linker_sym_index as u32, elements_addr) } @@ -1518,7 +1516,7 @@ impl<'a> WasmBackend<'a> { let has_return_val = ret_type.is_some(); let (fn_index, linker_symbol_index) = match self.builtin_sym_index_map.get(name) { - Some(sym_idx) => match &self.linker_symbols[*sym_idx] { + Some(sym_idx) => match &self.module.linking.symbol_table[*sym_idx] { SymInfo::Function(WasmObjectSymbol::Imported { index, .. }) => { (*index, *sym_idx as u32) } @@ -1543,12 +1541,12 @@ impl<'a> WasmBackend<'a> { self.module.import.entries.push(import); // Provide symbol information for the linker - let sym_idx = self.linker_symbols.len(); + let sym_idx = self.module.linking.symbol_table.len(); let sym_info = SymInfo::Function(WasmObjectSymbol::Imported { flags: WASM_SYM_UNDEFINED, index: import_index, }); - self.linker_symbols.push(sym_info); + self.module.linking.symbol_table.push(sym_info); // Remember that we have created all of this data, and don't need to do it again self.builtin_sym_index_map.insert(name, sym_idx); @@ -1568,7 +1566,7 @@ impl<'a> WasmBackend<'a> { /// } fn _debug_current_proc_is(&self, linker_name: &'static str) -> bool { let (_, linker_sym_index) = self.proc_symbols[self.debug_current_proc_index]; - let sym_info = &self.linker_symbols[linker_sym_index as usize]; + let sym_info = &self.module.linking.symbol_table[linker_sym_index as usize]; match sym_info { SymInfo::Function(WasmObjectSymbol::Defined { name, .. }) => name == linker_name, _ => false, diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 2c3ac8479c..55c6779464 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -131,7 +131,7 @@ pub fn build_module_help<'a>( backend.build_proc(proc); } - let module = backend.finalize_module(); + let module = backend.into_module(); Ok((module, main_fn_index.unwrap())) } diff --git a/compiler/gen_wasm/src/wasm_module/linking.rs b/compiler/gen_wasm/src/wasm_module/linking.rs index a8e62cceeb..4e14b12f1e 100644 --- a/compiler/gen_wasm/src/wasm_module/linking.rs +++ b/compiler/gen_wasm/src/wasm_module/linking.rs @@ -1,6 +1,5 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; -use roc_reporting::internal_error; use super::sections::{update_section_size, write_custom_section_header}; use super::serialize::{SerialBuffer, Serialize}; @@ -183,8 +182,12 @@ pub struct LinkingSegment { } impl Serialize for LinkingSegment { - fn serialize(&self, _buffer: &mut T) { - todo!(); + fn serialize(&self, buffer: &mut T) { + buffer.encode_u32(self.name.len() as u32); + buffer.append_slice(self.name.as_bytes()); + let align_bytes_pow2 = self.alignment as u32; + buffer.encode_u32(align_bytes_pow2); + buffer.encode_u32(self.flags); } } @@ -418,35 +421,25 @@ impl Serialize for SymInfo { // Linking subsections //---------------------------------------------------------------- +#[repr(u8)] #[derive(Debug)] -pub enum LinkingSubSection<'a> { - /// Extra metadata about the data segments. - SegmentInfo(Vec<'a, LinkingSegment>), - /// Specifies a list of constructor functions to be called at startup. - /// These constructors will be called in priority order after memory has been initialized. - InitFuncs(Vec<'a, LinkingInitFunc>), - /// Specifies the COMDAT groups of associated linking objects, which are linked only once and all together. - ComdatInfo(Vec<'a, LinkingComdat<'a>>), - /// Specifies extra information about the symbols present in the module. - SymbolTable(Vec<'a, SymInfo>), +enum SubSectionId { + SegmentInfo = 5, + InitFuncs = 6, + ComdatInfo = 7, + SymbolTable = 8, } -impl<'a> Serialize for LinkingSubSection<'a> { - fn serialize(&self, buffer: &mut T) { - buffer.append_u8(match self { - Self::SegmentInfo(_) => 5, - Self::InitFuncs(_) => 6, - Self::ComdatInfo(_) => 7, - Self::SymbolTable(_) => 8, - }); +fn serialize_subsection<'a, I: Serialize, T: SerialBuffer>( + buffer: &mut T, + id: SubSectionId, + items: &[I], +) { + if !items.is_empty() { + buffer.append_u8(id as u8); let payload_len_index = buffer.reserve_padded_u32(); let payload_start_index = buffer.size(); - match self { - Self::SegmentInfo(items) => items.serialize(buffer), - Self::InitFuncs(items) => items.serialize(buffer), - Self::ComdatInfo(items) => items.serialize(buffer), - Self::SymbolTable(items) => items.serialize(buffer), - } + items.serialize(buffer); buffer.overwrite_padded_u32( payload_len_index, (buffer.size() - payload_start_index) as u32, @@ -460,35 +453,39 @@ impl<'a> Serialize for LinkingSubSection<'a> { const LINKING_VERSION: u8 = 2; +/// The spec describes this in very weird way, so we're doing something saner. +/// They call it an "array" of subsections with different variants, BUT this "array" +/// has an implicit length, and none of the items can be repeated, so a struct is better. +/// No point writing code to "find" the symbol table, when we know there's exactly one. #[derive(Debug)] pub struct LinkingSection<'a> { - pub subsections: Vec<'a, LinkingSubSection<'a>>, + pub symbol_table: Vec<'a, SymInfo>, + pub segment_info: Vec<'a, LinkingSegment>, + pub init_funcs: Vec<'a, LinkingInitFunc>, + pub comdat_info: Vec<'a, LinkingComdat<'a>>, } impl<'a> LinkingSection<'a> { pub fn new(arena: &'a Bump) -> Self { LinkingSection { - subsections: Vec::with_capacity_in(1, arena), + symbol_table: Vec::with_capacity_in(16, arena), + segment_info: Vec::with_capacity_in(16, arena), + init_funcs: Vec::with_capacity_in(0, arena), + comdat_info: Vec::with_capacity_in(0, arena), } } - - pub fn symbol_table_mut(&mut self) -> &mut Vec<'a, SymInfo> { - for sub in self.subsections.iter_mut() { - if let LinkingSubSection::SymbolTable(syminfos) = sub { - return syminfos; - } - } - internal_error!("Symbol table not found"); - } } impl<'a> Serialize for LinkingSection<'a> { fn serialize(&self, buffer: &mut T) { let header_indices = write_custom_section_header(buffer, "linking"); buffer.append_u8(LINKING_VERSION); - for subsection in self.subsections.iter() { - subsection.serialize(buffer); - } + + serialize_subsection(buffer, SubSectionId::SymbolTable, &self.symbol_table); + serialize_subsection(buffer, SubSectionId::SegmentInfo, &self.segment_info); + serialize_subsection(buffer, SubSectionId::InitFuncs, &self.init_funcs); + serialize_subsection(buffer, SubSectionId::ComdatInfo, &self.comdat_info); + update_section_size(buffer, header_indices); } } diff --git a/compiler/gen_wasm/src/wasm_module/mod.rs b/compiler/gen_wasm/src/wasm_module/mod.rs index 5ff36428ed..7c643f97b3 100644 --- a/compiler/gen_wasm/src/wasm_module/mod.rs +++ b/compiler/gen_wasm/src/wasm_module/mod.rs @@ -5,5 +5,5 @@ pub mod sections; pub mod serialize; pub use code_builder::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState}; -pub use linking::{LinkingSubSection, SymInfo}; +pub use linking::SymInfo; pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature, WasmModule}; diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 391e887c33..9823f3c144 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -728,13 +728,11 @@ impl<'a> WasmModule<'a> { code_section_body_index: usize, n_imported_fns: u32, ) { - let symbol_table = self.linking.symbol_table_mut(); - // Lookup vector of symbol index to new function index - let mut new_index_lookup = std::vec::Vec::with_capacity(symbol_table.len()); + let mut new_index_lookup = std::vec::Vec::with_capacity(self.linking.symbol_table.len()); // Modify symbol table entries and fill the lookup vector - for sym_info in symbol_table.iter_mut() { + for sym_info in self.linking.symbol_table.iter_mut() { match sym_info { SymInfo::Function(WasmObjectSymbol::Defined { index, .. }) => { let new_fn_index = *index + n_imported_fns; diff --git a/compiler/test_gen/src/helpers/wasm32_test_result.rs b/compiler/test_gen/src/helpers/wasm32_test_result.rs index 316ef31bde..bac8dc71e4 100644 --- a/compiler/test_gen/src/helpers/wasm32_test_result.rs +++ b/compiler/test_gen/src/helpers/wasm32_test_result.rs @@ -27,12 +27,12 @@ pub trait Wasm32TestResult { index, }); - let symbol_table = module.linking.symbol_table_mut(); - symbol_table.push(SymInfo::Function(WasmObjectSymbol::Defined { + let linker_symbol = SymInfo::Function(WasmObjectSymbol::Defined { flags: 0, index, name: wrapper_name.to_string(), - })); + }); + module.linking.symbol_table.push(linker_symbol); let mut code_builder = CodeBuilder::new(arena); Self::build_wrapper_body(&mut code_builder, main_function_index); From a1f737d6d8ccf040c35ef98af3ee4db38d7adea0 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 7 Jan 2022 23:46:25 +0000 Subject: [PATCH 149/541] Wasm: Create OpaqueSection for sections that may be used in builtins but not by us --- compiler/gen_wasm/src/backend.rs | 8 ++--- compiler/gen_wasm/src/wasm_module/sections.rs | 36 +++++++++++++++---- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 854531f78a..24a3b9c301 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -23,7 +23,7 @@ use crate::wasm_module::linking::{ }; use crate::wasm_module::sections::{ CodeSection, DataMode, DataSection, DataSegment, ExportSection, FunctionSection, GlobalSection, - Import, ImportDesc, ImportSection, MemorySection, TypeSection, WasmModule, + Import, ImportDesc, ImportSection, MemorySection, OpaqueSection, TypeSection, WasmModule, }; use crate::wasm_module::{ code_builder, CodeBuilder, ConstExpr, Export, ExportType, Global, GlobalType, LocalId, @@ -109,14 +109,14 @@ impl<'a> WasmBackend<'a> { types: TypeSection::new(arena, num_procs), import: ImportSection::new(arena), function: FunctionSection::new(arena, num_procs), - table: (), + table: OpaqueSection::default(), memory: MemorySection::new(MEMORY_INIT_SIZE), global: GlobalSection { entries: bumpalo::vec![in arena; stack_pointer], }, export: ExportSection { entries: exports }, - start: (), - element: (), + start: OpaqueSection::default(), + element: OpaqueSection::default(), code: CodeSection { preloaded_count: 0, preloaded_bytes: Vec::with_capacity_in(0, arena), diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 9823f3c144..bc8b5dc363 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -627,20 +627,44 @@ impl SectionCounter { } } +/// A Wasm module section that we don't use for Roc code, +/// but may be present in a preloaded binary +#[derive(Debug)] +pub struct OpaqueSection<'a> { + bytes: &'a [u8], +} + +impl<'a> OpaqueSection<'a> { + pub fn new(bytes: &'a [u8]) -> Self { + Self { bytes } + } +} + +impl<'a> Default for OpaqueSection<'a> { + fn default() -> Self { + Self { bytes: &[] } + } +} + +impl Serialize for OpaqueSection<'_> { + fn serialize(&self, buffer: &mut T) { + if !self.bytes.is_empty() { + buffer.append_slice(&self.bytes); + } + } +} + #[derive(Debug)] pub struct WasmModule<'a> { pub types: TypeSection<'a>, pub import: ImportSection<'a>, pub function: FunctionSection<'a>, - /// Dummy placeholder for tables (used for function pointers and host references) - pub table: (), + pub table: OpaqueSection<'a>, pub memory: MemorySection, pub global: GlobalSection<'a>, pub export: ExportSection<'a>, - /// Dummy placeholder for start function. In Roc, this would be part of the platform. - pub start: (), - /// Dummy placeholder for table elements. Roc does not use tables. - pub element: (), + pub start: OpaqueSection<'a>, + pub element: OpaqueSection<'a>, pub code: CodeSection<'a>, pub data: DataSection<'a>, pub linking: LinkingSection<'a>, From c89f74f7e8a4fd76e18dd12b5f8660f3378af662 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 7 Jan 2022 23:53:06 +0000 Subject: [PATCH 150/541] Wasm: clippy fixes --- compiler/gen_wasm/src/wasm_module/linking.rs | 2 +- compiler/gen_wasm/src/wasm_module/sections.rs | 11 ++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/linking.rs b/compiler/gen_wasm/src/wasm_module/linking.rs index 4e14b12f1e..048ede0418 100644 --- a/compiler/gen_wasm/src/wasm_module/linking.rs +++ b/compiler/gen_wasm/src/wasm_module/linking.rs @@ -430,7 +430,7 @@ enum SubSectionId { SymbolTable = 8, } -fn serialize_subsection<'a, I: Serialize, T: SerialBuffer>( +fn serialize_subsection( buffer: &mut T, id: SubSectionId, items: &[I], diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index bc8b5dc363..b418a659eb 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -629,7 +629,7 @@ impl SectionCounter { /// A Wasm module section that we don't use for Roc code, /// but may be present in a preloaded binary -#[derive(Debug)] +#[derive(Debug, Default)] pub struct OpaqueSection<'a> { bytes: &'a [u8], } @@ -640,16 +640,10 @@ impl<'a> OpaqueSection<'a> { } } -impl<'a> Default for OpaqueSection<'a> { - fn default() -> Self { - Self { bytes: &[] } - } -} - impl Serialize for OpaqueSection<'_> { fn serialize(&self, buffer: &mut T) { if !self.bytes.is_empty() { - buffer.append_slice(&self.bytes); + buffer.append_slice(self.bytes); } } } @@ -682,7 +676,6 @@ impl<'a> WasmModule<'a> { /// Serialize the module to bytes /// (Mutates some data related to linking) - #[allow(clippy::unit_arg)] pub fn serialize_mut(&mut self, buffer: &mut T) { buffer.append_u8(0); buffer.append_slice("asm".as_bytes()); From 59278a02d6427e1f954327d9565bad62fbe7b2cd Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 8 Jan 2022 00:05:59 +0000 Subject: [PATCH 151/541] Wasm: Create a LinkingSegment to keep string data aligned correctly --- compiler/gen_wasm/src/backend.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 24a3b9c301..1c8dbdf1c4 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -18,8 +18,8 @@ use crate::layout::{CallConv, ReturnMethod, StackMemoryFormat, WasmLayout}; use crate::low_level::{dispatch_low_level, LowlevelBuildResult}; use crate::storage::{StackMemoryLocation, Storage, StoredValue, StoredValueKind}; use crate::wasm_module::linking::{ - DataSymbol, LinkingSection, RelocationSection, WasmObjectSymbol, WASM_SYM_BINDING_WEAK, - WASM_SYM_UNDEFINED, + DataSymbol, LinkingSection, LinkingSegment, RelocationSection, WasmObjectSymbol, + WASM_SYM_BINDING_WEAK, WASM_SYM_UNDEFINED, }; use crate::wasm_module::sections::{ CodeSection, DataMode, DataSection, DataSegment, ExportSection, FunctionSection, GlobalSection, @@ -1403,7 +1403,7 @@ impl<'a> WasmBackend<'a> { self.code_builder.i64_store(Align::Bytes4, offset); } else { let (linker_sym_index, elements_addr) = - self.lookup_string_constant(string, sym, layout); + self.create_string_constant(string, sym, layout); self.code_builder.get_local(local_id); self.code_builder @@ -1423,9 +1423,9 @@ impl<'a> WasmBackend<'a> { }; } - /// Look up a string constant in our internal data structures + /// Create a string constant in the module data section /// Return the data we need for code gen: linker symbol index and memory address - fn lookup_string_constant( + fn create_string_constant( &mut self, string: &'a str, sym: Symbol, @@ -1449,19 +1449,27 @@ impl<'a> WasmBackend<'a> { let segment_index = self.module.data.append_segment(segment); - // Generate linker info + // Generate linker symbol let name = self .layout_ids .get(sym, layout) .to_symbol_string(sym, self.interns); + let linker_symbol = SymInfo::Data(DataSymbol::Defined { flags: 0, - name, + name: name.clone(), segment_index, segment_offset: 4, size: string.len() as u32, }); + // Ensure the linker keeps the segment aligned when relocating it + self.module.linking.segment_info.push(LinkingSegment { + name, + alignment: Align::Bytes4, + flags: 0, + }); + let linker_sym_index = self.module.linking.symbol_table.len(); self.module.linking.symbol_table.push(linker_symbol); From fc677e8d58e60267ccf6c7e7f71f0d22c8651927 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 8 Jan 2022 12:02:20 +0000 Subject: [PATCH 152/541] Wasm: add preloading logic for Types section --- compiler/gen_wasm/src/wasm_module/sections.rs | 72 ++++++++++++++++++- .../gen_wasm/src/wasm_module/serialize.rs | 25 ++++++- compiler/test_gen/src/helpers/wasm.rs | 2 + 3 files changed, 96 insertions(+), 3 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index b418a659eb..5a143b487b 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -5,7 +5,7 @@ use super::linking::{ IndexRelocType, LinkingSection, RelocationEntry, RelocationSection, SymInfo, WasmObjectSymbol, }; use super::opcodes::OpCode; -use super::serialize::{SerialBuffer, Serialize}; +use super::serialize::{decode_u32_or_panic, SerialBuffer, Serialize}; use super::{CodeBuilder, ValueType}; /******************************************************************* @@ -112,9 +112,13 @@ pub struct Signature<'a> { pub ret_type: Option, } +impl Signature<'_> { + pub const SEPARATOR: u8 = 0x60; +} + impl<'a> Serialize for Signature<'a> { fn serialize(&self, buffer: &mut T) { - buffer.append_u8(0x60); + buffer.append_u8(Self::SEPARATOR); self.param_types.serialize(buffer); self.ret_type.serialize(buffer); } @@ -161,6 +165,45 @@ impl<'a> TypeSection<'a> { sig_id as u32 } + + pub fn preload(arena: &'a Bump, section_body: &[u8]) -> Self { + if section_body.is_empty() { + return TypeSection { + arena, + bytes: Vec::new_in(arena), + offsets: Vec::new_in(arena), + }; + } + + let (count, content_offset) = decode_u32_or_panic(section_body); + + let mut bytes = Vec::with_capacity_in(section_body.len() * 2, arena); + bytes.extend_from_slice(§ion_body[content_offset..]); + + let mut offsets = Vec::with_capacity_in((count * 2) as usize, arena); + + let mut i = 0; + while i < bytes.len() { + offsets.push(i); + + let sep = bytes[i]; + debug_assert!(sep == Signature::SEPARATOR); + i += 1; + + let (n_params, n_params_size) = decode_u32_or_panic(&bytes[i..]); + i += n_params_size; // skip over the array length that we just decoded + i += n_params as usize; // skip over one byte per param type + + let n_return_values = bytes[i]; + i += 1 + n_return_values as usize; + } + + TypeSection { + arena, + bytes, + offsets, + } + } } impl<'a> Serialize for TypeSection<'a> { @@ -781,3 +824,28 @@ impl<'a> WasmModule<'a> { } } } + +/// Assertion to run on the generated Wasm module in every test +#[cfg(debug_assertions)] +pub fn test_assert_preload<'a>(arena: &'a Bump, wasm_module: &WasmModule<'a>) { + test_assert_types_preload(arena, &wasm_module.types); +} + +#[cfg(debug_assertions)] +fn test_assert_types_preload<'a>(arena: &'a Bump, original: &TypeSection<'a>) { + // Serialize the Type section that we built from Roc code + let mut original_serialized = Vec::with_capacity_in(original.bytes.len() + 10, arena); + original.serialize(&mut original_serialized); + + debug_assert!(original_serialized[0] == SectionId::Type as u8); + + // Reconstruct a new TypeSection by "pre-loading" the bytes + let body = &original_serialized[6..]; + + let preloaded = TypeSection::preload(arena, body); + + let mut preloaded_serialized = Vec::with_capacity_in(original.bytes.len() + 10, arena); + preloaded.serialize(&mut preloaded_serialized); + + debug_assert_eq!(original_serialized, preloaded_serialized); +} diff --git a/compiler/gen_wasm/src/wasm_module/serialize.rs b/compiler/gen_wasm/src/wasm_module/serialize.rs index 9445d8c30b..5aa7665062 100644 --- a/compiler/gen_wasm/src/wasm_module/serialize.rs +++ b/compiler/gen_wasm/src/wasm_module/serialize.rs @@ -1,6 +1,7 @@ -use std::fmt::Debug; +use std::{fmt::Debug, iter::FromIterator}; use bumpalo::collections::vec::Vec; +use roc_reporting::internal_error; pub trait Serialize { fn serialize(&self, buffer: &mut T); @@ -231,6 +232,28 @@ impl<'a> SerialBuffer for Vec<'a, u8> { } } +/// Decode an unsigned 32-bit integer from the provided buffer in LEB-128 format +/// Return the integer itself and the offset after it ends +pub fn decode_u32(bytes: &[u8]) -> Result<(u32, usize), String> { + let mut value = 0; + let mut shift = 0; + for (i, byte) in bytes.iter().take(5).enumerate() { + value += ((byte & 0x7f) as u32) << shift; + if (byte & 0x80) == 0 { + return Ok((value, i + 1)); + } + shift += 7; + } + Err(format!( + "Failed to decode u32 as LEB-128 from bytes: {:2x?}", + std::vec::Vec::from_iter(bytes.iter().take(5)) + )) +} + +pub fn decode_u32_or_panic(bytes: &[u8]) -> (u32, usize) { + decode_u32(bytes).unwrap_or_else(|e| internal_error!("{}", e)) +} + #[cfg(test)] mod tests { use super::*; diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index a5c018bf16..7998621dd4 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -138,6 +138,8 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( T::insert_test_wrapper(arena, &mut wasm_module, TEST_WRAPPER_NAME, main_fn_index); + roc_gen_wasm::wasm_module::sections::test_assert_preload(arena, &wasm_module); + let needs_linking = !wasm_module.import.entries.is_empty(); let mut app_module_bytes = std::vec::Vec::with_capacity(4096); From 8f73b722ff5a02192914c65c238ad95a059bf813 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 8 Jan 2022 12:56:13 +0000 Subject: [PATCH 153/541] Wasm: fix release build --- compiler/gen_wasm/src/wasm_module/sections.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 5a143b487b..f97233ebd6 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -826,12 +826,10 @@ impl<'a> WasmModule<'a> { } /// Assertion to run on the generated Wasm module in every test -#[cfg(debug_assertions)] pub fn test_assert_preload<'a>(arena: &'a Bump, wasm_module: &WasmModule<'a>) { test_assert_types_preload(arena, &wasm_module.types); } -#[cfg(debug_assertions)] fn test_assert_types_preload<'a>(arena: &'a Bump, original: &TypeSection<'a>) { // Serialize the Type section that we built from Roc code let mut original_serialized = Vec::with_capacity_in(original.bytes.len() + 10, arena); @@ -839,9 +837,8 @@ fn test_assert_types_preload<'a>(arena: &'a Bump, original: &TypeSection<'a>) { debug_assert!(original_serialized[0] == SectionId::Type as u8); - // Reconstruct a new TypeSection by "pre-loading" the bytes + // Reconstruct a new TypeSection by "pre-loading" the bytes of the original! let body = &original_serialized[6..]; - let preloaded = TypeSection::preload(arena, body); let mut preloaded_serialized = Vec::with_capacity_in(original.bytes.len() + 10, arena); From c5653951a463f062c63e1e446e7919c315c27adf Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 8 Jan 2022 13:48:18 +0000 Subject: [PATCH 154/541] Wasm: improve the TypeSection preload test --- compiler/gen_wasm/src/wasm_module/sections.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index f97233ebd6..4b2108ea00 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -841,8 +841,6 @@ fn test_assert_types_preload<'a>(arena: &'a Bump, original: &TypeSection<'a>) { let body = &original_serialized[6..]; let preloaded = TypeSection::preload(arena, body); - let mut preloaded_serialized = Vec::with_capacity_in(original.bytes.len() + 10, arena); - preloaded.serialize(&mut preloaded_serialized); - - debug_assert_eq!(original_serialized, preloaded_serialized); + debug_assert_eq!(original.offsets, preloaded.offsets); + debug_assert_eq!(original.bytes, preloaded.bytes); } From abe42781d51741d033195ab7af1782c18f5888f7 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 10 Jan 2022 18:58:48 -0500 Subject: [PATCH 155/541] Use unsigned LLVM intrinsic arithmetic for unsigned integers Closes #2331 --- compiler/builtins/src/bitcode.rs | 30 +++++++++------- compiler/gen_llvm/src/llvm/build.rs | 27 ++++++++------- compiler/test_gen/src/gen_num.rs | 54 +++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 25 deletions(-) diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 8492d6069b..e1a2a9f8f4 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -201,24 +201,28 @@ macro_rules! float_intrinsic { #[macro_export] macro_rules! int_intrinsic { - ($name:literal) => {{ + ($signed_name:literal, $unsigned_name:literal) => {{ let mut output = IntrinsicName::default(); - // These are LLVM types which don't include - // u64 for example - output.options[4] = concat!($name, ".i8"); - output.options[5] = concat!($name, ".i16"); - output.options[6] = concat!($name, ".i32"); - output.options[7] = concat!($name, ".i64"); - output.options[8] = concat!($name, ".i128"); - output.options[9] = concat!($name, ".i8"); - output.options[10] = concat!($name, ".i16"); - output.options[11] = concat!($name, ".i32"); - output.options[12] = concat!($name, ".i64"); - output.options[13] = concat!($name, ".i128"); + // The indeces align with the `Index` impl for `IntrinsicName`. + output.options[4] = concat!($unsigned_name, ".i8"); + output.options[5] = concat!($unsigned_name, ".i16"); + output.options[6] = concat!($unsigned_name, ".i32"); + output.options[7] = concat!($unsigned_name, ".i64"); + output.options[8] = concat!($unsigned_name, ".i128"); + + output.options[9] = concat!($signed_name, ".i8"); + output.options[10] = concat!($signed_name, ".i16"); + output.options[11] = concat!($signed_name, ".i32"); + output.options[12] = concat!($signed_name, ".i64"); + output.options[13] = concat!($signed_name, ".i128"); output }}; + + ($name:literal) => { + int_intrinsic!($name, $name) + }; } pub const NUM_ASIN: IntrinsicName = float_intrinsic!("roc_builtins.num.asin"); diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 01012f3137..30dedabf97 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -562,19 +562,19 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { }); add_float_intrinsic(ctx, module, &LLVM_FLOOR, |t| t.fn_type(&[t.into()], false)); - add_int_intrinsic(ctx, module, &LLVM_SADD_WITH_OVERFLOW, |t| { + add_int_intrinsic(ctx, module, &LLVM_ADD_WITH_OVERFLOW, |t| { let fields = [t.into(), i1_type.into()]; ctx.struct_type(&fields, false) .fn_type(&[t.into(), t.into()], false) }); - add_int_intrinsic(ctx, module, &LLVM_SSUB_WITH_OVERFLOW, |t| { + add_int_intrinsic(ctx, module, &LLVM_SUB_WITH_OVERFLOW, |t| { let fields = [t.into(), i1_type.into()]; ctx.struct_type(&fields, false) .fn_type(&[t.into(), t.into()], false) }); - add_int_intrinsic(ctx, module, &LLVM_SMUL_WITH_OVERFLOW, |t| { + add_int_intrinsic(ctx, module, &LLVM_MUL_WITH_OVERFLOW, |t| { let fields = [t.into(), i1_type.into()]; ctx.struct_type(&fields, false) .fn_type(&[t.into(), t.into()], false) @@ -602,9 +602,12 @@ static LLVM_STACK_SAVE: &str = "llvm.stacksave"; static LLVM_SETJMP: &str = "llvm.eh.sjlj.setjmp"; pub static LLVM_LONGJMP: &str = "llvm.eh.sjlj.longjmp"; -const LLVM_SADD_WITH_OVERFLOW: IntrinsicName = int_intrinsic!("llvm.sadd.with.overflow"); -const LLVM_SSUB_WITH_OVERFLOW: IntrinsicName = int_intrinsic!("llvm.ssub.with.overflow"); -const LLVM_SMUL_WITH_OVERFLOW: IntrinsicName = int_intrinsic!("llvm.smul.with.overflow"); +const LLVM_ADD_WITH_OVERFLOW: IntrinsicName = + int_intrinsic!("llvm.sadd.with.overflow", "llvm.uadd.with.overflow"); +const LLVM_SUB_WITH_OVERFLOW: IntrinsicName = + int_intrinsic!("llvm.ssub.with.overflow", "llvm.usub.with.overflow"); +const LLVM_MUL_WITH_OVERFLOW: IntrinsicName = + int_intrinsic!("llvm.smul.with.overflow", "llvm.umul.with.overflow"); fn add_intrinsic<'ctx>( module: &Module<'ctx>, @@ -6366,7 +6369,7 @@ fn build_int_binop<'a, 'ctx, 'env>( NumAdd => { let result = env .call_intrinsic( - &LLVM_SADD_WITH_OVERFLOW[int_width], + &LLVM_ADD_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ) .into_struct_value(); @@ -6375,13 +6378,13 @@ fn build_int_binop<'a, 'ctx, 'env>( } NumAddWrap => bd.build_int_add(lhs, rhs, "add_int_wrap").into(), NumAddChecked => env.call_intrinsic( - &LLVM_SADD_WITH_OVERFLOW[int_width], + &LLVM_ADD_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ), NumSub => { let result = env .call_intrinsic( - &LLVM_SSUB_WITH_OVERFLOW[int_width], + &LLVM_SUB_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ) .into_struct_value(); @@ -6390,13 +6393,13 @@ fn build_int_binop<'a, 'ctx, 'env>( } NumSubWrap => bd.build_int_sub(lhs, rhs, "sub_int").into(), NumSubChecked => env.call_intrinsic( - &LLVM_SSUB_WITH_OVERFLOW[int_width], + &LLVM_SUB_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ), NumMul => { let result = env .call_intrinsic( - &LLVM_SMUL_WITH_OVERFLOW[int_width], + &LLVM_MUL_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ) .into_struct_value(); @@ -6405,7 +6408,7 @@ fn build_int_binop<'a, 'ctx, 'env>( } NumMulWrap => bd.build_int_mul(lhs, rhs, "mul_int").into(), NumMulChecked => env.call_intrinsic( - &LLVM_SMUL_WITH_OVERFLOW[int_width], + &LLVM_MUL_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ), NumGt => bd.build_int_compare(SGT, lhs, rhs, "int_gt").into(), diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index c9ec3b7560..609ec5f35b 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -2101,3 +2101,57 @@ fn num_to_str() { RocStr ); } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn u8_addition_greater_than_i8() { + assert_evals_to!( + indoc!( + r#" + x : U8 + x = 100 + y : U8 + y = 100 + x + y + "# + ), + 200, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn u8_sub_greater_than_i8() { + assert_evals_to!( + indoc!( + r#" + x : U8 + x = 255 + y : U8 + y = 55 + x - y + "# + ), + 200, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn u8_mul_greater_than_i8() { + assert_evals_to!( + indoc!( + r#" + x : U8 + x = 40 + y : U8 + y = 5 + x * y + "# + ), + 200, + u8 + ) +} From 103e85f339f2ac653d41211d06891ae59e5522bb Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 10 Jan 2022 19:21:18 -0500 Subject: [PATCH 156/541] Pretty-print laayouts when PRETTY_PRINT_IR_SYMBOLS is true --- compiler/mono/src/ir.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 3a5c368d04..1a8f93f6b5 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -1632,16 +1632,19 @@ impl<'a> Stmt<'a> { use Stmt::*; match self { - Let(symbol, expr, _layout, cont) => alloc - .text("let ") - .append(symbol_to_doc(alloc, *symbol)) - //.append(" : ") - //.append(alloc.text(format!("{:?}", _layout))) - .append(" = ") - .append(expr.to_doc(alloc)) - .append(";") - .append(alloc.hardline()) - .append(cont.to_doc(alloc)), + Let(symbol, expr, layout, cont) => { + let mut doc = alloc.text("let ").append(symbol_to_doc(alloc, *symbol)); + if PRETTY_PRINT_IR_SYMBOLS { + doc = doc + .append(" : ") + .append(alloc.text(format!("{:?}", layout))); + } + doc.append(" = ") + .append(expr.to_doc(alloc)) + .append(";") + .append(alloc.hardline()) + .append(cont.to_doc(alloc)) + } Refcounting(modify, cont) => modify .to_doc(alloc) From 880bae55b0248dbbb7ff2deded9341021bd62c86 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 10 Jan 2022 19:41:07 -0500 Subject: [PATCH 157/541] Print u8-sized integers in repl properly Closes #2335 --- cli/src/repl/eval.rs | 8 +++++++- cli/tests/repl_eval.rs | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 28c553d6ce..bbd0e93a38 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -1036,6 +1036,12 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> FlatType::TagUnion(tags, _) if tags.len() == 1 => { let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); + // If this tag union represents a number, skip right to + // returning it as an Expr::Num + if let TagName::Private(Symbol::NUM_AT_NUM) = &tag_name { + return Expr::Num(env.arena.alloc_str(&value.to_string())); + } + let loc_tag_expr = { let tag_name = &tag_name.as_ident_str(env.interns, env.home); let tag_expr = if tag_name.starts_with('@') { @@ -1122,7 +1128,7 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); // If this tag union represents a number, skip right to - // returning tis as an Expr::Num + // returning it as an Expr::Num if let TagName::Private(Symbol::NUM_AT_NUM) = &tag_name { return num_expr; } diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index b4d83b61d1..af13cb5ac5 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -851,6 +851,20 @@ mod repl_eval { ) } + #[test] + fn print_u8s() { + expect_success( + indoc!( + r#" + x : U8 + x = 129 + x + "# + ), + "129 : U8", + ) + } + #[test] fn parse_problem() { expect_failure( From b5225c87d8572f2d742bc3b1eec4093927111566 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 10 Jan 2022 21:10:35 -0500 Subject: [PATCH 158/541] Make sure quicksort only ever uses non-negative values `low` can be zero, and so `low - 1` causes an integer overflow for unsigned ints. --- examples/benchmarks/Quicksort.roc | 8 ++++---- examples/quicksort/Quicksort.roc | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/benchmarks/Quicksort.roc b/examples/benchmarks/Quicksort.roc index ce85190a06..2267e45013 100644 --- a/examples/benchmarks/Quicksort.roc +++ b/examples/benchmarks/Quicksort.roc @@ -39,12 +39,12 @@ partition : Nat, Nat, List a, Order a -> [ Pair Nat (List a) ] partition = \low, high, initialList, order -> when List.get initialList high is Ok pivot -> - when partitionHelp (low - 1) low initialList order high pivot is + when partitionHelp low low initialList order high pivot is Pair newI newList -> - Pair (newI + 1) (swap (newI + 1) high newList) + Pair newI (swap newI high newList) Err _ -> - Pair (low - 1) initialList + Pair low initialList partitionHelp : Nat, Nat, List c, Order c, Nat, c -> [ Pair Nat (List c) ] partitionHelp = \i, j, list, order, high, pivot -> @@ -53,7 +53,7 @@ partitionHelp = \i, j, list, order, high, pivot -> Ok value -> when order value pivot is LT | EQ -> - partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) order high pivot + partitionHelp (i + 1) (j + 1) (swap i j list) order high pivot GT -> partitionHelp i (j + 1) list order high pivot diff --git a/examples/quicksort/Quicksort.roc b/examples/quicksort/Quicksort.roc index d86752e502..f4820c5ed0 100644 --- a/examples/quicksort/Quicksort.roc +++ b/examples/quicksort/Quicksort.roc @@ -23,12 +23,12 @@ partition : Nat, Nat, List (Num a) -> [ Pair Nat (List (Num a)) ] partition = \low, high, initialList -> when List.get initialList high is Ok pivot -> - when partitionHelp (low - 1) low initialList high pivot is + when partitionHelp low low initialList high pivot is Pair newI newList -> - Pair (newI + 1) (swap (newI + 1) high newList) + Pair newI (swap newI high newList) Err _ -> - Pair (low - 1) initialList + Pair low initialList partitionHelp : Nat, Nat, List (Num c), Nat, Num c -> [ Pair Nat (List (Num c)) ] partitionHelp = \i, j, list, high, pivot -> @@ -36,7 +36,7 @@ partitionHelp = \i, j, list, high, pivot -> when List.get list j is Ok value -> if value <= pivot then - partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot + partitionHelp (i + 1) (j + 1) (swap i j list) high pivot else partitionHelp i (j + 1) list high pivot From 4ea91b54eb974a73a2495a0a5e0ff5df09213bb5 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 10 Jan 2022 21:57:29 -0500 Subject: [PATCH 159/541] Fix additional unsigned sub overflow bug --- examples/benchmarks/Quicksort.roc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/benchmarks/Quicksort.roc b/examples/benchmarks/Quicksort.roc index 2267e45013..7605407c4a 100644 --- a/examples/benchmarks/Quicksort.roc +++ b/examples/benchmarks/Quicksort.roc @@ -30,7 +30,10 @@ quicksortHelp = \list, order, low, high -> when partition low high list order is Pair partitionIndex partitioned -> partitioned - |> quicksortHelp order low (partitionIndex - 1) + |> \lst -> + # TODO: this will be nicer if we have Num.subSaturated + high1 = if partitionIndex == 0 then 0 else partitionIndex - 1 + quicksortHelp lst order low high1 |> quicksortHelp order (partitionIndex + 1) high else list From 2c41c43aeaba05dc6f6081e0a7f1840303397495 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 10 Jan 2022 22:37:08 -0500 Subject: [PATCH 160/541] Implement saturated add/subtract --- compiler/builtins/docs/Num.roc | 22 +++++- compiler/builtins/src/std.rs | 14 ++++ compiler/can/src/builtins.rs | 12 ++++ compiler/gen_llvm/src/llvm/build.rs | 28 +++++++- compiler/gen_wasm/src/low_level.rs | 2 + compiler/module/src/low_level.rs | 4 ++ compiler/module/src/symbol.rs | 108 ++++++++++++++-------------- compiler/mono/src/borrow.rs | 12 ++-- compiler/test_gen/src/gen_num.rs | 36 ++++++++++ examples/benchmarks/Quicksort.roc | 5 +- 10 files changed, 177 insertions(+), 66 deletions(-) diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index b3c49daf95..aa47d9577a 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -501,7 +501,16 @@ add : Num a, Num a -> Num a ## ## This is the same as [Num.add] except if the operation overflows, instead of ## panicking or returning ∞ or -∞, it will return `Err Overflow`. -addCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]* +addChecked : Num a, Num a -> Result (Num a) [ Overflow ]* + +## Add two numbers, clamping on the maximum representable number rather than +## overflowing. +## +## This is the same as [Num.add] except for the saturating behavior if the +## addition is to overflow. +## For example, if `x : U8` is 200 and `y : U8` is 100, `addSaturated x y` will +## yield 255, the maximum value of a `U8`. +addSaturated : Num a, Num a -> Num a ## Subtract two numbers of the same type. ## @@ -528,7 +537,16 @@ sub : Num a, Num a -> Num a ## ## This is the same as [Num.sub] except if the operation overflows, instead of ## panicking or returning ∞ or -∞, it will return `Err Overflow`. -subCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]* +subChecked : Num a, Num a -> Result (Num a) [ Overflow ]* + +## Subtract two numbers, clamping on the minimum representable number rather +## than overflowing. +## +## This is the same as [Num.sub] except for the saturating behavior if the +## subtraction is to overflow. +## For example, if `x : U8` is 10 and `y : U8` is 20, `subSaturated x y` will +## yield 0, the minimum value of a `U8`. +subSaturated : Num a, Num a -> Num a ## Multiply two numbers of the same type. ## diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 4a7b422402..db6d917a7c 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -141,6 +141,13 @@ pub fn types() -> MutMap { Box::new(int_type(flex(TVAR1))), ); + // addSaturated : Num a, Num a -> Num a + add_top_level_function_type!( + Symbol::NUM_ADD_SATURATED, + vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], + Box::new(int_type(flex(TVAR1))), + ); + // sub or (-) : Num a, Num a -> Num a add_top_level_function_type!( Symbol::NUM_SUB, @@ -162,6 +169,13 @@ pub fn types() -> MutMap { Box::new(result_type(num_type(flex(TVAR1)), overflow())), ); + // subSaturated : Num a, Num a -> Num a + add_top_level_function_type!( + Symbol::NUM_SUB_SATURATED, + vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], + Box::new(int_type(flex(TVAR1))), + ); + // mul or (*) : Num a, Num a -> Num a add_top_level_function_type!( Symbol::NUM_MUL, diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 235f8ba9c2..04a1a1d9c8 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -156,9 +156,11 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_ADD => num_add, NUM_ADD_CHECKED => num_add_checked, NUM_ADD_WRAP => num_add_wrap, + NUM_ADD_SATURATED => num_add_saturated, NUM_SUB => num_sub, NUM_SUB_WRAP => num_sub_wrap, NUM_SUB_CHECKED => num_sub_checked, + NUM_SUB_SATURATED => num_sub_saturated, NUM_MUL => num_mul, NUM_MUL_WRAP => num_mul_wrap, NUM_MUL_CHECKED => num_mul_checked, @@ -641,6 +643,11 @@ fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { num_overflow_checked(symbol, var_store, LowLevel::NumAddChecked) } +/// Num.addSaturated : Int a, Int a -> Int a +fn num_add_saturated(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_binop(symbol, var_store, LowLevel::NumAddSaturated) +} + /// Num.sub : Num a, Num a -> Num a fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def { num_binop(symbol, var_store, LowLevel::NumSub) @@ -656,6 +663,11 @@ fn num_sub_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { num_overflow_checked(symbol, var_store, LowLevel::NumSubChecked) } +/// Num.subSaturated : Int a, Int a -> Int a +fn num_sub_saturated(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_binop(symbol, var_store, LowLevel::NumSubSaturated) +} + /// Num.mul : Num a, Num a -> Num a fn num_mul(symbol: Symbol, var_store: &mut VarStore) -> Def { num_binop(symbol, var_store, LowLevel::NumMul) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 30dedabf97..325316003a 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -493,6 +493,12 @@ fn add_int_intrinsic<'ctx, F>( }; } + check!(IntWidth::U8, ctx.i8_type()); + check!(IntWidth::U16, ctx.i16_type()); + check!(IntWidth::U32, ctx.i32_type()); + check!(IntWidth::U64, ctx.i64_type()); + check!(IntWidth::U128, ctx.i128_type()); + check!(IntWidth::I8, ctx.i8_type()); check!(IntWidth::I16, ctx.i16_type()); check!(IntWidth::I32, ctx.i32_type()); @@ -579,6 +585,14 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { ctx.struct_type(&fields, false) .fn_type(&[t.into(), t.into()], false) }); + + add_int_intrinsic(ctx, module, &LLVM_ADD_SATURATED, |t| { + t.fn_type(&[t.into(), t.into()], false) + }); + + add_int_intrinsic(ctx, module, &LLVM_SUB_SATURATED, |t| { + t.fn_type(&[t.into(), t.into()], false) + }); } const LLVM_POW: IntrinsicName = float_intrinsic!("llvm.pow"); @@ -609,6 +623,9 @@ const LLVM_SUB_WITH_OVERFLOW: IntrinsicName = const LLVM_MUL_WITH_OVERFLOW: IntrinsicName = int_intrinsic!("llvm.smul.with.overflow", "llvm.umul.with.overflow"); +const LLVM_ADD_SATURATED: IntrinsicName = int_intrinsic!("llvm.sadd.sat", "llvm.uadd.sat"); +const LLVM_SUB_SATURATED: IntrinsicName = int_intrinsic!("llvm.ssub.sat", "llvm.usub.sat"); + fn add_intrinsic<'ctx>( module: &Module<'ctx>, intrinsic_name: &str, @@ -5809,8 +5826,9 @@ fn run_low_level<'a, 'ctx, 'env>( } NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked - | NumIsMultipleOf | NumAddWrap | NumAddChecked | NumDivUnchecked | NumDivCeilUnchecked - | NumPow | NumPowInt | NumSubWrap | NumSubChecked | NumMulWrap | NumMulChecked => { + | NumIsMultipleOf | NumAddWrap | NumAddChecked | NumAddSaturated | NumDivUnchecked + | NumDivCeilUnchecked | NumPow | NumPowInt | NumSubWrap | NumSubChecked + | NumSubSaturated | NumMulWrap | NumMulChecked => { debug_assert_eq!(args.len(), 2); let (lhs_arg, lhs_layout) = load_symbol_and_layout(scope, &args[0]); @@ -6381,6 +6399,9 @@ fn build_int_binop<'a, 'ctx, 'env>( &LLVM_ADD_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ), + NumAddSaturated => { + env.call_intrinsic(&LLVM_ADD_SATURATED[int_width], &[lhs.into(), rhs.into()]) + } NumSub => { let result = env .call_intrinsic( @@ -6396,6 +6417,9 @@ fn build_int_binop<'a, 'ctx, 'env>( &LLVM_SUB_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ), + NumSubSaturated => { + env.call_intrinsic(&LLVM_SUB_SATURATED[int_width], &[lhs.into(), rhs.into()]) + } NumMul => { let result = env .call_intrinsic( diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index 77b631aa1d..8ae585238a 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -136,6 +136,7 @@ pub fn dispatch_low_level<'a>( }, NumToStr => return NotImplemented, NumAddChecked => return NotImplemented, + NumAddSaturated => return NotImplemented, NumSub => match ret_layout { WasmLayout::Primitive(value_type, _) => match value_type { I32 => code_builder.i32_sub(), @@ -168,6 +169,7 @@ pub fn dispatch_low_level<'a>( }, }, NumSubChecked => return NotImplemented, + NumSubSaturated => return NotImplemented, NumMul => match ret_layout { WasmLayout::Primitive(value_type, _) => match value_type { I32 => code_builder.i32_mul(), diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 49518adbe9..57f05e194d 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -69,9 +69,11 @@ pub enum LowLevel { NumAdd, NumAddWrap, NumAddChecked, + NumAddSaturated, NumSub, NumSubWrap, NumSubChecked, + NumSubSaturated, NumMul, NumMulWrap, NumMulChecked, @@ -269,9 +271,11 @@ impl LowLevelWrapperType { Symbol::NUM_ADD => CanBeReplacedBy(NumAdd), Symbol::NUM_ADD_WRAP => CanBeReplacedBy(NumAddWrap), Symbol::NUM_ADD_CHECKED => WrapperIsRequired, + Symbol::NUM_ADD_SATURATED => CanBeReplacedBy(NumAddSaturated), Symbol::NUM_SUB => CanBeReplacedBy(NumSub), Symbol::NUM_SUB_WRAP => CanBeReplacedBy(NumSubWrap), Symbol::NUM_SUB_CHECKED => WrapperIsRequired, + Symbol::NUM_SUB_SATURATED => CanBeReplacedBy(NumSubSaturated), Symbol::NUM_MUL => CanBeReplacedBy(NumMul), Symbol::NUM_MUL_WRAP => CanBeReplacedBy(NumMulWrap), Symbol::NUM_MUL_CHECKED => WrapperIsRequired, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index a1f6706576..237f72b199 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -940,59 +940,61 @@ define_builtins! { 52 NUM_FLOOR: "floor" 53 NUM_ADD_WRAP: "addWrap" 54 NUM_ADD_CHECKED: "addChecked" - 55 NUM_ATAN: "atan" - 56 NUM_ACOS: "acos" - 57 NUM_ASIN: "asin" - 58 NUM_AT_SIGNED128: "@Signed128" - 59 NUM_SIGNED128: "Signed128" imported - 60 NUM_AT_SIGNED64: "@Signed64" - 61 NUM_SIGNED64: "Signed64" imported - 62 NUM_AT_SIGNED32: "@Signed32" - 63 NUM_SIGNED32: "Signed32" imported - 64 NUM_AT_SIGNED16: "@Signed16" - 65 NUM_SIGNED16: "Signed16" imported - 66 NUM_AT_SIGNED8: "@Signed8" - 67 NUM_SIGNED8: "Signed8" imported - 68 NUM_AT_UNSIGNED128: "@Unsigned128" - 69 NUM_UNSIGNED128: "Unsigned128" imported - 70 NUM_AT_UNSIGNED64: "@Unsigned64" - 71 NUM_UNSIGNED64: "Unsigned64" imported - 72 NUM_AT_UNSIGNED32: "@Unsigned32" - 73 NUM_UNSIGNED32: "Unsigned32" imported - 74 NUM_AT_UNSIGNED16: "@Unsigned16" - 75 NUM_UNSIGNED16: "Unsigned16" imported - 76 NUM_AT_UNSIGNED8: "@Unsigned8" - 77 NUM_UNSIGNED8: "Unsigned8" imported - 78 NUM_AT_BINARY64: "@Binary64" - 79 NUM_BINARY64: "Binary64" imported - 80 NUM_AT_BINARY32: "@Binary32" - 81 NUM_BINARY32: "Binary32" imported - 82 NUM_BITWISE_AND: "bitwiseAnd" - 83 NUM_BITWISE_XOR: "bitwiseXor" - 84 NUM_BITWISE_OR: "bitwiseOr" - 85 NUM_SHIFT_LEFT: "shiftLeftBy" - 86 NUM_SHIFT_RIGHT: "shiftRightBy" - 87 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy" - 88 NUM_SUB_WRAP: "subWrap" - 89 NUM_SUB_CHECKED: "subChecked" - 90 NUM_MUL_WRAP: "mulWrap" - 91 NUM_MUL_CHECKED: "mulChecked" - 92 NUM_INT: "Int" imported - 93 NUM_FLOAT: "Float" imported - 94 NUM_AT_NATURAL: "@Natural" - 95 NUM_NATURAL: "Natural" imported - 96 NUM_NAT: "Nat" imported - 97 NUM_INT_CAST: "intCast" - 98 NUM_MAX_I128: "maxI128" - 99 NUM_IS_MULTIPLE_OF: "isMultipleOf" - 100 NUM_AT_DECIMAL: "@Decimal" - 101 NUM_DECIMAL: "Decimal" imported - 102 NUM_DEC: "Dec" imported // the Num.Dectype alias - 103 NUM_BYTES_TO_U16: "bytesToU16" - 104 NUM_BYTES_TO_U32: "bytesToU32" - 105 NUM_CAST_TO_NAT: "#castToNat" - 106 NUM_DIV_CEIL: "divCeil" - 107 NUM_TO_STR: "toStr" + 55 NUM_ADD_SATURATED: "addSaturated" + 56 NUM_ATAN: "atan" + 57 NUM_ACOS: "acos" + 58 NUM_ASIN: "asin" + 59 NUM_AT_SIGNED128: "@Signed128" + 60 NUM_SIGNED128: "Signed128" imported + 61 NUM_AT_SIGNED64: "@Signed64" + 62 NUM_SIGNED64: "Signed64" imported + 63 NUM_AT_SIGNED32: "@Signed32" + 64 NUM_SIGNED32: "Signed32" imported + 65 NUM_AT_SIGNED16: "@Signed16" + 66 NUM_SIGNED16: "Signed16" imported + 67 NUM_AT_SIGNED8: "@Signed8" + 68 NUM_SIGNED8: "Signed8" imported + 69 NUM_AT_UNSIGNED128: "@Unsigned128" + 70 NUM_UNSIGNED128: "Unsigned128" imported + 71 NUM_AT_UNSIGNED64: "@Unsigned64" + 72 NUM_UNSIGNED64: "Unsigned64" imported + 73 NUM_AT_UNSIGNED32: "@Unsigned32" + 74 NUM_UNSIGNED32: "Unsigned32" imported + 75 NUM_AT_UNSIGNED16: "@Unsigned16" + 76 NUM_UNSIGNED16: "Unsigned16" imported + 77 NUM_AT_UNSIGNED8: "@Unsigned8" + 78 NUM_UNSIGNED8: "Unsigned8" imported + 79 NUM_AT_BINARY64: "@Binary64" + 80 NUM_BINARY64: "Binary64" imported + 81 NUM_AT_BINARY32: "@Binary32" + 82 NUM_BINARY32: "Binary32" imported + 83 NUM_BITWISE_AND: "bitwiseAnd" + 84 NUM_BITWISE_XOR: "bitwiseXor" + 85 NUM_BITWISE_OR: "bitwiseOr" + 86 NUM_SHIFT_LEFT: "shiftLeftBy" + 87 NUM_SHIFT_RIGHT: "shiftRightBy" + 88 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy" + 89 NUM_SUB_WRAP: "subWrap" + 90 NUM_SUB_CHECKED: "subChecked" + 91 NUM_SUB_SATURATED: "subSaturated" + 92 NUM_MUL_WRAP: "mulWrap" + 93 NUM_MUL_CHECKED: "mulChecked" + 94 NUM_INT: "Int" imported + 95 NUM_FLOAT: "Float" imported + 96 NUM_AT_NATURAL: "@Natural" + 97 NUM_NATURAL: "Natural" imported + 98 NUM_NAT: "Nat" imported + 99 NUM_INT_CAST: "intCast" + 100 NUM_MAX_I128: "maxI128" + 101 NUM_IS_MULTIPLE_OF: "isMultipleOf" + 102 NUM_AT_DECIMAL: "@Decimal" + 103 NUM_DECIMAL: "Decimal" imported + 104 NUM_DEC: "Dec" imported // the Num.Dectype alias + 105 NUM_BYTES_TO_U16: "bytesToU16" + 106 NUM_BYTES_TO_U32: "bytesToU32" + 107 NUM_CAST_TO_NAT: "#castToNat" + 108 NUM_DIV_CEIL: "divCeil" + 109 NUM_TO_STR: "toStr" } 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 50b5e4498c..2109204d7a 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -972,11 +972,13 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]), - And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap | NumSubChecked - | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare - | NumDivUnchecked | NumDivCeilUnchecked | NumRemUnchecked | NumIsMultipleOf | NumPow - | NumPowInt | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy - | NumShiftRightBy | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]), + And | Or | NumAdd | NumAddWrap | NumAddChecked | NumAddSaturated | NumSub | NumSubWrap + | NumSubChecked | NumSubSaturated | NumMul | NumMulWrap | NumMulChecked | NumGt + | NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked | NumDivCeilUnchecked + | NumRemUnchecked | NumIsMultipleOf | NumPow | NumPowInt | NumBitwiseAnd + | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumShiftRightZfBy => { + arena.alloc_slice_copy(&[irrelevant, irrelevant]) + } NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index 609ec5f35b..3fc1227f70 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -2155,3 +2155,39 @@ fn u8_mul_greater_than_i8() { u8 ) } + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn add_saturated() { + assert_evals_to!( + indoc!( + r#" + x : U8 + x = 200 + y : U8 + y = 200 + Num.addSaturated x y + "# + ), + 255, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn sub_saturated() { + assert_evals_to!( + indoc!( + r#" + x : U8 + x = 10 + y : U8 + y = 20 + Num.subSaturated x y + "# + ), + 0, + u8 + ) +} diff --git a/examples/benchmarks/Quicksort.roc b/examples/benchmarks/Quicksort.roc index 7605407c4a..cc83902313 100644 --- a/examples/benchmarks/Quicksort.roc +++ b/examples/benchmarks/Quicksort.roc @@ -30,10 +30,7 @@ quicksortHelp = \list, order, low, high -> when partition low high list order is Pair partitionIndex partitioned -> partitioned - |> \lst -> - # TODO: this will be nicer if we have Num.subSaturated - high1 = if partitionIndex == 0 then 0 else partitionIndex - 1 - quicksortHelp lst order low high1 + |> quicksortHelp order low (Num.subSaturated partitionIndex 1) |> quicksortHelp order (partitionIndex + 1) high else list From fba1c64c4b389aa122ad30186a4f7349bc8ce894 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 10 Jan 2022 23:41:08 -0500 Subject: [PATCH 161/541] Fix another quicksort bug --- compiler/test_gen/src/gen_list.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 0005f295b4..08926c78c4 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -2085,7 +2085,7 @@ fn foobar2() { when partition low high list is Pair partitionIndex partitioned -> partitioned - |> quicksortHelp low (partitionIndex - 1) + |> quicksortHelp low (Num.subSaturated partitionIndex 1) |> quicksortHelp (partitionIndex + 1) high else list @@ -2106,9 +2106,9 @@ fn foobar2() { partition = \low, high, initialList -> when List.get initialList high is Ok pivot -> - when partitionHelp (low - 1) low initialList high pivot is + when partitionHelp low low initialList high pivot is Pair newI newList -> - Pair (newI + 1) (swap (newI + 1) high newList) + Pair newI (swap newI high newList) Err _ -> Pair (low - 1) initialList @@ -2121,7 +2121,7 @@ fn foobar2() { when List.get list j is Ok value -> if value <= pivot then - partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot + partitionHelp (i + 1) (j + 1) (swap i j list) high pivot else partitionHelp i (j + 1) list high pivot From fe114e5e0977fd103479e587abef5aeacff11953 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 10 Jan 2022 23:42:15 -0500 Subject: [PATCH 162/541] Improve test names --- compiler/test_gen/src/gen_list.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 08926c78c4..03d3bd0c15 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -1319,7 +1319,7 @@ fn list_reverse_empty_list() { #[test] #[cfg(any(feature = "gen-llvm"))] -fn foobarbaz() { +fn list_concat() { assert_evals_to!( indoc!( r#" @@ -2069,7 +2069,7 @@ fn gen_quicksort() { #[test] #[cfg(any(feature = "gen-llvm"))] -fn foobar2() { +fn quicksort() { with_larger_debug_stack(|| { assert_evals_to!( indoc!( @@ -2143,7 +2143,7 @@ fn foobar2() { #[test] #[cfg(any(feature = "gen-llvm"))] -fn foobar() { +fn quicksort_singleton() { with_larger_debug_stack(|| { assert_evals_to!( indoc!( From b2e05b92d1ef97342c7cc1a3daa242ceaba0d8b8 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 11 Jan 2022 21:03:57 +0100 Subject: [PATCH 163/541] make fastcc wrapper return by pointer if return type is large --- compiler/gen_llvm/src/llvm/build.rs | 45 ++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 01012f3137..fe910442cb 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -6212,6 +6212,7 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( // - a FAST_CALL_CONV wrapper that we make here, e.g. `roc_fx_putLine_fastcc_wrapper` let return_type = basic_type_from_layout(env, ret_layout); + let roc_return = RocReturn::from_layout(env, ret_layout); let cc_return = to_cc_return(env, ret_layout); let mut cc_argument_types = @@ -6249,8 +6250,17 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type); - let fastcc_type = - return_type.fn_type(&function_arguments(env, &fastcc_argument_types), false); + let fastcc_type = match roc_return { + RocReturn::Return => { + return_type.fn_type(&function_arguments(env, &fastcc_argument_types), false) + } + RocReturn::ByPointer => { + fastcc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into()); + env.context + .void_type() + .fn_type(&function_arguments(env, &fastcc_argument_types), false) + } + }; let fastcc_function = add_func( env.module, @@ -6265,12 +6275,16 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( let entry = context.append_basic_block(fastcc_function, "entry"); { builder.position_at_end(entry); - let return_pointer = env.builder.build_alloca(return_type, "return_value"); - let fastcc_parameters = fastcc_function.get_params(); + let mut fastcc_parameters = fastcc_function.get_params(); let mut cc_arguments = Vec::with_capacity_in(fastcc_parameters.len() + 1, env.arena); + let return_pointer = match roc_return { + RocReturn::Return => env.builder.build_alloca(return_type, "return_value"), + RocReturn::ByPointer => fastcc_parameters.pop().unwrap().into_pointer_value(), + }; + let it = fastcc_parameters.into_iter().zip(cc_argument_types.iter()); for (param, cc_type) in it { if param.get_type() == *cc_type { @@ -6289,14 +6303,25 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( let call = env.builder.build_call(cc_function, &cc_arguments, "tmp"); call.set_call_convention(C_CALL_CONV); - let return_value = match cc_return { - CCReturn::Return => call.try_as_basic_value().left().unwrap(), + match roc_return { + RocReturn::Return => { + let return_value = match cc_return { + CCReturn::Return => call.try_as_basic_value().left().unwrap(), - CCReturn::ByPointer => env.builder.build_load(return_pointer, "read_result"), - CCReturn::Void => return_type.const_zero(), - }; + CCReturn::ByPointer => { + env.builder.build_load(return_pointer, "read_result") + } + CCReturn::Void => return_type.const_zero(), + }; - builder.build_return(Some(&return_value)); + builder.build_return(Some(&return_value)); + } + RocReturn::ByPointer => { + debug_assert!(matches!(cc_return, CCReturn::ByPointer)); + + builder.build_return(None); + } + } } builder.position_at_end(old); From 1b149d215d097c367700d845d49634d82ea90a74 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 11 Jan 2022 22:08:58 +0100 Subject: [PATCH 164/541] pass pointer to C as first argument --- compiler/gen_llvm/src/llvm/build.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index fe910442cb..a8e244779f 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -6238,7 +6238,7 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( .void_type() .fn_type(&function_arguments(env, &cc_argument_types), false), CCReturn::ByPointer => { - cc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into()); + cc_argument_types.insert(0, return_type.ptr_type(AddressSpace::Generic).into()); env.context .void_type() .fn_type(&function_arguments(env, &cc_argument_types), false) @@ -6285,6 +6285,11 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( RocReturn::ByPointer => fastcc_parameters.pop().unwrap().into_pointer_value(), }; + if let CCReturn::ByPointer = cc_return { + cc_arguments.push(return_pointer.into()); + cc_argument_types.remove(0); + } + let it = fastcc_parameters.into_iter().zip(cc_argument_types.iter()); for (param, cc_type) in it { if param.get_type() == *cc_type { @@ -6296,10 +6301,6 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( } } - if let CCReturn::ByPointer = cc_return { - cc_arguments.push(return_pointer.into()); - } - let call = env.builder.build_call(cc_function, &cc_arguments, "tmp"); call.set_call_convention(C_CALL_CONV); From 92ec680a250bac2f153564a4a4252485833f502f Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 11 Jan 2022 21:54:03 +0000 Subject: [PATCH 165/541] Fix clippy error on non-wasm test builds --- compiler/test_gen/src/gen_refcount.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs index 87dbe52856..c62e27f19c 100644 --- a/compiler/test_gen/src/gen_refcount.rs +++ b/compiler/test_gen/src/gen_refcount.rs @@ -9,6 +9,7 @@ use roc_std::{RocList, RocStr}; // A "good enough" representation of a pointer for these tests, because // we ignore the return value. As long as it's the right stack size, it's fine. +#[allow(dead_code)] type Pointer = usize; #[test] From fefbb5312e655c55c67cf66009434ec6a9c628d9 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Tue, 11 Jan 2022 18:17:57 -0500 Subject: [PATCH 166/541] Yet more quicksort bugs --- compiler/test_gen/src/gen_list.rs | 114 +++--------------------------- 1 file changed, 11 insertions(+), 103 deletions(-) diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 03d3bd0c15..55845c777e 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -1903,98 +1903,6 @@ fn gen_swap() { ); } -// #[test] -#[cfg(any(feature = "gen-llvm"))] -// fn gen_partition() { -// assert_evals_to!( -// indoc!( -// r#" -// swap : I64, I64, List a -> List a -// swap = \i, j, list -> -// when Pair (List.get list i) (List.get list j) is -// Pair (Ok atI) (Ok atJ) -> -// list -// |> List.set i atJ -// |> List.set j atI -// -// _ -> -// [] -// partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ] -// partition = \low, high, initialList -> -// when List.get initialList high is -// Ok pivot -> -// when partitionHelp (low - 1) low initialList high pivot is -// Pair newI newList -> -// Pair (newI + 1) (swap (newI + 1) high newList) -// -// Err _ -> -// Pair (low - 1) initialList -// -// -// partitionHelp : I64, I64, List (Num a), I64, I64 -> [ Pair I64 (List (Num a)) ] -// partitionHelp = \i, j, list, high, pivot -> -// if j < high then -// when List.get list j is -// Ok value -> -// if value <= pivot then -// partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot -// else -// partitionHelp i (j + 1) list high pivot -// -// Err _ -> -// Pair i list -// else -// Pair i list -// -// # when partition 0 0 [ 1,2,3,4,5 ] is -// # Pair list _ -> list -// [ 1,3 ] -// "# -// ), -// RocList::from_slice(&[2, 1]), -// RocList -// ); -// } - -// #[test] -#[cfg(any(feature = "gen-llvm"))] -// fn gen_partition() { -// assert_evals_to!( -// indoc!( -// r#" -// swap : I64, I64, List a -> List a -// swap = \i, j, list -> -// when Pair (List.get list i) (List.get list j) is -// Pair (Ok atI) (Ok atJ) -> -// list -// |> List.set i atJ -// |> List.set j atI -// -// _ -> -// [] -// partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ] -// partition = \low, high, initialList -> -// when List.get initialList high is -// Ok pivot -> -// when partitionHelp (low - 1) low initialList high pivot is -// Pair newI newList -> -// Pair (newI + 1) (swap (newI + 1) high newList) -// -// Err _ -> -// Pair (low - 1) initialList -// -// -// partitionHelp : I64, I64, List (Num a), I64, I64 -> [ Pair I64 (List (Num a)) ] -// -// # when partition 0 0 [ 1,2,3,4,5 ] is -// # Pair list _ -> list -// [ 1,3 ] -// "# -// ), -// RocList::from_slice(&[2, 1]), -// RocList -// ); -// } #[test] #[cfg(any(feature = "gen-llvm"))] fn gen_quicksort() { @@ -2014,7 +1922,7 @@ fn gen_quicksort() { when partition low high list is Pair partitionIndex partitioned -> partitioned - |> quicksortHelp low (partitionIndex - 1) + |> quicksortHelp low (Num.subSaturated partitionIndex 1) |> quicksortHelp (partitionIndex + 1) high else list @@ -2035,12 +1943,12 @@ fn gen_quicksort() { partition = \low, high, initialList -> when List.get initialList high is Ok pivot -> - when partitionHelp (low - 1) low initialList high pivot is + when partitionHelp low low initialList high pivot is Pair newI newList -> - Pair (newI + 1) (swap (newI + 1) high newList) + Pair newI (swap newI high newList) Err _ -> - Pair (low - 1) initialList + Pair low initialList partitionHelp : Nat, Nat, List (Num a), Nat, (Num a) -> [ Pair Nat (List (Num a)) ] @@ -2049,7 +1957,7 @@ fn gen_quicksort() { when List.get list j is Ok value -> if value <= pivot then - partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot + partitionHelp (i + 1) (j + 1) (swap i j list) high pivot else partitionHelp i (j + 1) list high pivot @@ -2111,7 +2019,7 @@ fn quicksort() { Pair newI (swap newI high newList) Err _ -> - Pair (low - 1) initialList + Pair low initialList partitionHelp : Nat, Nat, List (Num a), Nat, Num a -> [ Pair Nat (List (Num a)) ] @@ -2159,7 +2067,7 @@ fn quicksort_singleton() { when partition low high list is Pair partitionIndex partitioned -> partitioned - |> quicksortHelp low (partitionIndex - 1) + |> quicksortHelp low (Num.subSaturated partitionIndex 1) |> quicksortHelp (partitionIndex + 1) high else list @@ -2180,12 +2088,12 @@ fn quicksort_singleton() { partition = \low, high, initialList -> when List.get initialList high is Ok pivot -> - when partitionHelp (low - 1) low initialList high pivot is + when partitionHelp low low initialList high pivot is Pair newI newList -> - Pair (newI + 1) (swap (newI + 1) high newList) + Pair newI (swap newI high newList) Err _ -> - Pair (low - 1) initialList + Pair low initialList partitionHelp : Nat, Nat, List (Num a), Nat, Num a -> [ Pair Nat (List (Num a)) ] @@ -2194,7 +2102,7 @@ fn quicksort_singleton() { when List.get list j is Ok value -> if value <= pivot then - partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot + partitionHelp (i + 1) (j + 1) (swap i j list) high pivot else partitionHelp i (j + 1) list high pivot From 6c4fcb65149edbc2c90f20ed0763065e0cda4312 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Tue, 11 Jan 2022 19:50:48 -0700 Subject: [PATCH 167/541] Maybe-fix some maybe-incorrect doc-comments This might affect `roc docs ...` parsing, but probably not. --- compiler/builtins/docs/Bool.roc | 4 ++-- compiler/builtins/docs/Num.roc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/builtins/docs/Bool.roc b/compiler/builtins/docs/Bool.roc index d4a3b673fe..29d8dbbc90 100644 --- a/compiler/builtins/docs/Bool.roc +++ b/compiler/builtins/docs/Bool.roc @@ -44,9 +44,9 @@ and : Bool, Bool -> Bool ## `a || b` is shorthand for `Bool.or a b`. ## ## >>> True || True -# +## ## >>> True || False -# +## ## >>> False || True ## ## >>> False || False diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index b3c49daf95..91a09cf94c 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -102,7 +102,7 @@ interface Num ## ## The number 1.5 technically has the type `Num (Fraction *)`, so when you pass ## two of them to [Num.add], the answer you get is `3.0 : Num (Fraction *)`. -# +## ## Similarly, the number 0x1 (that is, the integer 1 in hexadecimal notation) ## technically has the type `Num (Integer *)`, so when you pass two of them to ## [Num.add], the answer you get is `2 : Num (Integer *)`. From 5058e3cfc4d9ae1b44251173173222d7f6db9548 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Wed, 12 Jan 2022 22:00:48 -0500 Subject: [PATCH 168/541] Fix panic during reporting Closes #2326 --- reporting/src/error/type.rs | 26 ++++++++++---------- reporting/tests/test_reporting.rs | 40 +++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index 2420c901d4..ec477d78d7 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -9,7 +9,6 @@ use roc_types::pretty_print::{Parens, WILDCARD}; use roc_types::types::{Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt}; use std::path::PathBuf; -use crate::internal_error; use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; use ven_pretty::DocAllocator; @@ -2721,7 +2720,7 @@ fn type_problem_to_pretty<'b>( alloc.tip().append(line) } - (BadRigidVar(x, tipe), ExpectationContext::Annotation { on }) => { + (BadRigidVar(x, tipe), expectation) => { use ErrorType::*; let bad_rigid_var = |name: Lowercase, a_thing| { @@ -2735,17 +2734,23 @@ fn type_problem_to_pretty<'b>( }; let bad_double_wildcard = || { - alloc.tip().append(alloc.concat(vec![ + let mut hints_lines = vec![ alloc.reflow( "Any connection between types must use a named type variable, not a ", ), alloc.type_variable(WILDCARD.into()), - alloc.reflow("! Maybe the annotation "), - on, - alloc.reflow(" should have a named type variable in place of the "), - alloc.type_variable(WILDCARD.into()), - alloc.reflow("?"), - ])) + alloc.reflow("!"), + ]; + if let ExpectationContext::Annotation { on } = expectation { + hints_lines.append(&mut vec![ + alloc.reflow(" Maybe the annotation "), + on, + alloc.reflow(" should have a named type variable in place of the "), + alloc.type_variable(WILDCARD.into()), + alloc.reflow("?"), + ]); + } + alloc.tip().append(alloc.concat(hints_lines)) }; let bad_double_rigid = |a: Lowercase, b: Lowercase| { @@ -2781,9 +2786,6 @@ fn type_problem_to_pretty<'b>( ), } } - (BadRigidVar(_, _), expectation_context) => { - internal_error!("I thought mismatches between rigid vars could only happen in the context of a type annotation, but here they're happening with a {:?}!", expectation_context) - } (IntFloat, _) => alloc.tip().append(alloc.concat(vec![ alloc.reflow("You can convert between "), diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index d2dae2c026..62b7e8dc2c 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -7034,4 +7034,44 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn issue_2326() { + report_problem_as( + indoc!( + r#" + C a b : a -> D a b + D a b : { a, b } + + f : C a Nat -> D a Nat + f = \c -> c 6 + f + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 1st argument to `c` is not what I expect: + + 5│ f = \c -> c 6 + ^ + + This argument is a number of type: + + Num a + + But `c` needs the 1st argument to be: + + a + + Tip: The type annotation uses the type variable `a` to say that this + definition can produce any type of value. But in the body I see that + it will only produce a `Num` value of a single specific type. Maybe + change the type annotation to be more specific? Maybe change the code + to be more general? + "# + ), + ) + } } From 5de9581b62f97657e0fad9eee5131af92b7cb942 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 7 Jan 2022 18:01:16 +0000 Subject: [PATCH 169/541] Wasm: pre-link app-independent code before running tests (builtins, plaform & libc) --- compiler/test_gen/Cargo.toml | 3 ++ compiler/test_gen/build.rs | 65 ++++++++------------------- compiler/test_gen/src/helpers/wasm.rs | 8 ---- 3 files changed, 21 insertions(+), 55 deletions(-) diff --git a/compiler/test_gen/Cargo.toml b/compiler/test_gen/Cargo.toml index bd6ddfd5d1..162b058fd8 100644 --- a/compiler/test_gen/Cargo.toml +++ b/compiler/test_gen/Cargo.toml @@ -9,6 +9,9 @@ edition = "2018" name = "test_gen" path = "src/tests.rs" +[build-dependencies] +roc_builtins = { path = "../builtins" } + [dev-dependencies] roc_gen_llvm = { path = "../gen_llvm" } roc_gen_dev = { path = "../gen_dev" } diff --git a/compiler/test_gen/build.rs b/compiler/test_gen/build.rs index 5f775d6400..d648695d4a 100644 --- a/compiler/test_gen/build.rs +++ b/compiler/test_gen/build.rs @@ -1,3 +1,4 @@ +use roc_builtins::bitcode; use std::env; use std::ffi::OsStr; use std::path::Path; @@ -5,8 +6,6 @@ use std::process::Command; const PLATFORM_FILENAME: &str = "wasm_test_platform"; const OUT_DIR_VAR: &str = "TEST_GEN_OUT"; -const LIBC_PATH_VAR: &str = "TEST_GEN_WASM_LIBC_PATH"; -const COMPILER_RT_PATH_VAR: &str = "TEST_GEN_WASM_COMPILER_RT_PATH"; fn main() { println!("cargo:rerun-if-changed=build.rs"); @@ -20,8 +19,7 @@ fn build_wasm() { println!("cargo:rustc-env={}={}", OUT_DIR_VAR, out_dir); - build_wasm_test_platform(&out_dir); - build_wasm_libc(&out_dir); + build_wasm_platform_and_builtins(&out_dir); } fn zig_executable() -> String { @@ -31,52 +29,25 @@ fn zig_executable() -> String { } } -fn build_wasm_test_platform(out_dir: &str) { +/// Create an all-in-one object file: platform + builtins + libc +fn build_wasm_platform_and_builtins(out_dir: &str) { println!("cargo:rerun-if-changed=src/helpers/{}.c", PLATFORM_FILENAME); - run_command( - Path::new("."), - &zig_executable(), - [ - "build-obj", - "-target", - "wasm32-wasi", - "-lc", - &format!("src/helpers/{}.c", PLATFORM_FILENAME), - &format!("-femit-bin={}/{}.o", out_dir, PLATFORM_FILENAME), - ], - ); -} + // See discussion with Luuk de Gram (Zig contributor) + // https://github.com/rtfeldman/roc/pull/2181#pullrequestreview-839608063 + // This builds a library file that exports everything. It has no linker data but we don't need that. + let args = [ + "build-lib", + "-target", + "wasm32-wasi", + "-lc", + "-dynamic", // -dynamic ensures libc code goes into the binary + bitcode::BUILTINS_WASM32_OBJ_PATH, + &format!("src/helpers/{}.c", PLATFORM_FILENAME), + &format!("-femit-bin={}/{}.o", out_dir, PLATFORM_FILENAME), + ]; -fn build_wasm_libc(out_dir: &str) { - let source_path = "src/helpers/dummy_libc_program.c"; - println!("cargo:rerun-if-changed={}", source_path); - let cwd = Path::new("."); - let zig_cache_dir = format!("{}/zig-cache-wasm32", out_dir); - - run_command( - cwd, - &zig_executable(), - [ - "build-exe", // must be an executable or it won't compile libc - "-target", - "wasm32-wasi", - "-lc", - source_path, - "-femit-bin=/dev/null", - "--global-cache-dir", - &zig_cache_dir, - ], - ); - - let libc_path = run_command(cwd, "find", [&zig_cache_dir, "-name", "libc.a"]); - let compiler_rt_path = run_command(cwd, "find", [&zig_cache_dir, "-name", "compiler_rt.o"]); - - println!("cargo:rustc-env={}={}", LIBC_PATH_VAR, libc_path); - println!( - "cargo:rustc-env={}={}", - COMPILER_RT_PATH_VAR, compiler_rt_path - ); + run_command(Path::new("."), &zig_executable(), args); } fn feature_is_enabled(feature_name: &str) -> bool { diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index f1499241ad..d6106a1f3f 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -8,7 +8,6 @@ use wasmer::{Memory, WasmPtr}; use crate::helpers::from_wasm32_memory::FromWasm32Memory; use crate::helpers::wasm32_test_result::Wasm32TestResult; -use roc_builtins::bitcode; use roc_can::builtins::builtin_defs_map; use roc_collections::all::{MutMap, MutSet}; use roc_gen_wasm::{DEBUG_LOG_SETTINGS, MEMORY_NAME}; @@ -16,8 +15,6 @@ use roc_gen_wasm::{DEBUG_LOG_SETTINGS, MEMORY_NAME}; // Should manually match build.rs const PLATFORM_FILENAME: &str = "wasm_test_platform"; const OUT_DIR_VAR: &str = "TEST_GEN_OUT"; -const LIBC_PATH_VAR: &str = "TEST_GEN_WASM_LIBC_PATH"; -const COMPILER_RT_PATH_VAR: &str = "TEST_GEN_WASM_COMPILER_RT_PATH"; #[allow(unused_imports)] use roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS; @@ -175,8 +172,6 @@ fn run_linker( let app_o_file = wasm_build_dir.join("app.o"); let test_out_dir = std::env::var(OUT_DIR_VAR).unwrap(); let test_platform_o = format!("{}/{}.o", test_out_dir, PLATFORM_FILENAME); - let libc_a_file = std::env::var(LIBC_PATH_VAR).unwrap(); - let compiler_rt_o_file = std::env::var(COMPILER_RT_PATH_VAR).unwrap(); // write the module to a file so the linker can access it std::fs::write(&app_o_file, &app_module_bytes).unwrap(); @@ -185,10 +180,7 @@ fn run_linker( "wasm-ld", // input files app_o_file.to_str().unwrap(), - bitcode::BUILTINS_WASM32_OBJ_PATH, &test_platform_o, - &libc_a_file, - &compiler_rt_o_file, // output "-o", final_wasm_file.to_str().unwrap(), From 8e7f398e500f90fc39090e932a3ac983c65517c9 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 8 Jan 2022 15:16:41 +0000 Subject: [PATCH 170/541] Wasm: Remove code to adjust function indices based on imports With pre-linking we don't need this anymore. Pure Roc code can't add Imports. --- compiler/gen_wasm/src/wasm_module/sections.rs | 77 +------------------ 1 file changed, 2 insertions(+), 75 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 4b2108ea00..b766024fd1 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -1,9 +1,7 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; -use super::linking::{ - IndexRelocType, LinkingSection, RelocationEntry, RelocationSection, SymInfo, WasmObjectSymbol, -}; +use super::linking::{LinkingSection, RelocationEntry, RelocationSection}; use super::opcodes::OpCode; use super::serialize::{decode_u32_or_panic, SerialBuffer, Serialize}; use super::{CodeBuilder, ValueType}; @@ -730,13 +728,6 @@ impl<'a> WasmModule<'a> { section_index: 0, }; - // If we have imports, then references to other functions need to be re-indexed. - // Modify exports before serializing them, since we don't have linker data for them - let n_imported_fns = self.import.function_count() as u32; - if n_imported_fns > 0 { - self.finalize_exported_fn_indices(n_imported_fns); - } - counter.serialize_and_count(buffer, &self.types); counter.serialize_and_count(buffer, &self.import); counter.serialize_and_count(buffer, &self.function); @@ -749,16 +740,9 @@ impl<'a> WasmModule<'a> { // Code section is the only one with relocations so we can stop counting let code_section_index = counter.section_index; - let code_section_body_index = self - .code + self.code .serialize_with_relocs(buffer, &mut self.relocations.entries); - // If we have imports, references to other functions need to be re-indexed. - // Simplest to do after serialization, using linker data - if n_imported_fns > 0 { - self.finalize_code_fn_indices(buffer, code_section_body_index, n_imported_fns); - } - self.data.serialize(buffer); self.linking.serialize(buffer); @@ -766,63 +750,6 @@ impl<'a> WasmModule<'a> { self.relocations.target_section_index = Some(code_section_index); self.relocations.serialize(buffer); } - - /// Shift indices of exported functions to make room for imported functions, - /// which come first in the function index space. - /// Must be called after traversing the full IR, but before export section is serialized. - fn finalize_exported_fn_indices(&mut self, n_imported_fns: u32) { - for export in self.export.entries.iter_mut() { - if export.ty == ExportType::Func { - export.index += n_imported_fns; - } - } - } - - /// Re-index internally-defined functions to make room for imported functions. - /// Imported functions come first in the index space, but we didn't know how many we needed until now. - /// We do this after serializing the code section, since we have linker data that is literally - /// *designed* for changing function indices in serialized code! - fn finalize_code_fn_indices( - &mut self, - buffer: &mut T, - code_section_body_index: usize, - n_imported_fns: u32, - ) { - // Lookup vector of symbol index to new function index - let mut new_index_lookup = std::vec::Vec::with_capacity(self.linking.symbol_table.len()); - - // Modify symbol table entries and fill the lookup vector - for sym_info in self.linking.symbol_table.iter_mut() { - match sym_info { - SymInfo::Function(WasmObjectSymbol::Defined { index, .. }) => { - let new_fn_index = *index + n_imported_fns; - *index = new_fn_index; - new_index_lookup.push(new_fn_index); - } - SymInfo::Function(WasmObjectSymbol::Imported { index, .. }) => { - new_index_lookup.push(*index); - } - _ => { - // Symbol is not a function, so we won't look it up. Use a dummy value. - new_index_lookup.push(u32::MAX); - } - } - } - - // Modify call instructions, using linker data - for reloc in &self.relocations.entries { - if let RelocationEntry::Index { - type_id: IndexRelocType::FunctionIndexLeb, - offset, - symbol_index, - } = reloc - { - let new_fn_index = new_index_lookup[*symbol_index as usize]; - let buffer_index = code_section_body_index + (*offset as usize); - buffer.overwrite_padded_u32(buffer_index, new_fn_index); - } - } - } } /// Assertion to run on the generated Wasm module in every test From d88b86e8845a233e4f42895510d96d455e1b17df Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 8 Jan 2022 15:17:52 +0000 Subject: [PATCH 171/541] Wasm: change TypeSection tests to unit tests rather than integration tests --- compiler/gen_wasm/src/wasm_module/sections.rs | 62 +++++++++++++------ compiler/test_gen/src/helpers/wasm.rs | 2 - 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index b766024fd1..d47fff12e0 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -752,22 +752,48 @@ impl<'a> WasmModule<'a> { } } -/// Assertion to run on the generated Wasm module in every test -pub fn test_assert_preload<'a>(arena: &'a Bump, wasm_module: &WasmModule<'a>) { - test_assert_types_preload(arena, &wasm_module.types); -} - -fn test_assert_types_preload<'a>(arena: &'a Bump, original: &TypeSection<'a>) { - // Serialize the Type section that we built from Roc code - let mut original_serialized = Vec::with_capacity_in(original.bytes.len() + 10, arena); - original.serialize(&mut original_serialized); - - debug_assert!(original_serialized[0] == SectionId::Type as u8); - - // Reconstruct a new TypeSection by "pre-loading" the bytes of the original! - let body = &original_serialized[6..]; - let preloaded = TypeSection::preload(arena, body); - - debug_assert_eq!(original.offsets, preloaded.offsets); - debug_assert_eq!(original.bytes, preloaded.bytes); +#[cfg(test)] +mod tests { + use super::*; + use bumpalo::{self, collections::Vec, Bump}; + + fn test_assert_types_preload<'a>(arena: &'a Bump, original: &TypeSection<'a>) { + // Serialize the Type section that we built from Roc code + let mut original_serialized = Vec::with_capacity_in(6 + original.bytes.len(), arena); + original.serialize(&mut original_serialized); + + debug_assert!(original_serialized[0] == SectionId::Type as u8); + + // Reconstruct a new TypeSection by "pre-loading" the bytes of the original! + let body = &original_serialized[6..]; + let preloaded = TypeSection::preload(arena, body); + + debug_assert_eq!(original.offsets, preloaded.offsets); + debug_assert_eq!(original.bytes, preloaded.bytes); + } + + #[test] + fn test_type_section() { + use ValueType::*; + let arena = &Bump::new(); + let signatures = [ + Signature { + param_types: bumpalo::vec![in arena], + ret_type: None, + }, + Signature { + param_types: bumpalo::vec![in arena; I32, I64, F32, F64], + ret_type: None, + }, + Signature { + param_types: bumpalo::vec![in arena; I32, I32, I32], + ret_type: Some(I32), + }, + ]; + let mut section = TypeSection::new(arena, signatures.len()); + for sig in signatures { + section.insert(sig); + } + test_assert_types_preload(arena, §ion); + } } diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index d6106a1f3f..aefc5c64e8 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -135,8 +135,6 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( T::insert_test_wrapper(arena, &mut wasm_module, TEST_WRAPPER_NAME, main_fn_index); - roc_gen_wasm::wasm_module::sections::test_assert_preload(arena, &wasm_module); - let needs_linking = !wasm_module.import.entries.is_empty(); let mut app_module_bytes = std::vec::Vec::with_capacity(4096); From b8ab6af20338559d457fadb401812fa8aa07148e Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 8 Jan 2022 15:29:13 +0000 Subject: [PATCH 172/541] Wasm: move WasmModule definition to mod.rs --- compiler/gen_wasm/src/backend.rs | 4 +- compiler/gen_wasm/src/wasm_module/mod.rs | 101 +++++++++++++++++- compiler/gen_wasm/src/wasm_module/sections.rs | 96 +---------------- 3 files changed, 104 insertions(+), 97 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 1c8dbdf1c4..af21fd770b 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -23,11 +23,11 @@ use crate::wasm_module::linking::{ }; use crate::wasm_module::sections::{ CodeSection, DataMode, DataSection, DataSegment, ExportSection, FunctionSection, GlobalSection, - Import, ImportDesc, ImportSection, MemorySection, OpaqueSection, TypeSection, WasmModule, + Import, ImportDesc, ImportSection, MemorySection, OpaqueSection, TypeSection, }; use crate::wasm_module::{ code_builder, CodeBuilder, ConstExpr, Export, ExportType, Global, GlobalType, LocalId, - Signature, SymInfo, ValueType, + Signature, SymInfo, ValueType, WasmModule, }; use crate::{ copy_memory, round_up_to_alignment, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, diff --git a/compiler/gen_wasm/src/wasm_module/mod.rs b/compiler/gen_wasm/src/wasm_module/mod.rs index 7c643f97b3..8b61947ff1 100644 --- a/compiler/gen_wasm/src/wasm_module/mod.rs +++ b/compiler/gen_wasm/src/wasm_module/mod.rs @@ -6,4 +6,103 @@ pub mod serialize; pub use code_builder::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState}; pub use linking::SymInfo; -pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature, WasmModule}; +pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature}; + +use self::linking::{LinkingSection, RelocationSection}; +use self::sections::{ + CodeSection, DataSection, ExportSection, FunctionSection, GlobalSection, ImportSection, + MemorySection, OpaqueSection, TypeSection, +}; +use self::serialize::{SerialBuffer, Serialize}; + +#[derive(Debug)] +pub struct WasmModule<'a> { + pub types: TypeSection<'a>, + pub import: ImportSection<'a>, + pub function: FunctionSection<'a>, + pub table: OpaqueSection<'a>, + pub memory: MemorySection, + pub global: GlobalSection<'a>, + pub export: ExportSection<'a>, + pub start: OpaqueSection<'a>, + pub element: OpaqueSection<'a>, + pub code: CodeSection<'a>, + pub data: DataSection<'a>, + pub linking: LinkingSection<'a>, + pub relocations: RelocationSection<'a>, +} + +impl<'a> WasmModule<'a> { + pub const WASM_VERSION: u32 = 1; + + /// Create entries in the Type and Function sections for a function signature + pub fn add_function_signature(&mut self, signature: Signature<'a>) { + let index = self.types.insert(signature); + self.function.add_sig(index); + } + + /// Serialize the module to bytes + /// (Mutates some data related to linking) + pub fn serialize_mut(&mut self, buffer: &mut T) { + buffer.append_u8(0); + buffer.append_slice("asm".as_bytes()); + buffer.write_unencoded_u32(Self::WASM_VERSION); + + // Keep track of (non-empty) section indices for linking + let mut counter = SectionCounter { + buffer_size: buffer.size(), + section_index: 0, + }; + + counter.serialize_and_count(buffer, &self.types); + counter.serialize_and_count(buffer, &self.import); + counter.serialize_and_count(buffer, &self.function); + counter.serialize_and_count(buffer, &self.table); + counter.serialize_and_count(buffer, &self.memory); + counter.serialize_and_count(buffer, &self.global); + counter.serialize_and_count(buffer, &self.export); + counter.serialize_and_count(buffer, &self.start); + counter.serialize_and_count(buffer, &self.element); + + // Code section is the only one with relocations so we can stop counting + let code_section_index = counter.section_index; + self.code + .serialize_with_relocs(buffer, &mut self.relocations.entries); + + self.data.serialize(buffer); + + self.linking.serialize(buffer); + + self.relocations.target_section_index = Some(code_section_index); + self.relocations.serialize(buffer); + } +} + +/// Helper struct to count non-empty sections. +/// Needed to generate linking data, which refers to target sections by index. +struct SectionCounter { + buffer_size: usize, + section_index: u32, +} + +impl SectionCounter { + /// Update the section counter if buffer size increased since last call + #[inline] + fn update(&mut self, buffer: &mut SB) { + let new_size = buffer.size(); + if new_size > self.buffer_size { + self.section_index += 1; + self.buffer_size = new_size; + } + } + + #[inline] + fn serialize_and_count( + &mut self, + buffer: &mut SB, + section: &S, + ) { + section.serialize(buffer); + self.update(buffer); + } +} diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index d47fff12e0..254ccbc904 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -1,7 +1,7 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; -use super::linking::{LinkingSection, RelocationEntry, RelocationSection}; +use super::linking::RelocationEntry; use super::opcodes::OpCode; use super::serialize::{decode_u32_or_panic, SerialBuffer, Serialize}; use super::{CodeBuilder, ValueType}; @@ -328,7 +328,7 @@ impl<'a> FunctionSection<'a> { } } - fn add_sig(&mut self, sig_id: u32) { + pub(super) fn add_sig(&mut self, sig_id: u32) { self.bytes.encode_u32(sig_id); self.count += 1; } @@ -639,35 +639,6 @@ impl Serialize for DataSection<'_> { * *******************************************************************/ -/// Helper struct to count non-empty sections. -/// Needed to generate linking data, which refers to target sections by index. -struct SectionCounter { - buffer_size: usize, - section_index: u32, -} - -impl SectionCounter { - /// Update the section counter if buffer size increased since last call - #[inline] - fn update(&mut self, buffer: &mut SB) { - let new_size = buffer.size(); - if new_size > self.buffer_size { - self.section_index += 1; - self.buffer_size = new_size; - } - } - - #[inline] - fn serialize_and_count( - &mut self, - buffer: &mut SB, - section: &S, - ) { - section.serialize(buffer); - self.update(buffer); - } -} - /// A Wasm module section that we don't use for Roc code, /// but may be present in a preloaded binary #[derive(Debug, Default)] @@ -689,69 +660,6 @@ impl Serialize for OpaqueSection<'_> { } } -#[derive(Debug)] -pub struct WasmModule<'a> { - pub types: TypeSection<'a>, - pub import: ImportSection<'a>, - pub function: FunctionSection<'a>, - pub table: OpaqueSection<'a>, - pub memory: MemorySection, - pub global: GlobalSection<'a>, - pub export: ExportSection<'a>, - pub start: OpaqueSection<'a>, - pub element: OpaqueSection<'a>, - pub code: CodeSection<'a>, - pub data: DataSection<'a>, - pub linking: LinkingSection<'a>, - pub relocations: RelocationSection<'a>, -} - -impl<'a> WasmModule<'a> { - pub const WASM_VERSION: u32 = 1; - - /// Create entries in the Type and Function sections for a function signature - pub fn add_function_signature(&mut self, signature: Signature<'a>) { - let index = self.types.insert(signature); - self.function.add_sig(index); - } - - /// Serialize the module to bytes - /// (Mutates some data related to linking) - pub fn serialize_mut(&mut self, buffer: &mut T) { - buffer.append_u8(0); - buffer.append_slice("asm".as_bytes()); - buffer.write_unencoded_u32(Self::WASM_VERSION); - - // Keep track of (non-empty) section indices for linking - let mut counter = SectionCounter { - buffer_size: buffer.size(), - section_index: 0, - }; - - counter.serialize_and_count(buffer, &self.types); - counter.serialize_and_count(buffer, &self.import); - counter.serialize_and_count(buffer, &self.function); - counter.serialize_and_count(buffer, &self.table); - counter.serialize_and_count(buffer, &self.memory); - counter.serialize_and_count(buffer, &self.global); - counter.serialize_and_count(buffer, &self.export); - counter.serialize_and_count(buffer, &self.start); - counter.serialize_and_count(buffer, &self.element); - - // Code section is the only one with relocations so we can stop counting - let code_section_index = counter.section_index; - self.code - .serialize_with_relocs(buffer, &mut self.relocations.entries); - - self.data.serialize(buffer); - - self.linking.serialize(buffer); - - self.relocations.target_section_index = Some(code_section_index); - self.relocations.serialize(buffer); - } -} - #[cfg(test)] mod tests { use super::*; From 5a39002e8b9719132b410a19859c2d7acc94573f Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 8 Jan 2022 16:12:48 +0000 Subject: [PATCH 173/541] Wasm: Serialize WasmModule without linking info --- compiler/gen_wasm/src/lib.rs | 4 +-- .../gen_wasm/src/wasm_module/code_builder.rs | 35 +++++++++++++++++++ compiler/gen_wasm/src/wasm_module/mod.rs | 22 +++++++++++- compiler/gen_wasm/src/wasm_module/sections.rs | 15 ++++++++ .../gen_wasm/src/wasm_module/serialize.rs | 2 +- compiler/test_gen/src/helpers/wasm.rs | 2 +- 6 files changed, 75 insertions(+), 5 deletions(-) diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 55c6779464..c0f3a8e259 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -40,9 +40,9 @@ pub fn build_module<'a>( interns: &'a mut Interns, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) -> Result, String> { - let (mut wasm_module, _) = build_module_help(env, interns, procedures)?; + let (wasm_module, _) = build_module_help(env, interns, procedures)?; let mut buffer = std::vec::Vec::with_capacity(4096); - wasm_module.serialize_mut(&mut buffer); + wasm_module.serialize(&mut buffer); Ok(buffer) } diff --git a/compiler/gen_wasm/src/wasm_module/code_builder.rs b/compiler/gen_wasm/src/wasm_module/code_builder.rs index 09510a070d..20b6db9c82 100644 --- a/compiler/gen_wasm/src/wasm_module/code_builder.rs +++ b/compiler/gen_wasm/src/wasm_module/code_builder.rs @@ -156,6 +156,12 @@ pub struct CodeBuilder<'a> { relocations: Vec<'a, RelocationEntry>, } +impl<'a> Serialize for CodeBuilder<'a> { + fn serialize(&self, buffer: &mut T) { + self.serialize_without_relocs(buffer); + } +} + #[allow(clippy::new_without_default)] impl<'a> CodeBuilder<'a> { pub fn new(arena: &'a Bump) -> Self { @@ -470,6 +476,35 @@ impl<'a> CodeBuilder<'a> { ***********************************************************/ + /// Serialize all byte vectors in the right order + /// Also update relocation offsets relative to the base offset (code section body start) + pub fn serialize_without_relocs(&self, buffer: &mut T) { + buffer.append_slice(&self.inner_length); + buffer.append_slice(&self.preamble); + + let mut code_pos = 0; + let mut insert_iter = self.insertions.iter(); + loop { + let next_insert = insert_iter.next(); + let next_pos = match next_insert { + Some(Insertion { at, .. }) => *at, + None => self.code.len(), + }; + + buffer.append_slice(&self.code[code_pos..next_pos]); + + match next_insert { + Some(Insertion { at, start, end }) => { + buffer.append_slice(&self.insert_bytes[*start..*end]); + code_pos = *at; + } + None => { + break; + } + } + } + } + /// Serialize all byte vectors in the right order /// Also update relocation offsets relative to the base offset (code section body start) pub fn serialize_with_relocs( diff --git a/compiler/gen_wasm/src/wasm_module/mod.rs b/compiler/gen_wasm/src/wasm_module/mod.rs index 8b61947ff1..63797d2eb5 100644 --- a/compiler/gen_wasm/src/wasm_module/mod.rs +++ b/compiler/gen_wasm/src/wasm_module/mod.rs @@ -41,9 +41,29 @@ impl<'a> WasmModule<'a> { self.function.add_sig(index); } + /// Serialize the module to bytes + /// (not using Serialize trait because it's just one more thing to export) + pub fn serialize(&self, buffer: &mut T) { + buffer.append_u8(0); + buffer.append_slice("asm".as_bytes()); + buffer.write_unencoded_u32(Self::WASM_VERSION); + + self.types.serialize(buffer); + self.import.serialize(buffer); + self.function.serialize(buffer); + self.table.serialize(buffer); + self.memory.serialize(buffer); + self.global.serialize(buffer); + self.export.serialize(buffer); + self.start.serialize(buffer); + self.element.serialize(buffer); + self.code.serialize(buffer); + self.data.serialize(buffer); + } + /// Serialize the module to bytes /// (Mutates some data related to linking) - pub fn serialize_mut(&mut self, buffer: &mut T) { + pub fn serialize_with_linker_data_mut(&mut self, buffer: &mut T) { buffer.append_u8(0); buffer.append_slice("asm".as_bytes()); buffer.write_unencoded_u32(Self::WASM_VERSION); diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 254ccbc904..d0679e5570 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -557,6 +557,21 @@ impl<'a> CodeSection<'a> { } } +impl<'a> Serialize for CodeSection<'a> { + fn serialize(&self, buffer: &mut T) { + let header_indices = write_section_header(buffer, SectionId::Code); + buffer.encode_u32(self.preloaded_count + self.code_builders.len() as u32); + + buffer.append_slice(&self.preloaded_bytes); + + for code_builder in self.code_builders.iter() { + code_builder.serialize(buffer); + } + + update_section_size(buffer, header_indices); + } +} + /******************************************************************* * * Data section diff --git a/compiler/gen_wasm/src/wasm_module/serialize.rs b/compiler/gen_wasm/src/wasm_module/serialize.rs index 5aa7665062..75dab23f80 100644 --- a/compiler/gen_wasm/src/wasm_module/serialize.rs +++ b/compiler/gen_wasm/src/wasm_module/serialize.rs @@ -3,7 +3,7 @@ use std::{fmt::Debug, iter::FromIterator}; use bumpalo::collections::vec::Vec; use roc_reporting::internal_error; -pub trait Serialize { +pub(super) trait Serialize { fn serialize(&self, buffer: &mut T); } diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index aefc5c64e8..41975e2068 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -138,7 +138,7 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( let needs_linking = !wasm_module.import.entries.is_empty(); let mut app_module_bytes = std::vec::Vec::with_capacity(4096); - wasm_module.serialize_mut(&mut app_module_bytes); + wasm_module.serialize(&mut app_module_bytes); (app_module_bytes, needs_linking) } From 56316da870ffcbeb7d8ad751ace9ede0ca32637d Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 8 Jan 2022 16:14:25 +0000 Subject: [PATCH 174/541] Wasm: skip linking step in tests --- compiler/test_gen/src/helpers/mod.rs | 1 + compiler/test_gen/src/helpers/wasm.rs | 113 ++++---------------------- 2 files changed, 19 insertions(+), 95 deletions(-) diff --git a/compiler/test_gen/src/helpers/mod.rs b/compiler/test_gen/src/helpers/mod.rs index 6d2c6dfcd4..5eac4eb5cd 100644 --- a/compiler/test_gen/src/helpers/mod.rs +++ b/compiler/test_gen/src/helpers/mod.rs @@ -10,6 +10,7 @@ pub mod wasm; #[cfg(feature = "gen-wasm")] pub mod wasm32_test_result; +#[allow(dead_code)] pub fn zig_executable() -> String { match std::env::var("ROC_ZIG") { Ok(path) => path, diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 41975e2068..fbbe434629 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -3,7 +3,6 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use std::path::{Path, PathBuf}; -use tempfile::{tempdir, TempDir}; use wasmer::{Memory, WasmPtr}; use crate::helpers::from_wasm32_memory::FromWasm32Memory; @@ -12,13 +11,13 @@ use roc_can::builtins::builtin_defs_map; use roc_collections::all::{MutMap, MutSet}; use roc_gen_wasm::{DEBUG_LOG_SETTINGS, MEMORY_NAME}; +#[allow(unused_imports)] +use roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS; + // Should manually match build.rs const PLATFORM_FILENAME: &str = "wasm_test_platform"; const OUT_DIR_VAR: &str = "TEST_GEN_OUT"; -#[allow(unused_imports)] -use roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS; - const TEST_WRAPPER_NAME: &str = "test_wrapper"; fn promote_expr_to_module(src: &str) -> String { @@ -47,27 +46,16 @@ pub fn compile_and_load<'a, T: Wasm32TestResult>( src: &str, stdlib: &'a roc_builtins::std::StdLib, _test_wrapper_type_info: PhantomData, - test_type: TestType, + _test_type: TestType, ) -> wasmer::Instance { - let (app_module_bytes, needs_linking) = - compile_roc_to_wasm_bytes(arena, src, stdlib, _test_wrapper_type_info); + let compiled_bytes = compile_roc_to_wasm_bytes(arena, src, stdlib, _test_wrapper_type_info); - let keep_test_binary = DEBUG_LOG_SETTINGS.keep_test_binary; - let build_dir_hash = if keep_test_binary { - // Keep the output files for debugging, in a directory with a hash in the name - Some(src_hash(src)) - } else { - // Use a temporary build directory for linking, then delete it - None + if DEBUG_LOG_SETTINGS.keep_test_binary { + let build_dir_hash = src_hash(src); + save_wasm_file(&compiled_bytes, build_dir_hash) }; - let final_bytes = if needs_linking || keep_test_binary { - run_linker(app_module_bytes, build_dir_hash, test_type) - } else { - app_module_bytes - }; - - load_bytes_into_runtime(final_bytes) + load_bytes_into_runtime(compiled_bytes) } fn src_hash(src: &str) -> u64 { @@ -81,7 +69,7 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( src: &str, stdlib: &'a roc_builtins::std::StdLib, _test_wrapper_type_info: PhantomData, -) -> (Vec, bool) { +) -> Vec { let filename = PathBuf::from("Test.roc"); let src_dir = Path::new("fake/test/path"); @@ -135,86 +123,21 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( T::insert_test_wrapper(arena, &mut wasm_module, TEST_WRAPPER_NAME, main_fn_index); - let needs_linking = !wasm_module.import.entries.is_empty(); - let mut app_module_bytes = std::vec::Vec::with_capacity(4096); wasm_module.serialize(&mut app_module_bytes); - (app_module_bytes, needs_linking) + app_module_bytes } -fn run_linker( - app_module_bytes: Vec, - build_dir_hash: Option, - test_type: TestType, -) -> Vec { - let tmp_dir: TempDir; // directory for normal test runs, deleted when dropped - let debug_dir: String; // persistent directory for debugging +fn save_wasm_file(app_module_bytes: &[u8], build_dir_hash: u64) { + let debug_dir_str = format!("/tmp/roc/gen_wasm/{:016x}", build_dir_hash); + let debug_dir_path = Path::new(&debug_dir_str); + let final_wasm_file = debug_dir_path.join("final.wasm"); - let wasm_build_dir: &Path = if let Some(src_hash) = build_dir_hash { - debug_dir = format!("/tmp/roc/gen_wasm/{:016x}", src_hash); - std::fs::remove_file(format!("{}/app.o", debug_dir)).unwrap_or_else(|_| {}); - std::fs::remove_file(format!("{}/final.wasm", debug_dir)).unwrap_or_else(|_| {}); - std::fs::create_dir_all(&debug_dir).unwrap(); - println!( - "Debug commands:\n\twasm-objdump -dx {}/app.o\n\twasm-objdump -dx {}/final.wasm", - &debug_dir, &debug_dir, - ); - Path::new(&debug_dir) - } else { - tmp_dir = tempdir().unwrap(); - tmp_dir.path() - }; + std::fs::create_dir_all(debug_dir_path).unwrap(); + std::fs::write(&final_wasm_file, app_module_bytes).unwrap(); - let final_wasm_file = wasm_build_dir.join("final.wasm"); - let app_o_file = wasm_build_dir.join("app.o"); - let test_out_dir = std::env::var(OUT_DIR_VAR).unwrap(); - let test_platform_o = format!("{}/{}.o", test_out_dir, PLATFORM_FILENAME); - - // write the module to a file so the linker can access it - std::fs::write(&app_o_file, &app_module_bytes).unwrap(); - - let mut args = vec![ - "wasm-ld", - // input files - app_o_file.to_str().unwrap(), - &test_platform_o, - // output - "-o", - final_wasm_file.to_str().unwrap(), - // we don't define `_start` - "--no-entry", - // If you only specify test_wrapper, it will stop at the call to UserApp_main_1 - // But if you specify both exports, you get all the dependencies. - // - // It seems that it will not write out an export you didn't explicitly specify, - // even if it's a dependency of another export! - "--export", - "test_wrapper", - "--export", - "#UserApp_main_1", - ]; - - if matches!(test_type, TestType::Refcount) { - // If we always export this, tests run ~2.5x slower! Not sure why. - args.extend_from_slice(&["--export", "init_refcount_test"]); - } - - let linker_output = std::process::Command::new(&crate::helpers::zig_executable()) - .args(&args) - .output() - .unwrap(); - - if !linker_output.status.success() { - print!("\nLINKER FAILED\n"); - for arg in args { - print!("{} ", arg); - } - println!("\n{}", std::str::from_utf8(&linker_output.stdout).unwrap()); - println!("{}", std::str::from_utf8(&linker_output.stderr).unwrap()); - } - - std::fs::read(final_wasm_file).unwrap() + println!("Debug command:\n\twasm-objdump -dx {}", final_wasm_file.to_str().unwrap()); } fn load_bytes_into_runtime(bytes: Vec) -> wasmer::Instance { From fb5ac051551dd6a4bc6f925544dd00b38840d3ef Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 8 Jan 2022 17:12:48 +0000 Subject: [PATCH 175/541] Wasm: Optional debug code in test_gen build script --- compiler/test_gen/build.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/test_gen/build.rs b/compiler/test_gen/build.rs index d648695d4a..ea60706621 100644 --- a/compiler/test_gen/build.rs +++ b/compiler/test_gen/build.rs @@ -47,7 +47,11 @@ fn build_wasm_platform_and_builtins(out_dir: &str) { &format!("-femit-bin={}/{}.o", out_dir, PLATFORM_FILENAME), ]; - run_command(Path::new("."), &zig_executable(), args); + let zig = zig_executable(); + + // println!("{} {}", zig, args.join(" ")); + + run_command(Path::new("."), &zig, args); } fn feature_is_enabled(feature_name: &str) -> bool { From f4ac5bffa31f032584797fd2d156b622ff9dcc5b Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 8 Jan 2022 17:13:49 +0000 Subject: [PATCH 176/541] Wasm: replace builtins imports with a hashmap --- compiler/gen_wasm/src/backend.rs | 55 +++++--------------------------- 1 file changed, 8 insertions(+), 47 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index af21fd770b..432d4feb10 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -19,20 +19,19 @@ use crate::low_level::{dispatch_low_level, LowlevelBuildResult}; use crate::storage::{StackMemoryLocation, Storage, StoredValue, StoredValueKind}; use crate::wasm_module::linking::{ DataSymbol, LinkingSection, LinkingSegment, RelocationSection, WasmObjectSymbol, - WASM_SYM_BINDING_WEAK, WASM_SYM_UNDEFINED, + WASM_SYM_BINDING_WEAK, }; use crate::wasm_module::sections::{ CodeSection, DataMode, DataSection, DataSegment, ExportSection, FunctionSection, GlobalSection, - Import, ImportDesc, ImportSection, MemorySection, OpaqueSection, TypeSection, + ImportSection, MemorySection, OpaqueSection, TypeSection, }; use crate::wasm_module::{ code_builder, CodeBuilder, ConstExpr, Export, ExportType, Global, GlobalType, LocalId, Signature, SymInfo, ValueType, WasmModule, }; use crate::{ - copy_memory, round_up_to_alignment, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, - DEBUG_LOG_SETTINGS, MEMORY_NAME, PTR_SIZE, PTR_TYPE, STACK_POINTER_GLOBAL_ID, - STACK_POINTER_NAME, + copy_memory, round_up_to_alignment, CopyMemoryConfig, Env, DEBUG_LOG_SETTINGS, MEMORY_NAME, + PTR_SIZE, PTR_TYPE, STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME, }; /// The memory address where the constants data will be loaded during module instantiation. @@ -48,7 +47,7 @@ pub struct WasmBackend<'a> { module: WasmModule<'a>, layout_ids: LayoutIds<'a>, next_constant_addr: u32, - builtin_sym_index_map: MutMap<&'a str, usize>, + preloaded_fn_index_map: MutMap<&'a str, u32>, proc_symbols: Vec<'a, (Symbol, u32)>, helper_proc_gen: CodeGenHelp<'a>, @@ -136,7 +135,7 @@ impl<'a> WasmBackend<'a> { layout_ids, next_constant_addr: CONST_SEGMENT_BASE_ADDR, - builtin_sym_index_map: MutMap::default(), + preloaded_fn_index_map: MutMap::default(), proc_symbols, helper_proc_gen, @@ -1522,46 +1521,8 @@ impl<'a> WasmBackend<'a> { ) { let num_wasm_args = param_types.len(); let has_return_val = ret_type.is_some(); - - let (fn_index, linker_symbol_index) = match self.builtin_sym_index_map.get(name) { - Some(sym_idx) => match &self.module.linking.symbol_table[*sym_idx] { - SymInfo::Function(WasmObjectSymbol::Imported { index, .. }) => { - (*index, *sym_idx as u32) - } - x => internal_error!("Invalid linker symbol for builtin {}: {:?}", name, x), - }, - - None => { - // Wasm function signature - let signature = Signature { - param_types, - ret_type, - }; - let signature_index = self.module.types.insert(signature); - - // Declare it as an import since it comes from a different .o file - let import_index = self.module.import.entries.len() as u32; - let import = Import { - module: BUILTINS_IMPORT_MODULE_NAME, - name: name.to_string(), - description: ImportDesc::Func { signature_index }, - }; - self.module.import.entries.push(import); - - // Provide symbol information for the linker - let sym_idx = self.module.linking.symbol_table.len(); - let sym_info = SymInfo::Function(WasmObjectSymbol::Imported { - flags: WASM_SYM_UNDEFINED, - index: import_index, - }); - self.module.linking.symbol_table.push(sym_info); - - // Remember that we have created all of this data, and don't need to do it again - self.builtin_sym_index_map.insert(name, sym_idx); - - (import_index, sym_idx as u32) - } - }; + let fn_index = self.preloaded_fn_index_map[name]; + let linker_symbol_index = u32::MAX; self.code_builder .call(fn_index, linker_symbol_index, num_wasm_args, has_return_val); From 5d5e0eca96cd7e57ba88905437fcbbbaf2afc580 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 8 Jan 2022 18:23:09 +0000 Subject: [PATCH 177/541] Wasm: Convert remaining sections to store bytes, and add .size() methods --- compiler/gen_wasm/src/backend.rs | 8 +- .../gen_wasm/src/wasm_module/code_builder.rs | 4 + compiler/gen_wasm/src/wasm_module/mod.rs | 18 +- compiler/gen_wasm/src/wasm_module/sections.rs | 176 +++++++++++++----- .../src/helpers/wasm32_test_result.rs | 2 +- 5 files changed, 154 insertions(+), 54 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 432d4feb10..76661fc4cf 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -109,11 +109,9 @@ impl<'a> WasmBackend<'a> { import: ImportSection::new(arena), function: FunctionSection::new(arena, num_procs), table: OpaqueSection::default(), - memory: MemorySection::new(MEMORY_INIT_SIZE), - global: GlobalSection { - entries: bumpalo::vec![in arena; stack_pointer], - }, - export: ExportSection { entries: exports }, + memory: MemorySection::new(arena, MEMORY_INIT_SIZE), + global: GlobalSection::new(arena, &[stack_pointer]), + export: ExportSection::new(arena, &exports), start: OpaqueSection::default(), element: OpaqueSection::default(), code: CodeSection { diff --git a/compiler/gen_wasm/src/wasm_module/code_builder.rs b/compiler/gen_wasm/src/wasm_module/code_builder.rs index 20b6db9c82..5676508287 100644 --- a/compiler/gen_wasm/src/wasm_module/code_builder.rs +++ b/compiler/gen_wasm/src/wasm_module/code_builder.rs @@ -476,6 +476,10 @@ impl<'a> CodeBuilder<'a> { ***********************************************************/ + pub fn size(&self) -> usize { + self.inner_length.len() + self.preamble.len() + self.code.len() + self.insert_bytes.len() + } + /// Serialize all byte vectors in the right order /// Also update relocation offsets relative to the base offset (code section body start) pub fn serialize_without_relocs(&self, buffer: &mut T) { diff --git a/compiler/gen_wasm/src/wasm_module/mod.rs b/compiler/gen_wasm/src/wasm_module/mod.rs index 63797d2eb5..2a4260f9a4 100644 --- a/compiler/gen_wasm/src/wasm_module/mod.rs +++ b/compiler/gen_wasm/src/wasm_module/mod.rs @@ -21,7 +21,7 @@ pub struct WasmModule<'a> { pub import: ImportSection<'a>, pub function: FunctionSection<'a>, pub table: OpaqueSection<'a>, - pub memory: MemorySection, + pub memory: MemorySection<'a>, pub global: GlobalSection<'a>, pub export: ExportSection<'a>, pub start: OpaqueSection<'a>, @@ -96,6 +96,22 @@ impl<'a> WasmModule<'a> { self.relocations.target_section_index = Some(code_section_index); self.relocations.serialize(buffer); } + + /// Module size in bytes (assuming no linker data) + /// May be slightly overestimated. Intended for allocating buffer capacity. + pub fn size(&self) -> usize { + self.types.size() + + self.import.size() + + self.function.size() + + self.table.size() + + self.memory.size() + + self.global.size() + + self.export.size() + + self.start.size() + + self.element.size() + + self.code.size() + + self.data.size() + } } /// Helper struct to count non-empty sections. diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index d0679e5570..91da93b0b2 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -69,19 +69,6 @@ pub fn update_section_size(buffer: &mut T, header_indices: Sect buffer.overwrite_padded_u32(header_indices.size_index, size as u32); } -/// Serialize a section that is just a vector of some struct -fn serialize_vector_section( - buffer: &mut B, - section_id: SectionId, - subsections: &[T], -) { - if !subsections.is_empty() { - let header_indices = write_section_header(buffer, section_id); - subsections.serialize(buffer); - update_section_size(buffer, header_indices); - } -} - /// Serialize a section that is stored as bytes and a count fn serialize_bytes_section( buffer: &mut B, @@ -97,6 +84,15 @@ fn serialize_bytes_section( } } +/// Most Wasm sections consist of an ID, a byte length, an item count, and a payload +fn size_of_bytes_section(payload: &[u8]) -> usize { + let id = 1; + let byte_length = 5; + let item_count = 5; + + id + byte_length + item_count + payload.len() +} + /******************************************************************* * * Type section @@ -139,6 +135,10 @@ impl<'a> TypeSection<'a> { } } + pub fn size(&self) -> usize { + size_of_bytes_section(&self.bytes) + } + /// Find a matching signature or insert a new one. Return the index. pub fn insert(&mut self, signature: Signature<'a>) -> u32 { let mut sig_bytes = Vec::with_capacity_in(signature.param_types.len() + 4, self.arena); @@ -283,27 +283,31 @@ impl Serialize for Import { #[derive(Debug)] pub struct ImportSection<'a> { - pub entries: Vec<'a, Import>, + pub count: u32, + pub bytes: Vec<'a, u8>, } impl<'a> ImportSection<'a> { pub fn new(arena: &'a Bump) -> Self { ImportSection { - entries: bumpalo::vec![in arena], + count: 0, + bytes: bumpalo::vec![in arena], } } - pub fn function_count(&self) -> usize { - self.entries - .iter() - .filter(|import| matches!(import.description, ImportDesc::Func { .. })) - .count() + pub fn append(&mut self, import: Import) { + import.serialize(&mut self.bytes); + self.count += 1; + } + + pub fn size(&self) -> usize { + size_of_bytes_section(&self.bytes) } } impl<'a> Serialize for ImportSection<'a> { fn serialize(&self, buffer: &mut T) { - serialize_vector_section(buffer, SectionId::Import, &self.entries); + serialize_bytes_section(buffer, SectionId::Import, self.count, &self.bytes); } } @@ -328,10 +332,14 @@ impl<'a> FunctionSection<'a> { } } - pub(super) fn add_sig(&mut self, sig_id: u32) { + pub fn add_sig(&mut self, sig_id: u32) { self.bytes.encode_u32(sig_id); self.count += 1; } + + pub fn size(&self) -> usize { + size_of_bytes_section(&self.bytes) + } } impl<'a> Serialize for FunctionSection<'a> { fn serialize(&self, buffer: &mut T) { @@ -368,38 +376,39 @@ impl Serialize for Limits { } #[derive(Debug)] -pub struct MemorySection(Option); +pub struct MemorySection<'a> { + pub count: u32, + pub bytes: Vec<'a, u8>, +} -impl MemorySection { +impl<'a> MemorySection<'a> { pub const PAGE_SIZE: u32 = 64 * 1024; - pub fn new(bytes: u32) -> Self { - if bytes == 0 { - MemorySection(None) + pub fn new(arena: &'a Bump, memory_bytes: u32) -> Self { + if memory_bytes == 0 { + MemorySection { + count: 0, + bytes: bumpalo::vec![in arena], + } } else { - let pages = (bytes + Self::PAGE_SIZE - 1) / Self::PAGE_SIZE; - MemorySection(Some(Limits::Min(pages))) + let pages = (memory_bytes + Self::PAGE_SIZE - 1) / Self::PAGE_SIZE; + let limits = Limits::Min(pages); + + let mut bytes = Vec::with_capacity_in(12, arena); + limits.serialize(&mut bytes); + + MemorySection { count: 1, bytes } } } - pub fn min_size(&self) -> Option { - match self { - MemorySection(Some(Limits::Min(min))) | MemorySection(Some(Limits::MinMax(min, _))) => { - Some(min * Self::PAGE_SIZE) - } - MemorySection(None) => None, - } + pub fn size(&self) -> usize { + size_of_bytes_section(&self.bytes) } } -impl Serialize for MemorySection { +impl<'a> Serialize for MemorySection<'a> { fn serialize(&self, buffer: &mut T) { - if let Some(limits) = &self.0 { - let header_indices = write_section_header(buffer, SectionId::Memory); - buffer.append_u8(1); - limits.serialize(buffer); - update_section_size(buffer, header_indices); - } + serialize_bytes_section(buffer, SectionId::Memory, self.count, &self.bytes); } } @@ -473,12 +482,36 @@ impl Serialize for Global { #[derive(Debug)] pub struct GlobalSection<'a> { - pub entries: Vec<'a, Global>, + pub count: u32, + pub bytes: Vec<'a, u8>, +} + +impl<'a> GlobalSection<'a> { + pub fn new(arena: &'a Bump, globals: &[Global]) -> Self { + let capacity = 13 * globals.len(); + let mut bytes = Vec::with_capacity_in(capacity, arena); + for global in globals { + global.serialize(&mut bytes); + } + GlobalSection { + count: globals.len() as u32, + bytes, + } + } + + pub fn append(&mut self, global: Global) { + global.serialize(&mut self.bytes); + self.count += 1; + } + + pub fn size(&self) -> usize { + size_of_bytes_section(&self.bytes) + } } impl<'a> Serialize for GlobalSection<'a> { fn serialize(&self, buffer: &mut T) { - serialize_vector_section(buffer, SectionId::Global, &self.entries); + serialize_bytes_section(buffer, SectionId::Global, self.count, &self.bytes); } } @@ -503,6 +536,17 @@ pub struct Export { pub ty: ExportType, pub index: u32, } + +impl Export { + fn size(&self) -> usize { + let name_len = 5; + let ty = 1; + let index = 5; + + self.name.len() + name_len + ty + index + } +} + impl Serialize for Export { fn serialize(&self, buffer: &mut T) { self.name.serialize(buffer); @@ -513,12 +557,36 @@ impl Serialize for Export { #[derive(Debug)] pub struct ExportSection<'a> { - pub entries: Vec<'a, Export>, + pub count: u32, + pub bytes: Vec<'a, u8>, +} + +impl<'a> ExportSection<'a> { + pub fn new(arena: &'a Bump, exports: &[Export]) -> Self { + let capacity = exports.iter().map(|e| e.size()).sum(); + let mut bytes = Vec::with_capacity_in(capacity, arena); + for export in exports { + export.serialize(&mut bytes); + } + ExportSection { + count: exports.len() as u32, + bytes, + } + } + + pub fn append(&mut self, export: Export) { + export.serialize(&mut self.bytes); + self.count += 1; + } + + pub fn size(&self) -> usize { + size_of_bytes_section(&self.bytes) + } } impl<'a> Serialize for ExportSection<'a> { fn serialize(&self, buffer: &mut T) { - serialize_vector_section(buffer, SectionId::Export, &self.entries); + serialize_bytes_section(buffer, SectionId::Export, self.count, &self.bytes); } } @@ -555,6 +623,12 @@ impl<'a> CodeSection<'a> { update_section_size(buffer, header_indices); code_section_body_index } + + pub fn size(&self) -> usize { + let preloaded_size = size_of_bytes_section(&self.preloaded_bytes); + let builders_size: usize = self.code_builders.iter().map(|cb| cb.size()).sum(); + preloaded_size + builders_size + } } impl<'a> Serialize for CodeSection<'a> { @@ -636,6 +710,10 @@ impl<'a> DataSection<'a> { segment.serialize(&mut self.bytes); index } + + pub fn size(&self) -> usize { + size_of_bytes_section(&self.bytes) + } } impl Serialize for DataSection<'_> { @@ -665,6 +743,10 @@ impl<'a> OpaqueSection<'a> { pub fn new(bytes: &'a [u8]) -> Self { Self { bytes } } + + pub fn size(&self) -> usize { + self.bytes.len() + } } impl Serialize for OpaqueSection<'_> { diff --git a/compiler/test_gen/src/helpers/wasm32_test_result.rs b/compiler/test_gen/src/helpers/wasm32_test_result.rs index bac8dc71e4..da1fe5cb59 100644 --- a/compiler/test_gen/src/helpers/wasm32_test_result.rs +++ b/compiler/test_gen/src/helpers/wasm32_test_result.rs @@ -21,7 +21,7 @@ pub trait Wasm32TestResult { ret_type: Some(ValueType::I32), }); - module.export.entries.push(Export { + module.export.append(Export { name: wrapper_name.to_string(), ty: ExportType::Func, index, From fd79613f0dafac3fd99b6c4300b0b3e6881081fa Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 9 Jan 2022 07:48:48 +0000 Subject: [PATCH 178/541] Wasm: load platform object file in tests and pass the bytes to the backend --- compiler/gen_wasm/src/lib.rs | 4 +++- compiler/test_gen/src/helpers/wasm.rs | 21 +++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index c0f3a8e259..111c537359 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -38,9 +38,10 @@ pub struct Env<'a> { pub fn build_module<'a>( env: &'a Env<'a>, interns: &'a mut Interns, + platform_bytes: &[u8], procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) -> Result, String> { - let (wasm_module, _) = build_module_help(env, interns, procedures)?; + let (wasm_module, _) = build_module_help(env, interns, platform_bytes, procedures)?; let mut buffer = std::vec::Vec::with_capacity(4096); wasm_module.serialize(&mut buffer); Ok(buffer) @@ -49,6 +50,7 @@ pub fn build_module<'a>( pub fn build_module_help<'a>( env: &'a Env<'a>, interns: &'a mut Interns, + platform_bytes: &[u8], procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) -> Result<(WasmModule<'a>, u32), String> { let mut layout_ids = LayoutIds::default(); diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index fbbe434629..7693bc9fd8 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -48,7 +48,10 @@ pub fn compile_and_load<'a, T: Wasm32TestResult>( _test_wrapper_type_info: PhantomData, _test_type: TestType, ) -> wasmer::Instance { - let compiled_bytes = compile_roc_to_wasm_bytes(arena, src, stdlib, _test_wrapper_type_info); + let platform_bytes = load_platform_and_builtins(); + + let compiled_bytes = + compile_roc_to_wasm_bytes(arena, stdlib, &platform_bytes, src, _test_wrapper_type_info); if DEBUG_LOG_SETTINGS.keep_test_binary { let build_dir_hash = src_hash(src); @@ -58,6 +61,12 @@ pub fn compile_and_load<'a, T: Wasm32TestResult>( load_bytes_into_runtime(compiled_bytes) } +fn load_platform_and_builtins() -> std::vec::Vec { + let out_dir = std::env::var(OUT_DIR_VAR).unwrap(); + let platform_path = Path::new(&out_dir).join([PLATFORM_FILENAME, "o"].join(".")); + std::fs::read(&platform_path).unwrap() +} + fn src_hash(src: &str) -> u64 { let mut hash_state = DefaultHasher::new(); src.hash(&mut hash_state); @@ -66,8 +75,9 @@ fn src_hash(src: &str) -> u64 { fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( arena: &'a bumpalo::Bump, - src: &str, stdlib: &'a roc_builtins::std::StdLib, + preload_bytes: &[u8], + src: &str, _test_wrapper_type_info: PhantomData, ) -> Vec { let filename = PathBuf::from("Test.roc"); @@ -119,7 +129,7 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( }; let (mut wasm_module, main_fn_index) = - roc_gen_wasm::build_module_help(&env, &mut interns, procedures).unwrap(); + roc_gen_wasm::build_module_help(&env, &mut interns, platform_bytes, procedures).unwrap(); T::insert_test_wrapper(arena, &mut wasm_module, TEST_WRAPPER_NAME, main_fn_index); @@ -137,7 +147,10 @@ fn save_wasm_file(app_module_bytes: &[u8], build_dir_hash: u64) { std::fs::create_dir_all(debug_dir_path).unwrap(); std::fs::write(&final_wasm_file, app_module_bytes).unwrap(); - println!("Debug command:\n\twasm-objdump -dx {}", final_wasm_file.to_str().unwrap()); + println!( + "Debug command:\n\twasm-objdump -dx {}", + final_wasm_file.to_str().unwrap() + ); } fn load_bytes_into_runtime(bytes: Vec) -> wasmer::Instance { From d3554b2ac0008e5e6044514edf5b5cc6e48110bc Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 9 Jan 2022 09:01:43 +0000 Subject: [PATCH 179/541] Wasm: Create Section trait for common functionality between module sections --- compiler/gen_wasm/src/wasm_module/mod.rs | 2 +- compiler/gen_wasm/src/wasm_module/sections.rs | 251 +++++++++--------- 2 files changed, 124 insertions(+), 129 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/mod.rs b/compiler/gen_wasm/src/wasm_module/mod.rs index 2a4260f9a4..6cefa46b4f 100644 --- a/compiler/gen_wasm/src/wasm_module/mod.rs +++ b/compiler/gen_wasm/src/wasm_module/mod.rs @@ -11,7 +11,7 @@ pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature} use self::linking::{LinkingSection, RelocationSection}; use self::sections::{ CodeSection, DataSection, ExportSection, FunctionSection, GlobalSection, ImportSection, - MemorySection, OpaqueSection, TypeSection, + MemorySection, OpaqueSection, Section, TypeSection, }; use self::serialize::{SerialBuffer, Serialize}; diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 91da93b0b2..919d7904a0 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -3,7 +3,7 @@ use bumpalo::Bump; use super::linking::RelocationEntry; use super::opcodes::OpCode; -use super::serialize::{decode_u32_or_panic, SerialBuffer, Serialize}; +use super::serialize::{decode_u32_or_panic, parse_u32_or_panic, SerialBuffer, Serialize}; use super::{CodeBuilder, ValueType}; /******************************************************************* @@ -32,6 +32,86 @@ pub enum SectionId { DataCount = 12, } +const SIZE_ENCODED_U32: usize = 5; +const SIZE_SECTION_HEADER: usize = std::mem::size_of::() + 2 * SIZE_ENCODED_U32; + +pub trait Section<'a>: Sized { + const ID: SectionId; + + fn get_bytes(&self) -> &[u8]; + fn get_count(&self) -> u32; + + fn size(&self) -> usize { + SIZE_SECTION_HEADER + self.get_bytes().len() + } + + fn preload(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self; +} + +macro_rules! section_impl { + ($structname: ident, $id: expr) => { + impl<'a> Section<'a> for $structname<'a> { + const ID: SectionId = $id; + + fn get_bytes(&self) -> &[u8] { + &self.bytes + } + + fn get_count(&self) -> u32 { + self.count + } + + fn preload(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self { + let (count, initial_bytes) = parse_section(Self::ID, module_bytes, cursor); + let mut bytes = Vec::with_capacity_in(initial_bytes.len() * 2, arena); + bytes.extend_from_slice(initial_bytes); + $structname { bytes, count } + } + + fn size(&self) -> usize { + let id = 1; + let encoded_length = 5; + let encoded_count = 5; + + id + encoded_length + encoded_count + self.bytes.len() + } + } + }; +} + +impl<'a, Sec> Serialize for Sec +where + Sec: Section<'a>, +{ + fn serialize(&self, buffer: &mut B) { + if !self.get_bytes().is_empty() { + let header_indices = write_section_header(buffer, Self::ID); + buffer.encode_u32(self.get_count()); + buffer.append_slice(self.get_bytes()); + update_section_size(buffer, header_indices); + } + } +} + +fn parse_section<'a>(id: SectionId, module_bytes: &'a [u8], cursor: &mut usize) -> (u32, &'a [u8]) { + if module_bytes[*cursor] != id as u8 { + return (0, &[]); + } + *cursor += 1; + + let section_size = parse_u32_or_panic(module_bytes, cursor); + let count_offset = *cursor; + let count = parse_u32_or_panic(module_bytes, cursor); + let body_offset = *cursor; + + let next_section_start = count_offset + section_size as usize; + let body = &module_bytes[body_offset..next_section_start]; + + *cursor = next_section_start; + + (count, body) +} + pub struct SectionHeaderIndices { size_index: usize, body_index: usize, @@ -69,30 +149,6 @@ pub fn update_section_size(buffer: &mut T, header_indices: Sect buffer.overwrite_padded_u32(header_indices.size_index, size as u32); } -/// Serialize a section that is stored as bytes and a count -fn serialize_bytes_section( - buffer: &mut B, - section_id: SectionId, - count: u32, - bytes: &[u8], -) { - if !bytes.is_empty() { - let header_indices = write_section_header(buffer, section_id); - buffer.encode_u32(count); - buffer.append_slice(bytes); - update_section_size(buffer, header_indices); - } -} - -/// Most Wasm sections consist of an ID, a byte length, an item count, and a payload -fn size_of_bytes_section(payload: &[u8]) -> usize { - let id = 1; - let byte_length = 5; - let item_count = 5; - - id + byte_length + item_count + payload.len() -} - /******************************************************************* * * Type section @@ -135,10 +191,6 @@ impl<'a> TypeSection<'a> { } } - pub fn size(&self) -> usize { - size_of_bytes_section(&self.bytes) - } - /// Find a matching signature or insert a new one. Return the index. pub fn insert(&mut self, signature: Signature<'a>) -> u32 { let mut sig_bytes = Vec::with_capacity_in(signature.param_types.len() + 4, self.arena); @@ -164,54 +216,44 @@ impl<'a> TypeSection<'a> { sig_id as u32 } - pub fn preload(arena: &'a Bump, section_body: &[u8]) -> Self { - if section_body.is_empty() { - return TypeSection { - arena, - bytes: Vec::new_in(arena), - offsets: Vec::new_in(arena), - }; - } - - let (count, content_offset) = decode_u32_or_panic(section_body); - - let mut bytes = Vec::with_capacity_in(section_body.len() * 2, arena); - bytes.extend_from_slice(§ion_body[content_offset..]); - - let mut offsets = Vec::with_capacity_in((count * 2) as usize, arena); - + fn populate_offsets(&mut self) { + self.offsets.clear(); let mut i = 0; - while i < bytes.len() { - offsets.push(i); + while i < self.bytes.len() { + self.offsets.push(i); - let sep = bytes[i]; - debug_assert!(sep == Signature::SEPARATOR); + debug_assert!(self.bytes[i] == Signature::SEPARATOR); i += 1; - let (n_params, n_params_size) = decode_u32_or_panic(&bytes[i..]); + let (n_params, n_params_size) = decode_u32_or_panic(&self.bytes[i..]); i += n_params_size; // skip over the array length that we just decoded i += n_params as usize; // skip over one byte per param type - let n_return_values = bytes[i]; + let n_return_values = self.bytes[i]; i += 1 + n_return_values as usize; } - - TypeSection { - arena, - bytes, - offsets, - } } } -impl<'a> Serialize for TypeSection<'a> { - fn serialize(&self, buffer: &mut T) { - serialize_bytes_section( - buffer, - SectionId::Type, - self.offsets.len() as u32, - &self.bytes, - ); +impl<'a> Section<'a> for TypeSection<'a> { + const ID: SectionId = SectionId::Type; + + fn get_bytes(&self) -> &[u8] { + &self.bytes + } + fn get_count(&self) -> u32 { + self.offsets.len() as u32 + } + + fn preload(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self { + let (count, initial_bytes) = parse_section(Self::ID, module_bytes, cursor); + let mut bytes = Vec::with_capacity_in(initial_bytes.len() * 2, arena); + bytes.extend_from_slice(initial_bytes); + TypeSection { + arena, + bytes, + offsets: Vec::with_capacity_in(2 * count as usize, arena), + } } } @@ -299,17 +341,9 @@ impl<'a> ImportSection<'a> { import.serialize(&mut self.bytes); self.count += 1; } - - pub fn size(&self) -> usize { - size_of_bytes_section(&self.bytes) - } } -impl<'a> Serialize for ImportSection<'a> { - fn serialize(&self, buffer: &mut T) { - serialize_bytes_section(buffer, SectionId::Import, self.count, &self.bytes); - } -} +section_impl!(ImportSection, SectionId::Import); /******************************************************************* * @@ -336,16 +370,9 @@ impl<'a> FunctionSection<'a> { self.bytes.encode_u32(sig_id); self.count += 1; } +} - pub fn size(&self) -> usize { - size_of_bytes_section(&self.bytes) - } -} -impl<'a> Serialize for FunctionSection<'a> { - fn serialize(&self, buffer: &mut T) { - serialize_bytes_section(buffer, SectionId::Function, self.count, &self.bytes); - } -} +section_impl!(FunctionSection, SectionId::Function); /******************************************************************* * @@ -400,17 +427,9 @@ impl<'a> MemorySection<'a> { MemorySection { count: 1, bytes } } } - - pub fn size(&self) -> usize { - size_of_bytes_section(&self.bytes) - } } -impl<'a> Serialize for MemorySection<'a> { - fn serialize(&self, buffer: &mut T) { - serialize_bytes_section(buffer, SectionId::Memory, self.count, &self.bytes); - } -} +section_impl!(MemorySection, SectionId::Memory); /******************************************************************* * @@ -503,17 +522,9 @@ impl<'a> GlobalSection<'a> { global.serialize(&mut self.bytes); self.count += 1; } - - pub fn size(&self) -> usize { - size_of_bytes_section(&self.bytes) - } } -impl<'a> Serialize for GlobalSection<'a> { - fn serialize(&self, buffer: &mut T) { - serialize_bytes_section(buffer, SectionId::Global, self.count, &self.bytes); - } -} +section_impl!(GlobalSection, SectionId::Global); /******************************************************************* * @@ -578,17 +589,9 @@ impl<'a> ExportSection<'a> { export.serialize(&mut self.bytes); self.count += 1; } - - pub fn size(&self) -> usize { - size_of_bytes_section(&self.bytes) - } } -impl<'a> Serialize for ExportSection<'a> { - fn serialize(&self, buffer: &mut T) { - serialize_bytes_section(buffer, SectionId::Export, self.count, &self.bytes); - } -} +section_impl!(ExportSection, SectionId::Export); /******************************************************************* * @@ -625,10 +628,12 @@ impl<'a> CodeSection<'a> { } pub fn size(&self) -> usize { - let preloaded_size = size_of_bytes_section(&self.preloaded_bytes); let builders_size: usize = self.code_builders.iter().map(|cb| cb.size()).sum(); - preloaded_size + builders_size + + SIZE_SECTION_HEADER + self.preloaded_bytes.len() + builders_size } + + // fn preload(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self {} } impl<'a> Serialize for CodeSection<'a> { @@ -692,37 +697,27 @@ impl Serialize for DataSegment<'_> { #[derive(Debug)] pub struct DataSection<'a> { - segment_count: u32, + count: u32, bytes: Vec<'a, u8>, } impl<'a> DataSection<'a> { pub fn new(arena: &'a Bump) -> Self { DataSection { - segment_count: 0, + count: 0, bytes: bumpalo::vec![in arena], } } pub fn append_segment(&mut self, segment: DataSegment<'a>) -> u32 { - let index = self.segment_count; - self.segment_count += 1; + let index = self.count; + self.count += 1; segment.serialize(&mut self.bytes); index } - - pub fn size(&self) -> usize { - size_of_bytes_section(&self.bytes) - } } -impl Serialize for DataSection<'_> { - fn serialize(&self, buffer: &mut T) { - if !self.bytes.is_empty() { - serialize_bytes_section(buffer, SectionId::Data, self.segment_count, &self.bytes); - } - } -} +section_impl!(DataSection, SectionId::Data); /******************************************************************* * @@ -771,7 +766,7 @@ mod tests { // Reconstruct a new TypeSection by "pre-loading" the bytes of the original! let body = &original_serialized[6..]; - let preloaded = TypeSection::preload(arena, body); + let preloaded = TypeSection::populate_offsets(arena, body); debug_assert_eq!(original.offsets, preloaded.offsets); debug_assert_eq!(original.bytes, preloaded.bytes); From 9c0abcd0da1add7540acc11943081a0d8c5fdf1d Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 9 Jan 2022 10:47:35 +0000 Subject: [PATCH 180/541] Wasm: Preload WasmModule from object file bytes --- compiler/build/src/program.rs | 10 +- compiler/gen_wasm/src/backend.rs | 76 ++------------- compiler/gen_wasm/src/lib.rs | 9 +- compiler/gen_wasm/src/wasm_module/mod.rs | 45 ++++++++- compiler/gen_wasm/src/wasm_module/sections.rs | 92 ++++++++++--------- .../gen_wasm/src/wasm_module/serialize.rs | 7 ++ compiler/test_gen/src/helpers/wasm.rs | 2 +- 7 files changed, 121 insertions(+), 120 deletions(-) diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 98ea9de4ce..a41cb7d41a 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -513,7 +513,15 @@ fn gen_from_mono_module_dev_wasm32( exposed_to_host, }; - let bytes = roc_gen_wasm::build_module(&env, &mut interns, procedures).unwrap(); + let todo_platform_and_builtins_object_file_bytes = &[]; + + let bytes = roc_gen_wasm::build_module( + &env, + &mut interns, + todo_platform_and_builtins_object_file_bytes, + procedures, + ) + .unwrap(); std::fs::write(&app_o_file, &bytes).expect("failed to write object to file"); diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 76661fc4cf..a88c4cfc3e 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -17,21 +17,14 @@ use roc_reporting::internal_error; use crate::layout::{CallConv, ReturnMethod, StackMemoryFormat, WasmLayout}; use crate::low_level::{dispatch_low_level, LowlevelBuildResult}; use crate::storage::{StackMemoryLocation, Storage, StoredValue, StoredValueKind}; -use crate::wasm_module::linking::{ - DataSymbol, LinkingSection, LinkingSegment, RelocationSection, WasmObjectSymbol, - WASM_SYM_BINDING_WEAK, -}; -use crate::wasm_module::sections::{ - CodeSection, DataMode, DataSection, DataSegment, ExportSection, FunctionSection, GlobalSection, - ImportSection, MemorySection, OpaqueSection, TypeSection, -}; +use crate::wasm_module::linking::{DataSymbol, LinkingSegment, WasmObjectSymbol}; +use crate::wasm_module::sections::{DataMode, DataSegment}; use crate::wasm_module::{ - code_builder, CodeBuilder, ConstExpr, Export, ExportType, Global, GlobalType, LocalId, - Signature, SymInfo, ValueType, WasmModule, + code_builder, CodeBuilder, LocalId, Signature, SymInfo, ValueType, WasmModule, }; use crate::{ - copy_memory, round_up_to_alignment, CopyMemoryConfig, Env, DEBUG_LOG_SETTINGS, MEMORY_NAME, - PTR_SIZE, PTR_TYPE, STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME, + copy_memory, round_up_to_alignment, CopyMemoryConfig, Env, DEBUG_LOG_SETTINGS, PTR_SIZE, + PTR_TYPE, }; /// The memory address where the constants data will be loaded during module instantiation. @@ -68,62 +61,9 @@ impl<'a> WasmBackend<'a> { interns: &'a mut Interns, layout_ids: LayoutIds<'a>, proc_symbols: Vec<'a, (Symbol, u32)>, - mut linker_symbols: Vec<'a, SymInfo>, - mut exports: Vec<'a, Export>, + module: WasmModule<'a>, helper_proc_gen: CodeGenHelp<'a>, ) -> Self { - const MEMORY_INIT_SIZE: u32 = 1024 * 1024; - let arena = env.arena; - let num_procs = proc_symbols.len(); - - exports.push(Export { - name: MEMORY_NAME.to_string(), - ty: ExportType::Mem, - index: 0, - }); - - let stack_pointer = Global { - ty: GlobalType { - value_type: ValueType::I32, - is_mutable: true, - }, - init: ConstExpr::I32(MEMORY_INIT_SIZE as i32), - }; - - exports.push(Export { - name: STACK_POINTER_NAME.to_string(), - ty: ExportType::Global, - index: STACK_POINTER_GLOBAL_ID, - }); - - linker_symbols.push(SymInfo::Global(WasmObjectSymbol::Defined { - flags: WASM_SYM_BINDING_WEAK, // TODO: this works but means external .o files decide how much stack we have! - index: STACK_POINTER_GLOBAL_ID, - name: STACK_POINTER_NAME.to_string(), - })); - let mut linking = LinkingSection::new(arena); - linking.symbol_table = linker_symbols; - - let module = WasmModule { - types: TypeSection::new(arena, num_procs), - import: ImportSection::new(arena), - function: FunctionSection::new(arena, num_procs), - table: OpaqueSection::default(), - memory: MemorySection::new(arena, MEMORY_INIT_SIZE), - global: GlobalSection::new(arena, &[stack_pointer]), - export: ExportSection::new(arena, &exports), - start: OpaqueSection::default(), - element: OpaqueSection::default(), - code: CodeSection { - preloaded_count: 0, - preloaded_bytes: Vec::with_capacity_in(0, arena), - code_builders: Vec::with_capacity_in(num_procs, arena), - }, - data: DataSection::new(arena), - linking, - relocations: RelocationSection::new(arena, "reloc.CODE"), - }; - WasmBackend { env, interns, @@ -140,8 +80,8 @@ impl<'a> WasmBackend<'a> { // Function-level data block_depth: 0, joinpoint_label_map: MutMap::default(), - code_builder: CodeBuilder::new(arena), - storage: Storage::new(arena), + code_builder: CodeBuilder::new(env.arena), + storage: Storage::new(env.arena), debug_current_proc_index: 0, } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 111c537359..3c88b7e1f1 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -38,10 +38,10 @@ pub struct Env<'a> { pub fn build_module<'a>( env: &'a Env<'a>, interns: &'a mut Interns, - platform_bytes: &[u8], + preload_bytes: &[u8], procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) -> Result, String> { - let (wasm_module, _) = build_module_help(env, interns, platform_bytes, procedures)?; + let (wasm_module, _) = build_module_help(env, interns, preload_bytes, procedures)?; let mut buffer = std::vec::Vec::with_capacity(4096); wasm_module.serialize(&mut buffer); Ok(buffer) @@ -50,7 +50,7 @@ pub fn build_module<'a>( pub fn build_module_help<'a>( env: &'a Env<'a>, interns: &'a mut Interns, - platform_bytes: &[u8], + preload_bytes: &[u8], procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) -> Result<(WasmModule<'a>, u32), String> { let mut layout_ids = LayoutIds::default(); @@ -97,8 +97,7 @@ pub fn build_module_help<'a>( interns, layout_ids, proc_symbols, - linker_symbols, - exports, + WasmModule::preload(env.arena, preload_bytes), CodeGenHelp::new(env.arena, IntWidth::I32, env.module_id), ); diff --git a/compiler/gen_wasm/src/wasm_module/mod.rs b/compiler/gen_wasm/src/wasm_module/mod.rs index 6cefa46b4f..768b4cd8ab 100644 --- a/compiler/gen_wasm/src/wasm_module/mod.rs +++ b/compiler/gen_wasm/src/wasm_module/mod.rs @@ -4,14 +4,16 @@ pub mod opcodes; pub mod sections; pub mod serialize; +use bumpalo::Bump; pub use code_builder::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState}; pub use linking::SymInfo; +use roc_reporting::internal_error; pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature}; use self::linking::{LinkingSection, RelocationSection}; use self::sections::{ CodeSection, DataSection, ExportSection, FunctionSection, GlobalSection, ImportSection, - MemorySection, OpaqueSection, Section, TypeSection, + MemorySection, OpaqueSection, Section, SectionId, TypeSection, }; use self::serialize::{SerialBuffer, Serialize}; @@ -112,6 +114,47 @@ impl<'a> WasmModule<'a> { + self.code.size() + self.data.size() } + + pub fn preload(arena: &'a Bump, bytes: &[u8]) -> Self { + let is_valid_magic_number = &bytes[0..4] == "\0asm".as_bytes(); + let is_valid_version = &bytes[4..8] == Self::WASM_VERSION.to_le_bytes(); + if !is_valid_magic_number || !is_valid_version { + internal_error!("Invalid Wasm object file header for platform & builtins"); + } + + let mut cursor: usize = 8; + + let mut types = TypeSection::preload(arena, bytes, &mut cursor); + types.cache_offsets(); + let import = ImportSection::preload(arena, bytes, &mut cursor); + let function = FunctionSection::preload(arena, bytes, &mut cursor); + let table = OpaqueSection::preload(SectionId::Table, arena, bytes, &mut cursor); + let memory = MemorySection::preload(arena, bytes, &mut cursor); + let global = GlobalSection::preload(arena, bytes, &mut cursor); + let export = ExportSection::preload(arena, bytes, &mut cursor); + let start = OpaqueSection::preload(SectionId::Start, arena, bytes, &mut cursor); + let element = OpaqueSection::preload(SectionId::Element, arena, bytes, &mut cursor); + let code = CodeSection::preload(arena, bytes, &mut cursor); + let data = DataSection::preload(arena, bytes, &mut cursor); + let linking = LinkingSection::new(arena); + let relocations = RelocationSection::new(arena, "reloc.CODE"); + + WasmModule { + types, + import, + function, + table, + memory, + global, + export, + start, + element, + code, + data, + linking, + relocations, + } + } } /// Helper struct to count non-empty sections. diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 919d7904a0..c5b07fabee 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -100,12 +100,12 @@ fn parse_section<'a>(id: SectionId, module_bytes: &'a [u8], cursor: &mut usize) *cursor += 1; let section_size = parse_u32_or_panic(module_bytes, cursor); - let count_offset = *cursor; + let count_start = *cursor; let count = parse_u32_or_panic(module_bytes, cursor); - let body_offset = *cursor; + let body_start = *cursor; - let next_section_start = count_offset + section_size as usize; - let body = &module_bytes[body_offset..next_section_start]; + let next_section_start = count_start + section_size as usize; + let body = &module_bytes[body_start..next_section_start]; *cursor = next_section_start; @@ -183,14 +183,6 @@ pub struct TypeSection<'a> { } impl<'a> TypeSection<'a> { - pub fn new(arena: &'a Bump, capacity: usize) -> Self { - TypeSection { - arena, - bytes: Vec::with_capacity_in(capacity * 4, arena), - offsets: Vec::with_capacity_in(capacity, arena), - } - } - /// Find a matching signature or insert a new one. Return the index. pub fn insert(&mut self, signature: Signature<'a>) -> u32 { let mut sig_bytes = Vec::with_capacity_in(signature.param_types.len() + 4, self.arena); @@ -216,7 +208,7 @@ impl<'a> TypeSection<'a> { sig_id as u32 } - fn populate_offsets(&mut self) { + pub fn cache_offsets(&mut self) { self.offsets.clear(); let mut i = 0; while i < self.bytes.len() { @@ -330,13 +322,6 @@ pub struct ImportSection<'a> { } impl<'a> ImportSection<'a> { - pub fn new(arena: &'a Bump) -> Self { - ImportSection { - count: 0, - bytes: bumpalo::vec![in arena], - } - } - pub fn append(&mut self, import: Import) { import.serialize(&mut self.bytes); self.count += 1; @@ -359,13 +344,6 @@ pub struct FunctionSection<'a> { } impl<'a> FunctionSection<'a> { - pub fn new(arena: &'a Bump, capacity: usize) -> Self { - FunctionSection { - count: 0, - bytes: Vec::with_capacity_in(capacity, arena), - } - } - pub fn add_sig(&mut self, sig_id: u32) { self.bytes.encode_u32(sig_id); self.count += 1; @@ -633,7 +611,16 @@ impl<'a> CodeSection<'a> { SIZE_SECTION_HEADER + self.preloaded_bytes.len() + builders_size } - // fn preload(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self {} + pub fn preload(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self { + let (preloaded_count, initial_bytes) = parse_section(SectionId::Code, module_bytes, cursor); + let mut preloaded_bytes = Vec::with_capacity_in(initial_bytes.len() * 2, arena); + preloaded_bytes.extend_from_slice(initial_bytes); + CodeSection { + preloaded_count, + preloaded_bytes, + code_builders: Vec::with_capacity_in(0, arena), + } + } } impl<'a> Serialize for CodeSection<'a> { @@ -702,13 +689,6 @@ pub struct DataSection<'a> { } impl<'a> DataSection<'a> { - pub fn new(arena: &'a Bump) -> Self { - DataSection { - count: 0, - bytes: bumpalo::vec![in arena], - } - } - pub fn append_segment(&mut self, segment: DataSegment<'a>) -> u32 { let index = self.count; self.count += 1; @@ -735,20 +715,38 @@ pub struct OpaqueSection<'a> { } impl<'a> OpaqueSection<'a> { - pub fn new(bytes: &'a [u8]) -> Self { - Self { bytes } - } - pub fn size(&self) -> usize { self.bytes.len() } + + pub fn preload( + id: SectionId, + arena: &'a Bump, + module_bytes: &[u8], + cursor: &mut usize, + ) -> Self { + let bytes: &[u8]; + + if module_bytes[*cursor] != id as u8 { + bytes = &[]; + } else { + let section_start = *cursor; + *cursor += 1; + let section_size = parse_u32_or_panic(module_bytes, cursor); + let next_section_start = *cursor + section_size as usize; + bytes = &module_bytes[section_start..next_section_start]; + *cursor = next_section_start; + }; + + OpaqueSection { + bytes: arena.alloc_slice_clone(bytes), + } + } } impl Serialize for OpaqueSection<'_> { fn serialize(&self, buffer: &mut T) { - if !self.bytes.is_empty() { - buffer.append_slice(self.bytes); - } + buffer.append_slice(self.bytes); } } @@ -766,7 +764,7 @@ mod tests { // Reconstruct a new TypeSection by "pre-loading" the bytes of the original! let body = &original_serialized[6..]; - let preloaded = TypeSection::populate_offsets(arena, body); + let preloaded = TypeSection::cache_offsets(arena, body); debug_assert_eq!(original.offsets, preloaded.offsets); debug_assert_eq!(original.bytes, preloaded.bytes); @@ -790,7 +788,13 @@ mod tests { ret_type: Some(I32), }, ]; - let mut section = TypeSection::new(arena, signatures.len()); + let capacity = signatures.len(); + let mut section = TypeSection { + arena, + bytes: Vec::with_capacity_in(capacity * 4, arena), + offsets: Vec::with_capacity_in(capacity, arena), + }; + for sig in signatures { section.insert(sig); } diff --git a/compiler/gen_wasm/src/wasm_module/serialize.rs b/compiler/gen_wasm/src/wasm_module/serialize.rs index 75dab23f80..e7c8dead3e 100644 --- a/compiler/gen_wasm/src/wasm_module/serialize.rs +++ b/compiler/gen_wasm/src/wasm_module/serialize.rs @@ -254,6 +254,13 @@ pub fn decode_u32_or_panic(bytes: &[u8]) -> (u32, usize) { decode_u32(bytes).unwrap_or_else(|e| internal_error!("{}", e)) } +pub fn parse_u32_or_panic(bytes: &[u8], cursor: &mut usize) -> u32 { + let (value, new_offset) = + decode_u32(&bytes[*cursor..]).unwrap_or_else(|e| internal_error!("{}", e)); + *cursor = new_offset; + value +} + #[cfg(test)] mod tests { use super::*; diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 7693bc9fd8..b3a721624d 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -129,7 +129,7 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( }; let (mut wasm_module, main_fn_index) = - roc_gen_wasm::build_module_help(&env, &mut interns, platform_bytes, procedures).unwrap(); + roc_gen_wasm::build_module_help(&env, &mut interns, preload_bytes, procedures).unwrap(); T::insert_test_wrapper(arena, &mut wasm_module, TEST_WRAPPER_NAME, main_fn_index); From 3067358a336e6a4b0c0b51dda8b6e40b4b1dc026 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 9 Jan 2022 11:11:37 +0000 Subject: [PATCH 181/541] Wasm: test for LEB-128 decoder --- compiler/gen_wasm/src/wasm_module/serialize.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/compiler/gen_wasm/src/wasm_module/serialize.rs b/compiler/gen_wasm/src/wasm_module/serialize.rs index e7c8dead3e..0c948fba73 100644 --- a/compiler/gen_wasm/src/wasm_module/serialize.rs +++ b/compiler/gen_wasm/src/wasm_module/serialize.rs @@ -436,4 +436,18 @@ mod tests { &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f], ); } + + #[test] + fn test_decode_u32() { + assert_eq!(decode_u32(&[0]), Ok((0, 1))); + assert_eq!(decode_u32(&[64]), Ok((64, 1))); + assert_eq!(decode_u32(&[0x7f]), Ok((0x7f, 1))); + assert_eq!(decode_u32(&[0x80, 0x01]), Ok((0x80, 2))); + assert_eq!(decode_u32(&[0xff, 0x7f]), Ok((0x3fff, 2))); + assert_eq!(decode_u32(&[0x80, 0x80, 0x01]), Ok((0x4000, 3))); + assert_eq!(decode_u32(&[0xff, 0xff, 0xff, 0xff, 0x0f]), Ok((u32::MAX, 5))); + assert!(matches!(decode_u32(&[0x80; 6]), Err(_))); + assert!(matches!(decode_u32(&[0x80; 2]), Err(_))); + assert!(matches!(decode_u32(&[]), Err(_))); + } } From f9fbe461d1a143175f2bb1ed2ddf00102ba4c773 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 9 Jan 2022 11:31:41 +0000 Subject: [PATCH 182/541] Wasm: Fix section parsing bug --- compiler/gen_wasm/src/wasm_module/sections.rs | 9 +++---- .../gen_wasm/src/wasm_module/serialize.rs | 26 ++++++++++++++++--- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index c5b07fabee..9651e72cc5 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -760,11 +760,10 @@ mod tests { let mut original_serialized = Vec::with_capacity_in(6 + original.bytes.len(), arena); original.serialize(&mut original_serialized); - debug_assert!(original_serialized[0] == SectionId::Type as u8); - - // Reconstruct a new TypeSection by "pre-loading" the bytes of the original! - let body = &original_serialized[6..]; - let preloaded = TypeSection::cache_offsets(arena, body); + // Reconstruct a new TypeSection by "pre-loading" the bytes of the original + let mut cursor = 0; + let mut preloaded = TypeSection::preload(arena, &original_serialized, &mut cursor); + preloaded.cache_offsets(); debug_assert_eq!(original.offsets, preloaded.offsets); debug_assert_eq!(original.bytes, preloaded.bytes); diff --git a/compiler/gen_wasm/src/wasm_module/serialize.rs b/compiler/gen_wasm/src/wasm_module/serialize.rs index 0c948fba73..ce5ab5ef9d 100644 --- a/compiler/gen_wasm/src/wasm_module/serialize.rs +++ b/compiler/gen_wasm/src/wasm_module/serialize.rs @@ -255,9 +255,8 @@ pub fn decode_u32_or_panic(bytes: &[u8]) -> (u32, usize) { } pub fn parse_u32_or_panic(bytes: &[u8], cursor: &mut usize) -> u32 { - let (value, new_offset) = - decode_u32(&bytes[*cursor..]).unwrap_or_else(|e| internal_error!("{}", e)); - *cursor = new_offset; + let (value, len) = decode_u32(&bytes[*cursor..]).unwrap_or_else(|e| internal_error!("{}", e)); + *cursor += len; value } @@ -445,9 +444,28 @@ mod tests { assert_eq!(decode_u32(&[0x80, 0x01]), Ok((0x80, 2))); assert_eq!(decode_u32(&[0xff, 0x7f]), Ok((0x3fff, 2))); assert_eq!(decode_u32(&[0x80, 0x80, 0x01]), Ok((0x4000, 3))); - assert_eq!(decode_u32(&[0xff, 0xff, 0xff, 0xff, 0x0f]), Ok((u32::MAX, 5))); + assert_eq!( + decode_u32(&[0xff, 0xff, 0xff, 0xff, 0x0f]), + Ok((u32::MAX, 5)) + ); assert!(matches!(decode_u32(&[0x80; 6]), Err(_))); assert!(matches!(decode_u32(&[0x80; 2]), Err(_))); assert!(matches!(decode_u32(&[]), Err(_))); } + + #[test] + fn test_parse_u32_sequence() { + let bytes = &[0, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0x0f]; + let expected = [0, 128, u32::MAX]; + let mut cursor = 0; + + assert_eq!(parse_u32_or_panic(bytes, &mut cursor), expected[0]); + assert_eq!(cursor, 1); + + assert_eq!(parse_u32_or_panic(bytes, &mut cursor), expected[1]); + assert_eq!(cursor, 3); + + assert_eq!(parse_u32_or_panic(bytes, &mut cursor), expected[2]); + assert_eq!(cursor, 8); + } } From fa46ab95fce421d24aa8f7e59183f6cef835ee17 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 9 Jan 2022 11:46:17 +0000 Subject: [PATCH 183/541] Wasm: rename some constants --- compiler/gen_wasm/src/wasm_module/sections.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 9651e72cc5..594a7bb075 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -32,8 +32,8 @@ pub enum SectionId { DataCount = 12, } -const SIZE_ENCODED_U32: usize = 5; -const SIZE_SECTION_HEADER: usize = std::mem::size_of::() + 2 * SIZE_ENCODED_U32; +const MAX_SIZE_ENCODED_U32: usize = 5; +const MAX_SIZE_SECTION_HEADER: usize = std::mem::size_of::() + 2 * MAX_SIZE_ENCODED_U32; pub trait Section<'a>: Sized { const ID: SectionId; @@ -42,7 +42,7 @@ pub trait Section<'a>: Sized { fn get_count(&self) -> u32; fn size(&self) -> usize { - SIZE_SECTION_HEADER + self.get_bytes().len() + MAX_SIZE_SECTION_HEADER + self.get_bytes().len() } fn preload(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self; @@ -608,7 +608,7 @@ impl<'a> CodeSection<'a> { pub fn size(&self) -> usize { let builders_size: usize = self.code_builders.iter().map(|cb| cb.size()).sum(); - SIZE_SECTION_HEADER + self.preloaded_bytes.len() + builders_size + MAX_SIZE_SECTION_HEADER + self.preloaded_bytes.len() + builders_size } pub fn preload(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self { From 9f0e0d50994e9619138644486741b30de91bad10 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 9 Jan 2022 15:06:06 +0000 Subject: [PATCH 184/541] Wasm: create a hashmap of exported functions --- compiler/gen_wasm/src/lib.rs | 4 +- compiler/gen_wasm/src/wasm_module/sections.rs | 51 ++++++++++--------- .../src/helpers/wasm32_test_result.rs | 2 +- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 3c88b7e1f1..63921f366f 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -77,9 +77,9 @@ pub fn build_module_help<'a>( .to_symbol_string(sym, interns); if env.exposed_to_host.contains(&sym) { - main_fn_index = Some(fn_index); + maybe_main_fn_index = Some(fn_index); exports.push(Export { - name: fn_name.clone(), + name: env.arena.alloc_slice_copy(fn_name.as_bytes()), ty: ExportType::Func, index: fn_index, }); diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 594a7bb075..79b220e640 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -1,5 +1,6 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; +use roc_collections::all::MutMap; use super::linking::RelocationEntry; use super::opcodes::OpCode; @@ -520,23 +521,13 @@ pub enum ExportType { } #[derive(Debug)] -pub struct Export { - pub name: String, +pub struct Export<'a> { + pub name: &'a [u8], pub ty: ExportType, pub index: u32, } -impl Export { - fn size(&self) -> usize { - let name_len = 5; - let ty = 1; - let index = 5; - - self.name.len() + name_len + ty + index - } -} - -impl Serialize for Export { +impl Serialize for Export<'_> { fn serialize(&self, buffer: &mut T) { self.name.serialize(buffer); buffer.append_u8(self.ty as u8); @@ -551,22 +542,32 @@ pub struct ExportSection<'a> { } impl<'a> ExportSection<'a> { - pub fn new(arena: &'a Bump, exports: &[Export]) -> Self { - let capacity = exports.iter().map(|e| e.size()).sum(); - let mut bytes = Vec::with_capacity_in(capacity, arena); - for export in exports { - export.serialize(&mut bytes); - } - ExportSection { - count: exports.len() as u32, - bytes, - } - } - pub fn append(&mut self, export: Export) { export.serialize(&mut self.bytes); self.count += 1; } + + pub fn function_index_map(&self, arena: &'a Bump) -> MutMap<&'a [u8], u32> { + let mut map = MutMap::default(); + + let mut cursor = 0; + while cursor < self.bytes.len() { + let name_len = parse_u32_or_panic(&self.bytes, &mut cursor); + let name_end = cursor + name_len as usize; + let name_bytes = &self.bytes[cursor..name_end]; + let ty = self.bytes[name_end]; + + cursor = name_end + 1; + let index = parse_u32_or_panic(&self.bytes, &mut cursor); + + if ty == ExportType::Func as u8 { + let name: &'a [u8] = arena.alloc_slice_clone(name_bytes); + map.insert(name, index); + } + } + + map + } } section_impl!(ExportSection, SectionId::Export); diff --git a/compiler/test_gen/src/helpers/wasm32_test_result.rs b/compiler/test_gen/src/helpers/wasm32_test_result.rs index da1fe5cb59..e8a799f51c 100644 --- a/compiler/test_gen/src/helpers/wasm32_test_result.rs +++ b/compiler/test_gen/src/helpers/wasm32_test_result.rs @@ -22,7 +22,7 @@ pub trait Wasm32TestResult { }); module.export.append(Export { - name: wrapper_name.to_string(), + name: arena.alloc_slice_copy(wrapper_name.as_bytes()), ty: ExportType::Func, index, }); From 9f8f31b2b6b227afe888a11a5d0fe41ad18f3d3c Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 9 Jan 2022 15:06:54 +0000 Subject: [PATCH 185/541] Wasm: adjust function indices for preloading --- compiler/gen_wasm/src/backend.rs | 6 ++++-- compiler/gen_wasm/src/lib.rs | 9 ++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index a88c4cfc3e..e7e39531e4 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -539,14 +539,16 @@ impl<'a> WasmBackend<'a> { CallConv::C, ); - for (func_index, (ir_sym, linker_sym_index)) in + for (roc_proc_index, (ir_sym, linker_sym_index)) in self.proc_symbols.iter().enumerate() { + let wasm_fn_index = + self.module.code.preloaded_count + roc_proc_index as u32; if ir_sym == func_sym { let num_wasm_args = param_types.len(); let has_return_val = ret_type.is_some(); self.code_builder.call( - func_index as u32, + wasm_fn_index, *linker_sym_index, num_wasm_args, has_return_val, diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 63921f366f..b718ff9f43 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -58,7 +58,7 @@ pub fn build_module_help<'a>( let mut proc_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena); let mut linker_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena); let mut exports = Vec::with_capacity_in(4, env.arena); - let mut main_fn_index = None; + let mut maybe_main_fn_index = None; // Collect the symbols & names for the procedures, // and filter out procs we're going to inline @@ -92,12 +92,15 @@ pub fn build_module_help<'a>( fn_index += 1; } + let initial_module = WasmModule::preload(env.arena, preload_bytes); + let main_function_index = maybe_main_fn_index.unwrap() + initial_module.code.preloaded_count; + let mut backend = WasmBackend::new( env, interns, layout_ids, proc_symbols, - WasmModule::preload(env.arena, preload_bytes), + initial_module, CodeGenHelp::new(env.arena, IntWidth::I32, env.module_id), ); @@ -134,7 +137,7 @@ pub fn build_module_help<'a>( let module = backend.into_module(); - Ok((module, main_fn_index.unwrap())) + Ok((module, main_function_index)) } pub struct CopyMemoryConfig { From 8a384ffa88a5fbc4404390982e017c5658ad4509 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 9 Jan 2022 15:55:35 +0000 Subject: [PATCH 186/541] Wasm: parse just enough of the Import section to count functions --- compiler/gen_wasm/src/wasm_module/sections.rs | 92 ++++++++++++++++++- .../gen_wasm/src/wasm_module/serialize.rs | 25 +++++ 2 files changed, 112 insertions(+), 5 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 79b220e640..c314106136 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -1,10 +1,13 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; use roc_collections::all::MutMap; +use roc_reporting::internal_error; use super::linking::RelocationEntry; use super::opcodes::OpCode; -use super::serialize::{decode_u32_or_panic, parse_u32_or_panic, SerialBuffer, Serialize}; +use super::serialize::{ + decode_u32_or_panic, parse_u32_or_panic, SerialBuffer, Serialize, SkipBytes, +}; use super::{CodeBuilder, ValueType}; /******************************************************************* @@ -276,6 +279,13 @@ impl Serialize for TableType { } } +impl SkipBytes for TableType { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) { + u8::skip_bytes(bytes, cursor); + Limits::skip_bytes(bytes, cursor); + } +} + #[derive(Debug)] pub enum ImportDesc { Func { signature_index: u32 }, @@ -291,25 +301,48 @@ pub struct Import { pub description: ImportDesc, } +#[repr(u8)] +enum ImportTypeId { + Func = 0, + Table = 1, + Mem = 2, + Global = 3, +} + +impl From for ImportTypeId { + fn from(x: u8) -> Self { + match x { + 0 => Self::Func, + 1 => Self::Table, + 2 => Self::Mem, + 3 => Self::Global, + _ => internal_error!( + "Invalid ImportTypeId {} in platform/builtins object file", + x + ), + } + } +} + impl Serialize for Import { fn serialize(&self, buffer: &mut T) { self.module.serialize(buffer); self.name.serialize(buffer); match &self.description { ImportDesc::Func { signature_index } => { - buffer.append_u8(0); + buffer.append_u8(ImportTypeId::Func as u8); buffer.encode_u32(*signature_index); } ImportDesc::Table { ty } => { - buffer.append_u8(1); + buffer.append_u8(ImportTypeId::Table as u8); ty.serialize(buffer); } ImportDesc::Mem { limits } => { - buffer.append_u8(2); + buffer.append_u8(ImportTypeId::Mem as u8); limits.serialize(buffer); } ImportDesc::Global { ty } => { - buffer.append_u8(3); + buffer.append_u8(ImportTypeId::Global as u8); ty.serialize(buffer); } } @@ -327,6 +360,36 @@ impl<'a> ImportSection<'a> { import.serialize(&mut self.bytes); self.count += 1; } + + pub fn function_count(&self) -> u32 { + let mut f_count = 0; + let mut cursor = 0; + while cursor < self.bytes.len() { + String::skip_bytes(&self.bytes, &mut cursor); + String::skip_bytes(&self.bytes, &mut cursor); + + let type_id = self.bytes[cursor]; + cursor += 1; + + match ImportTypeId::from(type_id) { + ImportTypeId::Func => { + f_count += 1; + u32::skip_bytes(&self.bytes, &mut cursor); + } + ImportTypeId::Table => { + TableType::skip_bytes(&self.bytes, &mut cursor); + } + ImportTypeId::Mem => { + Limits::skip_bytes(&self.bytes, &mut cursor); + } + ImportTypeId::Global => { + GlobalType::skip_bytes(&self.bytes, &mut cursor); + } + } + } + + f_count + } } section_impl!(ImportSection, SectionId::Import); @@ -381,6 +444,19 @@ impl Serialize for Limits { } } +impl SkipBytes for Limits { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) { + if bytes[*cursor] == 0 { + u8::skip_bytes(bytes, cursor); + u32::skip_bytes(bytes, cursor); + } else { + u8::skip_bytes(bytes, cursor); + u32::skip_bytes(bytes, cursor); + u32::skip_bytes(bytes, cursor); + } + } +} + #[derive(Debug)] pub struct MemorySection<'a> { pub count: u32, @@ -429,6 +505,12 @@ impl Serialize for GlobalType { } } +impl SkipBytes for GlobalType { + fn skip_bytes(_bytes: &[u8], cursor: &mut usize) { + *cursor += 2; + } +} + /// Constant expression for initialising globals or data segments /// Note: This is restricted for simplicity, but the spec allows arbitrary constant expressions #[derive(Debug)] diff --git a/compiler/gen_wasm/src/wasm_module/serialize.rs b/compiler/gen_wasm/src/wasm_module/serialize.rs index ce5ab5ef9d..883cd90ccd 100644 --- a/compiler/gen_wasm/src/wasm_module/serialize.rs +++ b/compiler/gen_wasm/src/wasm_module/serialize.rs @@ -260,6 +260,31 @@ pub fn parse_u32_or_panic(bytes: &[u8], cursor: &mut usize) -> u32 { value } +/// Skip over serialized bytes for a type +/// This may, or may not, require looking at the byte values +pub trait SkipBytes { + fn skip_bytes(bytes: &[u8], cursor: &mut usize); +} + +impl SkipBytes for u32 { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) { + parse_u32_or_panic(bytes, cursor); + } +} + +impl SkipBytes for u8 { + fn skip_bytes(_bytes: &[u8], cursor: &mut usize) { + *cursor += 1; + } +} + +impl SkipBytes for String { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) { + let len = parse_u32_or_panic(bytes, cursor); + *cursor += len as usize; + } +} + #[cfg(test)] mod tests { use super::*; From c8181c3a19f001535badbc65fba11f700fb81d84 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 9 Jan 2022 16:14:54 +0000 Subject: [PATCH 187/541] Wasm: use parsed data from object file in the backend --- compiler/gen_wasm/src/backend.rs | 13 ++++++++----- compiler/gen_wasm/src/lib.rs | 14 +++++++++++++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index e7e39531e4..c1b34873a8 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -40,7 +40,8 @@ pub struct WasmBackend<'a> { module: WasmModule<'a>, layout_ids: LayoutIds<'a>, next_constant_addr: u32, - preloaded_fn_index_map: MutMap<&'a str, u32>, + fn_index_offset: u32, + preloaded_functions_map: MutMap<&'a [u8], u32>, proc_symbols: Vec<'a, (Symbol, u32)>, helper_proc_gen: CodeGenHelp<'a>, @@ -62,6 +63,8 @@ impl<'a> WasmBackend<'a> { layout_ids: LayoutIds<'a>, proc_symbols: Vec<'a, (Symbol, u32)>, module: WasmModule<'a>, + fn_index_offset: u32, + preloaded_functions_map: MutMap<&'a [u8], u32>, helper_proc_gen: CodeGenHelp<'a>, ) -> Self { WasmBackend { @@ -73,7 +76,8 @@ impl<'a> WasmBackend<'a> { layout_ids, next_constant_addr: CONST_SEGMENT_BASE_ADDR, - preloaded_fn_index_map: MutMap::default(), + fn_index_offset, + preloaded_functions_map, proc_symbols, helper_proc_gen, @@ -542,8 +546,7 @@ impl<'a> WasmBackend<'a> { for (roc_proc_index, (ir_sym, linker_sym_index)) in self.proc_symbols.iter().enumerate() { - let wasm_fn_index = - self.module.code.preloaded_count + roc_proc_index as u32; + let wasm_fn_index = self.fn_index_offset + roc_proc_index as u32; if ir_sym == func_sym { let num_wasm_args = param_types.len(); let has_return_val = ret_type.is_some(); @@ -1461,7 +1464,7 @@ impl<'a> WasmBackend<'a> { ) { let num_wasm_args = param_types.len(); let has_return_val = ret_type.is_some(); - let fn_index = self.preloaded_fn_index_map[name]; + let fn_index = self.preloaded_functions_map[name.as_bytes()]; let linker_symbol_index = u32::MAX; self.code_builder diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index b718ff9f43..6996b2a13b 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -92,8 +92,17 @@ pub fn build_module_help<'a>( fn_index += 1; } + // Pre-load the WasmModule with data from the platform & builtins object file let initial_module = WasmModule::preload(env.arena, preload_bytes); - let main_function_index = maybe_main_fn_index.unwrap() + initial_module.code.preloaded_count; + + // Adjust Wasm function indices to account for functions from the object file + let runtime_import_fn_count: u32 = initial_module.import.function_count(); // to be imported at runtime (e.g. WASI) + let fn_index_offset: u32 = runtime_import_fn_count + initial_module.code.preloaded_count; + + // Get a map of name to index for the preloaded functions + // Assumes the preloaded object file has all symbols exported, as per `zig build-lib -dymamic` + let preloaded_functions_map: MutMap<&'a [u8], u32> = + initial_module.export.function_index_map(env.arena); let mut backend = WasmBackend::new( env, @@ -101,6 +110,8 @@ pub fn build_module_help<'a>( layout_ids, proc_symbols, initial_module, + fn_index_offset, + preloaded_functions_map, CodeGenHelp::new(env.arena, IntWidth::I32, env.module_id), ); @@ -137,6 +148,7 @@ pub fn build_module_help<'a>( let module = backend.into_module(); + let main_function_index = maybe_main_fn_index.unwrap() + fn_index_offset; Ok((module, main_function_index)) } From 4f15fb3967fe3a5f6c2dd120817b177f9b79638d Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 9 Jan 2022 16:38:23 +0000 Subject: [PATCH 188/541] Wasm: Adjust funciton index for test_wrapper export declaration --- compiler/test_gen/src/helpers/wasm32_test_result.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/test_gen/src/helpers/wasm32_test_result.rs b/compiler/test_gen/src/helpers/wasm32_test_result.rs index e8a799f51c..e76581eefa 100644 --- a/compiler/test_gen/src/helpers/wasm32_test_result.rs +++ b/compiler/test_gen/src/helpers/wasm32_test_result.rs @@ -14,7 +14,8 @@ pub trait Wasm32TestResult { wrapper_name: &str, main_function_index: u32, ) { - let index = module.code.code_builders.len() as u32; + // Assumes the main function was the first one to be generated + let index = main_function_index + module.code.code_builders.len() as u32; module.add_function_signature(Signature { param_types: Vec::with_capacity_in(0, arena), From dd79a9b35aa0b4316d9ba3e1c9c75bd14c6c5dd2 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 9 Jan 2022 16:41:53 +0000 Subject: [PATCH 189/541] Clippy fixes --- compiler/gen_wasm/src/backend.rs | 1 + compiler/gen_wasm/src/lib.rs | 2 +- compiler/gen_wasm/src/wasm_module/mod.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index c1b34873a8..c89c6b16d2 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -57,6 +57,7 @@ pub struct WasmBackend<'a> { } impl<'a> WasmBackend<'a> { + #[allow(clippy::too_many_arguments)] pub fn new( env: &'a Env<'a>, interns: &'a mut Interns, diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 6996b2a13b..ca591f95fb 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -233,5 +233,5 @@ pub const DEBUG_LOG_SETTINGS: WasmDebugLogSettings = WasmDebugLogSettings { helper_procs_ir: false && cfg!(debug_assertions), let_stmt_ir: false && cfg!(debug_assertions), instructions: false && cfg!(debug_assertions), - keep_test_binary: false && cfg!(debug_assertions), + keep_test_binary: true && cfg!(debug_assertions), }; diff --git a/compiler/gen_wasm/src/wasm_module/mod.rs b/compiler/gen_wasm/src/wasm_module/mod.rs index 768b4cd8ab..cb55201d0d 100644 --- a/compiler/gen_wasm/src/wasm_module/mod.rs +++ b/compiler/gen_wasm/src/wasm_module/mod.rs @@ -117,7 +117,7 @@ impl<'a> WasmModule<'a> { pub fn preload(arena: &'a Bump, bytes: &[u8]) -> Self { let is_valid_magic_number = &bytes[0..4] == "\0asm".as_bytes(); - let is_valid_version = &bytes[4..8] == Self::WASM_VERSION.to_le_bytes(); + let is_valid_version = bytes[4..8] == Self::WASM_VERSION.to_le_bytes(); if !is_valid_magic_number || !is_valid_version { internal_error!("Invalid Wasm object file header for platform & builtins"); } From b46124234f18100458e61c7291e0e6e4c87aeffd Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 10 Jan 2022 08:26:55 +0000 Subject: [PATCH 190/541] Wasm: Add mock functions to HTML debugger for the full set of WASI imports --- .../test_gen/src/helpers/debug-wasm-test.html | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/compiler/test_gen/src/helpers/debug-wasm-test.html b/compiler/test_gen/src/helpers/debug-wasm-test.html index ca3ecd8a2b..890aa4cf24 100644 --- a/compiler/test_gen/src/helpers/debug-wasm-test.html +++ b/compiler/test_gen/src/helpers/debug-wasm-test.html @@ -294,6 +294,26 @@ } } + // Signatures from wasm_test_platform.o + const sig2 = (i32) => {}; + const sig6 = (i32a, i32b) => 0; + const sig7 = (i32a, i32b, i32c) => 0; + const sig9 = (i32a, i64b, i32c) => 0; + const sig10 = (i32a, i64b, i64c, i32d) => 0; + const sig11 = (i32a, i64b, i64c) => 0; + const sig12 = (i32a) => 0; + const sig13 = (i32a, i64b) => 0; + const sig14 = (i32a, i32b, i32c, i64d, i32e) => 0; + const sig15 = (i32a, i32b, i32c, i32d) => 0; + const sig16 = (i32a, i64b, i32c, i32d) => 0; + const sig17 = (i32a, i32b, i32c, i32d, i32e) => 0; + const sig18 = (i32a, i32b, i32c, i32d, i64e, i64f, i32g) => 0; + const sig19 = (i32a, i32b, i32c, i32d, i32e, i32f, i32g) => 0; + const sig20 = (i32a, i32b, i32c, i32d, i32e, i64f, i64g, i32h, i32i) => + 0; + const sig21 = (i32a, i32b, i32c, i32d, i32e, i32f) => 0; + const sig22 = () => 0; + return { wasi_snapshot_preview1: { fd_close, @@ -301,6 +321,51 @@ fd_seek, fd_write, proc_exit, + args_get: sig6, + args_sizes_get: sig6, + environ_get: sig6, + environ_sizes_get: sig6, + clock_res_get: sig6, + clock_time_get: sig9, + fd_advise: sig10, + fd_allocate: sig11, + // fd_close: sig12, + fd_datasync: sig12, + // fd_fdstat_get: sig6, + fd_fdstat_set_flags: sig6, + fd_fdstat_set_rights: sig11, + fd_filestat_get: sig6, + fd_filestat_set_size: sig13, + fd_filestat_set_times: sig10, + fd_pread: sig14, + fd_prestat_get: sig6, + fd_prestat_dir_name: sig7, + fd_pwrite: sig14, + fd_read: sig15, + fd_readdir: sig14, + fd_renumber: sig6, + // fd_seek: sig16, + fd_sync: sig12, + fd_tell: sig6, + // fd_write: sig15, + path_create_directory: sig7, + path_filestat_get: sig17, + path_filestat_set_times: sig18, + path_link: sig19, + path_open: sig20, + path_readlink: sig21, + path_remove_directory: sig7, + path_rename: sig21, + path_symlink: sig17, + path_unlink_file: sig7, + poll_oneoff: sig15, + // proc_exit: sig2, + proc_raise: sig12, + sched_yield: sig22, + random_get: sig6, + sock_recv: sig21, + sock_send: sig17, + sock_shutdown: sig6, }, }; } From 535927a85ea9a5c22ba5949da7a1f26dc4d4fe3d Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 10 Jan 2022 09:58:41 +0000 Subject: [PATCH 191/541] Wasm: fix test_wrapper function index --- compiler/test_gen/src/helpers/wasm32_test_result.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/test_gen/src/helpers/wasm32_test_result.rs b/compiler/test_gen/src/helpers/wasm32_test_result.rs index e76581eefa..34e2a86bea 100644 --- a/compiler/test_gen/src/helpers/wasm32_test_result.rs +++ b/compiler/test_gen/src/helpers/wasm32_test_result.rs @@ -14,8 +14,9 @@ pub trait Wasm32TestResult { wrapper_name: &str, main_function_index: u32, ) { - // Assumes the main function was the first one to be generated - let index = main_function_index + module.code.code_builders.len() as u32; + let index = module.import.function_count() + + module.code.preloaded_count + + module.code.code_builders.len() as u32; module.add_function_signature(Signature { param_types: Vec::with_capacity_in(0, arena), From e655ab7d3b90b2b0d2bd355309443ec649369671 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 10 Jan 2022 20:16:56 -0500 Subject: [PATCH 192/541] Module comments for reset-reuse Figuring out what this module was doing, and why, took me a bit less than half an hour. We should document what's happening for others in the future so they don't need to follow up on Zulip necessarily. --- compiler/load/src/file.rs | 4 +- compiler/mono/src/ir.rs | 23 +++---- compiler/mono/src/reset_reuse.rs | 15 +++++ .../test_mono/generated/alias_variable.txt | 4 +- .../alias_variable_and_return_it.txt | 2 +- .../generated/branch_store_variable.txt | 8 +-- .../test_mono/generated/closure_in_list.txt | 16 ++--- compiler/test_mono/generated/dict.txt | 8 +-- .../generated/empty_list_of_function_type.txt | 40 +++++------ compiler/test_mono/generated/factorial.txt | 20 +++--- compiler/test_mono/generated/fst.txt | 6 +- .../generated/guard_pattern_true.txt | 16 ++--- compiler/test_mono/generated/has_none.txt | 30 ++++----- .../if_guard_bind_variable_false.txt | 16 ++--- .../test_mono/generated/if_multi_branch.txt | 10 +-- .../test_mono/generated/ir_assignment.txt | 2 +- compiler/test_mono/generated/ir_int_add.txt | 20 +++--- .../test_mono/generated/ir_int_literal.txt | 2 +- compiler/test_mono/generated/ir_plus.txt | 8 +-- compiler/test_mono/generated/ir_round.txt | 6 +- compiler/test_mono/generated/ir_two_defs.txt | 8 +-- compiler/test_mono/generated/ir_when_idiv.txt | 28 ++++---- compiler/test_mono/generated/ir_when_just.txt | 20 +++--- .../test_mono/generated/ir_when_maybe.txt | 14 ++-- .../test_mono/generated/ir_when_record.txt | 8 +-- .../test_mono/generated/ir_when_these.txt | 14 ++-- compiler/test_mono/generated/is_nil.txt | 18 ++--- .../generated/let_with_record_pattern.txt | 8 +-- .../let_with_record_pattern_list.txt | 8 +-- compiler/test_mono/generated/let_x_in_x.txt | 6 +- .../generated/let_x_in_x_indirect.txt | 12 ++-- .../generated/linked_list_length_twice.txt | 26 ++++---- compiler/test_mono/generated/list_append.txt | 8 +-- .../generated/list_append_closure.txt | 10 +-- .../generated/list_cannot_update_inplace.txt | 30 ++++----- compiler/test_mono/generated/list_get.txt | 22 +++---- compiler/test_mono/generated/list_len.txt | 16 ++--- .../generated/list_pass_to_function.txt | 16 ++--- compiler/test_mono/generated/mk_pair_of.txt | 6 +- .../test_mono/generated/nested_closure.txt | 14 ++-- .../generated/nested_pattern_match.txt | 32 ++++----- .../test_mono/generated/one_element_tag.txt | 2 +- .../test_mono/generated/optional_when.txt | 50 +++++++------- compiler/test_mono/generated/peano.txt | 8 +-- compiler/test_mono/generated/peano1.txt | 18 ++--- compiler/test_mono/generated/peano2.txt | 28 ++++---- .../test_mono/generated/quicksort_help.txt | 36 +++++----- .../test_mono/generated/quicksort_swap.txt | 66 +++++++++---------- ...optional_field_function_no_use_default.txt | 18 ++--- ...rd_optional_field_function_use_default.txt | 12 ++-- ...cord_optional_field_let_no_use_default.txt | 16 ++--- .../record_optional_field_let_use_default.txt | 10 +-- compiler/test_mono/generated/rigids.txt | 62 ++++++++--------- compiler/test_mono/generated/simple_if.txt | 6 +- .../generated/somehow_drops_definitions.txt | 30 ++++----- .../generated/specialize_closures.txt | 36 +++++----- .../generated/specialize_lowlevel.txt | 30 ++++----- .../test_mono/generated/when_joinpoint.txt | 12 ++-- .../test_mono/generated/when_nested_maybe.txt | 32 ++++----- .../test_mono/generated/when_on_record.txt | 8 +-- .../test_mono/generated/when_on_result.txt | 26 ++++---- .../generated/when_on_two_values.txt | 28 ++++---- 62 files changed, 563 insertions(+), 551 deletions(-) diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 489f8a84f9..f85a9489f2 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -2083,8 +2083,8 @@ fn update<'a>( &mut state.procedures, ); - // display the mono IR of the module, for debug purposes - if roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS { + // Uncomment to display the mono IR of the module, for debug purposes + if false { let procs_string = state .procedures .values() diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 1a8f93f6b5..86fe0df692 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -1632,19 +1632,16 @@ impl<'a> Stmt<'a> { use Stmt::*; match self { - Let(symbol, expr, layout, cont) => { - let mut doc = alloc.text("let ").append(symbol_to_doc(alloc, *symbol)); - if PRETTY_PRINT_IR_SYMBOLS { - doc = doc - .append(" : ") - .append(alloc.text(format!("{:?}", layout))); - } - doc.append(" = ") - .append(expr.to_doc(alloc)) - .append(";") - .append(alloc.hardline()) - .append(cont.to_doc(alloc)) - } + Let(symbol, expr, _layout, cont) => alloc + .text("let ") + .append(symbol_to_doc(alloc, *symbol)) + .append(" : ") + .append(alloc.text(format!("{:?}", _layout))) + .append(" = ") + .append(expr.to_doc(alloc)) + .append(";") + .append(alloc.hardline()) + .append(cont.to_doc(alloc)), Refcounting(modify, cont) => modify .to_doc(alloc) diff --git a/compiler/mono/src/reset_reuse.rs b/compiler/mono/src/reset_reuse.rs index fc63890d73..d29597323f 100644 --- a/compiler/mono/src/reset_reuse.rs +++ b/compiler/mono/src/reset_reuse.rs @@ -1,3 +1,18 @@ +//! This module inserts reset/reuse statements into the mono IR. These statements provide an +//! opportunity to reduce memory pressure by reusing memory slots of non-shared values. From the +//! introduction of the relevant paper: +//! +//! > [We] have added two additional instructions to our IR: `let y = reset x` and +//! > `let z = (reuse y in ctor_i w)`. The two instructions are used together; if `x` +//! > is a shared value, then `y` is set to a special reference, and the reuse instruction +//! > just allocates a new constructor value `ctor_i w`. If `x` is not shared, then reset +//! > decrements the reference counters of the components of `x`, and `y` is set to `x`. +//! > Then, reuse reuses the memory cell used by `x` to store the constructor value `ctor_i w`. +//! +//! See also +//! - [Counting Immutable Beans](https://arxiv.org/pdf/1908.05647.pdf) (Ullrich and Moura, 2020) +//! - [The lean implementation](https://github.com/leanprover/lean4/blob/master/src/Lean/Compiler/IR/ResetReuse.lean) + use crate::inc_dec::{collect_stmt, occurring_variables_expr, JPLiveVarMap, LiveVarSet}; use crate::ir::{ BranchInfo, Call, Expr, ListLiteralElement, Proc, Stmt, UpdateModeId, UpdateModeIds, diff --git a/compiler/test_mono/generated/alias_variable.txt b/compiler/test_mono/generated/alias_variable.txt index 30f430e2d7..095d77af3a 100644 --- a/compiler/test_mono/generated/alias_variable.txt +++ b/compiler/test_mono/generated/alias_variable.txt @@ -1,4 +1,4 @@ procedure Test.0 (): - let Test.1 = 5i64; - let Test.3 = 3i64; + let Test.1 : Builtin(Int(I64)) = 5i64; + let Test.3 : Builtin(Int(I64)) = 3i64; ret Test.3; diff --git a/compiler/test_mono/generated/alias_variable_and_return_it.txt b/compiler/test_mono/generated/alias_variable_and_return_it.txt index 755d390183..ff5f614740 100644 --- a/compiler/test_mono/generated/alias_variable_and_return_it.txt +++ b/compiler/test_mono/generated/alias_variable_and_return_it.txt @@ -1,3 +1,3 @@ procedure Test.0 (): - let Test.1 = 5i64; + let Test.1 : Builtin(Int(I64)) = 5i64; ret Test.1; diff --git a/compiler/test_mono/generated/branch_store_variable.txt b/compiler/test_mono/generated/branch_store_variable.txt index 59f4d90045..567b83bd1f 100644 --- a/compiler/test_mono/generated/branch_store_variable.txt +++ b/compiler/test_mono/generated/branch_store_variable.txt @@ -1,9 +1,9 @@ procedure Test.0 (): - let Test.2 = 0i64; - let Test.5 = 1i64; - let Test.6 = lowlevel Eq Test.5 Test.2; + let Test.2 : Builtin(Int(I64)) = 0i64; + let Test.5 : Builtin(Int(I64)) = 1i64; + let Test.6 : Builtin(Bool) = lowlevel Eq Test.5 Test.2; if Test.6 then - let Test.3 = 12i64; + let Test.3 : Builtin(Int(I64)) = 12i64; ret Test.3; else ret Test.2; diff --git a/compiler/test_mono/generated/closure_in_list.txt b/compiler/test_mono/generated/closure_in_list.txt index 41c1e43828..390ee4acdc 100644 --- a/compiler/test_mono/generated/closure_in_list.txt +++ b/compiler/test_mono/generated/closure_in_list.txt @@ -1,20 +1,20 @@ procedure List.7 (#Attr.2): - let Test.7 = lowlevel ListLen #Attr.2; + let Test.7 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; ret Test.7; procedure Test.1 (Test.5): - let Test.2 = 41i64; - let Test.11 = Struct {Test.2}; - let Test.10 = Array [Test.11]; + let Test.2 : Builtin(Int(I64)) = 41i64; + let Test.11 : LambdaSet(LambdaSet { set: [(`#UserApp.choose`, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; + let Test.10 : Builtin(List(LambdaSet(LambdaSet { set: [(`#UserApp.choose`, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }))) = Array [Test.11]; ret Test.10; procedure Test.3 (Test.9, #Attr.12): - let Test.2 = StructAtIndex 0 #Attr.12; + let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 #Attr.12; ret Test.2; procedure Test.0 (): - let Test.8 = Struct {}; - let Test.4 = CallByName Test.1 Test.8; - let Test.6 = CallByName List.7 Test.4; + let Test.8 : Struct([]) = Struct {}; + let Test.4 : Builtin(List(LambdaSet(LambdaSet { set: [(`#UserApp.choose`, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }))) = CallByName Test.1 Test.8; + let Test.6 : Builtin(Int(U64)) = CallByName List.7 Test.4; dec Test.4; ret Test.6; diff --git a/compiler/test_mono/generated/dict.txt b/compiler/test_mono/generated/dict.txt index 42fa1a4676..ad2fbf5701 100644 --- a/compiler/test_mono/generated/dict.txt +++ b/compiler/test_mono/generated/dict.txt @@ -1,13 +1,13 @@ procedure Dict.2 (): - let Test.4 = lowlevel DictEmpty ; + let Test.4 : Builtin(Dict(Union(NonRecursive([])), Union(NonRecursive([])))) = lowlevel DictEmpty ; ret Test.4; procedure Dict.8 (#Attr.2): - let Test.3 = lowlevel DictSize #Attr.2; + let Test.3 : Builtin(Int(U64)) = lowlevel DictSize #Attr.2; dec #Attr.2; ret Test.3; procedure Test.0 (): - let Test.2 = CallByName Dict.2; - let Test.1 = CallByName Dict.8 Test.2; + let Test.2 : Builtin(Dict(Union(NonRecursive([])), Union(NonRecursive([])))) = CallByName Dict.2; + let Test.1 : Builtin(Int(U64)) = CallByName Dict.8 Test.2; ret Test.1; diff --git a/compiler/test_mono/generated/empty_list_of_function_type.txt b/compiler/test_mono/generated/empty_list_of_function_type.txt index 89aa3d309b..3789ce311c 100644 --- a/compiler/test_mono/generated/empty_list_of_function_type.txt +++ b/compiler/test_mono/generated/empty_list_of_function_type.txt @@ -1,43 +1,43 @@ procedure List.3 (#Attr.2, #Attr.3): - let Test.20 = lowlevel ListLen #Attr.2; - let Test.17 = lowlevel NumLt #Attr.3 Test.20; + let Test.20 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.17 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.20; if Test.17 then - let Test.19 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.18 = Ok Test.19; + let Test.19 : LambdaSet(LambdaSet { set: [(`#UserApp.myClosure`, [])], representation: Struct([]) }) = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.18 : Union(NonRecursive([[Struct([])], [LambdaSet(LambdaSet { set: [(`#UserApp.myClosure`, [])], representation: Struct([]) })]])) = Ok Test.19; ret Test.18; else - let Test.16 = Struct {}; - let Test.15 = Err Test.16; + let Test.16 : Struct([]) = Struct {}; + let Test.15 : Union(NonRecursive([[Struct([])], [LambdaSet(LambdaSet { set: [(`#UserApp.myClosure`, [])], representation: Struct([]) })]])) = Err Test.16; ret Test.15; procedure Test.2 (Test.6): - let Test.24 = "bar"; + let Test.24 : Builtin(Str) = "bar"; ret Test.24; procedure Test.0 (): - let Test.1 = Array []; + let Test.1 : Builtin(List(LambdaSet(LambdaSet { set: [], representation: Struct([]) }))) = Array []; joinpoint Test.22 Test.3: - let Test.14 = 0i64; - let Test.7 = CallByName List.3 Test.3 Test.14; + let Test.14 : Builtin(Int(U64)) = 0i64; + let Test.7 : Union(NonRecursive([[Struct([])], [LambdaSet(LambdaSet { set: [(`#UserApp.myClosure`, [])], representation: Struct([]) })]])) = CallByName List.3 Test.3 Test.14; dec Test.3; - let Test.11 = 1i64; - let Test.12 = GetTagId Test.7; - let Test.13 = lowlevel Eq Test.11 Test.12; + let Test.11 : Builtin(Int(U8)) = 1i64; + let Test.12 : Builtin(Int(U8)) = GetTagId Test.7; + let Test.13 : Builtin(Bool) = lowlevel Eq Test.11 Test.12; if Test.13 then - let Test.5 = UnionAtIndex (Id 1) (Index 0) Test.7; - let Test.9 = "foo"; - let Test.8 = CallByName Test.2 Test.9; + let Test.5 : LambdaSet(LambdaSet { set: [(`#UserApp.myClosure`, [])], representation: Struct([]) }) = UnionAtIndex (Id 1) (Index 0) Test.7; + let Test.9 : Builtin(Str) = "foo"; + let Test.8 : Builtin(Str) = CallByName Test.2 Test.9; dec Test.9; ret Test.8; else - let Test.10 = "bad!"; + let Test.10 : Builtin(Str) = "bad!"; ret Test.10; in - let Test.25 = false; + let Test.25 : Builtin(Bool) = false; if Test.25 then jump Test.22 Test.1; else dec Test.1; - let Test.23 = Struct {}; - let Test.21 = Array [Test.23]; + let Test.23 : LambdaSet(LambdaSet { set: [(`#UserApp.myClosure`, [])], representation: Struct([]) }) = Struct {}; + let Test.21 : Builtin(List(LambdaSet(LambdaSet { set: [(`#UserApp.myClosure`, [])], representation: Struct([]) }))) = Array [Test.23]; jump Test.22 Test.21; diff --git a/compiler/test_mono/generated/factorial.txt b/compiler/test_mono/generated/factorial.txt index 8abf602d63..3d5b2a9508 100644 --- a/compiler/test_mono/generated/factorial.txt +++ b/compiler/test_mono/generated/factorial.txt @@ -1,27 +1,27 @@ procedure Num.25 (#Attr.2, #Attr.3): - let Test.14 = lowlevel NumSub #Attr.2 #Attr.3; + let Test.14 : Builtin(Int(I64)) = lowlevel NumSub #Attr.2 #Attr.3; ret Test.14; procedure Num.26 (#Attr.2, #Attr.3): - let Test.12 = lowlevel NumMul #Attr.2 #Attr.3; + let Test.12 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; ret Test.12; procedure Test.1 (Test.17, Test.18): joinpoint Test.7 Test.2 Test.3: - let Test.15 = 0i64; - let Test.16 = lowlevel Eq Test.15 Test.2; + let Test.15 : Builtin(Int(I64)) = 0i64; + let Test.16 : Builtin(Bool) = lowlevel Eq Test.15 Test.2; if Test.16 then ret Test.3; else - let Test.13 = 1i64; - let Test.10 = CallByName Num.25 Test.2 Test.13; - let Test.11 = CallByName Num.26 Test.2 Test.3; + let Test.13 : Builtin(Int(I64)) = 1i64; + let Test.10 : Builtin(Int(I64)) = CallByName Num.25 Test.2 Test.13; + let Test.11 : Builtin(Int(I64)) = CallByName Num.26 Test.2 Test.3; jump Test.7 Test.10 Test.11; in jump Test.7 Test.17 Test.18; procedure Test.0 (): - let Test.5 = 10i64; - let Test.6 = 1i64; - let Test.4 = CallByName Test.1 Test.5 Test.6; + let Test.5 : Builtin(Int(I64)) = 10i64; + let Test.6 : Builtin(Int(I64)) = 1i64; + let Test.4 : Builtin(Int(I64)) = CallByName Test.1 Test.5 Test.6; ret Test.4; diff --git a/compiler/test_mono/generated/fst.txt b/compiler/test_mono/generated/fst.txt index fe81173aa5..f85b59b85b 100644 --- a/compiler/test_mono/generated/fst.txt +++ b/compiler/test_mono/generated/fst.txt @@ -3,9 +3,9 @@ procedure Test.1 (Test.2, Test.3): ret Test.2; procedure Test.0 (): - let Test.5 = Array [1i64, 2i64, 3i64]; - let Test.6 = Array [3i64, 2i64, 1i64]; - let Test.4 = CallByName Test.1 Test.5 Test.6; + let Test.5 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; + let Test.6 : Builtin(List(Builtin(Int(I64)))) = Array [3i64, 2i64, 1i64]; + let Test.4 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.5 Test.6; dec Test.6; dec Test.5; ret Test.4; diff --git a/compiler/test_mono/generated/guard_pattern_true.txt b/compiler/test_mono/generated/guard_pattern_true.txt index c0ee2f046d..b93ad5f177 100644 --- a/compiler/test_mono/generated/guard_pattern_true.txt +++ b/compiler/test_mono/generated/guard_pattern_true.txt @@ -1,25 +1,25 @@ procedure Test.1 (Test.3): - let Test.6 = 2i64; + let Test.6 : Builtin(Int(I64)) = 2i64; joinpoint Test.11: - let Test.10 = 0i64; + let Test.10 : Builtin(Int(I64)) = 0i64; ret Test.10; in - let Test.13 = 2i64; - let Test.14 = lowlevel Eq Test.13 Test.6; + let Test.13 : Builtin(Int(I64)) = 2i64; + let Test.14 : Builtin(Bool) = lowlevel Eq Test.13 Test.6; if Test.14 then joinpoint Test.8 Test.12: if Test.12 then - let Test.7 = 42i64; + let Test.7 : Builtin(Int(I64)) = 42i64; ret Test.7; else jump Test.11; in - let Test.9 = false; + let Test.9 : Builtin(Bool) = false; jump Test.8 Test.9; else jump Test.11; procedure Test.0 (): - let Test.5 = Struct {}; - let Test.4 = CallByName Test.1 Test.5; + let Test.5 : Struct([]) = Struct {}; + let Test.4 : Builtin(Int(I64)) = CallByName Test.1 Test.5; ret Test.4; diff --git a/compiler/test_mono/generated/has_none.txt b/compiler/test_mono/generated/has_none.txt index b6e7dba474..b97fc22ad0 100644 --- a/compiler/test_mono/generated/has_none.txt +++ b/compiler/test_mono/generated/has_none.txt @@ -1,30 +1,30 @@ procedure Test.3 (Test.29): joinpoint Test.13 Test.4: - let Test.23 = 1i64; - let Test.24 = GetTagId Test.4; - let Test.25 = lowlevel Eq Test.23 Test.24; + let Test.23 : Builtin(Bool) = 1i64; + let Test.24 : Builtin(Bool) = GetTagId Test.4; + let Test.25 : Builtin(Bool) = lowlevel Eq Test.23 Test.24; if Test.25 then - let Test.14 = false; + let Test.14 : Builtin(Bool) = false; ret Test.14; else - let Test.19 = UnionAtIndex (Id 0) (Index 0) Test.4; - let Test.20 = 1i64; - let Test.21 = GetTagId Test.19; - let Test.22 = lowlevel Eq Test.20 Test.21; + let Test.19 : Union(NonRecursive([[Builtin(Int(I64))], []])) = UnionAtIndex (Id 0) (Index 0) Test.4; + let Test.20 : Builtin(Int(U8)) = 1i64; + let Test.21 : Builtin(Int(U8)) = GetTagId Test.19; + let Test.22 : Builtin(Bool) = lowlevel Eq Test.20 Test.21; if Test.22 then - let Test.15 = true; + let Test.15 : Builtin(Bool) = true; ret Test.15; else - let Test.7 = UnionAtIndex (Id 0) (Index 1) Test.4; + let Test.7 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Union(NonRecursive([[Builtin(Int(I64))], []])), RecursivePointer] }) = UnionAtIndex (Id 0) (Index 1) Test.4; jump Test.13 Test.7; in jump Test.13 Test.29; procedure Test.0 (): - let Test.28 = 3i64; - let Test.26 = Just Test.28; - let Test.27 = Nil ; - let Test.12 = Cons Test.26 Test.27; - let Test.11 = CallByName Test.3 Test.12; + let Test.28 : Builtin(Int(I64)) = 3i64; + let Test.26 : Union(NonRecursive([[Builtin(Int(I64))], []])) = Just Test.28; + let Test.27 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Union(NonRecursive([[Builtin(Int(I64))], []])), RecursivePointer] }) = Nil ; + let Test.12 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Union(NonRecursive([[Builtin(Int(I64))], []])), RecursivePointer] }) = Cons Test.26 Test.27; + let Test.11 : Builtin(Bool) = CallByName Test.3 Test.12; dec Test.12; ret Test.11; diff --git a/compiler/test_mono/generated/if_guard_bind_variable_false.txt b/compiler/test_mono/generated/if_guard_bind_variable_false.txt index 1bb0f10960..d5a0da7ccc 100644 --- a/compiler/test_mono/generated/if_guard_bind_variable_false.txt +++ b/compiler/test_mono/generated/if_guard_bind_variable_false.txt @@ -1,22 +1,22 @@ procedure Bool.7 (#Attr.2, #Attr.3): - let Test.11 = lowlevel Eq #Attr.2 #Attr.3; + let Test.11 : Builtin(Bool) = lowlevel Eq #Attr.2 #Attr.3; ret Test.11; procedure Test.1 (Test.3): - let Test.6 = 10i64; + let Test.6 : Builtin(Int(I64)) = 10i64; joinpoint Test.8 Test.13: if Test.13 then - let Test.7 = 0i64; + let Test.7 : Builtin(Int(I64)) = 0i64; ret Test.7; else - let Test.12 = 42i64; + let Test.12 : Builtin(Int(I64)) = 42i64; ret Test.12; in - let Test.10 = 5i64; - let Test.9 = CallByName Bool.7 Test.6 Test.10; + let Test.10 : Builtin(Int(I64)) = 5i64; + let Test.9 : Builtin(Bool) = CallByName Bool.7 Test.6 Test.10; jump Test.8 Test.9; procedure Test.0 (): - let Test.5 = Struct {}; - let Test.4 = CallByName Test.1 Test.5; + let Test.5 : Struct([]) = Struct {}; + let Test.4 : Builtin(Int(I64)) = CallByName Test.1 Test.5; ret Test.4; diff --git a/compiler/test_mono/generated/if_multi_branch.txt b/compiler/test_mono/generated/if_multi_branch.txt index c4953af3c3..778549a457 100644 --- a/compiler/test_mono/generated/if_multi_branch.txt +++ b/compiler/test_mono/generated/if_multi_branch.txt @@ -1,13 +1,13 @@ procedure Test.0 (): - let Test.6 = true; + let Test.6 : Builtin(Bool) = true; if Test.6 then - let Test.7 = 1i64; + let Test.7 : Builtin(Int(I64)) = 1i64; ret Test.7; else - let Test.4 = false; + let Test.4 : Builtin(Bool) = false; if Test.4 then - let Test.5 = 2i64; + let Test.5 : Builtin(Int(I64)) = 2i64; ret Test.5; else - let Test.3 = 3i64; + let Test.3 : Builtin(Int(I64)) = 3i64; ret Test.3; diff --git a/compiler/test_mono/generated/ir_assignment.txt b/compiler/test_mono/generated/ir_assignment.txt index 755d390183..ff5f614740 100644 --- a/compiler/test_mono/generated/ir_assignment.txt +++ b/compiler/test_mono/generated/ir_assignment.txt @@ -1,3 +1,3 @@ procedure Test.0 (): - let Test.1 = 5i64; + let Test.1 : Builtin(Int(I64)) = 5i64; ret Test.1; diff --git a/compiler/test_mono/generated/ir_int_add.txt b/compiler/test_mono/generated/ir_int_add.txt index 0da2caed32..798fd708d7 100644 --- a/compiler/test_mono/generated/ir_int_add.txt +++ b/compiler/test_mono/generated/ir_int_add.txt @@ -1,19 +1,19 @@ procedure List.7 (#Attr.2): - let Test.6 = lowlevel ListLen #Attr.2; + let Test.6 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; ret Test.6; procedure Num.24 (#Attr.2, #Attr.3): - let Test.5 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.5 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.5; procedure Test.0 (): - let Test.1 = Array [1i64, 2i64]; - let Test.9 = 5i64; - let Test.10 = 4i64; - let Test.7 = CallByName Num.24 Test.9 Test.10; - let Test.8 = 3i64; - let Test.3 = CallByName Num.24 Test.7 Test.8; - let Test.4 = CallByName List.7 Test.1; + let Test.1 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64]; + let Test.9 : Builtin(Int(U64)) = 5i64; + let Test.10 : Builtin(Int(U64)) = 4i64; + let Test.7 : Builtin(Int(U64)) = CallByName Num.24 Test.9 Test.10; + let Test.8 : Builtin(Int(U64)) = 3i64; + let Test.3 : Builtin(Int(U64)) = CallByName Num.24 Test.7 Test.8; + let Test.4 : Builtin(Int(U64)) = CallByName List.7 Test.1; dec Test.1; - let Test.2 = CallByName Num.24 Test.3 Test.4; + let Test.2 : Builtin(Int(U64)) = CallByName Num.24 Test.3 Test.4; ret Test.2; diff --git a/compiler/test_mono/generated/ir_int_literal.txt b/compiler/test_mono/generated/ir_int_literal.txt index 755d390183..ff5f614740 100644 --- a/compiler/test_mono/generated/ir_int_literal.txt +++ b/compiler/test_mono/generated/ir_int_literal.txt @@ -1,3 +1,3 @@ procedure Test.0 (): - let Test.1 = 5i64; + let Test.1 : Builtin(Int(I64)) = 5i64; ret Test.1; diff --git a/compiler/test_mono/generated/ir_plus.txt b/compiler/test_mono/generated/ir_plus.txt index 1fa959e684..c4143d76e4 100644 --- a/compiler/test_mono/generated/ir_plus.txt +++ b/compiler/test_mono/generated/ir_plus.txt @@ -1,9 +1,9 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.4 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.4 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.4; procedure Test.0 (): - let Test.2 = 1i64; - let Test.3 = 2i64; - let Test.1 = CallByName Num.24 Test.2 Test.3; + let Test.2 : Builtin(Int(I64)) = 1i64; + let Test.3 : Builtin(Int(I64)) = 2i64; + let Test.1 : Builtin(Int(I64)) = CallByName Num.24 Test.2 Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/ir_round.txt b/compiler/test_mono/generated/ir_round.txt index 5448d22900..4fa406093e 100644 --- a/compiler/test_mono/generated/ir_round.txt +++ b/compiler/test_mono/generated/ir_round.txt @@ -1,8 +1,8 @@ procedure Num.47 (#Attr.2): - let Test.3 = lowlevel NumRound #Attr.2; + let Test.3 : Builtin(Int(I64)) = lowlevel NumRound #Attr.2; ret Test.3; procedure Test.0 (): - let Test.2 = 3.6f64; - let Test.1 = CallByName Num.47 Test.2; + let Test.2 : Builtin(Float(F64)) = 3.6f64; + let Test.1 : Builtin(Int(I64)) = CallByName Num.47 Test.2; ret Test.1; diff --git a/compiler/test_mono/generated/ir_two_defs.txt b/compiler/test_mono/generated/ir_two_defs.txt index 04a64113c5..1811869476 100644 --- a/compiler/test_mono/generated/ir_two_defs.txt +++ b/compiler/test_mono/generated/ir_two_defs.txt @@ -1,9 +1,9 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.4 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.4 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.4; procedure Test.0 (): - let Test.1 = 3i64; - let Test.2 = 4i64; - let Test.3 = CallByName Num.24 Test.1 Test.2; + let Test.1 : Builtin(Int(I64)) = 3i64; + let Test.2 : Builtin(Int(I64)) = 4i64; + let Test.3 : Builtin(Int(I64)) = CallByName Num.24 Test.1 Test.2; ret Test.3; diff --git a/compiler/test_mono/generated/ir_when_idiv.txt b/compiler/test_mono/generated/ir_when_idiv.txt index a7463bbad2..12893c9523 100644 --- a/compiler/test_mono/generated/ir_when_idiv.txt +++ b/compiler/test_mono/generated/ir_when_idiv.txt @@ -1,25 +1,25 @@ procedure Num.42 (#Attr.2, #Attr.3): - let Test.15 = 0i64; - let Test.12 = lowlevel NotEq #Attr.3 Test.15; + let Test.15 : Builtin(Int(I64)) = 0i64; + let Test.12 : Builtin(Bool) = lowlevel NotEq #Attr.3 Test.15; if Test.12 then - let Test.14 = lowlevel NumDivUnchecked #Attr.2 #Attr.3; - let Test.13 = Ok Test.14; + let Test.14 : Builtin(Int(I64)) = lowlevel NumDivUnchecked #Attr.2 #Attr.3; + let Test.13 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Ok Test.14; ret Test.13; else - let Test.11 = Struct {}; - let Test.10 = Err Test.11; + let Test.11 : Struct([]) = Struct {}; + let Test.10 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Err Test.11; ret Test.10; procedure Test.0 (): - let Test.8 = 1000i64; - let Test.9 = 10i64; - let Test.2 = CallByName Num.42 Test.8 Test.9; - let Test.5 = 1i64; - let Test.6 = GetTagId Test.2; - let Test.7 = lowlevel Eq Test.5 Test.6; + let Test.8 : Builtin(Int(I64)) = 1000i64; + let Test.9 : Builtin(Int(I64)) = 10i64; + let Test.2 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName Num.42 Test.8 Test.9; + let Test.5 : Builtin(Int(U8)) = 1i64; + let Test.6 : Builtin(Int(U8)) = GetTagId Test.2; + let Test.7 : Builtin(Bool) = lowlevel Eq Test.5 Test.6; if Test.7 then - let Test.1 = UnionAtIndex (Id 1) (Index 0) Test.2; + let Test.1 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.2; ret Test.1; else - let Test.4 = -1i64; + let Test.4 : Builtin(Int(I64)) = -1i64; ret Test.4; diff --git a/compiler/test_mono/generated/ir_when_just.txt b/compiler/test_mono/generated/ir_when_just.txt index 65d6052dd9..5019316f49 100644 --- a/compiler/test_mono/generated/ir_when_just.txt +++ b/compiler/test_mono/generated/ir_when_just.txt @@ -1,18 +1,18 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.6 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.6 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.6; procedure Test.0 (): - let Test.11 = 41i64; - let Test.1 = Just Test.11; - let Test.8 = 0i64; - let Test.9 = GetTagId Test.1; - let Test.10 = lowlevel Eq Test.8 Test.9; + let Test.11 : Builtin(Int(I64)) = 41i64; + let Test.1 : Union(NonRecursive([[Builtin(Int(I64))], []])) = Just Test.11; + let Test.8 : Builtin(Int(U8)) = 0i64; + let Test.9 : Builtin(Int(U8)) = GetTagId Test.1; + let Test.10 : Builtin(Bool) = lowlevel Eq Test.8 Test.9; if Test.10 then - let Test.3 = UnionAtIndex (Id 0) (Index 0) Test.1; - let Test.5 = 1i64; - let Test.4 = CallByName Num.24 Test.3 Test.5; + let Test.3 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.1; + let Test.5 : Builtin(Int(I64)) = 1i64; + let Test.4 : Builtin(Int(I64)) = CallByName Num.24 Test.3 Test.5; ret Test.4; else - let Test.7 = 1i64; + let Test.7 : Builtin(Int(I64)) = 1i64; ret Test.7; diff --git a/compiler/test_mono/generated/ir_when_maybe.txt b/compiler/test_mono/generated/ir_when_maybe.txt index 1cc90f1f16..348604c968 100644 --- a/compiler/test_mono/generated/ir_when_maybe.txt +++ b/compiler/test_mono/generated/ir_when_maybe.txt @@ -1,12 +1,12 @@ procedure Test.0 (): - let Test.9 = 3i64; - let Test.3 = Just Test.9; - let Test.6 = 0i64; - let Test.7 = GetTagId Test.3; - let Test.8 = lowlevel Eq Test.6 Test.7; + let Test.9 : Builtin(Int(I64)) = 3i64; + let Test.3 : Union(NonRecursive([[Builtin(Int(I64))], []])) = Just Test.9; + let Test.6 : Builtin(Int(U8)) = 0i64; + let Test.7 : Builtin(Int(U8)) = GetTagId Test.3; + let Test.8 : Builtin(Bool) = lowlevel Eq Test.6 Test.7; if Test.8 then - let Test.2 = UnionAtIndex (Id 0) (Index 0) Test.3; + let Test.2 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.3; ret Test.2; else - let Test.5 = 0i64; + let Test.5 : Builtin(Int(I64)) = 0i64; ret Test.5; diff --git a/compiler/test_mono/generated/ir_when_record.txt b/compiler/test_mono/generated/ir_when_record.txt index ed777a23d0..b9e3ef94fc 100644 --- a/compiler/test_mono/generated/ir_when_record.txt +++ b/compiler/test_mono/generated/ir_when_record.txt @@ -1,6 +1,6 @@ procedure Test.0 (): - let Test.4 = 1i64; - let Test.5 = 3.14f64; - let Test.2 = Struct {Test.4, Test.5}; - let Test.1 = StructAtIndex 0 Test.2; + let Test.4 : Builtin(Int(I64)) = 1i64; + let Test.5 : Builtin(Float(F64)) = 3.14f64; + let Test.2 : Struct([Builtin(Int(I64)), Builtin(Float(F64))]) = Struct {Test.4, Test.5}; + let Test.1 : Builtin(Int(I64)) = StructAtIndex 0 Test.2; ret Test.1; diff --git a/compiler/test_mono/generated/ir_when_these.txt b/compiler/test_mono/generated/ir_when_these.txt index bcba501785..08c3614351 100644 --- a/compiler/test_mono/generated/ir_when_these.txt +++ b/compiler/test_mono/generated/ir_when_these.txt @@ -1,18 +1,18 @@ procedure Test.0 (): - let Test.10 = 1i64; - let Test.11 = 2i64; - let Test.5 = These Test.10 Test.11; - let Test.9 = GetTagId Test.5; + let Test.10 : Builtin(Int(I64)) = 1i64; + let Test.11 : Builtin(Int(I64)) = 2i64; + let Test.5 : Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Int(I64))], [Builtin(Int(I64))]])) = These Test.10 Test.11; + let Test.9 : Builtin(Int(U8)) = GetTagId Test.5; switch Test.9: case 2: - let Test.2 = UnionAtIndex (Id 2) (Index 0) Test.5; + let Test.2 : Builtin(Int(I64)) = UnionAtIndex (Id 2) (Index 0) Test.5; ret Test.2; case 0: - let Test.3 = UnionAtIndex (Id 0) (Index 0) Test.5; + let Test.3 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.5; ret Test.3; default: - let Test.4 = UnionAtIndex (Id 1) (Index 0) Test.5; + let Test.4 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.5; ret Test.4; diff --git a/compiler/test_mono/generated/is_nil.txt b/compiler/test_mono/generated/is_nil.txt index 55329f43fc..5968c5178c 100644 --- a/compiler/test_mono/generated/is_nil.txt +++ b/compiler/test_mono/generated/is_nil.txt @@ -1,18 +1,18 @@ procedure Test.2 (Test.3): - let Test.12 = 1i64; - let Test.13 = GetTagId Test.3; - let Test.14 = lowlevel Eq Test.12 Test.13; + let Test.12 : Builtin(Bool) = 1i64; + let Test.13 : Builtin(Bool) = GetTagId Test.3; + let Test.14 : Builtin(Bool) = lowlevel Eq Test.12 Test.13; if Test.14 then - let Test.10 = true; + let Test.10 : Builtin(Bool) = true; ret Test.10; else - let Test.11 = false; + let Test.11 : Builtin(Bool) = false; ret Test.11; procedure Test.0 (): - let Test.15 = 2i64; - let Test.16 = Nil ; - let Test.9 = Cons Test.15 Test.16; - let Test.8 = CallByName Test.2 Test.9; + let Test.15 : Builtin(Int(I64)) = 2i64; + let Test.16 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Builtin(Int(I64)), RecursivePointer] }) = Nil ; + let Test.9 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Builtin(Int(I64)), RecursivePointer] }) = Cons Test.15 Test.16; + let Test.8 : Builtin(Bool) = CallByName Test.2 Test.9; dec Test.9; ret Test.8; diff --git a/compiler/test_mono/generated/let_with_record_pattern.txt b/compiler/test_mono/generated/let_with_record_pattern.txt index 51df825ebf..302c1de690 100644 --- a/compiler/test_mono/generated/let_with_record_pattern.txt +++ b/compiler/test_mono/generated/let_with_record_pattern.txt @@ -1,6 +1,6 @@ procedure Test.0 (): - let Test.4 = 2i64; - let Test.5 = 3.14f64; - let Test.3 = Struct {Test.4, Test.5}; - let Test.1 = StructAtIndex 0 Test.3; + let Test.4 : Builtin(Int(I64)) = 2i64; + let Test.5 : Builtin(Float(F64)) = 3.14f64; + let Test.3 : Struct([Builtin(Int(I64)), Builtin(Float(F64))]) = Struct {Test.4, Test.5}; + let Test.1 : Builtin(Int(I64)) = StructAtIndex 0 Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/let_with_record_pattern_list.txt b/compiler/test_mono/generated/let_with_record_pattern_list.txt index 91f6ea8249..1c22f23866 100644 --- a/compiler/test_mono/generated/let_with_record_pattern_list.txt +++ b/compiler/test_mono/generated/let_with_record_pattern_list.txt @@ -1,8 +1,8 @@ procedure Test.0 (): - let Test.4 = Array [1i64, 3i64, 4i64]; - let Test.5 = 3.14f64; - let Test.3 = Struct {Test.4, Test.5}; - let Test.1 = StructAtIndex 0 Test.3; + let Test.4 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 3i64, 4i64]; + let Test.5 : Builtin(Float(F64)) = 3.14f64; + let Test.3 : Struct([Builtin(List(Builtin(Int(I64)))), Builtin(Float(F64))]) = Struct {Test.4, Test.5}; + let Test.1 : Builtin(List(Builtin(Int(I64)))) = StructAtIndex 0 Test.3; inc Test.1; dec Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/let_x_in_x.txt b/compiler/test_mono/generated/let_x_in_x.txt index 567439fa80..39b16fd536 100644 --- a/compiler/test_mono/generated/let_x_in_x.txt +++ b/compiler/test_mono/generated/let_x_in_x.txt @@ -1,5 +1,5 @@ procedure Test.0 (): - let Test.1 = 5i64; - let Test.4 = 17i64; - let Test.2 = 1337i64; + let Test.1 : Builtin(Int(I64)) = 5i64; + let Test.4 : Builtin(Int(I64)) = 17i64; + let Test.2 : Builtin(Int(I64)) = 1337i64; ret Test.2; diff --git a/compiler/test_mono/generated/let_x_in_x_indirect.txt b/compiler/test_mono/generated/let_x_in_x_indirect.txt index 4fe43f886b..761529956b 100644 --- a/compiler/test_mono/generated/let_x_in_x_indirect.txt +++ b/compiler/test_mono/generated/let_x_in_x_indirect.txt @@ -1,8 +1,8 @@ procedure Test.0 (): - let Test.1 = 5i64; - let Test.4 = 17i64; - let Test.5 = 1i64; - let Test.2 = 1337i64; - let Test.7 = Struct {Test.2, Test.4}; - let Test.6 = StructAtIndex 0 Test.7; + let Test.1 : Builtin(Int(I64)) = 5i64; + let Test.4 : Builtin(Int(I64)) = 17i64; + let Test.5 : Builtin(Int(I64)) = 1i64; + let Test.2 : Builtin(Int(I64)) = 1337i64; + let Test.7 : Struct([Builtin(Int(I64)), Builtin(Int(I64))]) = Struct {Test.2, Test.4}; + let Test.6 : Builtin(Int(I64)) = StructAtIndex 0 Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/linked_list_length_twice.txt b/compiler/test_mono/generated/linked_list_length_twice.txt index a540fcc435..793926eec5 100644 --- a/compiler/test_mono/generated/linked_list_length_twice.txt +++ b/compiler/test_mono/generated/linked_list_length_twice.txt @@ -1,25 +1,25 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.10 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.10 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.10; procedure Test.3 (Test.5): - let Test.16 = 1i64; - let Test.17 = GetTagId Test.5; - let Test.18 = lowlevel Eq Test.16 Test.17; + let Test.16 : Builtin(Bool) = 1i64; + let Test.17 : Builtin(Bool) = GetTagId Test.5; + let Test.18 : Builtin(Bool) = lowlevel Eq Test.16 Test.17; if Test.18 then - let Test.12 = 0i64; + let Test.12 : Builtin(Int(I64)) = 0i64; ret Test.12; else - let Test.6 = UnionAtIndex (Id 0) (Index 1) Test.5; - let Test.14 = 1i64; - let Test.15 = CallByName Test.3 Test.6; - let Test.13 = CallByName Num.24 Test.14 Test.15; + let Test.6 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Builtin(Int(I64)), RecursivePointer] }) = UnionAtIndex (Id 0) (Index 1) Test.5; + let Test.14 : Builtin(Int(I64)) = 1i64; + let Test.15 : Builtin(Int(I64)) = CallByName Test.3 Test.6; + let Test.13 : Builtin(Int(I64)) = CallByName Num.24 Test.14 Test.15; ret Test.13; procedure Test.0 (): - let Test.2 = Nil ; - let Test.8 = CallByName Test.3 Test.2; - let Test.9 = CallByName Test.3 Test.2; + let Test.2 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Builtin(Int(I64)), RecursivePointer] }) = Nil ; + let Test.8 : Builtin(Int(I64)) = CallByName Test.3 Test.2; + let Test.9 : Builtin(Int(I64)) = CallByName Test.3 Test.2; dec Test.2; - let Test.7 = CallByName Num.24 Test.8 Test.9; + let Test.7 : Builtin(Int(I64)) = CallByName Num.24 Test.8 Test.9; ret Test.7; diff --git a/compiler/test_mono/generated/list_append.txt b/compiler/test_mono/generated/list_append.txt index 8cb3ba9758..2b0be9dd16 100644 --- a/compiler/test_mono/generated/list_append.txt +++ b/compiler/test_mono/generated/list_append.txt @@ -1,9 +1,9 @@ procedure List.5 (#Attr.2, #Attr.3): - let Test.4 = lowlevel ListAppend #Attr.2 #Attr.3; + let Test.4 : Builtin(List(Builtin(Int(I64)))) = lowlevel ListAppend #Attr.2 #Attr.3; ret Test.4; procedure Test.0 (): - let Test.2 = Array [1i64]; - let Test.3 = 2i64; - let Test.1 = CallByName List.5 Test.2 Test.3; + let Test.2 : Builtin(List(Builtin(Int(I64)))) = Array [1i64]; + let Test.3 : Builtin(Int(I64)) = 2i64; + let Test.1 : Builtin(List(Builtin(Int(I64)))) = CallByName List.5 Test.2 Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/list_append_closure.txt b/compiler/test_mono/generated/list_append_closure.txt index cffdbe7e48..3cd9bffd51 100644 --- a/compiler/test_mono/generated/list_append_closure.txt +++ b/compiler/test_mono/generated/list_append_closure.txt @@ -1,13 +1,13 @@ procedure List.5 (#Attr.2, #Attr.3): - let Test.7 = lowlevel ListAppend #Attr.2 #Attr.3; + let Test.7 : Builtin(List(Builtin(Int(I64)))) = lowlevel ListAppend #Attr.2 #Attr.3; ret Test.7; procedure Test.1 (Test.2): - let Test.6 = 42i64; - let Test.5 = CallByName List.5 Test.2 Test.6; + let Test.6 : Builtin(Int(I64)) = 42i64; + let Test.5 : Builtin(List(Builtin(Int(I64)))) = CallByName List.5 Test.2 Test.6; ret Test.5; procedure Test.0 (): - let Test.4 = Array [1i64, 2i64]; - let Test.3 = CallByName Test.1 Test.4; + let Test.4 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64]; + let Test.3 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.4; ret Test.3; diff --git a/compiler/test_mono/generated/list_cannot_update_inplace.txt b/compiler/test_mono/generated/list_cannot_update_inplace.txt index 88039ce8a0..113f7b74cb 100644 --- a/compiler/test_mono/generated/list_cannot_update_inplace.txt +++ b/compiler/test_mono/generated/list_cannot_update_inplace.txt @@ -1,37 +1,37 @@ procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.19 = lowlevel ListLen #Attr.2; - let Test.17 = lowlevel NumLt #Attr.3 Test.19; + let Test.19 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.17 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.19; if Test.17 then - let Test.18 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; + let Test.18 : Builtin(List(Builtin(Int(I64)))) = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; ret Test.18; else ret #Attr.2; procedure List.7 (#Attr.2): - let Test.9 = lowlevel ListLen #Attr.2; + let Test.9 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; ret Test.9; procedure Num.24 (#Attr.2, #Attr.3): - let Test.7 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.7 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.7; procedure Test.1 (): - let Test.10 = Array [1i64, 2i64, 3i64]; + let Test.10 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; ret Test.10; procedure Test.2 (Test.3): - let Test.14 = 0i64; - let Test.15 = 0i64; - let Test.13 = CallByName List.4 Test.3 Test.14 Test.15; + let Test.14 : Builtin(Int(U64)) = 0i64; + let Test.15 : Builtin(Int(I64)) = 0i64; + let Test.13 : Builtin(List(Builtin(Int(I64)))) = CallByName List.4 Test.3 Test.14 Test.15; ret Test.13; procedure Test.0 (): - let Test.12 = CallByName Test.1; - let Test.11 = CallByName Test.2 Test.12; - let Test.5 = CallByName List.7 Test.11; + let Test.12 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1; + let Test.11 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.2 Test.12; + let Test.5 : Builtin(Int(U64)) = CallByName List.7 Test.11; dec Test.11; - let Test.8 = CallByName Test.1; - let Test.6 = CallByName List.7 Test.8; + let Test.8 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1; + let Test.6 : Builtin(Int(U64)) = CallByName List.7 Test.8; dec Test.8; - let Test.4 = CallByName Num.24 Test.5 Test.6; + let Test.4 : Builtin(Int(U64)) = CallByName Num.24 Test.5 Test.6; ret Test.4; diff --git a/compiler/test_mono/generated/list_get.txt b/compiler/test_mono/generated/list_get.txt index 49280478d1..1ea41edce5 100644 --- a/compiler/test_mono/generated/list_get.txt +++ b/compiler/test_mono/generated/list_get.txt @@ -1,23 +1,23 @@ procedure List.3 (#Attr.2, #Attr.3): - let Test.13 = lowlevel ListLen #Attr.2; - let Test.10 = lowlevel NumLt #Attr.3 Test.13; + let Test.13 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.10 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.13; if Test.10 then - let Test.12 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.11 = Ok Test.12; + let Test.12 : Builtin(Int(I64)) = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.11 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Ok Test.12; ret Test.11; else - let Test.9 = Struct {}; - let Test.8 = Err Test.9; + let Test.9 : Struct([]) = Struct {}; + let Test.8 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Err Test.9; ret Test.8; procedure Test.1 (Test.2): - let Test.6 = Array [1i64, 2i64, 3i64]; - let Test.7 = 0i64; - let Test.5 = CallByName List.3 Test.6 Test.7; + let Test.6 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; + let Test.7 : Builtin(Int(U64)) = 0i64; + let Test.5 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName List.3 Test.6 Test.7; dec Test.6; ret Test.5; procedure Test.0 (): - let Test.4 = Struct {}; - let Test.3 = CallByName Test.1 Test.4; + let Test.4 : Struct([]) = Struct {}; + let Test.3 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName Test.1 Test.4; ret Test.3; diff --git a/compiler/test_mono/generated/list_len.txt b/compiler/test_mono/generated/list_len.txt index 3cc3d980bd..41e4167e0f 100644 --- a/compiler/test_mono/generated/list_len.txt +++ b/compiler/test_mono/generated/list_len.txt @@ -1,21 +1,21 @@ procedure List.7 (#Attr.2): - let Test.7 = lowlevel ListLen #Attr.2; + let Test.7 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; ret Test.7; procedure List.7 (#Attr.2): - let Test.8 = lowlevel ListLen #Attr.2; + let Test.8 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; ret Test.8; procedure Num.24 (#Attr.2, #Attr.3): - let Test.6 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.6 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.6; procedure Test.0 (): - let Test.1 = Array [1i64, 2i64, 3i64]; - let Test.2 = Array [1f64]; - let Test.4 = CallByName List.7 Test.1; + let Test.1 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; + let Test.2 : Builtin(List(Builtin(Float(F64)))) = Array [1f64]; + let Test.4 : Builtin(Int(U64)) = CallByName List.7 Test.1; dec Test.1; - let Test.5 = CallByName List.7 Test.2; + let Test.5 : Builtin(Int(U64)) = CallByName List.7 Test.2; dec Test.2; - let Test.3 = CallByName Num.24 Test.4 Test.5; + let Test.3 : Builtin(Int(U64)) = CallByName Num.24 Test.4 Test.5; ret Test.3; diff --git a/compiler/test_mono/generated/list_pass_to_function.txt b/compiler/test_mono/generated/list_pass_to_function.txt index c2d21a9d87..090b5affdd 100644 --- a/compiler/test_mono/generated/list_pass_to_function.txt +++ b/compiler/test_mono/generated/list_pass_to_function.txt @@ -1,19 +1,19 @@ procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.11 = lowlevel ListLen #Attr.2; - let Test.9 = lowlevel NumLt #Attr.3 Test.11; + let Test.11 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.9 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.11; if Test.9 then - let Test.10 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; + let Test.10 : Builtin(List(Builtin(Int(I64)))) = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; ret Test.10; else ret #Attr.2; procedure Test.2 (Test.3): - let Test.6 = 0i64; - let Test.7 = 0i64; - let Test.5 = CallByName List.4 Test.3 Test.6 Test.7; + let Test.6 : Builtin(Int(U64)) = 0i64; + let Test.7 : Builtin(Int(I64)) = 0i64; + let Test.5 : Builtin(List(Builtin(Int(I64)))) = CallByName List.4 Test.3 Test.6 Test.7; ret Test.5; procedure Test.0 (): - let Test.1 = Array [1i64, 2i64, 3i64]; - let Test.4 = CallByName Test.2 Test.1; + let Test.1 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; + let Test.4 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.2 Test.1; ret Test.4; diff --git a/compiler/test_mono/generated/mk_pair_of.txt b/compiler/test_mono/generated/mk_pair_of.txt index a223ddd1c2..98bd4b0d18 100644 --- a/compiler/test_mono/generated/mk_pair_of.txt +++ b/compiler/test_mono/generated/mk_pair_of.txt @@ -1,9 +1,9 @@ procedure Test.1 (Test.2): inc Test.2; - let Test.6 = Struct {Test.2, Test.2}; + let Test.6 : Struct([Builtin(List(Builtin(Int(I64)))), Builtin(List(Builtin(Int(I64))))]) = Struct {Test.2, Test.2}; ret Test.6; procedure Test.0 (): - let Test.5 = Array [1i64, 2i64, 3i64]; - let Test.4 = CallByName Test.1 Test.5; + let Test.5 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; + let Test.4 : Struct([Builtin(List(Builtin(Int(I64)))), Builtin(List(Builtin(Int(I64))))]) = CallByName Test.1 Test.5; ret Test.4; diff --git a/compiler/test_mono/generated/nested_closure.txt b/compiler/test_mono/generated/nested_closure.txt index 3d5c1411d7..0966a26cc8 100644 --- a/compiler/test_mono/generated/nested_closure.txt +++ b/compiler/test_mono/generated/nested_closure.txt @@ -1,15 +1,15 @@ procedure Test.1 (Test.5): - let Test.2 = 42i64; - let Test.3 = Struct {Test.2}; + let Test.2 : Builtin(Int(I64)) = 42i64; + let Test.3 : LambdaSet(LambdaSet { set: [(`#UserApp.1`, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; ret Test.3; procedure Test.3 (Test.9, #Attr.12): - let Test.2 = StructAtIndex 0 #Attr.12; + let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 #Attr.12; ret Test.2; procedure Test.0 (): - let Test.8 = Struct {}; - let Test.4 = CallByName Test.1 Test.8; - let Test.7 = Struct {}; - let Test.6 = CallByName Test.3 Test.7 Test.4; + let Test.8 : Struct([]) = Struct {}; + let Test.4 : LambdaSet(LambdaSet { set: [(`#UserApp.1`, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = CallByName Test.1 Test.8; + let Test.7 : Struct([]) = Struct {}; + let Test.6 : Builtin(Int(I64)) = CallByName Test.3 Test.7 Test.4; ret Test.6; diff --git a/compiler/test_mono/generated/nested_pattern_match.txt b/compiler/test_mono/generated/nested_pattern_match.txt index eba5cb4940..157843e919 100644 --- a/compiler/test_mono/generated/nested_pattern_match.txt +++ b/compiler/test_mono/generated/nested_pattern_match.txt @@ -1,28 +1,28 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.8 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.0 (): - let Test.20 = 41i64; - let Test.19 = Just Test.20; - let Test.2 = Just Test.19; + let Test.20 : Builtin(Int(I64)) = 41i64; + let Test.19 : Union(NonRecursive([[Builtin(Int(I64))], []])) = Just Test.20; + let Test.2 : Union(NonRecursive([[Union(NonRecursive([[Builtin(Int(I64))], []]))], []])) = Just Test.19; joinpoint Test.16: - let Test.9 = 1i64; + let Test.9 : Builtin(Int(I64)) = 1i64; ret Test.9; in - let Test.14 = 0i64; - let Test.15 = GetTagId Test.2; - let Test.18 = lowlevel Eq Test.14 Test.15; + let Test.14 : Builtin(Int(U8)) = 0i64; + let Test.15 : Builtin(Int(U8)) = GetTagId Test.2; + let Test.18 : Builtin(Bool) = lowlevel Eq Test.14 Test.15; if Test.18 then - let Test.11 = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.12 = 0i64; - let Test.13 = GetTagId Test.11; - let Test.17 = lowlevel Eq Test.12 Test.13; + let Test.11 : Union(NonRecursive([[Builtin(Int(I64))], []])) = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.12 : Builtin(Int(U8)) = 0i64; + let Test.13 : Builtin(Int(U8)) = GetTagId Test.11; + let Test.17 : Builtin(Bool) = lowlevel Eq Test.12 Test.13; if Test.17 then - let Test.10 = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.5 = UnionAtIndex (Id 0) (Index 0) Test.10; - let Test.7 = 1i64; - let Test.6 = CallByName Num.24 Test.5 Test.7; + let Test.10 : Union(NonRecursive([[Builtin(Int(I64))], []])) = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.10; + let Test.7 : Builtin(Int(I64)) = 1i64; + let Test.6 : Builtin(Int(I64)) = CallByName Num.24 Test.5 Test.7; ret Test.6; else jump Test.16; diff --git a/compiler/test_mono/generated/one_element_tag.txt b/compiler/test_mono/generated/one_element_tag.txt index 4ecfe61168..701a8080f6 100644 --- a/compiler/test_mono/generated/one_element_tag.txt +++ b/compiler/test_mono/generated/one_element_tag.txt @@ -1,3 +1,3 @@ procedure Test.0 (): - let Test.4 = 2i64; + let Test.4 : Builtin(Int(I64)) = 2i64; ret Test.4; diff --git a/compiler/test_mono/generated/optional_when.txt b/compiler/test_mono/generated/optional_when.txt index 37f5c5108f..bca94afc2d 100644 --- a/compiler/test_mono/generated/optional_when.txt +++ b/compiler/test_mono/generated/optional_when.txt @@ -1,42 +1,42 @@ procedure Num.26 (#Attr.2, #Attr.3): - let Test.17 = lowlevel NumMul #Attr.2 #Attr.3; + let Test.17 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; ret Test.17; procedure Test.1 (Test.6): - let Test.22 = StructAtIndex 1 Test.6; - let Test.23 = false; - let Test.24 = lowlevel Eq Test.23 Test.22; + let Test.22 : Builtin(Bool) = StructAtIndex 1 Test.6; + let Test.23 : Builtin(Bool) = false; + let Test.24 : Builtin(Bool) = lowlevel Eq Test.23 Test.22; if Test.24 then - let Test.8 = StructAtIndex 0 Test.6; + let Test.8 : Builtin(Int(I64)) = StructAtIndex 0 Test.6; ret Test.8; else - let Test.10 = StructAtIndex 0 Test.6; + let Test.10 : Builtin(Int(I64)) = StructAtIndex 0 Test.6; ret Test.10; procedure Test.1 (Test.6): - let Test.33 = false; - let Test.34 = lowlevel Eq Test.33 Test.6; + let Test.33 : Builtin(Bool) = false; + let Test.34 : Builtin(Bool) = lowlevel Eq Test.33 Test.6; if Test.34 then - let Test.8 = 3i64; + let Test.8 : Builtin(Int(I64)) = 3i64; ret Test.8; else - let Test.10 = 5i64; + let Test.10 : Builtin(Int(I64)) = 5i64; ret Test.10; procedure Test.0 (): - let Test.37 = true; - let Test.5 = CallByName Test.1 Test.37; - let Test.35 = false; - let Test.3 = CallByName Test.1 Test.35; - let Test.28 = 11i64; - let Test.29 = true; - let Test.27 = Struct {Test.28, Test.29}; - let Test.4 = CallByName Test.1 Test.27; - let Test.25 = 7i64; - let Test.26 = false; - let Test.19 = Struct {Test.25, Test.26}; - let Test.2 = CallByName Test.1 Test.19; - let Test.18 = CallByName Num.26 Test.2 Test.3; - let Test.16 = CallByName Num.26 Test.18 Test.4; - let Test.15 = CallByName Num.26 Test.16 Test.5; + let Test.37 : Builtin(Bool) = true; + let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.37; + let Test.35 : Builtin(Bool) = false; + let Test.3 : Builtin(Int(I64)) = CallByName Test.1 Test.35; + let Test.28 : Builtin(Int(I64)) = 11i64; + let Test.29 : Builtin(Bool) = true; + let Test.27 : Struct([Builtin(Int(I64)), Builtin(Bool)]) = Struct {Test.28, Test.29}; + let Test.4 : Builtin(Int(I64)) = CallByName Test.1 Test.27; + let Test.25 : Builtin(Int(I64)) = 7i64; + let Test.26 : Builtin(Bool) = false; + let Test.19 : Struct([Builtin(Int(I64)), Builtin(Bool)]) = Struct {Test.25, Test.26}; + let Test.2 : Builtin(Int(I64)) = CallByName Test.1 Test.19; + let Test.18 : Builtin(Int(I64)) = CallByName Num.26 Test.2 Test.3; + let Test.16 : Builtin(Int(I64)) = CallByName Num.26 Test.18 Test.4; + let Test.15 : Builtin(Int(I64)) = CallByName Num.26 Test.16 Test.5; ret Test.15; diff --git a/compiler/test_mono/generated/peano.txt b/compiler/test_mono/generated/peano.txt index 49814c63b4..b20043d155 100644 --- a/compiler/test_mono/generated/peano.txt +++ b/compiler/test_mono/generated/peano.txt @@ -1,6 +1,6 @@ procedure Test.0 (): - let Test.10 = Z ; - let Test.9 = S Test.10; - let Test.8 = S Test.9; - let Test.2 = S Test.8; + let Test.10 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = Z ; + let Test.9 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.10; + let Test.8 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.9; + let Test.2 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.8; ret Test.2; diff --git a/compiler/test_mono/generated/peano1.txt b/compiler/test_mono/generated/peano1.txt index bcf0d7a27d..55c0c229b4 100644 --- a/compiler/test_mono/generated/peano1.txt +++ b/compiler/test_mono/generated/peano1.txt @@ -1,15 +1,15 @@ procedure Test.0 (): - let Test.14 = Z ; - let Test.13 = S Test.14; - let Test.12 = S Test.13; - let Test.2 = S Test.12; - let Test.9 = 1i64; - let Test.10 = GetTagId Test.2; + let Test.14 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = Z ; + let Test.13 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.14; + let Test.12 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.13; + let Test.2 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.12; + let Test.9 : Builtin(Bool) = 1i64; + let Test.10 : Builtin(Bool) = GetTagId Test.2; dec Test.2; - let Test.11 = lowlevel Eq Test.9 Test.10; + let Test.11 : Builtin(Bool) = lowlevel Eq Test.9 Test.10; if Test.11 then - let Test.7 = 0i64; + let Test.7 : Builtin(Int(I64)) = 0i64; ret Test.7; else - let Test.8 = 1i64; + let Test.8 : Builtin(Int(I64)) = 1i64; ret Test.8; diff --git a/compiler/test_mono/generated/peano2.txt b/compiler/test_mono/generated/peano2.txt index 11ad76dbaf..152e09e19f 100644 --- a/compiler/test_mono/generated/peano2.txt +++ b/compiler/test_mono/generated/peano2.txt @@ -1,26 +1,26 @@ procedure Test.0 (): - let Test.20 = Z ; - let Test.19 = S Test.20; - let Test.18 = S Test.19; - let Test.2 = S Test.18; - let Test.15 = 0i64; - let Test.16 = GetTagId Test.2; - let Test.17 = lowlevel Eq Test.15 Test.16; + let Test.20 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = Z ; + let Test.19 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.20; + let Test.18 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.19; + let Test.2 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.18; + let Test.15 : Builtin(Bool) = 0i64; + let Test.16 : Builtin(Bool) = GetTagId Test.2; + let Test.17 : Builtin(Bool) = lowlevel Eq Test.15 Test.16; if Test.17 then - let Test.11 = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.11 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = UnionAtIndex (Id 0) (Index 0) Test.2; inc Test.11; dec Test.2; - let Test.12 = 0i64; - let Test.13 = GetTagId Test.11; + let Test.12 : Builtin(Bool) = 0i64; + let Test.13 : Builtin(Bool) = GetTagId Test.11; dec Test.11; - let Test.14 = lowlevel Eq Test.12 Test.13; + let Test.14 : Builtin(Bool) = lowlevel Eq Test.12 Test.13; if Test.14 then - let Test.7 = 1i64; + let Test.7 : Builtin(Int(I64)) = 1i64; ret Test.7; else - let Test.8 = 0i64; + let Test.8 : Builtin(Int(I64)) = 0i64; ret Test.8; else dec Test.2; - let Test.9 = 0i64; + let Test.9 : Builtin(Int(I64)) = 0i64; ret Test.9; diff --git a/compiler/test_mono/generated/quicksort_help.txt b/compiler/test_mono/generated/quicksort_help.txt index 81ba5892c3..062a9e39ce 100644 --- a/compiler/test_mono/generated/quicksort_help.txt +++ b/compiler/test_mono/generated/quicksort_help.txt @@ -1,32 +1,32 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.19 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.19 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.19; procedure Num.25 (#Attr.2, #Attr.3): - let Test.22 = lowlevel NumSub #Attr.2 #Attr.3; + let Test.22 : Builtin(Int(I64)) = lowlevel NumSub #Attr.2 #Attr.3; ret Test.22; procedure Num.27 (#Attr.2, #Attr.3): - let Test.26 = lowlevel NumLt #Attr.2 #Attr.3; + let Test.26 : Builtin(Bool) = lowlevel NumLt #Attr.2 #Attr.3; ret Test.26; procedure Test.1 (Test.27, Test.28, Test.29): joinpoint Test.12 Test.2 Test.3 Test.4: - let Test.14 = CallByName Num.27 Test.3 Test.4; + let Test.14 : Builtin(Bool) = CallByName Num.27 Test.3 Test.4; if Test.14 then dec Test.2; - let Test.25 = Array []; - let Test.24 = 0i64; - let Test.23 = Struct {Test.24, Test.25}; - let Test.5 = StructAtIndex 0 Test.23; - let Test.6 = StructAtIndex 1 Test.23; + let Test.25 : Builtin(List(Union(NonRecursive([])))) = Array []; + let Test.24 : Builtin(Int(I64)) = 0i64; + let Test.23 : Struct([Builtin(Int(I64)), Builtin(List(Union(NonRecursive([]))))]) = Struct {Test.24, Test.25}; + let Test.5 : Builtin(Int(I64)) = StructAtIndex 0 Test.23; + let Test.6 : Builtin(List(Union(NonRecursive([])))) = StructAtIndex 1 Test.23; inc Test.6; dec Test.23; - let Test.21 = 1i64; - let Test.20 = CallByName Num.25 Test.5 Test.21; - let Test.16 = CallByName Test.1 Test.6 Test.3 Test.20; - let Test.18 = 1i64; - let Test.17 = CallByName Num.24 Test.5 Test.18; + let Test.21 : Builtin(Int(I64)) = 1i64; + let Test.20 : Builtin(Int(I64)) = CallByName Num.25 Test.5 Test.21; + let Test.16 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.6 Test.3 Test.20; + let Test.18 : Builtin(Int(I64)) = 1i64; + let Test.17 : Builtin(Int(I64)) = CallByName Num.24 Test.5 Test.18; jump Test.12 Test.16 Test.17 Test.4; else ret Test.2; @@ -34,8 +34,8 @@ procedure Test.1 (Test.27, Test.28, Test.29): jump Test.12 Test.27 Test.28 Test.29; procedure Test.0 (): - let Test.9 = Array []; - let Test.10 = 0i64; - let Test.11 = 0i64; - let Test.8 = CallByName Test.1 Test.9 Test.10 Test.11; + let Test.9 : Builtin(List(Builtin(Int(I64)))) = Array []; + let Test.10 : Builtin(Int(I64)) = 0i64; + let Test.11 : Builtin(Int(I64)) = 0i64; + let Test.8 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.9 Test.10 Test.11; ret Test.8; diff --git a/compiler/test_mono/generated/quicksort_swap.txt b/compiler/test_mono/generated/quicksort_swap.txt index 5bb60a4c11..13e3c2824f 100644 --- a/compiler/test_mono/generated/quicksort_swap.txt +++ b/compiler/test_mono/generated/quicksort_swap.txt @@ -1,52 +1,52 @@ procedure List.3 (#Attr.2, #Attr.3): - let Test.37 = lowlevel ListLen #Attr.2; - let Test.34 = lowlevel NumLt #Attr.3 Test.37; + let Test.37 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.34 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.37; if Test.34 then - let Test.36 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.35 = Ok Test.36; + let Test.36 : Builtin(Int(I64)) = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.35 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Ok Test.36; ret Test.35; else - let Test.33 = Struct {}; - let Test.32 = Err Test.33; + let Test.33 : Struct([]) = Struct {}; + let Test.32 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Err Test.33; ret Test.32; procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.15 = lowlevel ListLen #Attr.2; - let Test.13 = lowlevel NumLt #Attr.3 Test.15; + let Test.15 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.13 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.15; if Test.13 then - let Test.14 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; + let Test.14 : Builtin(List(Builtin(Int(I64)))) = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; ret Test.14; else ret #Attr.2; procedure Test.1 (Test.2): - let Test.38 = 0i64; - let Test.30 = CallByName List.3 Test.2 Test.38; - let Test.31 = 0i64; - let Test.29 = CallByName List.3 Test.2 Test.31; - let Test.8 = Struct {Test.29, Test.30}; + let Test.38 : Builtin(Int(U64)) = 0i64; + let Test.30 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName List.3 Test.2 Test.38; + let Test.31 : Builtin(Int(U64)) = 0i64; + let Test.29 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName List.3 Test.2 Test.31; + let Test.8 : Struct([Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])), Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]]))]) = Struct {Test.29, Test.30}; joinpoint Test.26: - let Test.17 = Array []; + let Test.17 : Builtin(List(Builtin(Int(I64)))) = Array []; ret Test.17; in - let Test.23 = StructAtIndex 1 Test.8; - let Test.24 = 1i64; - let Test.25 = GetTagId Test.23; - let Test.28 = lowlevel Eq Test.24 Test.25; + let Test.23 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 1 Test.8; + let Test.24 : Builtin(Int(U8)) = 1i64; + let Test.25 : Builtin(Int(U8)) = GetTagId Test.23; + let Test.28 : Builtin(Bool) = lowlevel Eq Test.24 Test.25; if Test.28 then - let Test.20 = StructAtIndex 0 Test.8; - let Test.21 = 1i64; - let Test.22 = GetTagId Test.20; - let Test.27 = lowlevel Eq Test.21 Test.22; + let Test.20 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 0 Test.8; + let Test.21 : Builtin(Int(U8)) = 1i64; + let Test.22 : Builtin(Int(U8)) = GetTagId Test.20; + let Test.27 : Builtin(Bool) = lowlevel Eq Test.21 Test.22; if Test.27 then - let Test.19 = StructAtIndex 0 Test.8; - let Test.4 = UnionAtIndex (Id 1) (Index 0) Test.19; - let Test.18 = StructAtIndex 1 Test.8; - let Test.5 = UnionAtIndex (Id 1) (Index 0) Test.18; - let Test.16 = 0i64; - let Test.10 = CallByName List.4 Test.2 Test.16 Test.5; - let Test.11 = 0i64; - let Test.9 = CallByName List.4 Test.10 Test.11 Test.4; + let Test.19 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 0 Test.8; + let Test.4 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.19; + let Test.18 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 1 Test.8; + let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.18; + let Test.16 : Builtin(Int(U64)) = 0i64; + let Test.10 : Builtin(List(Builtin(Int(I64)))) = CallByName List.4 Test.2 Test.16 Test.5; + let Test.11 : Builtin(Int(U64)) = 0i64; + let Test.9 : Builtin(List(Builtin(Int(I64)))) = CallByName List.4 Test.10 Test.11 Test.4; ret Test.9; else dec Test.2; @@ -56,6 +56,6 @@ procedure Test.1 (Test.2): jump Test.26; procedure Test.0 (): - let Test.7 = Array [1i64, 2i64]; - let Test.6 = CallByName Test.1 Test.7; + let Test.7 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64]; + let Test.6 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt b/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt index 3992960017..360f017df4 100644 --- a/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt @@ -1,17 +1,17 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.8 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.1 (Test.4): - let Test.2 = StructAtIndex 0 Test.4; - let Test.3 = StructAtIndex 1 Test.4; - let Test.2 = 10i64; - let Test.7 = CallByName Num.24 Test.2 Test.3; + let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 Test.4; + let Test.3 : Builtin(Int(I64)) = StructAtIndex 1 Test.4; + let Test.2 : Builtin(Int(I64)) = 10i64; + let Test.7 : Builtin(Int(I64)) = CallByName Num.24 Test.2 Test.3; ret Test.7; procedure Test.0 (): - let Test.9 = 4i64; - let Test.10 = 9i64; - let Test.6 = Struct {Test.9, Test.10}; - let Test.5 = CallByName Test.1 Test.6; + let Test.9 : Builtin(Int(I64)) = 4i64; + let Test.10 : Builtin(Int(I64)) = 9i64; + let Test.6 : Struct([Builtin(Int(I64)), Builtin(Int(I64))]) = Struct {Test.9, Test.10}; + let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.6; ret Test.5; diff --git a/compiler/test_mono/generated/record_optional_field_function_use_default.txt b/compiler/test_mono/generated/record_optional_field_function_use_default.txt index 6b9b4191df..31a8235196 100644 --- a/compiler/test_mono/generated/record_optional_field_function_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_function_use_default.txt @@ -1,14 +1,14 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.8 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.1 (Test.4): - let Test.2 = 10i64; - let Test.2 = 10i64; - let Test.7 = CallByName Num.24 Test.2 Test.4; + let Test.2 : Builtin(Int(I64)) = 10i64; + let Test.2 : Builtin(Int(I64)) = 10i64; + let Test.7 : Builtin(Int(I64)) = CallByName Num.24 Test.2 Test.4; ret Test.7; procedure Test.0 (): - let Test.9 = 9i64; - let Test.5 = CallByName Test.1 Test.9; + let Test.9 : Builtin(Int(I64)) = 9i64; + let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.9; ret Test.5; diff --git a/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt b/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt index 2c5a7ec53f..cb651b5ae9 100644 --- a/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt @@ -1,16 +1,16 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.8 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.1 (Test.2): - let Test.3 = StructAtIndex 0 Test.2; - let Test.4 = StructAtIndex 1 Test.2; - let Test.7 = CallByName Num.24 Test.3 Test.4; + let Test.3 : Builtin(Int(I64)) = StructAtIndex 0 Test.2; + let Test.4 : Builtin(Int(I64)) = StructAtIndex 1 Test.2; + let Test.7 : Builtin(Int(I64)) = CallByName Num.24 Test.3 Test.4; ret Test.7; procedure Test.0 (): - let Test.9 = 4i64; - let Test.10 = 9i64; - let Test.6 = Struct {Test.9, Test.10}; - let Test.5 = CallByName Test.1 Test.6; + let Test.9 : Builtin(Int(I64)) = 4i64; + let Test.10 : Builtin(Int(I64)) = 9i64; + let Test.6 : Struct([Builtin(Int(I64)), Builtin(Int(I64))]) = Struct {Test.9, Test.10}; + let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.6; ret Test.5; diff --git a/compiler/test_mono/generated/record_optional_field_let_use_default.txt b/compiler/test_mono/generated/record_optional_field_let_use_default.txt index 7ea40fe7ab..1e42d1eb24 100644 --- a/compiler/test_mono/generated/record_optional_field_let_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_let_use_default.txt @@ -1,13 +1,13 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.8 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.1 (Test.2): - let Test.3 = 10i64; - let Test.7 = CallByName Num.24 Test.3 Test.2; + let Test.3 : Builtin(Int(I64)) = 10i64; + let Test.7 : Builtin(Int(I64)) = CallByName Num.24 Test.3 Test.2; ret Test.7; procedure Test.0 (): - let Test.9 = 9i64; - let Test.5 = CallByName Test.1 Test.9; + let Test.9 : Builtin(Int(I64)) = 9i64; + let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.9; ret Test.5; diff --git a/compiler/test_mono/generated/rigids.txt b/compiler/test_mono/generated/rigids.txt index 583b0a0182..5f0124657f 100644 --- a/compiler/test_mono/generated/rigids.txt +++ b/compiler/test_mono/generated/rigids.txt @@ -1,48 +1,48 @@ procedure List.3 (#Attr.2, #Attr.3): - let Test.39 = lowlevel ListLen #Attr.2; - let Test.36 = lowlevel NumLt #Attr.3 Test.39; + let Test.39 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.36 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.39; if Test.36 then - let Test.38 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.37 = Ok Test.38; + let Test.38 : Builtin(Int(I64)) = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.37 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Ok Test.38; ret Test.37; else - let Test.35 = Struct {}; - let Test.34 = Err Test.35; + let Test.35 : Struct([]) = Struct {}; + let Test.34 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Err Test.35; ret Test.34; procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.19 = lowlevel ListLen #Attr.2; - let Test.17 = lowlevel NumLt #Attr.3 Test.19; + let Test.19 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.17 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.19; if Test.17 then - let Test.18 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; + let Test.18 : Builtin(List(Builtin(Int(I64)))) = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; ret Test.18; else ret #Attr.2; procedure Test.1 (Test.2, Test.3, Test.4): - let Test.33 = CallByName List.3 Test.4 Test.3; - let Test.32 = CallByName List.3 Test.4 Test.2; - let Test.13 = Struct {Test.32, Test.33}; + let Test.33 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName List.3 Test.4 Test.3; + let Test.32 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName List.3 Test.4 Test.2; + let Test.13 : Struct([Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])), Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]]))]) = Struct {Test.32, Test.33}; joinpoint Test.29: - let Test.20 = Array []; + let Test.20 : Builtin(List(Builtin(Int(I64)))) = Array []; ret Test.20; in - let Test.26 = StructAtIndex 1 Test.13; - let Test.27 = 1i64; - let Test.28 = GetTagId Test.26; - let Test.31 = lowlevel Eq Test.27 Test.28; + let Test.26 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 1 Test.13; + let Test.27 : Builtin(Int(U8)) = 1i64; + let Test.28 : Builtin(Int(U8)) = GetTagId Test.26; + let Test.31 : Builtin(Bool) = lowlevel Eq Test.27 Test.28; if Test.31 then - let Test.23 = StructAtIndex 0 Test.13; - let Test.24 = 1i64; - let Test.25 = GetTagId Test.23; - let Test.30 = lowlevel Eq Test.24 Test.25; + let Test.23 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 0 Test.13; + let Test.24 : Builtin(Int(U8)) = 1i64; + let Test.25 : Builtin(Int(U8)) = GetTagId Test.23; + let Test.30 : Builtin(Bool) = lowlevel Eq Test.24 Test.25; if Test.30 then - let Test.22 = StructAtIndex 0 Test.13; - let Test.6 = UnionAtIndex (Id 1) (Index 0) Test.22; - let Test.21 = StructAtIndex 1 Test.13; - let Test.7 = UnionAtIndex (Id 1) (Index 0) Test.21; - let Test.15 = CallByName List.4 Test.4 Test.2 Test.7; - let Test.14 = CallByName List.4 Test.15 Test.3 Test.6; + let Test.22 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 0 Test.13; + let Test.6 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.22; + let Test.21 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 1 Test.13; + let Test.7 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.21; + let Test.15 : Builtin(List(Builtin(Int(I64)))) = CallByName List.4 Test.4 Test.2 Test.7; + let Test.14 : Builtin(List(Builtin(Int(I64)))) = CallByName List.4 Test.15 Test.3 Test.6; ret Test.14; else dec Test.4; @@ -52,8 +52,8 @@ procedure Test.1 (Test.2, Test.3, Test.4): jump Test.29; procedure Test.0 (): - let Test.10 = 0i64; - let Test.11 = 0i64; - let Test.12 = Array [1i64]; - let Test.9 = CallByName Test.1 Test.10 Test.11 Test.12; + let Test.10 : Builtin(Int(U64)) = 0i64; + let Test.11 : Builtin(Int(U64)) = 0i64; + let Test.12 : Builtin(List(Builtin(Int(I64)))) = Array [1i64]; + let Test.9 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.10 Test.11 Test.12; ret Test.9; diff --git a/compiler/test_mono/generated/simple_if.txt b/compiler/test_mono/generated/simple_if.txt index f796d9d4f2..ba3e24661c 100644 --- a/compiler/test_mono/generated/simple_if.txt +++ b/compiler/test_mono/generated/simple_if.txt @@ -1,8 +1,8 @@ procedure Test.0 (): - let Test.3 = true; + let Test.3 : Builtin(Bool) = true; if Test.3 then - let Test.4 = 1i64; + let Test.4 : Builtin(Int(I64)) = 1i64; ret Test.4; else - let Test.2 = 2i64; + let Test.2 : Builtin(Int(I64)) = 2i64; ret Test.2; diff --git a/compiler/test_mono/generated/somehow_drops_definitions.txt b/compiler/test_mono/generated/somehow_drops_definitions.txt index 2e7a497919..aed6376bbd 100644 --- a/compiler/test_mono/generated/somehow_drops_definitions.txt +++ b/compiler/test_mono/generated/somehow_drops_definitions.txt @@ -1,27 +1,27 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.27 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.27 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.27; procedure Num.26 (#Attr.2, #Attr.3): - let Test.22 = lowlevel NumMul #Attr.2 #Attr.3; + let Test.22 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; ret Test.22; procedure Test.1 (): - let Test.28 = 1i64; + let Test.28 : Builtin(Int(I64)) = 1i64; ret Test.28; procedure Test.2 (): - let Test.23 = 2i64; + let Test.23 : Builtin(Int(I64)) = 2i64; ret Test.23; procedure Test.3 (Test.6): - let Test.26 = CallByName Test.1; - let Test.25 = CallByName Num.24 Test.6 Test.26; + let Test.26 : Builtin(Int(I64)) = CallByName Test.1; + let Test.25 : Builtin(Int(I64)) = CallByName Num.24 Test.6 Test.26; ret Test.25; procedure Test.4 (Test.7): - let Test.21 = CallByName Test.2; - let Test.20 = CallByName Num.26 Test.7 Test.21; + let Test.21 : Builtin(Int(I64)) = CallByName Test.2; + let Test.20 : Builtin(Int(I64)) = CallByName Num.26 Test.7 Test.21; ret Test.20; procedure Test.5 (Test.8, Test.9): @@ -30,24 +30,24 @@ procedure Test.5 (Test.8, Test.9): in switch Test.8: case 0: - let Test.16 = CallByName Test.3 Test.9; + let Test.16 : Builtin(Int(I64)) = CallByName Test.3 Test.9; jump Test.15 Test.16; default: - let Test.17 = CallByName Test.4 Test.9; + let Test.17 : Builtin(Int(I64)) = CallByName Test.4 Test.9; jump Test.15 Test.17; procedure Test.0 (): joinpoint Test.19 Test.12: - let Test.13 = 42i64; - let Test.11 = CallByName Test.5 Test.12 Test.13; + let Test.13 : Builtin(Int(I64)) = 42i64; + let Test.11 : Builtin(Int(I64)) = CallByName Test.5 Test.12 Test.13; ret Test.11; in - let Test.24 = true; + let Test.24 : Builtin(Bool) = true; if Test.24 then - let Test.3 = false; + let Test.3 : LambdaSet(LambdaSet { set: [(`#UserApp.x`, []), (`#UserApp.one`, [])], representation: Builtin(Bool) }) = false; jump Test.19 Test.3; else - let Test.4 = true; + let Test.4 : LambdaSet(LambdaSet { set: [(`#UserApp.x`, []), (`#UserApp.one`, [])], representation: Builtin(Bool) }) = true; jump Test.19 Test.4; diff --git a/compiler/test_mono/generated/specialize_closures.txt b/compiler/test_mono/generated/specialize_closures.txt index d5d4592f76..42428a6f41 100644 --- a/compiler/test_mono/generated/specialize_closures.txt +++ b/compiler/test_mono/generated/specialize_closures.txt @@ -1,53 +1,53 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.28 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.28 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.28; procedure Num.26 (#Attr.2, #Attr.3): - let Test.25 = lowlevel NumMul #Attr.2 #Attr.3; + let Test.25 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; ret Test.25; procedure Test.1 (Test.2, Test.3): - let Test.17 = GetTagId Test.2; + let Test.17 : Builtin(Int(U8)) = GetTagId Test.2; joinpoint Test.18 Test.16: ret Test.16; in switch Test.17: case 0: - let Test.19 = CallByName Test.7 Test.3 Test.2; + let Test.19 : Builtin(Int(I64)) = CallByName Test.7 Test.3 Test.2; jump Test.18 Test.19; default: - let Test.20 = CallByName Test.8 Test.3 Test.2; + let Test.20 : Builtin(Int(I64)) = CallByName Test.8 Test.3 Test.2; jump Test.18 Test.20; procedure Test.7 (Test.10, #Attr.12): - let Test.4 = UnionAtIndex (Id 0) (Index 0) #Attr.12; - let Test.27 = CallByName Num.24 Test.10 Test.4; + let Test.4 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) #Attr.12; + let Test.27 : Builtin(Int(I64)) = CallByName Num.24 Test.10 Test.4; ret Test.27; procedure Test.8 (Test.11, #Attr.12): - let Test.6 = UnionAtIndex (Id 1) (Index 1) #Attr.12; - let Test.5 = UnionAtIndex (Id 1) (Index 0) #Attr.12; + let Test.6 : Builtin(Bool) = UnionAtIndex (Id 1) (Index 1) #Attr.12; + let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) #Attr.12; if Test.6 then - let Test.24 = CallByName Num.26 Test.11 Test.5; + let Test.24 : Builtin(Int(I64)) = CallByName Num.26 Test.11 Test.5; ret Test.24; else ret Test.11; procedure Test.0 (): - let Test.6 = true; - let Test.4 = 1i64; - let Test.5 = 2i64; + let Test.6 : Builtin(Bool) = true; + let Test.4 : Builtin(Int(I64)) = 1i64; + let Test.5 : Builtin(Int(I64)) = 2i64; joinpoint Test.22 Test.14: - let Test.15 = 42i64; - let Test.13 = CallByName Test.1 Test.14 Test.15; + let Test.15 : Builtin(Int(I64)) = 42i64; + let Test.13 : Builtin(Int(I64)) = CallByName Test.1 Test.14 Test.15; ret Test.13; in - let Test.26 = true; + let Test.26 : Builtin(Bool) = true; if Test.26 then - let Test.7 = ClosureTag(Test.7) Test.4; + let Test.7 : LambdaSet(LambdaSet { set: [(`#UserApp.increment`, [Builtin(Int(I64))]), (`#UserApp.double`, [Builtin(Int(I64)), Builtin(Bool)])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Bool)]])) }) = ClosureTag(Test.7) Test.4; jump Test.22 Test.7; else - let Test.8 = ClosureTag(Test.8) Test.5 Test.6; + let Test.8 : LambdaSet(LambdaSet { set: [(`#UserApp.increment`, [Builtin(Int(I64))]), (`#UserApp.double`, [Builtin(Int(I64)), Builtin(Bool)])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Bool)]])) }) = ClosureTag(Test.8) Test.5 Test.6; jump Test.22 Test.8; diff --git a/compiler/test_mono/generated/specialize_lowlevel.txt b/compiler/test_mono/generated/specialize_lowlevel.txt index 40dd98733d..1bb68683ce 100644 --- a/compiler/test_mono/generated/specialize_lowlevel.txt +++ b/compiler/test_mono/generated/specialize_lowlevel.txt @@ -1,44 +1,44 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.24 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.24 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.24; procedure Num.26 (#Attr.2, #Attr.3): - let Test.21 = lowlevel NumMul #Attr.2 #Attr.3; + let Test.21 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; ret Test.21; procedure Test.6 (Test.8, #Attr.12): - let Test.4 = UnionAtIndex (Id 0) (Index 0) #Attr.12; - let Test.23 = CallByName Num.24 Test.8 Test.4; + let Test.4 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) #Attr.12; + let Test.23 : Builtin(Int(I64)) = CallByName Num.24 Test.8 Test.4; ret Test.23; procedure Test.7 (Test.9, #Attr.12): - let Test.5 = UnionAtIndex (Id 1) (Index 0) #Attr.12; - let Test.20 = CallByName Num.26 Test.9 Test.5; + let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) #Attr.12; + let Test.20 : Builtin(Int(I64)) = CallByName Num.26 Test.9 Test.5; ret Test.20; procedure Test.0 (): - let Test.4 = 1i64; - let Test.5 = 2i64; - let Test.12 = 42i64; + let Test.4 : Builtin(Int(I64)) = 1i64; + let Test.5 : Builtin(Int(I64)) = 2i64; + let Test.12 : Builtin(Int(I64)) = 42i64; joinpoint Test.19 Test.13: - let Test.14 = GetTagId Test.13; + let Test.14 : Builtin(Int(U8)) = GetTagId Test.13; joinpoint Test.15 Test.11: ret Test.11; in switch Test.14: case 0: - let Test.16 = CallByName Test.6 Test.12 Test.13; + let Test.16 : Builtin(Int(I64)) = CallByName Test.6 Test.12 Test.13; jump Test.15 Test.16; default: - let Test.17 = CallByName Test.7 Test.12 Test.13; + let Test.17 : Builtin(Int(I64)) = CallByName Test.7 Test.12 Test.13; jump Test.15 Test.17; in - let Test.22 = true; + let Test.22 : Builtin(Bool) = true; if Test.22 then - let Test.6 = ClosureTag(Test.6) Test.4; + let Test.6 : LambdaSet(LambdaSet { set: [(`#UserApp.b`, [Builtin(Int(I64))]), (`#UserApp.increment`, [Builtin(Int(I64))])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) }) = ClosureTag(Test.6) Test.4; jump Test.19 Test.6; else - let Test.7 = ClosureTag(Test.7) Test.5; + let Test.7 : LambdaSet(LambdaSet { set: [(`#UserApp.b`, [Builtin(Int(I64))]), (`#UserApp.increment`, [Builtin(Int(I64))])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) }) = ClosureTag(Test.7) Test.5; jump Test.19 Test.7; diff --git a/compiler/test_mono/generated/when_joinpoint.txt b/compiler/test_mono/generated/when_joinpoint.txt index 3277764e1b..3366717fca 100644 --- a/compiler/test_mono/generated/when_joinpoint.txt +++ b/compiler/test_mono/generated/when_joinpoint.txt @@ -1,23 +1,23 @@ procedure Test.1 (Test.5): - let Test.2 = 0u8; + let Test.2 : Builtin(Int(U8)) = 0u8; joinpoint Test.9 Test.3: ret Test.3; in switch Test.2: case 1: - let Test.10 = 1i64; + let Test.10 : Builtin(Int(I64)) = 1i64; jump Test.9 Test.10; case 2: - let Test.11 = 2i64; + let Test.11 : Builtin(Int(I64)) = 2i64; jump Test.9 Test.11; default: - let Test.12 = 3i64; + let Test.12 : Builtin(Int(I64)) = 3i64; jump Test.9 Test.12; procedure Test.0 (): - let Test.7 = Struct {}; - let Test.6 = CallByName Test.1 Test.7; + let Test.7 : Struct([]) = Struct {}; + let Test.6 : Builtin(Int(I64)) = CallByName Test.1 Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/when_nested_maybe.txt b/compiler/test_mono/generated/when_nested_maybe.txt index eba5cb4940..157843e919 100644 --- a/compiler/test_mono/generated/when_nested_maybe.txt +++ b/compiler/test_mono/generated/when_nested_maybe.txt @@ -1,28 +1,28 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.8 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.0 (): - let Test.20 = 41i64; - let Test.19 = Just Test.20; - let Test.2 = Just Test.19; + let Test.20 : Builtin(Int(I64)) = 41i64; + let Test.19 : Union(NonRecursive([[Builtin(Int(I64))], []])) = Just Test.20; + let Test.2 : Union(NonRecursive([[Union(NonRecursive([[Builtin(Int(I64))], []]))], []])) = Just Test.19; joinpoint Test.16: - let Test.9 = 1i64; + let Test.9 : Builtin(Int(I64)) = 1i64; ret Test.9; in - let Test.14 = 0i64; - let Test.15 = GetTagId Test.2; - let Test.18 = lowlevel Eq Test.14 Test.15; + let Test.14 : Builtin(Int(U8)) = 0i64; + let Test.15 : Builtin(Int(U8)) = GetTagId Test.2; + let Test.18 : Builtin(Bool) = lowlevel Eq Test.14 Test.15; if Test.18 then - let Test.11 = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.12 = 0i64; - let Test.13 = GetTagId Test.11; - let Test.17 = lowlevel Eq Test.12 Test.13; + let Test.11 : Union(NonRecursive([[Builtin(Int(I64))], []])) = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.12 : Builtin(Int(U8)) = 0i64; + let Test.13 : Builtin(Int(U8)) = GetTagId Test.11; + let Test.17 : Builtin(Bool) = lowlevel Eq Test.12 Test.13; if Test.17 then - let Test.10 = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.5 = UnionAtIndex (Id 0) (Index 0) Test.10; - let Test.7 = 1i64; - let Test.6 = CallByName Num.24 Test.5 Test.7; + let Test.10 : Union(NonRecursive([[Builtin(Int(I64))], []])) = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.10; + let Test.7 : Builtin(Int(I64)) = 1i64; + let Test.6 : Builtin(Int(I64)) = CallByName Num.24 Test.5 Test.7; ret Test.6; else jump Test.16; diff --git a/compiler/test_mono/generated/when_on_record.txt b/compiler/test_mono/generated/when_on_record.txt index 339304f50f..7b96273077 100644 --- a/compiler/test_mono/generated/when_on_record.txt +++ b/compiler/test_mono/generated/when_on_record.txt @@ -1,9 +1,9 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.5 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.5 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.5; procedure Test.0 (): - let Test.6 = 2i64; - let Test.4 = 3i64; - let Test.3 = CallByName Num.24 Test.6 Test.4; + let Test.6 : Builtin(Int(I64)) = 2i64; + let Test.4 : Builtin(Int(I64)) = 3i64; + let Test.3 : Builtin(Int(I64)) = CallByName Num.24 Test.6 Test.4; ret Test.3; diff --git a/compiler/test_mono/generated/when_on_result.txt b/compiler/test_mono/generated/when_on_result.txt index 6b7b74b4ed..84144767fc 100644 --- a/compiler/test_mono/generated/when_on_result.txt +++ b/compiler/test_mono/generated/when_on_result.txt @@ -1,27 +1,27 @@ procedure Test.1 (Test.5): - let Test.19 = 2i64; - let Test.2 = Ok Test.19; + let Test.19 : Builtin(Int(I64)) = 2i64; + let Test.2 : Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) = Ok Test.19; joinpoint Test.9 Test.3: ret Test.3; in - let Test.16 = 1i64; - let Test.17 = GetTagId Test.2; - let Test.18 = lowlevel Eq Test.16 Test.17; + let Test.16 : Builtin(Int(U8)) = 1i64; + let Test.17 : Builtin(Int(U8)) = GetTagId Test.2; + let Test.18 : Builtin(Bool) = lowlevel Eq Test.16 Test.17; if Test.18 then - let Test.13 = UnionAtIndex (Id 1) (Index 0) Test.2; - let Test.14 = 3i64; - let Test.15 = lowlevel Eq Test.14 Test.13; + let Test.13 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.2; + let Test.14 : Builtin(Int(I64)) = 3i64; + let Test.15 : Builtin(Bool) = lowlevel Eq Test.14 Test.13; if Test.15 then - let Test.10 = 1i64; + let Test.10 : Builtin(Int(I64)) = 1i64; jump Test.9 Test.10; else - let Test.11 = 2i64; + let Test.11 : Builtin(Int(I64)) = 2i64; jump Test.9 Test.11; else - let Test.12 = 3i64; + let Test.12 : Builtin(Int(I64)) = 3i64; jump Test.9 Test.12; procedure Test.0 (): - let Test.7 = Struct {}; - let Test.6 = CallByName Test.1 Test.7; + let Test.7 : Struct([]) = Struct {}; + let Test.6 : Builtin(Int(I64)) = CallByName Test.1 Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/when_on_two_values.txt b/compiler/test_mono/generated/when_on_two_values.txt index c3de46f38e..34a0d9dd5f 100644 --- a/compiler/test_mono/generated/when_on_two_values.txt +++ b/compiler/test_mono/generated/when_on_two_values.txt @@ -1,26 +1,26 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.7 = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.7 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.7; procedure Test.0 (): - let Test.16 = 3i64; - let Test.15 = 2i64; - let Test.4 = Struct {Test.15, Test.16}; + let Test.16 : Builtin(Int(I64)) = 3i64; + let Test.15 : Builtin(Int(I64)) = 2i64; + let Test.4 : Struct([Builtin(Int(I64)), Builtin(Int(I64))]) = Struct {Test.15, Test.16}; joinpoint Test.12: - let Test.2 = StructAtIndex 0 Test.4; - let Test.3 = StructAtIndex 1 Test.4; - let Test.6 = CallByName Num.24 Test.2 Test.3; + let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 Test.4; + let Test.3 : Builtin(Int(I64)) = StructAtIndex 1 Test.4; + let Test.6 : Builtin(Int(I64)) = CallByName Num.24 Test.2 Test.3; ret Test.6; in - let Test.10 = StructAtIndex 1 Test.4; - let Test.11 = 3i64; - let Test.14 = lowlevel Eq Test.11 Test.10; + let Test.10 : Builtin(Int(I64)) = StructAtIndex 1 Test.4; + let Test.11 : Builtin(Int(I64)) = 3i64; + let Test.14 : Builtin(Bool) = lowlevel Eq Test.11 Test.10; if Test.14 then - let Test.8 = StructAtIndex 0 Test.4; - let Test.9 = 4i64; - let Test.13 = lowlevel Eq Test.9 Test.8; + let Test.8 : Builtin(Int(I64)) = StructAtIndex 0 Test.4; + let Test.9 : Builtin(Int(I64)) = 4i64; + let Test.13 : Builtin(Bool) = lowlevel Eq Test.9 Test.8; if Test.13 then - let Test.5 = 9i64; + let Test.5 : Builtin(Int(I64)) = 9i64; ret Test.5; else jump Test.12; From 8d372edda1a4bdfa0e17b5ccd007ec036dbd5354 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Tue, 11 Jan 2022 18:40:32 -0500 Subject: [PATCH 193/541] Add config variables for printing IR during mono stages --- compiler/README.md | 21 +++++++++++++++++++++ compiler/load/src/file.rs | 37 +++++++++++++++++++++++++------------ compiler/mono/src/ir.rs | 16 +++++++++++----- 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/compiler/README.md b/compiler/README.md index 77e55b5762..b78867e9f2 100644 --- a/compiler/README.md +++ b/compiler/README.md @@ -164,3 +164,24 @@ The compiler is invoked from the CLI via `build_file` in cli/src/build.rs | Code gen (unoptimized but fast, Wasm) | gen_wasm/src/lib.rs: build_module | For a more detailed understanding of the compilation phases, see the `Phase`, `BuildTask`, and `Msg` enums in `load/src/file.rs`. + +## Debugging intermediate representations + +### The mono IR + +If you observe a miscomplication, you may first want to check the generated mono +IR for your code - maybe there was a problem during specialization or layout +generation. One way to do this is to add a test to `test_mono/src/tests.rs` +and run the tests with `cargo test -p test_mono`; this will write the mono +IR to a file. + +You may also want to set some or all of the following environment variables: + +- `PRINT_IR_AFTER_SPECIALIZATION=1` prints the mono IR after function + specialization to stdout +- `PRINT_IR_AFTER_RESET_REUSE=1` prints the mono IR after insertion of + reset/reuse isntructions to stdout +- `PRINT_IR_AFTER_REFCOUNT=1` prints the mono IR after insertion of reference + counting instructions to stdout +- `PRETTY_PRINT_IR_SYMBOLS=1` instructs the pretty printer to dump all the + information it knows about the mono IR whenever it is printed diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index f85a9489f2..966b999cc6 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -1647,6 +1647,23 @@ fn start_tasks<'a>( Ok(()) } +#[cfg(debug_assertions)] +fn debug_print_ir(state: &State, flag: &str) { + if env::var(flag) != Ok("1".into()) { + return; + } + + let procs_string = state + .procedures + .values() + .map(|proc| proc.to_pretty(200)) + .collect::>(); + + let result = procs_string.join("\n"); + + println!("{}", result); +} + fn update<'a>( mut state: State<'a>, msg: Msg<'a>, @@ -2075,6 +2092,9 @@ fn update<'a>( && state.dependencies.solved_all() && state.goal_phase == Phase::MakeSpecializations { + #[cfg(debug_assertions)] + debug_print_ir(&state, "PRINT_IR_AFTER_SPECIALIZATION"); + Proc::insert_reset_reuse_operations( arena, module_id, @@ -2083,21 +2103,14 @@ fn update<'a>( &mut state.procedures, ); - // Uncomment to display the mono IR of the module, for debug purposes - if false { - let procs_string = state - .procedures - .values() - .map(|proc| proc.to_pretty(200)) - .collect::>(); - - let result = procs_string.join("\n"); - - println!("{}", result); - } + #[cfg(debug_assertions)] + debug_print_ir(&state, "PRINT_IR_AFTER_RESET_REUSE"); Proc::insert_refcount_operations(arena, &mut state.procedures); + #[cfg(debug_assertions)] + debug_print_ir(&state, "PRINT_IR_AFTER_REFCOUNT"); + // This is not safe with the new non-recursive RC updates that we do for tag unions // // Proc::optimize_refcount_operations( diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 86fe0df692..9d162a8c68 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -20,7 +20,13 @@ use roc_types::subs::{Content, FlatType, StorageSubs, Subs, Variable, VariableSu use std::collections::HashMap; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder}; -pub const PRETTY_PRINT_IR_SYMBOLS: bool = false; +fn pretty_print_ir_symbols() -> bool { + #[cfg(debug_assertions)] + if std::env::var("PRETTY_PRINT_IR_SYMBOLS") == Ok("1".into()) { + return true; + } + return false; +} // if your changes cause this number to go down, great! // please change it to the lower number. @@ -268,7 +274,7 @@ impl<'a> Proc<'a> { .iter() .map(|(_, symbol)| symbol_to_doc(alloc, *symbol)); - if PRETTY_PRINT_IR_SYMBOLS { + if pretty_print_ir_symbols() { alloc .text("procedure : ") .append(symbol_to_doc(alloc, self.name)) @@ -1119,7 +1125,7 @@ impl<'a> BranchInfo<'a> { tag_id, scrutinee, layout: _, - } if PRETTY_PRINT_IR_SYMBOLS => alloc + } if pretty_print_ir_symbols() => alloc .hardline() .append(" BranchInfo: { scrutinee: ") .append(symbol_to_doc(alloc, *scrutinee)) @@ -1128,7 +1134,7 @@ impl<'a> BranchInfo<'a> { .append("} "), _ => { - if PRETTY_PRINT_IR_SYMBOLS { + if pretty_print_ir_symbols() { alloc.text(" ") } else { alloc.text("") @@ -1463,7 +1469,7 @@ where { use roc_module::ident::ModuleName; - if PRETTY_PRINT_IR_SYMBOLS { + if pretty_print_ir_symbols() { alloc.text(format!("{:?}", symbol)) } else { let text = format!("{}", symbol); From c38ca2dfa49f4ce992252a712ea7d095bfbdbfdf Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Tue, 11 Jan 2022 19:07:18 -0500 Subject: [PATCH 194/541] Anything for you mr paperclippy --- compiler/mono/src/ir.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 9d162a8c68..970144319a 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -25,7 +25,7 @@ fn pretty_print_ir_symbols() -> bool { if std::env::var("PRETTY_PRINT_IR_SYMBOLS") == Ok("1".into()) { return true; } - return false; + false } // if your changes cause this number to go down, great! From 5f216122d619812c10d02bc0e637ab4530362f73 Mon Sep 17 00:00:00 2001 From: hafiz <20735482+ayazhafiz@users.noreply.github.com> Date: Wed, 12 Jan 2022 17:38:15 -0500 Subject: [PATCH 195/541] Update dev.rs --- compiler/test_gen/src/helpers/dev.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/test_gen/src/helpers/dev.rs b/compiler/test_gen/src/helpers/dev.rs index 68b1189936..86d5bad36b 100644 --- a/compiler/test_gen/src/helpers/dev.rs +++ b/compiler/test_gen/src/helpers/dev.rs @@ -7,7 +7,7 @@ use roc_region::all::LineInfo; use tempfile::tempdir; #[allow(unused_imports)] -use roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS; +use roc_mono::ir::pretty_print_ir_symbols; #[allow(dead_code)] fn promote_expr_to_module(src: &str) -> String { @@ -76,7 +76,7 @@ pub fn helper( // while you're working on the dev backend! { // println!("=========== Procedures =========="); - // if PRETTY_PRINT_IR_SYMBOLS { + // if pretty_print_ir_symbols() { // println!(""); // for proc in procedures.values() { // println!("{}", proc.to_pretty(200)); From 72ee2d63272c99ccc5e4f4bb63ba4c0a631913d5 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 13 Jan 2022 00:16:43 -0500 Subject: [PATCH 196/541] Mark pretty_print_ir_symbols public --- compiler/mono/src/ir.rs | 2 +- compiler/test_gen/src/helpers/wasm.rs | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 970144319a..c8a01fee96 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -20,7 +20,7 @@ use roc_types::subs::{Content, FlatType, StorageSubs, Subs, Variable, VariableSu use std::collections::HashMap; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder}; -fn pretty_print_ir_symbols() -> bool { +pub fn pretty_print_ir_symbols() -> bool { #[cfg(debug_assertions)] if std::env::var("PRETTY_PRINT_IR_SYMBOLS") == Ok("1".into()) { return true; diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index f1499241ad..ef19e44f47 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -19,9 +19,6 @@ const OUT_DIR_VAR: &str = "TEST_GEN_OUT"; const LIBC_PATH_VAR: &str = "TEST_GEN_WASM_LIBC_PATH"; const COMPILER_RT_PATH_VAR: &str = "TEST_GEN_WASM_COMPILER_RT_PATH"; -#[allow(unused_imports)] -use roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS; - const TEST_WRAPPER_NAME: &str = "test_wrapper"; fn promote_expr_to_module(src: &str) -> String { From 9f78eb2e01d958280fba0be2d97f133c41636e83 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 13 Jan 2022 18:30:36 -0500 Subject: [PATCH 197/541] Fix bug that caused extraneous assignment in IR generation Previously we would expand optional record fields to assignments when converting record patterns to "when" expressions. This resulted in incorrect code being generated. --- compiler/mono/src/ir.rs | 30 ++----------------- compiler/test_gen/src/gen_records.rs | 4 --- ...optional_field_function_no_use_default.txt | 1 - ...rd_optional_field_function_use_default.txt | 1 - 4 files changed, 3 insertions(+), 33 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 1a8f93f6b5..2b0f012280 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -1806,33 +1806,9 @@ fn patterns_to_when<'a>( for (pattern_var, pattern) in patterns.into_iter() { let context = crate::exhaustive::Context::BadArg; let mono_pattern = match from_can_pattern(env, layout_cache, &pattern.value) { - Ok((pat, assignments)) => { - for (symbol, variable, expr) in assignments.into_iter().rev() { - if let Ok(old_body) = body { - let def = roc_can::def::Def { - annotation: None, - expr_var: variable, - loc_expr: Loc::at(pattern.region, expr), - loc_pattern: Loc::at( - pattern.region, - roc_can::pattern::Pattern::Identifier(symbol), - ), - pattern_vars: std::iter::once((symbol, variable)).collect(), - }; - let new_expr = roc_can::expr::Expr::LetNonRec( - Box::new(def), - Box::new(old_body), - variable, - ); - let new_body = Loc { - region: pattern.region, - value: new_expr, - }; - - body = Ok(new_body); - } - } - + Ok((pat, _assignments)) => { + // Don't apply any assignments (e.g. to initialize optional variables) yet. + // We'll take care of that later when expanding the new "when" branch. pat } Err(runtime_error) => { diff --git a/compiler/test_gen/src/gen_records.rs b/compiler/test_gen/src/gen_records.rs index a6266b9a0c..f194de0939 100644 --- a/compiler/test_gen/src/gen_records.rs +++ b/compiler/test_gen/src/gen_records.rs @@ -587,9 +587,7 @@ fn optional_field_function_use_default() { #[test] #[cfg(any(feature = "gen-llvm"))] -#[ignore] fn optional_field_function_no_use_default() { - // blocked on https://github.com/rtfeldman/roc/issues/786 assert_evals_to!( indoc!( r#" @@ -608,9 +606,7 @@ fn optional_field_function_no_use_default() { #[test] #[cfg(any(feature = "gen-llvm"))] -#[ignore] fn optional_field_function_no_use_default_nested() { - // blocked on https://github.com/rtfeldman/roc/issues/786 assert_evals_to!( indoc!( r#" diff --git a/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt b/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt index 3992960017..d6bc301ad0 100644 --- a/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt @@ -5,7 +5,6 @@ procedure Num.24 (#Attr.2, #Attr.3): procedure Test.1 (Test.4): let Test.2 = StructAtIndex 0 Test.4; let Test.3 = StructAtIndex 1 Test.4; - let Test.2 = 10i64; let Test.7 = CallByName Num.24 Test.2 Test.3; ret Test.7; diff --git a/compiler/test_mono/generated/record_optional_field_function_use_default.txt b/compiler/test_mono/generated/record_optional_field_function_use_default.txt index 6b9b4191df..66f8ae5e70 100644 --- a/compiler/test_mono/generated/record_optional_field_function_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_function_use_default.txt @@ -3,7 +3,6 @@ procedure Num.24 (#Attr.2, #Attr.3): ret Test.8; procedure Test.1 (Test.4): - let Test.2 = 10i64; let Test.2 = 10i64; let Test.7 = CallByName Num.24 Test.2 Test.4; ret Test.7; From 6ea7d63380f2bcf210855b2b317cba09065cfcb1 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 13 Jan 2022 20:56:55 -0500 Subject: [PATCH 198/541] Make sure line offsets are generated properly for external programs Closes #2329 --- cli/tests/cli_run.rs | 55 +++++++++++++++++++++++++++++-- cli/tests/known_bad/TypeError.roc | 11 +++++++ cli/tests/known_bad/platform | 1 + cli_utils/src/helpers.rs | 13 ++++++++ compiler/build/src/program.rs | 2 +- 5 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 cli/tests/known_bad/TypeError.roc create mode 120000 cli/tests/known_bad/platform diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index e190e33981..3cfdd7f0a1 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -6,12 +6,16 @@ extern crate roc_collections; extern crate roc_load; extern crate roc_module; +#[macro_use] +extern crate indoc; + #[cfg(test)] mod cli_run { use cli_utils::helpers::{ - example_file, examples_dir, extract_valgrind_errors, fixture_file, run_cmd, run_roc, - run_with_valgrind, ValgrindError, ValgrindErrorXWhat, + example_file, examples_dir, extract_valgrind_errors, fixture_file, known_bad_file, run_cmd, + run_roc, run_with_valgrind, ValgrindError, ValgrindErrorXWhat, }; + use roc_test_utils::assert_multiline_str_eq; use serial_test::serial; use std::path::{Path, PathBuf}; @@ -44,6 +48,27 @@ mod cli_run { use_valgrind: bool, } + fn strip_colors(str: &str) -> String { + use roc_reporting::report::*; + str.replace(RED_CODE, "") + .replace(WHITE_CODE, "") + .replace(BLUE_CODE, "") + .replace(YELLOW_CODE, "") + .replace(GREEN_CODE, "") + .replace(CYAN_CODE, "") + .replace(MAGENTA_CODE, "") + .replace(RESET_CODE, "") + .replace(BOLD_CODE, "") + .replace(UNDERLINE_CODE, "") + } + + fn check_compile_error(file: &Path, flags: &[&str], expected: &str) { + let compile_out = run_roc(&[&["check", file.to_str().unwrap()], &flags[..]].concat()); + let err = compile_out.stdout.trim(); + let err = strip_colors(&err); + assert_multiline_str_eq!(err, expected.into()); + } + fn check_output_with_stdin( file: &Path, stdin: &[&str], @@ -740,6 +765,32 @@ mod cli_run { true, ); } + + #[test] + fn known_type_error() { + check_compile_error( + &known_bad_file("TypeError.roc"), + &[], + indoc!( + r#" + ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + + I cannot find a `d` value + + 10│ _ <- await (line d) + ^ + + Did you mean one of these? + + U8 + Ok + I8 + F64 + + ────────────────────────────────────────────────────────────────────────────────"# + ), + ); + } } #[allow(dead_code)] diff --git a/cli/tests/known_bad/TypeError.roc b/cli/tests/known_bad/TypeError.roc new file mode 100644 index 0000000000..9a36916ff4 --- /dev/null +++ b/cli/tests/known_bad/TypeError.roc @@ -0,0 +1,11 @@ +app "type-error" + packages { pf: "platform" } + imports [ pf.Stdout.{ line }, pf.Task.{ await } ] + provides [ main ] to pf + +main = + _ <- await (line "a") + _ <- await (line "b") + _ <- await (line "c") + _ <- await (line d) + line "e" diff --git a/cli/tests/known_bad/platform b/cli/tests/known_bad/platform new file mode 120000 index 0000000000..79724c8631 --- /dev/null +++ b/cli/tests/known_bad/platform @@ -0,0 +1 @@ +../../../examples/cli/platform \ No newline at end of file diff --git a/cli_utils/src/helpers.rs b/cli_utils/src/helpers.rs index ffd0bcafb9..8167fe38eb 100644 --- a/cli_utils/src/helpers.rs +++ b/cli_utils/src/helpers.rs @@ -294,6 +294,19 @@ pub fn fixture_file(dir_name: &str, file_name: &str) -> PathBuf { path } +#[allow(dead_code)] +pub fn known_bad_file(file_name: &str) -> PathBuf { + let mut path = root_dir(); + + // Descend into cli/tests/known_bad/{file_name} + path.push("cli"); + path.push("tests"); + path.push("known_bad"); + path.push(file_name); + + path +} + #[allow(dead_code)] pub fn repl_eval(input: &str) -> Out { let mut cmd = Command::new(path_to_roc_binary()); diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 98ea9de4ce..13c54c7005 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -82,7 +82,7 @@ fn report_problems_help( src_lines.extend(src.split('\n')); } - let lines = LineInfo::new(src); + let lines = LineInfo::new(&src_lines.join("\n")); // Report parsing and canonicalization problems let alloc = RocDocAllocator::new(&src_lines, *home, interns); From 6e67b77fa1aae9277a9e312a7c7234a75429c90d Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Thu, 13 Jan 2022 18:51:46 -0800 Subject: [PATCH 199/541] Remove awkward split between header_sources and defs sources --- compiler/build/src/program.rs | 10 +------ compiler/load/src/file.rs | 51 +---------------------------------- compiler/parse/src/state.rs | 6 ++--- 3 files changed, 5 insertions(+), 62 deletions(-) diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 13c54c7005..c895e3331a 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -31,7 +31,6 @@ const LLVM_VERSION: &str = "12"; pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> usize { report_problems_help( loaded.total_problems(), - &loaded.header_sources, &loaded.sources, &loaded.interns, &mut loaded.can_problems, @@ -43,7 +42,6 @@ pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> usize pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> usize { report_problems_help( loaded.total_problems(), - &loaded.header_sources, &loaded.sources, &loaded.interns, &mut loaded.can_problems, @@ -54,7 +52,6 @@ pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> usize { fn report_problems_help( total_problems: usize, - header_sources: &MutMap)>, sources: &MutMap)>, interns: &Interns, can_problems: &mut MutMap>, @@ -75,12 +72,7 @@ fn report_problems_help( for (home, (module_path, src)) in sources.iter() { let mut src_lines: Vec<&str> = Vec::new(); - if let Some((_, header_src)) = header_sources.get(home) { - src_lines.extend(header_src.split('\n')); - src_lines.extend(src.split('\n').skip(1)); - } else { - src_lines.extend(src.split('\n')); - } + src_lines.extend(src.split('\n')); let lines = LineInfo::new(&src_lines.join("\n")); diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 489f8a84f9..01e0aa4eb3 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -366,7 +366,6 @@ struct ModuleCache<'a> { mono_problems: MutMap>, sources: MutMap, - header_sources: MutMap, } fn start_phase<'a>( @@ -625,7 +624,6 @@ pub struct LoadedModule { pub dep_idents: MutMap, pub exposed_aliases: MutMap, pub exposed_values: Vec, - pub header_sources: MutMap)>, pub sources: MutMap)>, pub timings: MutMap, pub documentation: MutMap, @@ -672,7 +670,6 @@ struct ModuleHeader<'a> { package_qualified_imported_modules: MutSet>, exposes: Vec, exposed_imports: MutMap, - header_src: &'a str, parse_state: roc_parse::state::State<'a>, module_timing: ModuleTiming, } @@ -740,7 +737,6 @@ pub struct MonomorphizedModule<'a> { pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, pub entry_point: EntryPoint<'a>, pub exposed_to_host: MutMap, - pub header_sources: MutMap)>, pub sources: MutMap)>, pub timings: MutMap, } @@ -1739,11 +1735,6 @@ fn update<'a>( .exposed_symbols_by_module .insert(home, exposed_symbols); - state - .module_cache - .header_sources - .insert(home, (header.module_path.clone(), header.header_src)); - state .module_cache .imports @@ -2198,7 +2189,6 @@ fn finish_specialization( type_problems, can_problems, sources, - header_sources, .. } = module_cache; @@ -2207,11 +2197,6 @@ fn finish_specialization( .map(|(id, (path, src))| (id, (path, src.into()))) .collect(); - let header_sources: MutMap)> = header_sources - .into_iter() - .map(|(id, (path, src))| (id, (path, src.into()))) - .collect(); - let path_to_platform = { use PlatformPath::*; let package_name = match platform_path { @@ -2274,7 +2259,6 @@ fn finish_specialization( procedures, entry_point, sources, - header_sources, timings: state.timings, }) } @@ -2305,13 +2289,6 @@ fn finish( .map(|(id, (path, src))| (id, (path, src.into()))) .collect(); - let header_sources = state - .module_cache - .header_sources - .into_iter() - .map(|(id, (path, src))| (id, (path, src.into()))) - .collect(); - LoadedModule { module_id: state.root_id, interns, @@ -2323,7 +2300,6 @@ fn finish( exposed_aliases: exposed_aliases_by_symbol, exposed_values, exposed_to_host: exposed_vars_by_symbol.into_iter().collect(), - header_sources, sources, timings: state.timings, documentation, @@ -2379,10 +2355,6 @@ fn load_pkg_config<'a>( ))) } Ok((ast::Module::Platform { header }, parser_state)) => { - let delta = bytes.len() - parser_state.bytes().len(); - let chomped = &bytes[..delta]; - let header_src = unsafe { std::str::from_utf8_unchecked(chomped) }; - // make a Package-Config module that ultimately exposes `main` to the host let pkg_config_module_msg = fabricate_pkg_config_module( arena, @@ -2393,7 +2365,6 @@ fn load_pkg_config<'a>( module_ids.clone(), ident_ids_by_module.clone(), &header, - header_src, pkg_module_timing, ) .1; @@ -2530,11 +2501,6 @@ fn parse_header<'a>( match parsed { Ok((ast::Module::Interface { header }, parse_state)) => { - let header_src = unsafe { - let chomped = src_bytes.len() - parse_state.bytes().len(); - std::str::from_utf8_unchecked(&src_bytes[..chomped]) - }; - let info = HeaderInfo { loc_name: Loc { region: header.name.region, @@ -2543,7 +2509,6 @@ fn parse_header<'a>( filename, is_root_module, opt_shorthand, - header_src, packages: &[], exposes: unspace(arena, header.exposes.items), imports: unspace(arena, header.imports.items), @@ -2562,11 +2527,6 @@ fn parse_header<'a>( let mut pkg_config_dir = filename.clone(); pkg_config_dir.pop(); - let header_src = unsafe { - let chomped = src_bytes.len() - parse_state.bytes().len(); - std::str::from_utf8_unchecked(&src_bytes[..chomped]) - }; - let packages = unspace(arena, header.packages.items); let info = HeaderInfo { @@ -2577,7 +2537,6 @@ fn parse_header<'a>( filename, is_root_module, opt_shorthand, - header_src, packages, exposes: unspace(arena, header.provides.items), imports: unspace(arena, header.imports.items), @@ -2736,7 +2695,6 @@ struct HeaderInfo<'a> { filename: PathBuf, is_root_module: bool, opt_shorthand: Option<&'a str>, - header_src: &'a str, packages: &'a [Loc>], exposes: &'a [Loc>], imports: &'a [Loc>], @@ -2762,7 +2720,6 @@ fn send_header<'a>( exposes, imports, to_platform, - header_src, } = info; let declared_name: ModuleName = match &loc_name.value { @@ -2932,7 +2889,6 @@ fn send_header<'a>( package_qualified_imported_modules, deps_by_name, exposes: exposed, - header_src, parse_state, exposed_imports: scope, module_timing, @@ -2947,7 +2903,6 @@ struct PlatformHeaderInfo<'a> { filename: PathBuf, is_root_module: bool, shorthand: &'a str, - header_src: &'a str, app_module_id: ModuleId, packages: &'a [Loc>], provides: &'a [Loc>], @@ -2968,7 +2923,6 @@ fn send_header_two<'a>( filename, shorthand, is_root_module, - header_src, app_module_id, packages, provides, @@ -3162,7 +3116,6 @@ fn send_header_two<'a>( package_qualified_imported_modules, deps_by_name, exposes: exposed, - header_src, parse_state, exposed_imports: scope, module_timing, @@ -3310,14 +3263,12 @@ fn fabricate_pkg_config_module<'a>( module_ids: Arc>>, ident_ids_by_module: Arc>>, header: &PlatformHeader<'a>, - header_src: &'a str, module_timing: ModuleTiming, ) -> (ModuleId, Msg<'a>) { let info = PlatformHeaderInfo { filename, is_root_module: false, shorthand, - header_src, app_module_id, packages: &[], provides: unspace(arena, header.provides.items), @@ -3703,7 +3654,7 @@ where fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result, LoadingProblem<'a>> { let mut module_timing = header.module_timing; let parse_start = SystemTime::now(); - let source = header.parse_state.bytes(); + let source = header.parse_state.original_bytes(); let parse_state = header.parse_state; let parsed_defs = match module_defs().parse(arena, parse_state) { Ok((_, success, _state)) => success, diff --git a/compiler/parse/src/state.rs b/compiler/parse/src/state.rs index faf0132655..b6a198d6e7 100644 --- a/compiler/parse/src/state.rs +++ b/compiler/parse/src/state.rs @@ -34,7 +34,7 @@ impl<'a> State<'a> { self.original_bytes } - pub fn bytes(&self) -> &'a [u8] { + pub(crate) fn bytes(&self) -> &'a [u8] { &self.original_bytes[self.offset..] } @@ -43,7 +43,7 @@ impl<'a> State<'a> { } #[must_use] - pub fn advance(&self, offset: usize) -> State<'a> { + pub(crate) fn advance(&self, offset: usize) -> State<'a> { let mut state = self.clone(); debug_assert!(!state.bytes()[..offset].iter().any(|b| *b == b'\n')); state.offset += offset; @@ -51,7 +51,7 @@ impl<'a> State<'a> { } #[must_use] - pub fn advance_newline(&self) -> State<'a> { + pub(crate) fn advance_newline(&self) -> State<'a> { let mut state = self.clone(); state.offset += 1; state.line_start = state.pos(); From 2ade357ea97a3f5c7f5625a8a1a00d8fa11932a0 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 11 Jan 2022 08:17:20 +0000 Subject: [PATCH 200/541] Wasm: Specify the format of immediate operands for each opcode --- compiler/gen_wasm/src/wasm_module/opcodes.rs | 72 ++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/compiler/gen_wasm/src/wasm_module/opcodes.rs b/compiler/gen_wasm/src/wasm_module/opcodes.rs index 868b77375d..340e6516fb 100644 --- a/compiler/gen_wasm/src/wasm_module/opcodes.rs +++ b/compiler/gen_wasm/src/wasm_module/opcodes.rs @@ -1,3 +1,5 @@ +use roc_reporting::internal_error; + #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum OpCode { @@ -180,3 +182,73 @@ pub enum OpCode { F32REINTERPRETI32 = 0xbe, F64REINTERPRETI64 = 0xbf, } + +enum OpImmediates { + NoImmediate, + Byte1, + Bytes4, + Bytes8, + Leb32x1, + Leb64x1, + Leb32x2, + BrTable, +} + +impl From for OpImmediates { + fn from(op: OpCode) -> Self { + use OpImmediates::*; + use OpCode::*; + + match op { + UNREACHABLE => NoImmediate, + NOP => NoImmediate, + BLOCK | LOOP | IF => Byte1, + ELSE => NoImmediate, + END => NoImmediate, + BR | BRIF => Leb32x1, + BRTABLE => BrTable, + RETURN => NoImmediate, + CALL => Leb32x1, + CALLINDIRECT => Leb32x2, + DROP => NoImmediate, + SELECT => NoImmediate, + GETLOCAL | SETLOCAL | TEELOCAL => Leb32x1, + GETGLOBAL | SETGLOBAL => Leb32x1, + + I32LOAD | I64LOAD | F32LOAD | F64LOAD | I32LOAD8S | I32LOAD8U | I32LOAD16S + | I32LOAD16U | I64LOAD8S | I64LOAD8U | I64LOAD16S | I64LOAD16U | I64LOAD32S + | I64LOAD32U | I32STORE | I64STORE | F32STORE | F64STORE | I32STORE8 | I32STORE16 + | I64STORE8 | I64STORE16 | I64STORE32 => Leb32x2, + + CURRENTMEMORY | GROWMEMORY => Byte1, + + I32CONST => Leb32x1, + I64CONST => Leb64x1, + F32CONST => Bytes4, + F64CONST => Bytes8, + + I32EQZ | I32EQ | I32NE | I32LTS | I32LTU | I32GTS | I32GTU | I32LES | I32LEU + | I32GES | I32GEU | I64EQZ | I64EQ | I64NE | I64LTS | I64LTU | I64GTS | I64GTU + | I64LES | I64LEU | I64GES | I64GEU | F32EQ | F32NE | F32LT | F32GT | F32LE | F32GE + | F64EQ | F64NE | F64LT | F64GT | F64LE | F64GE | I32CLZ | I32CTZ | I32POPCNT + | I32ADD | I32SUB | I32MUL | I32DIVS | I32DIVU | I32REMS | I32REMU | I32AND | I32OR + | I32XOR | I32SHL | I32SHRS | I32SHRU | I32ROTL | I32ROTR | I64CLZ | I64CTZ + | I64POPCNT | I64ADD | I64SUB | I64MUL | I64DIVS | I64DIVU | I64REMS | I64REMU + | I64AND | I64OR | I64XOR | I64SHL | I64SHRS | I64SHRU | I64ROTL | I64ROTR | F32ABS + | F32NEG | F32CEIL | F32FLOOR | F32TRUNC | F32NEAREST | F32SQRT | F32ADD | F32SUB + | F32MUL | F32DIV | F32MIN | F32MAX | F32COPYSIGN | F64ABS | F64NEG | F64CEIL + | F64FLOOR | F64TRUNC | F64NEAREST | F64SQRT | F64ADD | F64SUB | F64MUL | F64DIV + | F64MIN | F64MAX | F64COPYSIGN | I32WRAPI64 | I32TRUNCSF32 | I32TRUNCUF32 + | I32TRUNCSF64 | I32TRUNCUF64 | I64EXTENDSI32 | I64EXTENDUI32 | I64TRUNCSF32 + | I64TRUNCUF32 | I64TRUNCSF64 | I64TRUNCUF64 | F32CONVERTSI32 | F32CONVERTUI32 + | F32CONVERTSI64 | F32CONVERTUI64 | F32DEMOTEF64 | F64CONVERTSI32 | F64CONVERTUI32 + | F64CONVERTSI64 | F64CONVERTUI64 | F64PROMOTEF32 | I32REINTERPRETF32 + | I64REINTERPRETF64 | F32REINTERPRETI32 | F64REINTERPRETI64 => NoImmediate, + + // Catch-all in case of an invalid cast from u8 to OpCode while parsing binary + // (rustc keeps this code, it has been verified in Compiler Explorer) + #[allow(unreachable_patterns)] + _ => internal_error!("Unknown Wasm instruction 0x{:02x}", op as u8), + } + } +} From 48f14f9a830826387b29c975abca50b862faf365 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 11 Jan 2022 08:54:42 +0000 Subject: [PATCH 201/541] Wasm: Implement SkipBytes for OpCode --- compiler/gen_wasm/src/wasm_module/opcodes.rs | 63 +++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/compiler/gen_wasm/src/wasm_module/opcodes.rs b/compiler/gen_wasm/src/wasm_module/opcodes.rs index 340e6516fb..b0e8deece6 100644 --- a/compiler/gen_wasm/src/wasm_module/opcodes.rs +++ b/compiler/gen_wasm/src/wasm_module/opcodes.rs @@ -1,5 +1,7 @@ use roc_reporting::internal_error; +use super::serialize::{parse_u32_or_panic, SkipBytes}; + #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum OpCode { @@ -196,8 +198,8 @@ enum OpImmediates { impl From for OpImmediates { fn from(op: OpCode) -> Self { - use OpImmediates::*; use OpCode::*; + use OpImmediates::*; match op { UNREACHABLE => NoImmediate, @@ -252,3 +254,62 @@ impl From for OpImmediates { } } } + +impl SkipBytes for OpCode { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) { + use OpImmediates::*; + + let opcode_byte: u8 = bytes[*cursor]; + + let opcode: OpCode = unsafe { std::mem::transmute(opcode_byte) }; + let immediates = OpImmediates::from(opcode); // will panic if transmute was invalid + + match immediates { + NoImmediate => { + *cursor += 1; + } + Byte1 => { + *cursor += 1 + 1; + } + Bytes4 => { + *cursor += 1 + 4; + } + Bytes8 => { + *cursor += 1 + 8; + } + Leb32x1 => { + let i = *cursor + 1; + *cursor = skip_leb(bytes, i, 5); + } + Leb64x1 => { + let i = *cursor + 1; + *cursor = skip_leb(bytes, i, 10); + } + Leb32x2 => { + let mut i = *cursor + 1; + i = skip_leb(bytes, i, 5); + i = skip_leb(bytes, i, 5); + *cursor = i; + } + BrTable => { + *cursor += 1; + let n_labels = 1 + parse_u32_or_panic(bytes, cursor); + let mut i = *cursor; + for _ in 0..n_labels { + i = skip_leb(bytes, i, 5); + } + *cursor = i; + } + } + } +} + +#[inline(always)] +fn skip_leb(bytes: &[u8], mut i: usize, max_len: usize) -> usize { + let imax = i + max_len; + while (bytes[i] & 0x80 != 0) && (i < imax) { + i += 1; + } + debug_assert!(i < imax); + i + 1 +} From 61d46be923063fe9c7aa5a35136cfb445fe8273c Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 11 Jan 2022 09:43:14 +0000 Subject: [PATCH 202/541] Wasm: Parse the Code section to collect metadata for dead function elimination --- compiler/gen_wasm/src/wasm_module/sections.rs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index c314106136..41ded7a1e0 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -667,6 +667,29 @@ pub struct CodeSection<'a> { pub code_builders: Vec<'a, CodeBuilder<'a>>, } +#[derive(Debug)] +pub struct DeadCodeMetadata<'a> { + /// Byte offset (in the module) where each function body can be found + code_offsets: Vec<'a, u32>, + /// Vector with one entry per *call*, containing the called function's index + calls: Vec<'a, u32>, + /// Vector with one entry per *function*, indicating its offset in `calls` + calls_offsets: Vec<'a, u32>, + /// Return types of each function (for making almost-empty dummy replacements) + ret_types: Vec<'a, u8>, +} + +impl<'a> DeadCodeMetadata<'a> { + pub fn new(arena: &'a Bump, func_count: usize) -> Self { + DeadCodeMetadata { + code_offsets: Vec::with_capacity_in(func_count, arena), + ret_types: Vec::with_capacity_in(func_count, arena), + calls: Vec::with_capacity_in(2 * func_count, arena), + calls_offsets: Vec::with_capacity_in(1 + func_count, arena), + } + } +} + impl<'a> CodeSection<'a> { /// Serialize the code builders for all functions, and get code relocations with final offsets pub fn serialize_with_relocs( @@ -704,6 +727,61 @@ impl<'a> CodeSection<'a> { code_builders: Vec::with_capacity_in(0, arena), } } + + /// Parse a Code section, collecting metadata that we can use to figure out + /// which functions are actually called, and which are not. + /// This would normally be done in a linker optimisation, but we want to be able to + /// use this backend without a linker. + pub fn parse_dead_code_metadata( + arena: &'a Bump, + module_bytes: &[u8], + cursor: &mut usize, + ) -> DeadCodeMetadata<'a> { + if module_bytes[*cursor] != SectionId::Code as u8 { + internal_error!("Expected Code section in object file at offset {}", *cursor); + } + *cursor += 1; + + let section_size = parse_u32_or_panic(module_bytes, cursor); + let count_start = *cursor; + let section_end = count_start + section_size as usize; + let func_count = parse_u32_or_panic(module_bytes, cursor); + + let mut metadata = DeadCodeMetadata::new(arena, func_count as usize); + + while *cursor < section_end { + metadata.code_offsets.push(*cursor as u32); + metadata.calls_offsets.push(metadata.calls.len() as u32); + + let func_size = parse_u32_or_panic(module_bytes, cursor); + let func_end = *cursor + func_size as usize; + + // Local variable declarations + let local_groups_count = parse_u32_or_panic(module_bytes, cursor); + for _ in 0..local_groups_count { + let _group_len = parse_u32_or_panic(module_bytes, cursor); + *cursor += 1; // ValueType + } + + // Instructions + while *cursor < func_end { + let opcode_byte: u8 = module_bytes[*cursor]; + if opcode_byte == OpCode::CALL as u8 { + *cursor += 1; + let call_index = parse_u32_or_panic(module_bytes, cursor); + metadata.calls.push(call_index as u32); + } else { + OpCode::skip_bytes(module_bytes, cursor); + } + } + } + + // Extra entries to mark the end of the last function + metadata.code_offsets.push(*cursor as u32); + metadata.calls_offsets.push(metadata.calls.len() as u32); + + metadata + } } impl<'a> Serialize for CodeSection<'a> { From 98400cae1b80cc6233f91ab1a09b6605d0c83981 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 11 Jan 2022 13:26:49 +0000 Subject: [PATCH 203/541] Wasm: Move code around --- .../gen_wasm/src/wasm_module/dead_code.rs | 127 ++++++++++++++++++ compiler/gen_wasm/src/wasm_module/sections.rs | 78 ----------- 2 files changed, 127 insertions(+), 78 deletions(-) create mode 100644 compiler/gen_wasm/src/wasm_module/dead_code.rs diff --git a/compiler/gen_wasm/src/wasm_module/dead_code.rs b/compiler/gen_wasm/src/wasm_module/dead_code.rs new file mode 100644 index 0000000000..e258bb8d52 --- /dev/null +++ b/compiler/gen_wasm/src/wasm_module/dead_code.rs @@ -0,0 +1,127 @@ + +#[derive(Debug)] +pub struct DeadCodeMetadata<'a> { + /// Byte offset (in the module) where each function body can be found + code_offsets: Vec<'a, u32>, + /// Vector with one entry per *call*, containing the called function's index + calls: Vec<'a, u32>, + /// Vector with one entry per *function*, indicating its offset in `calls` + calls_offsets: Vec<'a, u32>, + /// Return types of each function (for making almost-empty dummy replacements) + ret_types: Vec<'a, u8>, +} + +impl<'a> DeadCodeMetadata<'a> { + pub fn new(arena: &'a Bump, func_count: usize) -> Self { + DeadCodeMetadata { + code_offsets: Vec::with_capacity_in(func_count, arena), + ret_types: Vec::with_capacity_in(func_count, arena), + calls: Vec::with_capacity_in(2 * func_count, arena), + calls_offsets: Vec::with_capacity_in(1 + func_count, arena), + } + } +} + +/// Parse a Code section, collecting metadata that we can use to figure out +/// which functions are actually called, and which are not. +/// This would normally be done in a linker optimisation, but we want to be able to +/// use this backend without a linker. +pub fn parse_dead_code_metadata<'a>( + arena: &'a Bump, + module_bytes: &[u8], + cursor: &mut usize, +) -> DeadCodeMetadata<'a> { + if module_bytes[*cursor] != SectionId::Code as u8 { + internal_error!("Expected Code section in object file at offset {}", *cursor); + } + *cursor += 1; + + let section_size = parse_u32_or_panic(module_bytes, cursor); + let count_start = *cursor; + let section_end = count_start + section_size as usize; + let func_count = parse_u32_or_panic(module_bytes, cursor); + + let mut metadata = DeadCodeMetadata::new(arena, func_count as usize); + + while *cursor < section_end { + metadata.code_offsets.push(*cursor as u32); + metadata.calls_offsets.push(metadata.calls.len() as u32); + + let func_size = parse_u32_or_panic(module_bytes, cursor); + let func_end = *cursor + func_size as usize; + + // Local variable declarations + let local_groups_count = parse_u32_or_panic(module_bytes, cursor); + for _ in 0..local_groups_count { + let _group_len = parse_u32_or_panic(module_bytes, cursor); + *cursor += 1; // ValueType + } + + // Instructions + while *cursor < func_end { + let opcode_byte: u8 = module_bytes[*cursor]; + if opcode_byte == OpCode::CALL as u8 { + *cursor += 1; + let call_index = parse_u32_or_panic(module_bytes, cursor); + metadata.calls.push(call_index as u32); + } else { + OpCode::skip_bytes(module_bytes, cursor); + } + } + } + + // Extra entries to mark the end of the last function + metadata.code_offsets.push(*cursor as u32); + metadata.calls_offsets.push(metadata.calls.len() as u32); + + metadata +} + +/// Copy used functions (and their dependencies!) from an external module into our Code section +/// Replace unused functions with very small dummies, to avoid changing any indices +pub fn copy_used_functions<'a, T: SerialBuffer>( + arena: &'a Bump, + buffer: &mut T, + metadata: DeadCodeMetadata<'a>, + external_module: &[u8], + sorted_used_func_indices: &[u32], +) { + let [dummy_i32, dummy_i64, dummy_f32, dummy_f64, dummy_nil] = create_dummy_functions(arena); +} + +/// Create a set of dummy functions that just return a constant of each possible type +fn create_dummy_functions<'a>(arena: &'a Bump) -> [Vec<'a, u8>; 5] { + let mut code_builder_i32 = CodeBuilder::new(arena); + code_builder_i32.i32_const(0); + + let mut code_builder_i64 = CodeBuilder::new(arena); + code_builder_i64.i64_const(0); + + let mut code_builder_f32 = CodeBuilder::new(arena); + code_builder_f32.f32_const(0.0); + + let mut code_builder_f64 = CodeBuilder::new(arena); + code_builder_f64.f64_const(0.0); + + let mut code_builder_nil = CodeBuilder::new(arena); + + code_builder_i32.build_fn_header_and_footer(&[], 0, None); + code_builder_i64.build_fn_header_and_footer(&[], 0, None); + code_builder_f32.build_fn_header_and_footer(&[], 0, None); + code_builder_f64.build_fn_header_and_footer(&[], 0, None); + code_builder_nil.build_fn_header_and_footer(&[], 0, None); + + let mut dummy_i32 = Vec::with_capacity_in(code_builder_i32.size(), arena); + let mut dummy_i64 = Vec::with_capacity_in(code_builder_i64.size(), arena); + let mut dummy_f32 = Vec::with_capacity_in(code_builder_f32.size(), arena); + let mut dummy_f64 = Vec::with_capacity_in(code_builder_f64.size(), arena); + let mut dummy_nil = Vec::with_capacity_in(code_builder_nil.size(), arena); + + code_builder_i32.serialize(&mut dummy_i32); + code_builder_i64.serialize(&mut dummy_i64); + code_builder_f32.serialize(&mut dummy_f32); + code_builder_f64.serialize(&mut dummy_f64); + code_builder_nil.serialize(&mut dummy_nil); + + [dummy_i32, dummy_i64, dummy_f32, dummy_f64, dummy_nil] +} diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 41ded7a1e0..c314106136 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -667,29 +667,6 @@ pub struct CodeSection<'a> { pub code_builders: Vec<'a, CodeBuilder<'a>>, } -#[derive(Debug)] -pub struct DeadCodeMetadata<'a> { - /// Byte offset (in the module) where each function body can be found - code_offsets: Vec<'a, u32>, - /// Vector with one entry per *call*, containing the called function's index - calls: Vec<'a, u32>, - /// Vector with one entry per *function*, indicating its offset in `calls` - calls_offsets: Vec<'a, u32>, - /// Return types of each function (for making almost-empty dummy replacements) - ret_types: Vec<'a, u8>, -} - -impl<'a> DeadCodeMetadata<'a> { - pub fn new(arena: &'a Bump, func_count: usize) -> Self { - DeadCodeMetadata { - code_offsets: Vec::with_capacity_in(func_count, arena), - ret_types: Vec::with_capacity_in(func_count, arena), - calls: Vec::with_capacity_in(2 * func_count, arena), - calls_offsets: Vec::with_capacity_in(1 + func_count, arena), - } - } -} - impl<'a> CodeSection<'a> { /// Serialize the code builders for all functions, and get code relocations with final offsets pub fn serialize_with_relocs( @@ -727,61 +704,6 @@ impl<'a> CodeSection<'a> { code_builders: Vec::with_capacity_in(0, arena), } } - - /// Parse a Code section, collecting metadata that we can use to figure out - /// which functions are actually called, and which are not. - /// This would normally be done in a linker optimisation, but we want to be able to - /// use this backend without a linker. - pub fn parse_dead_code_metadata( - arena: &'a Bump, - module_bytes: &[u8], - cursor: &mut usize, - ) -> DeadCodeMetadata<'a> { - if module_bytes[*cursor] != SectionId::Code as u8 { - internal_error!("Expected Code section in object file at offset {}", *cursor); - } - *cursor += 1; - - let section_size = parse_u32_or_panic(module_bytes, cursor); - let count_start = *cursor; - let section_end = count_start + section_size as usize; - let func_count = parse_u32_or_panic(module_bytes, cursor); - - let mut metadata = DeadCodeMetadata::new(arena, func_count as usize); - - while *cursor < section_end { - metadata.code_offsets.push(*cursor as u32); - metadata.calls_offsets.push(metadata.calls.len() as u32); - - let func_size = parse_u32_or_panic(module_bytes, cursor); - let func_end = *cursor + func_size as usize; - - // Local variable declarations - let local_groups_count = parse_u32_or_panic(module_bytes, cursor); - for _ in 0..local_groups_count { - let _group_len = parse_u32_or_panic(module_bytes, cursor); - *cursor += 1; // ValueType - } - - // Instructions - while *cursor < func_end { - let opcode_byte: u8 = module_bytes[*cursor]; - if opcode_byte == OpCode::CALL as u8 { - *cursor += 1; - let call_index = parse_u32_or_panic(module_bytes, cursor); - metadata.calls.push(call_index as u32); - } else { - OpCode::skip_bytes(module_bytes, cursor); - } - } - } - - // Extra entries to mark the end of the last function - metadata.code_offsets.push(*cursor as u32); - metadata.calls_offsets.push(metadata.calls.len() as u32); - - metadata - } } impl<'a> Serialize for CodeSection<'a> { From 8a01c3f98a5330ffc54600f39e29d79ce5fdf28c Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 11 Jan 2022 21:16:20 +0000 Subject: [PATCH 204/541] Wasm: implement dead code elimination --- compiler/gen_wasm/src/backend.rs | 12 +- compiler/gen_wasm/src/lib.rs | 12 +- .../gen_wasm/src/wasm_module/code_builder.rs | 12 ++ .../gen_wasm/src/wasm_module/dead_code.rs | 172 +++++++++++++----- compiler/gen_wasm/src/wasm_module/mod.rs | 9 +- compiler/gen_wasm/src/wasm_module/sections.rs | 79 +++++++- 6 files changed, 230 insertions(+), 66 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index c89c6b16d2..af834e6233 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -2,7 +2,7 @@ use bumpalo::{self, collections::Vec}; use code_builder::Align; use roc_builtins::bitcode::{self, IntWidth}; -use roc_collections::all::MutMap; +use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::Ident; use roc_module::low_level::{LowLevel, LowLevelWrapperType}; use roc_module::symbol::{Interns, Symbol}; @@ -42,6 +42,7 @@ pub struct WasmBackend<'a> { next_constant_addr: u32, fn_index_offset: u32, preloaded_functions_map: MutMap<&'a [u8], u32>, + called_preload_fns: MutSet, proc_symbols: Vec<'a, (Symbol, u32)>, helper_proc_gen: CodeGenHelp<'a>, @@ -79,6 +80,7 @@ impl<'a> WasmBackend<'a> { next_constant_addr: CONST_SEGMENT_BASE_ADDR, fn_index_offset, preloaded_functions_map, + called_preload_fns: MutSet::default(), proc_symbols, helper_proc_gen, @@ -115,7 +117,12 @@ impl<'a> WasmBackend<'a> { self.module.linking.symbol_table.push(linker_symbol); } - pub fn into_module(self) -> WasmModule<'a> { + pub fn into_module(mut self, remove_dead_preloads: bool) -> WasmModule<'a> { + if remove_dead_preloads { + self.module + .code + .remove_dead_preloads(self.env.arena, self.called_preload_fns) + } self.module } @@ -1466,6 +1473,7 @@ impl<'a> WasmBackend<'a> { let num_wasm_args = param_types.len(); let has_return_val = ret_type.is_some(); let fn_index = self.preloaded_functions_map[name.as_bytes()]; + self.called_preload_fns.insert(fn_index); let linker_symbol_index = u32::MAX; self.code_builder diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index ca591f95fb..d084e75260 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -13,7 +13,6 @@ use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::layout::LayoutIds; -use roc_reporting::internal_error; use crate::backend::WasmBackend; use crate::wasm_module::{ @@ -42,7 +41,7 @@ pub fn build_module<'a>( procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) -> Result, String> { let (wasm_module, _) = build_module_help(env, interns, preload_bytes, procedures)?; - let mut buffer = std::vec::Vec::with_capacity(4096); + let mut buffer = std::vec::Vec::with_capacity(wasm_module.size()); wasm_module.serialize(&mut buffer); Ok(buffer) } @@ -59,6 +58,7 @@ pub fn build_module_help<'a>( let mut linker_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena); let mut exports = Vec::with_capacity_in(4, env.arena); let mut maybe_main_fn_index = None; + let eliminate_dead_preloads = true; // Collect the symbols & names for the procedures, // and filter out procs we're going to inline @@ -146,7 +146,7 @@ pub fn build_module_help<'a>( backend.build_proc(proc); } - let module = backend.into_module(); + let module = backend.into_module(eliminate_dead_preloads); let main_function_index = maybe_main_fn_index.unwrap() + fn_index_offset; Ok((module, main_function_index)) @@ -214,10 +214,6 @@ macro_rules! round_up_to_alignment { }; } -pub fn debug_panic(error: E) { - internal_error!("{:?}", error); -} - pub struct WasmDebugLogSettings { proc_start_end: bool, user_procs_ir: bool, @@ -233,5 +229,5 @@ pub const DEBUG_LOG_SETTINGS: WasmDebugLogSettings = WasmDebugLogSettings { helper_procs_ir: false && cfg!(debug_assertions), let_stmt_ir: false && cfg!(debug_assertions), instructions: false && cfg!(debug_assertions), - keep_test_binary: true && cfg!(debug_assertions), + keep_test_binary: false && cfg!(debug_assertions), }; diff --git a/compiler/gen_wasm/src/wasm_module/code_builder.rs b/compiler/gen_wasm/src/wasm_module/code_builder.rs index 5676508287..db51b2d22e 100644 --- a/compiler/gen_wasm/src/wasm_module/code_builder.rs +++ b/compiler/gen_wasm/src/wasm_module/code_builder.rs @@ -37,6 +37,18 @@ impl Serialize for ValueType { } } +impl From for ValueType { + fn from(x: u8) -> Self { + match x { + 0x7f => Self::I32, + 0x7e => Self::I64, + 0x7d => Self::F32, + 0x7c => Self::F64, + _ => internal_error!("Invalid ValueType 0x{:02x}", x), + } + } +} + const BLOCK_NO_RESULT: u8 = 0x40; /// A control block in our model of the VM diff --git a/compiler/gen_wasm/src/wasm_module/dead_code.rs b/compiler/gen_wasm/src/wasm_module/dead_code.rs index e258bb8d52..af0e1494da 100644 --- a/compiler/gen_wasm/src/wasm_module/dead_code.rs +++ b/compiler/gen_wasm/src/wasm_module/dead_code.rs @@ -1,14 +1,20 @@ +use bumpalo::collections::vec::Vec; +use bumpalo::Bump; + +use super::opcodes::OpCode; +use super::serialize::{parse_u32_or_panic, SerialBuffer, Serialize, SkipBytes}; +use super::{CodeBuilder, ValueType}; #[derive(Debug)] pub struct DeadCodeMetadata<'a> { - /// Byte offset (in the module) where each function body can be found + /// Byte offset where each function body can be found code_offsets: Vec<'a, u32>, /// Vector with one entry per *call*, containing the called function's index calls: Vec<'a, u32>, /// Vector with one entry per *function*, indicating its offset in `calls` calls_offsets: Vec<'a, u32>, /// Return types of each function (for making almost-empty dummy replacements) - ret_types: Vec<'a, u8>, + ret_types: Vec<'a, Option>, } impl<'a> DeadCodeMetadata<'a> { @@ -28,69 +34,110 @@ impl<'a> DeadCodeMetadata<'a> { /// use this backend without a linker. pub fn parse_dead_code_metadata<'a>( arena: &'a Bump, - module_bytes: &[u8], - cursor: &mut usize, + func_count: u32, + code_section_body: &[u8], + ret_types: Vec<'a, Option>, + signature_ids: Vec<'a, u32>, ) -> DeadCodeMetadata<'a> { - if module_bytes[*cursor] != SectionId::Code as u8 { - internal_error!("Expected Code section in object file at offset {}", *cursor); - } - *cursor += 1; - - let section_size = parse_u32_or_panic(module_bytes, cursor); - let count_start = *cursor; - let section_end = count_start + section_size as usize; - let func_count = parse_u32_or_panic(module_bytes, cursor); - let mut metadata = DeadCodeMetadata::new(arena, func_count as usize); + metadata + .ret_types + .extend(signature_ids.iter().map(|sig| ret_types[*sig as usize])); - while *cursor < section_end { - metadata.code_offsets.push(*cursor as u32); + let mut cursor: usize = 0; + while cursor < code_section_body.len() { + metadata.code_offsets.push(cursor as u32); metadata.calls_offsets.push(metadata.calls.len() as u32); - let func_size = parse_u32_or_panic(module_bytes, cursor); - let func_end = *cursor + func_size as usize; + let func_size = parse_u32_or_panic(code_section_body, &mut cursor); + let func_end = cursor + func_size as usize; // Local variable declarations - let local_groups_count = parse_u32_or_panic(module_bytes, cursor); + let local_groups_count = parse_u32_or_panic(code_section_body, &mut cursor); for _ in 0..local_groups_count { - let _group_len = parse_u32_or_panic(module_bytes, cursor); - *cursor += 1; // ValueType + parse_u32_or_panic(code_section_body, &mut cursor); + cursor += 1; // ValueType } // Instructions - while *cursor < func_end { - let opcode_byte: u8 = module_bytes[*cursor]; + while cursor < func_end { + let opcode_byte: u8 = code_section_body[cursor]; if opcode_byte == OpCode::CALL as u8 { - *cursor += 1; - let call_index = parse_u32_or_panic(module_bytes, cursor); + cursor += 1; + let call_index = parse_u32_or_panic(code_section_body, &mut cursor); metadata.calls.push(call_index as u32); } else { - OpCode::skip_bytes(module_bytes, cursor); + OpCode::skip_bytes(code_section_body, &mut cursor); } } } // Extra entries to mark the end of the last function - metadata.code_offsets.push(*cursor as u32); + metadata.code_offsets.push(cursor as u32); metadata.calls_offsets.push(metadata.calls.len() as u32); metadata } -/// Copy used functions (and their dependencies!) from an external module into our Code section -/// Replace unused functions with very small dummies, to avoid changing any indices -pub fn copy_used_functions<'a, T: SerialBuffer>( +/// Trace the dependencies of a list of functions +/// We've already collected metadata saying which functions call each other +/// Now we need to trace the dependency graphs of a specific subset of them +/// Result is the full set of builtins and platform functions used in the app. +/// The rest are "dead code" and can be eliminated. +pub fn trace_function_deps<'a, Indices: IntoIterator>( arena: &'a Bump, - buffer: &mut T, - metadata: DeadCodeMetadata<'a>, - external_module: &[u8], - sorted_used_func_indices: &[u32], -) { - let [dummy_i32, dummy_i64, dummy_f32, dummy_f64, dummy_nil] = create_dummy_functions(arena); + metadata: &DeadCodeMetadata<'a>, + called_from_app: Indices, +) -> Vec<'a, u32> { + let mut live_fn_indices: Vec<'a, u32> = Vec::with_capacity_in(metadata.calls.len(), arena); + live_fn_indices.extend(called_from_app); + + let num_funcs = metadata.calls_offsets.len(); + + // Current batch of functions whose call graphs we want to trace + let mut current_trace: Vec<'a, u32> = Vec::with_capacity_in(num_funcs, arena); + current_trace.clone_from(&live_fn_indices); + + // The next batch (don't want to modify the current one while we're iterating over it!) + let mut next_trace: Vec<'a, u32> = Vec::with_capacity_in(num_funcs, arena); + + // Fast lookup for what's already traced so we don't need to do it again + let mut already_traced: Vec<'a, bool> = Vec::from_iter_in((0..num_funcs).map(|_| false), arena); + + loop { + live_fn_indices.extend_from_slice(¤t_trace); + + for func_idx in current_trace.iter() { + let i = *func_idx as usize; + already_traced[i] = true; + let calls_start = metadata.calls_offsets[i] as usize; + let calls_end = metadata.calls_offsets[i + 1] as usize; + let called_indices: &[u32] = &metadata.calls[calls_start..calls_end]; + for called_idx in called_indices { + if !already_traced[*called_idx as usize] { + next_trace.push(*called_idx); + } + } + } + if next_trace.is_empty() { + break; + } + current_trace.clone_from(&next_trace); + next_trace.clear(); + } + + if true { + println!("Hey Brian, don't forget to remove this debug code"); + let unsorted_len = live_fn_indices.len(); + live_fn_indices.dedup(); + debug_assert!(unsorted_len == live_fn_indices.len()); + } + + live_fn_indices } -/// Create a set of dummy functions that just return a constant of each possible type -fn create_dummy_functions<'a>(arena: &'a Bump) -> [Vec<'a, u8>; 5] { +/// Create a set of minimum-size dummy functions for each possible return type +fn create_dummy_functions(arena: &Bump) -> [Vec<'_, u8>; 5] { let mut code_builder_i32 = CodeBuilder::new(arena); code_builder_i32.i32_const(0); @@ -111,11 +158,12 @@ fn create_dummy_functions<'a>(arena: &'a Bump) -> [Vec<'a, u8>; 5] { code_builder_f64.build_fn_header_and_footer(&[], 0, None); code_builder_nil.build_fn_header_and_footer(&[], 0, None); - let mut dummy_i32 = Vec::with_capacity_in(code_builder_i32.size(), arena); - let mut dummy_i64 = Vec::with_capacity_in(code_builder_i64.size(), arena); - let mut dummy_f32 = Vec::with_capacity_in(code_builder_f32.size(), arena); - let mut dummy_f64 = Vec::with_capacity_in(code_builder_f64.size(), arena); - let mut dummy_nil = Vec::with_capacity_in(code_builder_nil.size(), arena); + let capacity = code_builder_f64.size(); + let mut dummy_i32 = Vec::with_capacity_in(capacity, arena); + let mut dummy_i64 = Vec::with_capacity_in(capacity, arena); + let mut dummy_f32 = Vec::with_capacity_in(capacity, arena); + let mut dummy_f64 = Vec::with_capacity_in(capacity, arena); + let mut dummy_nil = Vec::with_capacity_in(capacity, arena); code_builder_i32.serialize(&mut dummy_i32); code_builder_i64.serialize(&mut dummy_i64); @@ -125,3 +173,41 @@ fn create_dummy_functions<'a>(arena: &'a Bump) -> [Vec<'a, u8>; 5] { [dummy_i32, dummy_i64, dummy_f32, dummy_f64, dummy_nil] } + +/// Copy used functions from an external module into our Code section +/// Replace unused functions with very small dummies, to avoid changing any indices +pub fn copy_live_and_replace_dead<'a, T: SerialBuffer>( + arena: &'a Bump, + buffer: &mut T, + metadata: &DeadCodeMetadata<'a>, + external_code: &[u8], + live_ext_fn_indices: &'a mut [u32], +) { + live_ext_fn_indices.sort_unstable(); + + let [dummy_i32, dummy_i64, dummy_f32, dummy_f64, dummy_nil] = create_dummy_functions(arena); + + let mut prev = 0; + for live32 in live_ext_fn_indices.iter() { + let live = *live32 as usize; + + // Replace dead functions with the minimal code body that will pass validation checks + for dead in prev..live { + let dummy_bytes = match metadata.ret_types[dead] { + Some(ValueType::I32) => &dummy_i32, + Some(ValueType::I64) => &dummy_i64, + Some(ValueType::F32) => &dummy_f32, + Some(ValueType::F64) => &dummy_f64, + None => &dummy_nil, + }; + buffer.append_slice(dummy_bytes); + } + + // Copy the body of the live function from the external module + let live_body_start = metadata.code_offsets[live] as usize; + let live_body_end = metadata.code_offsets[live + 1] as usize; + buffer.append_slice(&external_code[live_body_start..live_body_end]); + + prev = live + 1; + } +} diff --git a/compiler/gen_wasm/src/wasm_module/mod.rs b/compiler/gen_wasm/src/wasm_module/mod.rs index cb55201d0d..e273817f72 100644 --- a/compiler/gen_wasm/src/wasm_module/mod.rs +++ b/compiler/gen_wasm/src/wasm_module/mod.rs @@ -1,4 +1,5 @@ pub mod code_builder; +mod dead_code; pub mod linking; pub mod opcodes; pub mod sections; @@ -44,7 +45,6 @@ impl<'a> WasmModule<'a> { } /// Serialize the module to bytes - /// (not using Serialize trait because it's just one more thing to export) pub fn serialize(&self, buffer: &mut T) { buffer.append_u8(0); buffer.append_slice("asm".as_bytes()); @@ -125,16 +125,19 @@ impl<'a> WasmModule<'a> { let mut cursor: usize = 8; let mut types = TypeSection::preload(arena, bytes, &mut cursor); - types.cache_offsets(); + let ret_types = types.parse_preloaded_data(arena); + let import = ImportSection::preload(arena, bytes, &mut cursor); let function = FunctionSection::preload(arena, bytes, &mut cursor); + let signature_ids = function.parse_preloaded_data(arena); + let table = OpaqueSection::preload(SectionId::Table, arena, bytes, &mut cursor); let memory = MemorySection::preload(arena, bytes, &mut cursor); let global = GlobalSection::preload(arena, bytes, &mut cursor); let export = ExportSection::preload(arena, bytes, &mut cursor); let start = OpaqueSection::preload(SectionId::Start, arena, bytes, &mut cursor); let element = OpaqueSection::preload(SectionId::Element, arena, bytes, &mut cursor); - let code = CodeSection::preload(arena, bytes, &mut cursor); + let code = CodeSection::preload(arena, bytes, &mut cursor, ret_types, signature_ids); let data = DataSection::preload(arena, bytes, &mut cursor); let linking = LinkingSection::new(arena); let relocations = RelocationSection::new(arena, "reloc.CODE"); diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index c314106136..fb19adc2ea 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -3,6 +3,9 @@ use bumpalo::Bump; use roc_collections::all::MutMap; use roc_reporting::internal_error; +use super::dead_code::{ + copy_live_and_replace_dead, parse_dead_code_metadata, trace_function_deps, DeadCodeMetadata, +}; use super::linking::RelocationEntry; use super::opcodes::OpCode; use super::serialize::{ @@ -212,8 +215,10 @@ impl<'a> TypeSection<'a> { sig_id as u32 } - pub fn cache_offsets(&mut self) { + pub fn parse_preloaded_data(&mut self, arena: &'a Bump) -> Vec<'a, Option> { self.offsets.clear(); + let mut ret_types = Vec::with_capacity_in(self.offsets.capacity(), arena); + let mut i = 0; while i < self.bytes.len() { self.offsets.push(i); @@ -226,8 +231,18 @@ impl<'a> TypeSection<'a> { i += n_params as usize; // skip over one byte per param type let n_return_values = self.bytes[i]; - i += 1 + n_return_values as usize; + i += 1; + + ret_types.push(if n_return_values == 0 { + None + } else { + Some(ValueType::from(self.bytes[i])) + }); + + i += n_return_values as usize; } + + ret_types } } @@ -412,6 +427,15 @@ impl<'a> FunctionSection<'a> { self.bytes.encode_u32(sig_id); self.count += 1; } + + pub fn parse_preloaded_data(&self, arena: &'a Bump) -> Vec<'a, u32> { + let mut preload_signature_ids = Vec::with_capacity_in(self.count as usize, arena); + let mut cursor = 0; + while cursor < self.bytes.len() { + preload_signature_ids.push(parse_u32_or_panic(&self.bytes, &mut cursor)); + } + preload_signature_ids + } } section_impl!(FunctionSection, SectionId::Function); @@ -663,8 +687,9 @@ section_impl!(ExportSection, SectionId::Export); #[derive(Debug)] pub struct CodeSection<'a> { pub preloaded_count: u32, - pub preloaded_bytes: Vec<'a, u8>, + pub preloaded_bytes: &'a [u8], pub code_builders: Vec<'a, CodeBuilder<'a>>, + dead_code_metadata: DeadCodeMetadata<'a>, } impl<'a> CodeSection<'a> { @@ -677,8 +702,6 @@ impl<'a> CodeSection<'a> { let header_indices = write_section_header(buffer, SectionId::Code); buffer.encode_u32(self.preloaded_count + self.code_builders.len() as u32); - buffer.append_slice(&self.preloaded_bytes); - for code_builder in self.code_builders.iter() { code_builder.serialize_with_relocs(buffer, relocations, header_indices.body_index); } @@ -694,16 +717,52 @@ impl<'a> CodeSection<'a> { MAX_SIZE_SECTION_HEADER + self.preloaded_bytes.len() + builders_size } - pub fn preload(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self { + pub fn preload( + arena: &'a Bump, + module_bytes: &[u8], + cursor: &mut usize, + ret_types: Vec<'a, Option>, + signature_ids: Vec<'a, u32>, + ) -> Self { let (preloaded_count, initial_bytes) = parse_section(SectionId::Code, module_bytes, cursor); - let mut preloaded_bytes = Vec::with_capacity_in(initial_bytes.len() * 2, arena); - preloaded_bytes.extend_from_slice(initial_bytes); + let preloaded_bytes = arena.alloc_slice_copy(initial_bytes); + + // TODO: Try to move this metadata preparation to platform build time + let dead_code_metadata = parse_dead_code_metadata( + arena, + preloaded_count, + initial_bytes, + ret_types, + signature_ids, + ); + CodeSection { preloaded_count, preloaded_bytes, code_builders: Vec::with_capacity_in(0, arena), + dead_code_metadata, } } + + pub fn remove_dead_preloads(&mut self, arena: &'a Bump, called_preload_fns: T) + where + T: IntoIterator, + { + let mut live_ext_fn_indices = + trace_function_deps(arena, &self.dead_code_metadata, called_preload_fns); + + let mut buffer = Vec::with_capacity_in(self.preloaded_bytes.len(), arena); + + copy_live_and_replace_dead( + arena, + &mut buffer, + &self.dead_code_metadata, + self.preloaded_bytes, + &mut live_ext_fn_indices, + ); + + self.preloaded_bytes = buffer.into_bump_slice(); + } } impl<'a> Serialize for CodeSection<'a> { @@ -711,7 +770,7 @@ impl<'a> Serialize for CodeSection<'a> { let header_indices = write_section_header(buffer, SectionId::Code); buffer.encode_u32(self.preloaded_count + self.code_builders.len() as u32); - buffer.append_slice(&self.preloaded_bytes); + buffer.append_slice(self.preloaded_bytes); for code_builder in self.code_builders.iter() { code_builder.serialize(buffer); @@ -846,7 +905,7 @@ mod tests { // Reconstruct a new TypeSection by "pre-loading" the bytes of the original let mut cursor = 0; let mut preloaded = TypeSection::preload(arena, &original_serialized, &mut cursor); - preloaded.cache_offsets(); + preloaded.parse_preloaded_data(arena); debug_assert_eq!(original.offsets, preloaded.offsets); debug_assert_eq!(original.bytes, preloaded.bytes); From 7a4593170c36fe5c2601ff58b3eb6223c9f517af Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 12 Jan 2022 08:26:18 +0000 Subject: [PATCH 205/541] Wasm: reuse existing SkipBytes code for opcodes --- compiler/gen_wasm/src/wasm_module/opcodes.rs | 29 +++++-------------- .../gen_wasm/src/wasm_module/serialize.rs | 20 ++++++++++++- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/opcodes.rs b/compiler/gen_wasm/src/wasm_module/opcodes.rs index b0e8deece6..2dd2d7e3c5 100644 --- a/compiler/gen_wasm/src/wasm_module/opcodes.rs +++ b/compiler/gen_wasm/src/wasm_module/opcodes.rs @@ -278,38 +278,25 @@ impl SkipBytes for OpCode { *cursor += 1 + 8; } Leb32x1 => { - let i = *cursor + 1; - *cursor = skip_leb(bytes, i, 5); + *cursor += 1; + u32::skip_bytes(bytes, cursor); } Leb64x1 => { - let i = *cursor + 1; - *cursor = skip_leb(bytes, i, 10); + *cursor += 1; + u64::skip_bytes(bytes, cursor); } Leb32x2 => { - let mut i = *cursor + 1; - i = skip_leb(bytes, i, 5); - i = skip_leb(bytes, i, 5); - *cursor = i; + *cursor += 1; + u32::skip_bytes(bytes, cursor); + u32::skip_bytes(bytes, cursor); } BrTable => { *cursor += 1; let n_labels = 1 + parse_u32_or_panic(bytes, cursor); - let mut i = *cursor; for _ in 0..n_labels { - i = skip_leb(bytes, i, 5); + u32::skip_bytes(bytes, cursor); } - *cursor = i; } } } } - -#[inline(always)] -fn skip_leb(bytes: &[u8], mut i: usize, max_len: usize) -> usize { - let imax = i + max_len; - while (bytes[i] & 0x80 != 0) && (i < imax) { - i += 1; - } - debug_assert!(i < imax); - i + 1 -} diff --git a/compiler/gen_wasm/src/wasm_module/serialize.rs b/compiler/gen_wasm/src/wasm_module/serialize.rs index 883cd90ccd..cdfd5a31df 100644 --- a/compiler/gen_wasm/src/wasm_module/serialize.rs +++ b/compiler/gen_wasm/src/wasm_module/serialize.rs @@ -268,7 +268,25 @@ pub trait SkipBytes { impl SkipBytes for u32 { fn skip_bytes(bytes: &[u8], cursor: &mut usize) { - parse_u32_or_panic(bytes, cursor); + let imax = 5; + let mut i = *cursor; + while (bytes[i] & 0x80 != 0) && (i < imax) { + i += 1; + } + debug_assert!(i < imax); + *cursor = i + 1 + } +} + +impl SkipBytes for u64 { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) { + let imax = 10; + let mut i = *cursor; + while (bytes[i] & 0x80 != 0) && (i < imax) { + i += 1; + } + debug_assert!(i < imax); + *cursor = i + 1 } } From ca2597973e3f3bff43ec056e841ed1e3e71b3445 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 12 Jan 2022 08:45:24 +0000 Subject: [PATCH 206/541] Wasm: store function_count on the ImportSection --- compiler/gen_wasm/src/lib.rs | 3 +- compiler/gen_wasm/src/wasm_module/sections.rs | 28 +++++++++++++++---- .../src/helpers/wasm32_test_result.rs | 2 +- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index d084e75260..d1f71b9619 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -96,8 +96,7 @@ pub fn build_module_help<'a>( let initial_module = WasmModule::preload(env.arena, preload_bytes); // Adjust Wasm function indices to account for functions from the object file - let runtime_import_fn_count: u32 = initial_module.import.function_count(); // to be imported at runtime (e.g. WASI) - let fn_index_offset: u32 = runtime_import_fn_count + initial_module.code.preloaded_count; + let fn_index_offset: u32 = initial_module.import.function_count + initial_module.code.preloaded_count; // Get a map of name to index for the preloaded functions // Assumes the preloaded object file has all symbols exported, as per `zig build-lib -dymamic` diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index fb19adc2ea..2366d62b09 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -56,7 +56,7 @@ pub trait Section<'a>: Sized { } macro_rules! section_impl { - ($structname: ident, $id: expr) => { + ($structname: ident, $id: expr, $from_count_and_bytes: expr) => { impl<'a> Section<'a> for $structname<'a> { const ID: SectionId = $id; @@ -72,7 +72,7 @@ macro_rules! section_impl { let (count, initial_bytes) = parse_section(Self::ID, module_bytes, cursor); let mut bytes = Vec::with_capacity_in(initial_bytes.len() * 2, arena); bytes.extend_from_slice(initial_bytes); - $structname { bytes, count } + $from_count_and_bytes(count, bytes) } fn size(&self) -> usize { @@ -84,6 +84,13 @@ macro_rules! section_impl { } } }; + + ($structname: ident, $id: expr) => { + section_impl!($structname, $id, |count, bytes| $structname { + bytes, + count + }); + }; } impl<'a, Sec> Serialize for Sec @@ -367,6 +374,7 @@ impl Serialize for Import { #[derive(Debug)] pub struct ImportSection<'a> { pub count: u32, + pub function_count: u32, pub bytes: Vec<'a, u8>, } @@ -376,7 +384,7 @@ impl<'a> ImportSection<'a> { self.count += 1; } - pub fn function_count(&self) -> u32 { + fn update_function_count(&mut self) { let mut f_count = 0; let mut cursor = 0; while cursor < self.bytes.len() { @@ -403,11 +411,21 @@ impl<'a> ImportSection<'a> { } } - f_count + self.function_count = f_count; + } + + pub fn from_count_and_bytes(count: u32, bytes: Vec<'a, u8>) -> Self { + let mut created = ImportSection { + bytes, + count, + function_count: 0, + }; + created.update_function_count(); + created } } -section_impl!(ImportSection, SectionId::Import); +section_impl!(ImportSection, SectionId::Import, ImportSection::from_count_and_bytes); /******************************************************************* * diff --git a/compiler/test_gen/src/helpers/wasm32_test_result.rs b/compiler/test_gen/src/helpers/wasm32_test_result.rs index 34e2a86bea..b96f3e03fa 100644 --- a/compiler/test_gen/src/helpers/wasm32_test_result.rs +++ b/compiler/test_gen/src/helpers/wasm32_test_result.rs @@ -14,7 +14,7 @@ pub trait Wasm32TestResult { wrapper_name: &str, main_function_index: u32, ) { - let index = module.import.function_count() + let index = module.import.function_count + module.code.preloaded_count + module.code.code_builders.len() as u32; From 9dabc2db15f0c87ae8a37c07e487b7ce16c863cb Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 12 Jan 2022 09:31:00 +0000 Subject: [PATCH 207/541] Wasm: adjust dead code elimination to account for import function indices --- compiler/gen_wasm/src/backend.rs | 8 +- .../gen_wasm/src/wasm_module/dead_code.rs | 101 ++++++++++++------ compiler/gen_wasm/src/wasm_module/mod.rs | 9 +- compiler/gen_wasm/src/wasm_module/sections.rs | 32 ++++-- .../gen_wasm/src/wasm_module/serialize.rs | 35 +++--- 5 files changed, 124 insertions(+), 61 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index af834e6233..960d99a610 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -119,9 +119,11 @@ impl<'a> WasmBackend<'a> { pub fn into_module(mut self, remove_dead_preloads: bool) -> WasmModule<'a> { if remove_dead_preloads { - self.module - .code - .remove_dead_preloads(self.env.arena, self.called_preload_fns) + self.module.code.remove_dead_preloads( + self.env.arena, + self.module.import.function_count, + self.called_preload_fns, + ) } self.module } diff --git a/compiler/gen_wasm/src/wasm_module/dead_code.rs b/compiler/gen_wasm/src/wasm_module/dead_code.rs index af0e1494da..7d46ae95c5 100644 --- a/compiler/gen_wasm/src/wasm_module/dead_code.rs +++ b/compiler/gen_wasm/src/wasm_module/dead_code.rs @@ -18,12 +18,27 @@ pub struct DeadCodeMetadata<'a> { } impl<'a> DeadCodeMetadata<'a> { - pub fn new(arena: &'a Bump, func_count: usize) -> Self { + pub fn new(arena: &'a Bump, import_fn_count: u32, fn_count: u32) -> Self { + let capacity = (import_fn_count + fn_count) as usize; + + let mut code_offsets = Vec::with_capacity_in(capacity, arena); + let mut ret_types = Vec::with_capacity_in(capacity, arena); + let calls = Vec::with_capacity_in(2 * capacity, arena); + let mut calls_offsets = Vec::with_capacity_in(1 + capacity, arena); + + // Imported functions have zero code length and no calls + code_offsets.extend(std::iter::repeat(0).take(import_fn_count as usize)); + calls_offsets.extend(std::iter::repeat(0).take(import_fn_count as usize)); + + // We don't care about import return types + // Return types are for replacing dead functions with dummies, which doesn't apply to imports + ret_types.extend(std::iter::repeat(None).take(import_fn_count as usize)); + DeadCodeMetadata { - code_offsets: Vec::with_capacity_in(func_count, arena), - ret_types: Vec::with_capacity_in(func_count, arena), - calls: Vec::with_capacity_in(2 * func_count, arena), - calls_offsets: Vec::with_capacity_in(1 + func_count, arena), + code_offsets, + ret_types, + calls, + calls_offsets, } } } @@ -34,15 +49,18 @@ impl<'a> DeadCodeMetadata<'a> { /// use this backend without a linker. pub fn parse_dead_code_metadata<'a>( arena: &'a Bump, - func_count: u32, + fn_count: u32, code_section_body: &[u8], - ret_types: Vec<'a, Option>, - signature_ids: Vec<'a, u32>, + signature_ret_types: Vec<'a, Option>, + internal_fn_sig_ids: Vec<'a, u32>, + import_fn_count: u32, ) -> DeadCodeMetadata<'a> { - let mut metadata = DeadCodeMetadata::new(arena, func_count as usize); - metadata - .ret_types - .extend(signature_ids.iter().map(|sig| ret_types[*sig as usize])); + let mut metadata = DeadCodeMetadata::new(arena, import_fn_count, fn_count); + metadata.ret_types.extend( + internal_fn_sig_ids + .iter() + .map(|sig| signature_ret_types[*sig as usize]), + ); let mut cursor: usize = 0; while cursor < code_section_body.len() { @@ -89,20 +107,20 @@ pub fn trace_function_deps<'a, Indices: IntoIterator>( metadata: &DeadCodeMetadata<'a>, called_from_app: Indices, ) -> Vec<'a, u32> { - let mut live_fn_indices: Vec<'a, u32> = Vec::with_capacity_in(metadata.calls.len(), arena); - live_fn_indices.extend(called_from_app); + let num_funcs = metadata.ret_types.len(); - let num_funcs = metadata.calls_offsets.len(); + // All functions that get called from the app, directly or indirectly + let mut live_fn_indices = Vec::with_capacity_in(num_funcs, arena); - // Current batch of functions whose call graphs we want to trace - let mut current_trace: Vec<'a, u32> = Vec::with_capacity_in(num_funcs, arena); - current_trace.clone_from(&live_fn_indices); + // Current & next batch of functions whose call graphs we want to trace through the metadata + // (2 separate vectors so that we're not iterating over the same one we're changing) + // If the max call depth is N then we will do N traces or less + let mut current_trace = Vec::with_capacity_in(num_funcs, arena); + current_trace.extend(called_from_app); + let mut next_trace = Vec::with_capacity_in(num_funcs, arena); - // The next batch (don't want to modify the current one while we're iterating over it!) - let mut next_trace: Vec<'a, u32> = Vec::with_capacity_in(num_funcs, arena); - - // Fast lookup for what's already traced so we don't need to do it again - let mut already_traced: Vec<'a, bool> = Vec::from_iter_in((0..num_funcs).map(|_| false), arena); + // Fast per-function lookup table to see if its dependencies have already been traced + let mut already_traced = Vec::from_iter_in(std::iter::repeat(false).take(num_funcs), arena); loop { live_fn_indices.extend_from_slice(¤t_trace); @@ -126,13 +144,6 @@ pub fn trace_function_deps<'a, Indices: IntoIterator>( next_trace.clear(); } - if true { - println!("Hey Brian, don't forget to remove this debug code"); - let unsorted_len = live_fn_indices.len(); - live_fn_indices.dedup(); - debug_assert!(unsorted_len == live_fn_indices.len()); - } - live_fn_indices } @@ -181,15 +192,20 @@ pub fn copy_live_and_replace_dead<'a, T: SerialBuffer>( buffer: &mut T, metadata: &DeadCodeMetadata<'a>, external_code: &[u8], - live_ext_fn_indices: &'a mut [u32], + import_fn_count: u32, + mut live_ext_fn_indices: Vec<'a, u32>, ) { live_ext_fn_indices.sort_unstable(); let [dummy_i32, dummy_i64, dummy_f32, dummy_f64, dummy_nil] = create_dummy_functions(arena); - let mut prev = 0; - for live32 in live_ext_fn_indices.iter() { - let live = *live32 as usize; + let mut prev = import_fn_count as usize; + for live32 in live_ext_fn_indices.into_iter() { + if live32 < import_fn_count { + continue; + } + + let live = live32 as usize; // Replace dead functions with the minimal code body that will pass validation checks for dead in prev..live { @@ -210,4 +226,21 @@ pub fn copy_live_and_replace_dead<'a, T: SerialBuffer>( prev = live + 1; } + + let num_preloaded_fns = metadata.ret_types.len(); + // Replace dead functions with the minimal code body that will pass validation checks + for dead in prev..num_preloaded_fns { + if dead < import_fn_count as usize { + continue; + } + let ret_type = metadata.ret_types[dead]; + let dummy_bytes = match ret_type { + Some(ValueType::I32) => &dummy_i32, + Some(ValueType::I64) => &dummy_i64, + Some(ValueType::F32) => &dummy_f32, + Some(ValueType::F64) => &dummy_f64, + None => &dummy_nil, + }; + buffer.append_slice(dummy_bytes); + } } diff --git a/compiler/gen_wasm/src/wasm_module/mod.rs b/compiler/gen_wasm/src/wasm_module/mod.rs index e273817f72..c1519bee6f 100644 --- a/compiler/gen_wasm/src/wasm_module/mod.rs +++ b/compiler/gen_wasm/src/wasm_module/mod.rs @@ -137,7 +137,14 @@ impl<'a> WasmModule<'a> { let export = ExportSection::preload(arena, bytes, &mut cursor); let start = OpaqueSection::preload(SectionId::Start, arena, bytes, &mut cursor); let element = OpaqueSection::preload(SectionId::Element, arena, bytes, &mut cursor); - let code = CodeSection::preload(arena, bytes, &mut cursor, ret_types, signature_ids); + let code = CodeSection::preload( + arena, + bytes, + &mut cursor, + ret_types, + signature_ids, + import.function_count, + ); let data = DataSection::preload(arena, bytes, &mut cursor); let linking = LinkingSection::new(arena); let relocations = RelocationSection::new(arena, "reloc.CODE"); diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 2366d62b09..bfd38347f1 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -324,6 +324,7 @@ pub struct Import { } #[repr(u8)] +#[derive(Debug)] enum ImportTypeId { Func = 0, Table = 1, @@ -391,10 +392,10 @@ impl<'a> ImportSection<'a> { String::skip_bytes(&self.bytes, &mut cursor); String::skip_bytes(&self.bytes, &mut cursor); - let type_id = self.bytes[cursor]; + let type_id = ImportTypeId::from(self.bytes[cursor]); cursor += 1; - match ImportTypeId::from(type_id) { + match type_id { ImportTypeId::Func => { f_count += 1; u32::skip_bytes(&self.bytes, &mut cursor); @@ -425,7 +426,11 @@ impl<'a> ImportSection<'a> { } } -section_impl!(ImportSection, SectionId::Import, ImportSection::from_count_and_bytes); +section_impl!( + ImportSection, + SectionId::Import, + ImportSection::from_count_and_bytes +); /******************************************************************* * @@ -740,7 +745,8 @@ impl<'a> CodeSection<'a> { module_bytes: &[u8], cursor: &mut usize, ret_types: Vec<'a, Option>, - signature_ids: Vec<'a, u32>, + internal_fn_sig_ids: Vec<'a, u32>, + import_fn_count: u32, ) -> Self { let (preloaded_count, initial_bytes) = parse_section(SectionId::Code, module_bytes, cursor); let preloaded_bytes = arena.alloc_slice_copy(initial_bytes); @@ -751,7 +757,8 @@ impl<'a> CodeSection<'a> { preloaded_count, initial_bytes, ret_types, - signature_ids, + internal_fn_sig_ids, + import_fn_count, ); CodeSection { @@ -762,11 +769,13 @@ impl<'a> CodeSection<'a> { } } - pub fn remove_dead_preloads(&mut self, arena: &'a Bump, called_preload_fns: T) - where - T: IntoIterator, - { - let mut live_ext_fn_indices = + pub fn remove_dead_preloads>( + &mut self, + arena: &'a Bump, + import_fn_count: u32, + called_preload_fns: T, + ) { + let live_ext_fn_indices = trace_function_deps(arena, &self.dead_code_metadata, called_preload_fns); let mut buffer = Vec::with_capacity_in(self.preloaded_bytes.len(), arena); @@ -776,7 +785,8 @@ impl<'a> CodeSection<'a> { &mut buffer, &self.dead_code_metadata, self.preloaded_bytes, - &mut live_ext_fn_indices, + import_fn_count, + live_ext_fn_indices, ); self.preloaded_bytes = buffer.into_bump_slice(); diff --git a/compiler/gen_wasm/src/wasm_module/serialize.rs b/compiler/gen_wasm/src/wasm_module/serialize.rs index cdfd5a31df..b61376968b 100644 --- a/compiler/gen_wasm/src/wasm_module/serialize.rs +++ b/compiler/gen_wasm/src/wasm_module/serialize.rs @@ -268,25 +268,27 @@ pub trait SkipBytes { impl SkipBytes for u32 { fn skip_bytes(bytes: &[u8], cursor: &mut usize) { - let imax = 5; - let mut i = *cursor; - while (bytes[i] & 0x80 != 0) && (i < imax) { - i += 1; + const MAX_LEN: usize = 5; + for (i, byte) in bytes.iter().enumerate().skip(*cursor).take(MAX_LEN) { + if byte & 0x80 == 0 { + *cursor = i + 1; + return; + } } - debug_assert!(i < imax); - *cursor = i + 1 + internal_error!("Invalid LEB encoding"); } } impl SkipBytes for u64 { fn skip_bytes(bytes: &[u8], cursor: &mut usize) { - let imax = 10; - let mut i = *cursor; - while (bytes[i] & 0x80 != 0) && (i < imax) { - i += 1; + const MAX_LEN: usize = 10; + for (i, byte) in bytes.iter().enumerate().skip(*cursor).take(MAX_LEN) { + if byte & 0x80 == 0 { + *cursor = i + 1; + return; + } } - debug_assert!(i < imax); - *cursor = i + 1 + internal_error!("Invalid LEB encoding"); } } @@ -299,6 +301,15 @@ impl SkipBytes for u8 { impl SkipBytes for String { fn skip_bytes(bytes: &[u8], cursor: &mut usize) { let len = parse_u32_or_panic(bytes, cursor); + + if false { + let str_bytes = &bytes[*cursor..(*cursor + len as usize)]; + println!( + "Skipping String {:?}", + String::from_utf8(str_bytes.to_vec()).unwrap() + ); + } + *cursor += len as usize; } } From 6b204d11a21370f939aa1fa731f52374b19a300c Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 12 Jan 2022 19:33:38 +0000 Subject: [PATCH 208/541] Wasm: sort and deduplicate on each round of live function tracing --- compiler/gen_wasm/src/lib.rs | 3 ++- compiler/gen_wasm/src/wasm_module/dead_code.rs | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index d1f71b9619..4f832d452c 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -96,7 +96,8 @@ pub fn build_module_help<'a>( let initial_module = WasmModule::preload(env.arena, preload_bytes); // Adjust Wasm function indices to account for functions from the object file - let fn_index_offset: u32 = initial_module.import.function_count + initial_module.code.preloaded_count; + let fn_index_offset: u32 = + initial_module.import.function_count + initial_module.code.preloaded_count; // Get a map of name to index for the preloaded functions // Assumes the preloaded object file has all symbols exported, as per `zig build-lib -dymamic` diff --git a/compiler/gen_wasm/src/wasm_module/dead_code.rs b/compiler/gen_wasm/src/wasm_module/dead_code.rs index 7d46ae95c5..49d2f0d6d8 100644 --- a/compiler/gen_wasm/src/wasm_module/dead_code.rs +++ b/compiler/gen_wasm/src/wasm_module/dead_code.rs @@ -140,6 +140,9 @@ pub fn trace_function_deps<'a, Indices: IntoIterator>( if next_trace.is_empty() { break; } + next_trace.sort_unstable(); + next_trace.dedup(); + current_trace.clone_from(&next_trace); next_trace.clear(); } From 22d7ccfbd7cc2313f234417d72400148449b0bca Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 13 Jan 2022 18:30:51 +0000 Subject: [PATCH 209/541] Wasm: Comment explaining DCE --- .../gen_wasm/src/wasm_module/dead_code.rs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/compiler/gen_wasm/src/wasm_module/dead_code.rs b/compiler/gen_wasm/src/wasm_module/dead_code.rs index 49d2f0d6d8..c09e6b406f 100644 --- a/compiler/gen_wasm/src/wasm_module/dead_code.rs +++ b/compiler/gen_wasm/src/wasm_module/dead_code.rs @@ -5,6 +5,32 @@ use super::opcodes::OpCode; use super::serialize::{parse_u32_or_panic, SerialBuffer, Serialize, SkipBytes}; use super::{CodeBuilder, ValueType}; +/* + +DEAD CODE ELIMINATION + +Or, more specifically, "dead function replacement" + +- On pre-loading the object file: + - Analyse its call graph by finding all `call` instructions in the Code section, + and checking which function index they refer to. Store this in a `DeadCodeMetadata` + - Later we will need to know the return type of each function, so scan the Type and Function + sections to get that information and store it in `DeadCodeMetadata` too. +- While compiling Roc code: + - Run the backend as usual, adding more data into various sections of the Wasm module + - Whenever a call to a builtin or platform function is made, record its index in a Set. + These are the "live" preloaded functions that we are not allowed to eliminate. +- Call graph analysis: + - Starting with the set of live preloaded functions, trace their call graphs using the info we + collected earlier in `DeadCodeMetadata`. Mark all function indices in the call graph as "live". +- Dead function replacement: + - We actually don't want to just *delete* dead functions, because that would change the *indices* + of the live functions, invalidating all references to them, such as `call` instructions. + - Instead, we replace the dead functions with a tiny but *valid* function that has the same return type! + For example the minimal function returning `i32` contains just one instruction: `i32.const 0` + - This replacement happens during the final serialization phase +*/ + #[derive(Debug)] pub struct DeadCodeMetadata<'a> { /// Byte offset where each function body can be found From 3d00217b53c4da002aebfb2915a4cd7f6bc6de92 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 13 Jan 2022 18:41:16 +0000 Subject: [PATCH 210/541] Wasm: rename build_module_help -> build_module_without_test_wrapper --- compiler/gen_wasm/src/lib.rs | 12 ++++++++---- compiler/test_gen/src/helpers/wasm.rs | 8 ++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 4f832d452c..791626da06 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -34,24 +34,28 @@ pub struct Env<'a> { pub exposed_to_host: MutSet, } +/// Entry point for production pub fn build_module<'a>( env: &'a Env<'a>, interns: &'a mut Interns, preload_bytes: &[u8], procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) -> Result, String> { - let (wasm_module, _) = build_module_help(env, interns, preload_bytes, procedures)?; + // In production we don't want the test wrapper, just serialize it + let (wasm_module, _) = + build_module_without_test_wrapper(env, interns, preload_bytes, procedures); let mut buffer = std::vec::Vec::with_capacity(wasm_module.size()); wasm_module.serialize(&mut buffer); Ok(buffer) } -pub fn build_module_help<'a>( +/// Entry point for integration tests (test_gen) +pub fn build_module_without_test_wrapper<'a>( env: &'a Env<'a>, interns: &'a mut Interns, preload_bytes: &[u8], procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, -) -> Result<(WasmModule<'a>, u32), String> { +) -> (WasmModule<'a>, u32) { let mut layout_ids = LayoutIds::default(); let mut procs = Vec::with_capacity_in(procedures.len(), env.arena); let mut proc_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena); @@ -149,7 +153,7 @@ pub fn build_module_help<'a>( let module = backend.into_module(eliminate_dead_preloads); let main_function_index = maybe_main_fn_index.unwrap() + fn_index_offset; - Ok((module, main_function_index)) + (module, main_function_index) } pub struct CopyMemoryConfig { diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 952f9e1abb..0869214de8 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -125,8 +125,12 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( exposed_to_host, }; - let (mut wasm_module, main_fn_index) = - roc_gen_wasm::build_module_help(&env, &mut interns, preload_bytes, procedures).unwrap(); + let (mut wasm_module, main_fn_index) = roc_gen_wasm::build_module_without_test_wrapper( + &env, + &mut interns, + preload_bytes, + procedures, + ); T::insert_test_wrapper(arena, &mut wasm_module, TEST_WRAPPER_NAME, main_fn_index); From c7da7ca689b815b32bb2d4574e66808ac0ebf5bc Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 14 Jan 2022 13:38:55 +0000 Subject: [PATCH 211/541] Wasm: Parse the Name section, export init_refcount_test, and don't DCE exports --- compiler/gen_wasm/src/backend.rs | 36 ++-- compiler/gen_wasm/src/lib.rs | 21 +-- .../gen_wasm/src/wasm_module/dead_code.rs | 20 ++- compiler/gen_wasm/src/wasm_module/mod.rs | 35 +++- compiler/gen_wasm/src/wasm_module/sections.rs | 167 +++++++++++++++--- compiler/test_gen/src/helpers/wasm.rs | 34 ++-- 6 files changed, 242 insertions(+), 71 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 960d99a610..24f71cf832 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -20,11 +20,12 @@ use crate::storage::{StackMemoryLocation, Storage, StoredValue, StoredValueKind} use crate::wasm_module::linking::{DataSymbol, LinkingSegment, WasmObjectSymbol}; use crate::wasm_module::sections::{DataMode, DataSegment}; use crate::wasm_module::{ - code_builder, CodeBuilder, LocalId, Signature, SymInfo, ValueType, WasmModule, + code_builder, CodeBuilder, Export, ExportType, LocalId, Signature, SymInfo, ValueType, + WasmModule, }; use crate::{ - copy_memory, round_up_to_alignment, CopyMemoryConfig, Env, DEBUG_LOG_SETTINGS, PTR_SIZE, - PTR_TYPE, + copy_memory, round_up_to_alignment, CopyMemoryConfig, Env, DEBUG_LOG_SETTINGS, MEMORY_NAME, + PTR_SIZE, PTR_TYPE, STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME, }; /// The memory address where the constants data will be loaded during module instantiation. @@ -41,7 +42,6 @@ pub struct WasmBackend<'a> { layout_ids: LayoutIds<'a>, next_constant_addr: u32, fn_index_offset: u32, - preloaded_functions_map: MutMap<&'a [u8], u32>, called_preload_fns: MutSet, proc_symbols: Vec<'a, (Symbol, u32)>, helper_proc_gen: CodeGenHelp<'a>, @@ -64,11 +64,21 @@ impl<'a> WasmBackend<'a> { interns: &'a mut Interns, layout_ids: LayoutIds<'a>, proc_symbols: Vec<'a, (Symbol, u32)>, - module: WasmModule<'a>, + mut module: WasmModule<'a>, fn_index_offset: u32, - preloaded_functions_map: MutMap<&'a [u8], u32>, helper_proc_gen: CodeGenHelp<'a>, ) -> Self { + module.export.append(Export { + name: MEMORY_NAME.as_bytes(), + ty: ExportType::Mem, + index: 0, + }); + module.export.append(Export { + name: STACK_POINTER_NAME.as_bytes(), + ty: ExportType::Global, + index: STACK_POINTER_GLOBAL_ID, + }); + WasmBackend { env, interns, @@ -79,7 +89,6 @@ impl<'a> WasmBackend<'a> { layout_ids, next_constant_addr: CONST_SEGMENT_BASE_ADDR, fn_index_offset, - preloaded_functions_map, called_preload_fns: MutSet::default(), proc_symbols, helper_proc_gen, @@ -117,15 +126,8 @@ impl<'a> WasmBackend<'a> { self.module.linking.symbol_table.push(linker_symbol); } - pub fn into_module(mut self, remove_dead_preloads: bool) -> WasmModule<'a> { - if remove_dead_preloads { - self.module.code.remove_dead_preloads( - self.env.arena, - self.module.import.function_count, - self.called_preload_fns, - ) - } - self.module + pub fn finalize(self) -> (WasmModule<'a>, MutSet) { + (self.module, self.called_preload_fns) } /// Register the debug names of Symbols in a global lookup table @@ -1474,7 +1476,7 @@ impl<'a> WasmBackend<'a> { ) { let num_wasm_args = param_types.len(); let has_return_val = ret_type.is_some(); - let fn_index = self.preloaded_functions_map[name.as_bytes()]; + let fn_index = self.module.names.functions[name.as_bytes()]; self.called_preload_fns.insert(fn_index); let linker_symbol_index = u32::MAX; diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 791626da06..0849bf2027 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -41,9 +41,11 @@ pub fn build_module<'a>( preload_bytes: &[u8], procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) -> Result, String> { - // In production we don't want the test wrapper, just serialize it - let (wasm_module, _) = + let (mut wasm_module, called_preload_fns, _) = build_module_without_test_wrapper(env, interns, preload_bytes, procedures); + + wasm_module.remove_dead_preloads(env.arena, called_preload_fns); + let mut buffer = std::vec::Vec::with_capacity(wasm_module.size()); wasm_module.serialize(&mut buffer); Ok(buffer) @@ -55,14 +57,13 @@ pub fn build_module_without_test_wrapper<'a>( interns: &'a mut Interns, preload_bytes: &[u8], procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, -) -> (WasmModule<'a>, u32) { +) -> (WasmModule<'a>, MutSet, u32) { let mut layout_ids = LayoutIds::default(); let mut procs = Vec::with_capacity_in(procedures.len(), env.arena); let mut proc_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena); let mut linker_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena); let mut exports = Vec::with_capacity_in(4, env.arena); let mut maybe_main_fn_index = None; - let eliminate_dead_preloads = true; // Collect the symbols & names for the procedures, // and filter out procs we're going to inline @@ -103,11 +104,6 @@ pub fn build_module_without_test_wrapper<'a>( let fn_index_offset: u32 = initial_module.import.function_count + initial_module.code.preloaded_count; - // Get a map of name to index for the preloaded functions - // Assumes the preloaded object file has all symbols exported, as per `zig build-lib -dymamic` - let preloaded_functions_map: MutMap<&'a [u8], u32> = - initial_module.export.function_index_map(env.arena); - let mut backend = WasmBackend::new( env, interns, @@ -115,7 +111,6 @@ pub fn build_module_without_test_wrapper<'a>( proc_symbols, initial_module, fn_index_offset, - preloaded_functions_map, CodeGenHelp::new(env.arena, IntWidth::I32, env.module_id), ); @@ -150,10 +145,10 @@ pub fn build_module_without_test_wrapper<'a>( backend.build_proc(proc); } - let module = backend.into_module(eliminate_dead_preloads); - + let (module, called_preload_fns) = backend.finalize(); let main_function_index = maybe_main_fn_index.unwrap() + fn_index_offset; - (module, main_function_index) + + (module, called_preload_fns, main_function_index) } pub struct CopyMemoryConfig { diff --git a/compiler/gen_wasm/src/wasm_module/dead_code.rs b/compiler/gen_wasm/src/wasm_module/dead_code.rs index c09e6b406f..320f72c8bc 100644 --- a/compiler/gen_wasm/src/wasm_module/dead_code.rs +++ b/compiler/gen_wasm/src/wasm_module/dead_code.rs @@ -131,22 +131,32 @@ pub fn parse_dead_code_metadata<'a>( pub fn trace_function_deps<'a, Indices: IntoIterator>( arena: &'a Bump, metadata: &DeadCodeMetadata<'a>, + exported_fns: &[u32], called_from_app: Indices, ) -> Vec<'a, u32> { - let num_funcs = metadata.ret_types.len(); + let num_preloads = metadata.ret_types.len(); // All functions that get called from the app, directly or indirectly - let mut live_fn_indices = Vec::with_capacity_in(num_funcs, arena); + let mut live_fn_indices = Vec::with_capacity_in(num_preloads, arena); // Current & next batch of functions whose call graphs we want to trace through the metadata // (2 separate vectors so that we're not iterating over the same one we're changing) // If the max call depth is N then we will do N traces or less - let mut current_trace = Vec::with_capacity_in(num_funcs, arena); + let mut current_trace = Vec::with_capacity_in(num_preloads, arena); + let mut next_trace = Vec::with_capacity_in(num_preloads, arena); + + // Start with preloaded functions called from the app or exported directly to Wasm host current_trace.extend(called_from_app); - let mut next_trace = Vec::with_capacity_in(num_funcs, arena); + current_trace.extend( + exported_fns + .iter() + .filter(|idx| **idx < num_preloads as u32), + ); + current_trace.sort_unstable(); + current_trace.dedup(); // Fast per-function lookup table to see if its dependencies have already been traced - let mut already_traced = Vec::from_iter_in(std::iter::repeat(false).take(num_funcs), arena); + let mut already_traced = Vec::from_iter_in(std::iter::repeat(false).take(num_preloads), arena); loop { live_fn_indices.extend_from_slice(¤t_trace); diff --git a/compiler/gen_wasm/src/wasm_module/mod.rs b/compiler/gen_wasm/src/wasm_module/mod.rs index c1519bee6f..6ac96253a6 100644 --- a/compiler/gen_wasm/src/wasm_module/mod.rs +++ b/compiler/gen_wasm/src/wasm_module/mod.rs @@ -11,13 +11,17 @@ pub use linking::SymInfo; use roc_reporting::internal_error; pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature}; +use crate::wasm_module::serialize::SkipBytes; + use self::linking::{LinkingSection, RelocationSection}; use self::sections::{ CodeSection, DataSection, ExportSection, FunctionSection, GlobalSection, ImportSection, - MemorySection, OpaqueSection, Section, SectionId, TypeSection, + MemorySection, NameSection, OpaqueSection, Section, SectionId, TypeSection, }; use self::serialize::{SerialBuffer, Serialize}; +/// A representation of the WebAssembly binary file format +/// https://webassembly.github.io/spec/core/binary/modules.html #[derive(Debug)] pub struct WasmModule<'a> { pub types: TypeSection<'a>, @@ -31,6 +35,7 @@ pub struct WasmModule<'a> { pub element: OpaqueSection<'a>, pub code: CodeSection<'a>, pub data: DataSection<'a>, + pub names: NameSection<'a>, pub linking: LinkingSection<'a>, pub relocations: RelocationSection<'a>, } @@ -128,15 +133,23 @@ impl<'a> WasmModule<'a> { let ret_types = types.parse_preloaded_data(arena); let import = ImportSection::preload(arena, bytes, &mut cursor); + let function = FunctionSection::preload(arena, bytes, &mut cursor); let signature_ids = function.parse_preloaded_data(arena); let table = OpaqueSection::preload(SectionId::Table, arena, bytes, &mut cursor); + let memory = MemorySection::preload(arena, bytes, &mut cursor); + let global = GlobalSection::preload(arena, bytes, &mut cursor); - let export = ExportSection::preload(arena, bytes, &mut cursor); + + ExportSection::skip_bytes(bytes, &mut cursor); + let export = ExportSection::empty(arena); + let start = OpaqueSection::preload(SectionId::Start, arena, bytes, &mut cursor); + let element = OpaqueSection::preload(SectionId::Element, arena, bytes, &mut cursor); + let code = CodeSection::preload( arena, bytes, @@ -145,7 +158,11 @@ impl<'a> WasmModule<'a> { signature_ids, import.function_count, ); + let data = DataSection::preload(arena, bytes, &mut cursor); + + // Metadata sections + let names = NameSection::parse(arena, bytes, &mut cursor); let linking = LinkingSection::new(arena); let relocations = RelocationSection::new(arena, "reloc.CODE"); @@ -161,10 +178,24 @@ impl<'a> WasmModule<'a> { element, code, data, + names, linking, relocations, } } + + pub fn remove_dead_preloads>( + &mut self, + arena: &'a Bump, + called_preload_fns: T, + ) { + self.code.remove_dead_preloads( + arena, + self.import.function_count, + &self.export.function_indices, + called_preload_fns, + ) + } } /// Helper struct to count non-empty sections. diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index bfd38347f1..8b9ae099c0 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -668,38 +668,53 @@ impl Serialize for Export<'_> { pub struct ExportSection<'a> { pub count: u32, pub bytes: Vec<'a, u8>, + pub function_indices: Vec<'a, u32>, } impl<'a> ExportSection<'a> { + const ID: SectionId = SectionId::Export; + pub fn append(&mut self, export: Export) { export.serialize(&mut self.bytes); self.count += 1; + if matches!(export.ty, ExportType::Func) { + self.function_indices.push(export.index); + } } - pub fn function_index_map(&self, arena: &'a Bump) -> MutMap<&'a [u8], u32> { - let mut map = MutMap::default(); + pub fn size(&self) -> usize { + let id = 1; + let encoded_length = 5; + let encoded_count = 5; - let mut cursor = 0; - while cursor < self.bytes.len() { - let name_len = parse_u32_or_panic(&self.bytes, &mut cursor); - let name_end = cursor + name_len as usize; - let name_bytes = &self.bytes[cursor..name_end]; - let ty = self.bytes[name_end]; + id + encoded_length + encoded_count + self.bytes.len() + } - cursor = name_end + 1; - let index = parse_u32_or_panic(&self.bytes, &mut cursor); - - if ty == ExportType::Func as u8 { - let name: &'a [u8] = arena.alloc_slice_clone(name_bytes); - map.insert(name, index); - } + pub fn empty(arena: &'a Bump) -> Self { + ExportSection { + count: 0, + bytes: Vec::with_capacity_in(256, arena), + function_indices: Vec::with_capacity_in(4, arena), } - - map } } -section_impl!(ExportSection, SectionId::Export); +impl SkipBytes for ExportSection<'_> { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) { + parse_section(Self::ID, bytes, cursor); + } +} + +impl<'a> Serialize for ExportSection<'a> { + fn serialize(&self, buffer: &mut T) { + if !self.bytes.is_empty() { + let header_indices = write_section_header(buffer, Self::ID); + buffer.encode_u32(self.count); + buffer.append_slice(&self.bytes); + update_section_size(buffer, header_indices); + } + } +} /******************************************************************* * @@ -769,14 +784,19 @@ impl<'a> CodeSection<'a> { } } - pub fn remove_dead_preloads>( + pub(super) fn remove_dead_preloads>( &mut self, arena: &'a Bump, import_fn_count: u32, + exported_fns: &[u32], called_preload_fns: T, ) { - let live_ext_fn_indices = - trace_function_deps(arena, &self.dead_code_metadata, called_preload_fns); + let live_ext_fn_indices = trace_function_deps( + arena, + &self.dead_code_metadata, + exported_fns, + called_preload_fns, + ); let mut buffer = Vec::with_capacity_in(self.preloaded_bytes.len(), arena); @@ -871,9 +891,7 @@ section_impl!(DataSection, SectionId::Data); /******************************************************************* * - * Module - * - * https://webassembly.github.io/spec/core/binary/modules.html + * Opaque section * *******************************************************************/ @@ -920,6 +938,107 @@ impl Serialize for OpaqueSection<'_> { } } +/******************************************************************* + * + * Name section + * https://webassembly.github.io/spec/core/appendix/custom.html#name-section + * + *******************************************************************/ + +#[repr(u8)] +#[allow(dead_code)] +enum NameSubSections { + ModuleName = 0, + FunctionNames = 1, + LocalNames = 2, +} + +#[derive(Debug, Default)] +pub struct NameSection<'a> { + pub functions: MutMap<&'a [u8], u32>, +} + +impl<'a> NameSection<'a> { + const ID: SectionId = SectionId::Custom; + const NAME: &'static str = "name"; + + pub fn parse(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self { + let functions = MutMap::default(); + let mut section = NameSection { functions }; + section.parse_help(arena, module_bytes, cursor); + section + } + + fn parse_help(&mut self, arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) { + // Custom section ID + let section_id_byte = module_bytes[*cursor]; + if section_id_byte != Self::ID as u8 { + internal_error!( + "Expected section ID 0x{:x}, but found 0x{:x} at offset 0x{:x}", + Self::ID as u8, + section_id_byte, + *cursor + ); + } + *cursor += 1; + + // Section size + let section_size = parse_u32_or_panic(module_bytes, cursor); + let section_end = *cursor + section_size as usize; + + // Custom section name + let section_name_len = parse_u32_or_panic(module_bytes, cursor); + let section_name_end = *cursor + section_name_len as usize; + let section_name = &module_bytes[*cursor..section_name_end]; + if section_name != Self::NAME.as_bytes() { + internal_error!( + "Expected Custon section {:?}, found {:?}", + Self::NAME, + std::str::from_utf8(section_name) + ); + } + *cursor = section_name_end; + + // Find function names subsection + let mut found_function_names = false; + for _possible_subsection_id in 0..2 { + let subsection_id = module_bytes[*cursor]; + *cursor += 1; + let subsection_size = parse_u32_or_panic(module_bytes, cursor); + if subsection_id == NameSubSections::FunctionNames as u8 { + found_function_names = true; + break; + } + *cursor += subsection_size as usize; + if *cursor >= section_end { + internal_error!("Failed to parse Name section"); + } + } + if !found_function_names { + internal_error!("Failed to parse Name section"); + } + + // Function names + let num_entries = parse_u32_or_panic(module_bytes, cursor) as usize; + for _ in 0..num_entries { + let fn_index = parse_u32_or_panic(module_bytes, cursor); + let name_len = parse_u32_or_panic(module_bytes, cursor); + let name_end = *cursor + name_len as usize; + let name_bytes: &[u8] = &module_bytes[*cursor..name_end]; + *cursor = name_end; + + self.functions + .insert(arena.alloc_slice_copy(name_bytes), fn_index); + } + } +} + +/******************************************************************* + * + * Unit tests + * + *******************************************************************/ + #[cfg(test)] mod tests { use super::*; diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 0869214de8..21d6c6b36c 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -1,4 +1,5 @@ use core::cell::Cell; +use roc_gen_wasm::wasm_module::{Export, ExportType}; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; @@ -16,6 +17,7 @@ const PLATFORM_FILENAME: &str = "wasm_test_platform"; const OUT_DIR_VAR: &str = "TEST_GEN_OUT"; const TEST_WRAPPER_NAME: &str = "test_wrapper"; +const INIT_REFCOUNT_NAME: &str = "init_refcount_test"; fn promote_expr_to_module(src: &str) -> String { let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n"); @@ -125,17 +127,29 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( exposed_to_host, }; - let (mut wasm_module, main_fn_index) = roc_gen_wasm::build_module_without_test_wrapper( - &env, - &mut interns, - preload_bytes, - procedures, - ); + let (mut module, called_preload_fns, main_fn_index) = + roc_gen_wasm::build_module_without_test_wrapper( + &env, + &mut interns, + preload_bytes, + procedures, + ); - T::insert_test_wrapper(arena, &mut wasm_module, TEST_WRAPPER_NAME, main_fn_index); + T::insert_test_wrapper(arena, &mut module, TEST_WRAPPER_NAME, main_fn_index); - let mut app_module_bytes = std::vec::Vec::with_capacity(4096); - wasm_module.serialize(&mut app_module_bytes); + // Export the initialiser function for refcount tests + let init_refcount_bytes = INIT_REFCOUNT_NAME.as_bytes(); + let init_refcount_idx = module.names.functions[init_refcount_bytes]; + module.export.append(Export { + name: arena.alloc_slice_copy(init_refcount_bytes), + ty: ExportType::Func, + index: init_refcount_idx, + }); + + module.remove_dead_preloads(env.arena, called_preload_fns); + + let mut app_module_bytes = std::vec::Vec::with_capacity(module.size()); + module.serialize(&mut app_module_bytes); app_module_bytes } @@ -235,7 +249,7 @@ where let memory = instance.exports.get_memory(MEMORY_NAME).unwrap(); let expected_len = num_refcounts as i32; - let init_refcount_test = instance.exports.get_function("init_refcount_test").unwrap(); + let init_refcount_test = instance.exports.get_function(INIT_REFCOUNT_NAME).unwrap(); let init_result = init_refcount_test.call(&[wasmer::Value::I32(expected_len)]); let refcount_vector_addr = match init_result { Err(e) => return Err(format!("{:?}", e)), From e7dc442af0b544cc2a8a3d5e0c634f4694f0d5ce Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 14 Jan 2022 17:07:08 +0000 Subject: [PATCH 212/541] Wasm: fix double-counting bug in dead function replacement --- .../gen_wasm/src/wasm_module/dead_code.rs | 75 +++++++------------ compiler/gen_wasm/src/wasm_module/sections.rs | 5 +- 2 files changed, 32 insertions(+), 48 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/dead_code.rs b/compiler/gen_wasm/src/wasm_module/dead_code.rs index 320f72c8bc..5c2b6141ab 100644 --- a/compiler/gen_wasm/src/wasm_module/dead_code.rs +++ b/compiler/gen_wasm/src/wasm_module/dead_code.rs @@ -224,62 +224,45 @@ fn create_dummy_functions(arena: &Bump) -> [Vec<'_, u8>; 5] { [dummy_i32, dummy_i64, dummy_f32, dummy_f64, dummy_nil] } -/// Copy used functions from an external module into our Code section +/// Copy used functions from preloaded object file into our Code section /// Replace unused functions with very small dummies, to avoid changing any indices -pub fn copy_live_and_replace_dead<'a, T: SerialBuffer>( +pub fn copy_live_and_replace_dead_preloads<'a, T: SerialBuffer>( arena: &'a Bump, buffer: &mut T, metadata: &DeadCodeMetadata<'a>, external_code: &[u8], import_fn_count: u32, - mut live_ext_fn_indices: Vec<'a, u32>, + mut live_preload_indices: Vec<'a, u32>, ) { - live_ext_fn_indices.sort_unstable(); + let preload_idx_start = import_fn_count as usize; + let preload_idx_end = metadata.ret_types.len(); let [dummy_i32, dummy_i64, dummy_f32, dummy_f64, dummy_nil] = create_dummy_functions(arena); - let mut prev = import_fn_count as usize; - for live32 in live_ext_fn_indices.into_iter() { - if live32 < import_fn_count { - continue; + live_preload_indices.sort_unstable(); + live_preload_indices.dedup(); + + let mut live_iter = live_preload_indices.iter(); + let mut next_live_idx = live_iter.next(); + for i in preload_idx_start..preload_idx_end { + match next_live_idx { + Some(live) if *live as usize == i => { + next_live_idx = live_iter.next(); + let live_body_start = metadata.code_offsets[i] as usize; + let live_body_end = metadata.code_offsets[i + 1] as usize; + buffer.append_slice(&external_code[live_body_start..live_body_end]); + } + _ => { + let ret_type = metadata.ret_types[i]; + let dummy_bytes = match ret_type { + Some(ValueType::I32) => &dummy_i32, + Some(ValueType::I64) => &dummy_i64, + Some(ValueType::F32) => &dummy_f32, + Some(ValueType::F64) => &dummy_f64, + None => &dummy_nil, + }; + buffer.append_slice(dummy_bytes); + } } - - let live = live32 as usize; - - // Replace dead functions with the minimal code body that will pass validation checks - for dead in prev..live { - let dummy_bytes = match metadata.ret_types[dead] { - Some(ValueType::I32) => &dummy_i32, - Some(ValueType::I64) => &dummy_i64, - Some(ValueType::F32) => &dummy_f32, - Some(ValueType::F64) => &dummy_f64, - None => &dummy_nil, - }; - buffer.append_slice(dummy_bytes); - } - - // Copy the body of the live function from the external module - let live_body_start = metadata.code_offsets[live] as usize; - let live_body_end = metadata.code_offsets[live + 1] as usize; - buffer.append_slice(&external_code[live_body_start..live_body_end]); - - prev = live + 1; - } - - let num_preloaded_fns = metadata.ret_types.len(); - // Replace dead functions with the minimal code body that will pass validation checks - for dead in prev..num_preloaded_fns { - if dead < import_fn_count as usize { - continue; - } - let ret_type = metadata.ret_types[dead]; - let dummy_bytes = match ret_type { - Some(ValueType::I32) => &dummy_i32, - Some(ValueType::I64) => &dummy_i64, - Some(ValueType::F32) => &dummy_f32, - Some(ValueType::F64) => &dummy_f64, - None => &dummy_nil, - }; - buffer.append_slice(dummy_bytes); } } diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 8b9ae099c0..e78342a10e 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -4,7 +4,8 @@ use roc_collections::all::MutMap; use roc_reporting::internal_error; use super::dead_code::{ - copy_live_and_replace_dead, parse_dead_code_metadata, trace_function_deps, DeadCodeMetadata, + copy_live_and_replace_dead_preloads, parse_dead_code_metadata, trace_function_deps, + DeadCodeMetadata, }; use super::linking::RelocationEntry; use super::opcodes::OpCode; @@ -800,7 +801,7 @@ impl<'a> CodeSection<'a> { let mut buffer = Vec::with_capacity_in(self.preloaded_bytes.len(), arena); - copy_live_and_replace_dead( + copy_live_and_replace_dead_preloads( arena, &mut buffer, &self.dead_code_metadata, From 4311b5a4103cfd9448f297836703af71cfe2ef63 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 14 Jan 2022 18:17:52 +0000 Subject: [PATCH 213/541] Wasm: Make dead functions `unreachable`, and the same for all return types --- .../gen_wasm/src/wasm_module/dead_code.rs | 95 ++++--------------- compiler/gen_wasm/src/wasm_module/mod.rs | 18 +--- compiler/gen_wasm/src/wasm_module/sections.rs | 45 ++------- .../gen_wasm/src/wasm_module/serialize.rs | 4 - 4 files changed, 27 insertions(+), 135 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/dead_code.rs b/compiler/gen_wasm/src/wasm_module/dead_code.rs index 5c2b6141ab..3b22bddfc1 100644 --- a/compiler/gen_wasm/src/wasm_module/dead_code.rs +++ b/compiler/gen_wasm/src/wasm_module/dead_code.rs @@ -3,7 +3,7 @@ use bumpalo::Bump; use super::opcodes::OpCode; use super::serialize::{parse_u32_or_panic, SerialBuffer, Serialize, SkipBytes}; -use super::{CodeBuilder, ValueType}; +use super::CodeBuilder; /* @@ -14,8 +14,6 @@ Or, more specifically, "dead function replacement" - On pre-loading the object file: - Analyse its call graph by finding all `call` instructions in the Code section, and checking which function index they refer to. Store this in a `DeadCodeMetadata` - - Later we will need to know the return type of each function, so scan the Type and Function - sections to get that information and store it in `DeadCodeMetadata` too. - While compiling Roc code: - Run the backend as usual, adding more data into various sections of the Wasm module - Whenever a call to a builtin or platform function is made, record its index in a Set. @@ -24,45 +22,37 @@ Or, more specifically, "dead function replacement" - Starting with the set of live preloaded functions, trace their call graphs using the info we collected earlier in `DeadCodeMetadata`. Mark all function indices in the call graph as "live". - Dead function replacement: - - We actually don't want to just *delete* dead functions, because that would change the *indices* + - We actually don't want to just *delete* dead functions, because that would change the indices of the live functions, invalidating all references to them, such as `call` instructions. - - Instead, we replace the dead functions with a tiny but *valid* function that has the same return type! - For example the minimal function returning `i32` contains just one instruction: `i32.const 0` - - This replacement happens during the final serialization phase + - Instead, during serialization, we replace its body with a single `unreachable` instruction */ #[derive(Debug)] pub struct DeadCodeMetadata<'a> { + num_preloads: usize, /// Byte offset where each function body can be found code_offsets: Vec<'a, u32>, /// Vector with one entry per *call*, containing the called function's index calls: Vec<'a, u32>, /// Vector with one entry per *function*, indicating its offset in `calls` calls_offsets: Vec<'a, u32>, - /// Return types of each function (for making almost-empty dummy replacements) - ret_types: Vec<'a, Option>, } impl<'a> DeadCodeMetadata<'a> { pub fn new(arena: &'a Bump, import_fn_count: u32, fn_count: u32) -> Self { - let capacity = (import_fn_count + fn_count) as usize; + let num_preloads = (import_fn_count + fn_count) as usize; - let mut code_offsets = Vec::with_capacity_in(capacity, arena); - let mut ret_types = Vec::with_capacity_in(capacity, arena); - let calls = Vec::with_capacity_in(2 * capacity, arena); - let mut calls_offsets = Vec::with_capacity_in(1 + capacity, arena); + let mut code_offsets = Vec::with_capacity_in(num_preloads, arena); + let calls = Vec::with_capacity_in(2 * num_preloads, arena); + let mut calls_offsets = Vec::with_capacity_in(1 + num_preloads, arena); // Imported functions have zero code length and no calls code_offsets.extend(std::iter::repeat(0).take(import_fn_count as usize)); calls_offsets.extend(std::iter::repeat(0).take(import_fn_count as usize)); - // We don't care about import return types - // Return types are for replacing dead functions with dummies, which doesn't apply to imports - ret_types.extend(std::iter::repeat(None).take(import_fn_count as usize)); - DeadCodeMetadata { + num_preloads, code_offsets, - ret_types, calls, calls_offsets, } @@ -77,16 +67,9 @@ pub fn parse_dead_code_metadata<'a>( arena: &'a Bump, fn_count: u32, code_section_body: &[u8], - signature_ret_types: Vec<'a, Option>, - internal_fn_sig_ids: Vec<'a, u32>, import_fn_count: u32, ) -> DeadCodeMetadata<'a> { let mut metadata = DeadCodeMetadata::new(arena, import_fn_count, fn_count); - metadata.ret_types.extend( - internal_fn_sig_ids - .iter() - .map(|sig| signature_ret_types[*sig as usize]), - ); let mut cursor: usize = 0; while cursor < code_section_body.len() { @@ -134,7 +117,7 @@ pub fn trace_function_deps<'a, Indices: IntoIterator>( exported_fns: &[u32], called_from_app: Indices, ) -> Vec<'a, u32> { - let num_preloads = metadata.ret_types.len(); + let num_preloads = metadata.num_preloads; // All functions that get called from the app, directly or indirectly let mut live_fn_indices = Vec::with_capacity_in(num_preloads, arena); @@ -186,44 +169,6 @@ pub fn trace_function_deps<'a, Indices: IntoIterator>( live_fn_indices } -/// Create a set of minimum-size dummy functions for each possible return type -fn create_dummy_functions(arena: &Bump) -> [Vec<'_, u8>; 5] { - let mut code_builder_i32 = CodeBuilder::new(arena); - code_builder_i32.i32_const(0); - - let mut code_builder_i64 = CodeBuilder::new(arena); - code_builder_i64.i64_const(0); - - let mut code_builder_f32 = CodeBuilder::new(arena); - code_builder_f32.f32_const(0.0); - - let mut code_builder_f64 = CodeBuilder::new(arena); - code_builder_f64.f64_const(0.0); - - let mut code_builder_nil = CodeBuilder::new(arena); - - code_builder_i32.build_fn_header_and_footer(&[], 0, None); - code_builder_i64.build_fn_header_and_footer(&[], 0, None); - code_builder_f32.build_fn_header_and_footer(&[], 0, None); - code_builder_f64.build_fn_header_and_footer(&[], 0, None); - code_builder_nil.build_fn_header_and_footer(&[], 0, None); - - let capacity = code_builder_f64.size(); - let mut dummy_i32 = Vec::with_capacity_in(capacity, arena); - let mut dummy_i64 = Vec::with_capacity_in(capacity, arena); - let mut dummy_f32 = Vec::with_capacity_in(capacity, arena); - let mut dummy_f64 = Vec::with_capacity_in(capacity, arena); - let mut dummy_nil = Vec::with_capacity_in(capacity, arena); - - code_builder_i32.serialize(&mut dummy_i32); - code_builder_i64.serialize(&mut dummy_i64); - code_builder_f32.serialize(&mut dummy_f32); - code_builder_f64.serialize(&mut dummy_f64); - code_builder_nil.serialize(&mut dummy_nil); - - [dummy_i32, dummy_i64, dummy_f32, dummy_f64, dummy_nil] -} - /// Copy used functions from preloaded object file into our Code section /// Replace unused functions with very small dummies, to avoid changing any indices pub fn copy_live_and_replace_dead_preloads<'a, T: SerialBuffer>( @@ -235,16 +180,20 @@ pub fn copy_live_and_replace_dead_preloads<'a, T: SerialBuffer>( mut live_preload_indices: Vec<'a, u32>, ) { let preload_idx_start = import_fn_count as usize; - let preload_idx_end = metadata.ret_types.len(); - let [dummy_i32, dummy_i64, dummy_f32, dummy_f64, dummy_nil] = create_dummy_functions(arena); + // Create a dummy function with just a single `unreachable` instruction + let mut dummy_builder = CodeBuilder::new(arena); + dummy_builder.unreachable_(); + dummy_builder.build_fn_header_and_footer(&[], 0, None); + let mut dummy_bytes = Vec::with_capacity_in(dummy_builder.size(), arena); + dummy_builder.serialize(&mut dummy_bytes); live_preload_indices.sort_unstable(); live_preload_indices.dedup(); let mut live_iter = live_preload_indices.iter(); let mut next_live_idx = live_iter.next(); - for i in preload_idx_start..preload_idx_end { + for i in preload_idx_start..metadata.num_preloads { match next_live_idx { Some(live) if *live as usize == i => { next_live_idx = live_iter.next(); @@ -253,15 +202,7 @@ pub fn copy_live_and_replace_dead_preloads<'a, T: SerialBuffer>( buffer.append_slice(&external_code[live_body_start..live_body_end]); } _ => { - let ret_type = metadata.ret_types[i]; - let dummy_bytes = match ret_type { - Some(ValueType::I32) => &dummy_i32, - Some(ValueType::I64) => &dummy_i64, - Some(ValueType::F32) => &dummy_f32, - Some(ValueType::F64) => &dummy_f64, - None => &dummy_nil, - }; - buffer.append_slice(dummy_bytes); + buffer.append_slice(&dummy_bytes); } } } diff --git a/compiler/gen_wasm/src/wasm_module/mod.rs b/compiler/gen_wasm/src/wasm_module/mod.rs index 6ac96253a6..5f494967b8 100644 --- a/compiler/gen_wasm/src/wasm_module/mod.rs +++ b/compiler/gen_wasm/src/wasm_module/mod.rs @@ -130,34 +130,20 @@ impl<'a> WasmModule<'a> { let mut cursor: usize = 8; let mut types = TypeSection::preload(arena, bytes, &mut cursor); - let ret_types = types.parse_preloaded_data(arena); + types.parse_offsets(); let import = ImportSection::preload(arena, bytes, &mut cursor); - let function = FunctionSection::preload(arena, bytes, &mut cursor); - let signature_ids = function.parse_preloaded_data(arena); - let table = OpaqueSection::preload(SectionId::Table, arena, bytes, &mut cursor); - let memory = MemorySection::preload(arena, bytes, &mut cursor); - let global = GlobalSection::preload(arena, bytes, &mut cursor); ExportSection::skip_bytes(bytes, &mut cursor); let export = ExportSection::empty(arena); let start = OpaqueSection::preload(SectionId::Start, arena, bytes, &mut cursor); - let element = OpaqueSection::preload(SectionId::Element, arena, bytes, &mut cursor); - - let code = CodeSection::preload( - arena, - bytes, - &mut cursor, - ret_types, - signature_ids, - import.function_count, - ); + let code = CodeSection::preload(arena, bytes, &mut cursor, import.function_count); let data = DataSection::preload(arena, bytes, &mut cursor); diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index e78342a10e..0bbec1a3a9 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -9,9 +9,7 @@ use super::dead_code::{ }; use super::linking::RelocationEntry; use super::opcodes::OpCode; -use super::serialize::{ - decode_u32_or_panic, parse_u32_or_panic, SerialBuffer, Serialize, SkipBytes, -}; +use super::serialize::{parse_u32_or_panic, SerialBuffer, Serialize, SkipBytes}; use super::{CodeBuilder, ValueType}; /******************************************************************* @@ -223,9 +221,8 @@ impl<'a> TypeSection<'a> { sig_id as u32 } - pub fn parse_preloaded_data(&mut self, arena: &'a Bump) -> Vec<'a, Option> { + pub fn parse_offsets(&mut self) { self.offsets.clear(); - let mut ret_types = Vec::with_capacity_in(self.offsets.capacity(), arena); let mut i = 0; while i < self.bytes.len() { @@ -234,23 +231,12 @@ impl<'a> TypeSection<'a> { debug_assert!(self.bytes[i] == Signature::SEPARATOR); i += 1; - let (n_params, n_params_size) = decode_u32_or_panic(&self.bytes[i..]); - i += n_params_size; // skip over the array length that we just decoded + let n_params = parse_u32_or_panic(&self.bytes, &mut i); i += n_params as usize; // skip over one byte per param type let n_return_values = self.bytes[i]; - i += 1; - - ret_types.push(if n_return_values == 0 { - None - } else { - Some(ValueType::from(self.bytes[i])) - }); - - i += n_return_values as usize; + i += 1 + n_return_values as usize; } - - ret_types } } @@ -451,15 +437,6 @@ impl<'a> FunctionSection<'a> { self.bytes.encode_u32(sig_id); self.count += 1; } - - pub fn parse_preloaded_data(&self, arena: &'a Bump) -> Vec<'a, u32> { - let mut preload_signature_ids = Vec::with_capacity_in(self.count as usize, arena); - let mut cursor = 0; - while cursor < self.bytes.len() { - preload_signature_ids.push(parse_u32_or_panic(&self.bytes, &mut cursor)); - } - preload_signature_ids - } } section_impl!(FunctionSection, SectionId::Function); @@ -760,22 +737,14 @@ impl<'a> CodeSection<'a> { arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize, - ret_types: Vec<'a, Option>, - internal_fn_sig_ids: Vec<'a, u32>, import_fn_count: u32, ) -> Self { let (preloaded_count, initial_bytes) = parse_section(SectionId::Code, module_bytes, cursor); let preloaded_bytes = arena.alloc_slice_copy(initial_bytes); // TODO: Try to move this metadata preparation to platform build time - let dead_code_metadata = parse_dead_code_metadata( - arena, - preloaded_count, - initial_bytes, - ret_types, - internal_fn_sig_ids, - import_fn_count, - ); + let dead_code_metadata = + parse_dead_code_metadata(arena, preloaded_count, initial_bytes, import_fn_count); CodeSection { preloaded_count, @@ -1053,7 +1022,7 @@ mod tests { // Reconstruct a new TypeSection by "pre-loading" the bytes of the original let mut cursor = 0; let mut preloaded = TypeSection::preload(arena, &original_serialized, &mut cursor); - preloaded.parse_preloaded_data(arena); + preloaded.parse_offsets(); debug_assert_eq!(original.offsets, preloaded.offsets); debug_assert_eq!(original.bytes, preloaded.bytes); diff --git a/compiler/gen_wasm/src/wasm_module/serialize.rs b/compiler/gen_wasm/src/wasm_module/serialize.rs index b61376968b..105eb34cfa 100644 --- a/compiler/gen_wasm/src/wasm_module/serialize.rs +++ b/compiler/gen_wasm/src/wasm_module/serialize.rs @@ -250,10 +250,6 @@ pub fn decode_u32(bytes: &[u8]) -> Result<(u32, usize), String> { )) } -pub fn decode_u32_or_panic(bytes: &[u8]) -> (u32, usize) { - decode_u32(bytes).unwrap_or_else(|e| internal_error!("{}", e)) -} - pub fn parse_u32_or_panic(bytes: &[u8], cursor: &mut usize) -> u32 { let (value, len) = decode_u32(&bytes[*cursor..]).unwrap_or_else(|e| internal_error!("{}", e)); *cursor += len; From c5134fa0154cec231b312be1e0c8ae21f7375fde Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 14 Jan 2022 18:37:52 +0000 Subject: [PATCH 214/541] Wasm: rename DeadCodeMetadata to PreloadsCallGraph --- .../gen_wasm/src/wasm_module/dead_code.rs | 60 ++++++++++--------- compiler/gen_wasm/src/wasm_module/sections.rs | 14 ++--- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/dead_code.rs b/compiler/gen_wasm/src/wasm_module/dead_code.rs index 3b22bddfc1..33950ee42c 100644 --- a/compiler/gen_wasm/src/wasm_module/dead_code.rs +++ b/compiler/gen_wasm/src/wasm_module/dead_code.rs @@ -13,14 +13,14 @@ Or, more specifically, "dead function replacement" - On pre-loading the object file: - Analyse its call graph by finding all `call` instructions in the Code section, - and checking which function index they refer to. Store this in a `DeadCodeMetadata` + and checking which function index they refer to. Store this in a `PreloadsCallGraph` - While compiling Roc code: - Run the backend as usual, adding more data into various sections of the Wasm module - Whenever a call to a builtin or platform function is made, record its index in a Set. These are the "live" preloaded functions that we are not allowed to eliminate. - Call graph analysis: - Starting with the set of live preloaded functions, trace their call graphs using the info we - collected earlier in `DeadCodeMetadata`. Mark all function indices in the call graph as "live". + collected earlier in `PreloadsCallGraph`. Mark all function indices in the call graph as "live". - Dead function replacement: - We actually don't want to just *delete* dead functions, because that would change the indices of the live functions, invalidating all references to them, such as `call` instructions. @@ -28,7 +28,7 @@ Or, more specifically, "dead function replacement" */ #[derive(Debug)] -pub struct DeadCodeMetadata<'a> { +pub struct PreloadsCallGraph<'a> { num_preloads: usize, /// Byte offset where each function body can be found code_offsets: Vec<'a, u32>, @@ -38,7 +38,7 @@ pub struct DeadCodeMetadata<'a> { calls_offsets: Vec<'a, u32>, } -impl<'a> DeadCodeMetadata<'a> { +impl<'a> PreloadsCallGraph<'a> { pub fn new(arena: &'a Bump, import_fn_count: u32, fn_count: u32) -> Self { let num_preloads = (import_fn_count + fn_count) as usize; @@ -50,7 +50,7 @@ impl<'a> DeadCodeMetadata<'a> { code_offsets.extend(std::iter::repeat(0).take(import_fn_count as usize)); calls_offsets.extend(std::iter::repeat(0).take(import_fn_count as usize)); - DeadCodeMetadata { + PreloadsCallGraph { num_preloads, code_offsets, calls, @@ -63,36 +63,38 @@ impl<'a> DeadCodeMetadata<'a> { /// which functions are actually called, and which are not. /// This would normally be done in a linker optimisation, but we want to be able to /// use this backend without a linker. -pub fn parse_dead_code_metadata<'a>( +pub fn parse_preloads_call_graph<'a>( arena: &'a Bump, fn_count: u32, code_section_body: &[u8], import_fn_count: u32, -) -> DeadCodeMetadata<'a> { - let mut metadata = DeadCodeMetadata::new(arena, import_fn_count, fn_count); +) -> PreloadsCallGraph<'a> { + let mut call_graph = PreloadsCallGraph::new(arena, import_fn_count, fn_count); + // Iterate over the bytes of the Code section let mut cursor: usize = 0; while cursor < code_section_body.len() { - metadata.code_offsets.push(cursor as u32); - metadata.calls_offsets.push(metadata.calls.len() as u32); + // Record the start of a function + call_graph.code_offsets.push(cursor as u32); + call_graph.calls_offsets.push(call_graph.calls.len() as u32); let func_size = parse_u32_or_panic(code_section_body, &mut cursor); let func_end = cursor + func_size as usize; - // Local variable declarations + // Skip over local variable declarations let local_groups_count = parse_u32_or_panic(code_section_body, &mut cursor); for _ in 0..local_groups_count { parse_u32_or_panic(code_section_body, &mut cursor); cursor += 1; // ValueType } - // Instructions + // Parse `call` instructions and skip over all other instructions while cursor < func_end { let opcode_byte: u8 = code_section_body[cursor]; if opcode_byte == OpCode::CALL as u8 { cursor += 1; let call_index = parse_u32_or_panic(code_section_body, &mut cursor); - metadata.calls.push(call_index as u32); + call_graph.calls.push(call_index as u32); } else { OpCode::skip_bytes(code_section_body, &mut cursor); } @@ -100,29 +102,29 @@ pub fn parse_dead_code_metadata<'a>( } // Extra entries to mark the end of the last function - metadata.code_offsets.push(cursor as u32); - metadata.calls_offsets.push(metadata.calls.len() as u32); + call_graph.code_offsets.push(cursor as u32); + call_graph.calls_offsets.push(call_graph.calls.len() as u32); - metadata + call_graph } /// Trace the dependencies of a list of functions -/// We've already collected metadata saying which functions call each other +/// We've already collected call_graph saying which functions call each other /// Now we need to trace the dependency graphs of a specific subset of them /// Result is the full set of builtins and platform functions used in the app. /// The rest are "dead code" and can be eliminated. -pub fn trace_function_deps<'a, Indices: IntoIterator>( +pub fn trace_call_graph<'a, Indices: IntoIterator>( arena: &'a Bump, - metadata: &DeadCodeMetadata<'a>, + call_graph: &PreloadsCallGraph<'a>, exported_fns: &[u32], called_from_app: Indices, ) -> Vec<'a, u32> { - let num_preloads = metadata.num_preloads; + let num_preloads = call_graph.num_preloads; // All functions that get called from the app, directly or indirectly let mut live_fn_indices = Vec::with_capacity_in(num_preloads, arena); - // Current & next batch of functions whose call graphs we want to trace through the metadata + // Current & next batch of functions whose call graphs we want to trace through the call_graph // (2 separate vectors so that we're not iterating over the same one we're changing) // If the max call depth is N then we will do N traces or less let mut current_trace = Vec::with_capacity_in(num_preloads, arena); @@ -147,9 +149,9 @@ pub fn trace_function_deps<'a, Indices: IntoIterator>( for func_idx in current_trace.iter() { let i = *func_idx as usize; already_traced[i] = true; - let calls_start = metadata.calls_offsets[i] as usize; - let calls_end = metadata.calls_offsets[i + 1] as usize; - let called_indices: &[u32] = &metadata.calls[calls_start..calls_end]; + let calls_start = call_graph.calls_offsets[i] as usize; + let calls_end = call_graph.calls_offsets[i + 1] as usize; + let called_indices: &[u32] = &call_graph.calls[calls_start..calls_end]; for called_idx in called_indices { if !already_traced[*called_idx as usize] { next_trace.push(*called_idx); @@ -171,10 +173,10 @@ pub fn trace_function_deps<'a, Indices: IntoIterator>( /// Copy used functions from preloaded object file into our Code section /// Replace unused functions with very small dummies, to avoid changing any indices -pub fn copy_live_and_replace_dead_preloads<'a, T: SerialBuffer>( +pub fn copy_preloads_shrinking_dead_fns<'a, T: SerialBuffer>( arena: &'a Bump, buffer: &mut T, - metadata: &DeadCodeMetadata<'a>, + call_graph: &PreloadsCallGraph<'a>, external_code: &[u8], import_fn_count: u32, mut live_preload_indices: Vec<'a, u32>, @@ -193,12 +195,12 @@ pub fn copy_live_and_replace_dead_preloads<'a, T: SerialBuffer>( let mut live_iter = live_preload_indices.iter(); let mut next_live_idx = live_iter.next(); - for i in preload_idx_start..metadata.num_preloads { + for i in preload_idx_start..call_graph.num_preloads { match next_live_idx { Some(live) if *live as usize == i => { next_live_idx = live_iter.next(); - let live_body_start = metadata.code_offsets[i] as usize; - let live_body_end = metadata.code_offsets[i + 1] as usize; + let live_body_start = call_graph.code_offsets[i] as usize; + let live_body_end = call_graph.code_offsets[i + 1] as usize; buffer.append_slice(&external_code[live_body_start..live_body_end]); } _ => { diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 0bbec1a3a9..213892f5d8 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -4,8 +4,8 @@ use roc_collections::all::MutMap; use roc_reporting::internal_error; use super::dead_code::{ - copy_live_and_replace_dead_preloads, parse_dead_code_metadata, trace_function_deps, - DeadCodeMetadata, + copy_preloads_shrinking_dead_fns, parse_preloads_call_graph, trace_call_graph, + PreloadsCallGraph, }; use super::linking::RelocationEntry; use super::opcodes::OpCode; @@ -705,7 +705,7 @@ pub struct CodeSection<'a> { pub preloaded_count: u32, pub preloaded_bytes: &'a [u8], pub code_builders: Vec<'a, CodeBuilder<'a>>, - dead_code_metadata: DeadCodeMetadata<'a>, + dead_code_metadata: PreloadsCallGraph<'a>, } impl<'a> CodeSection<'a> { @@ -742,9 +742,9 @@ impl<'a> CodeSection<'a> { let (preloaded_count, initial_bytes) = parse_section(SectionId::Code, module_bytes, cursor); let preloaded_bytes = arena.alloc_slice_copy(initial_bytes); - // TODO: Try to move this metadata preparation to platform build time + // TODO: Try to move this call_graph preparation to platform build time let dead_code_metadata = - parse_dead_code_metadata(arena, preloaded_count, initial_bytes, import_fn_count); + parse_preloads_call_graph(arena, preloaded_count, initial_bytes, import_fn_count); CodeSection { preloaded_count, @@ -761,7 +761,7 @@ impl<'a> CodeSection<'a> { exported_fns: &[u32], called_preload_fns: T, ) { - let live_ext_fn_indices = trace_function_deps( + let live_ext_fn_indices = trace_call_graph( arena, &self.dead_code_metadata, exported_fns, @@ -770,7 +770,7 @@ impl<'a> CodeSection<'a> { let mut buffer = Vec::with_capacity_in(self.preloaded_bytes.len(), arena); - copy_live_and_replace_dead_preloads( + copy_preloads_shrinking_dead_fns( arena, &mut buffer, &self.dead_code_metadata, From f4137013cb5699f3b0b7ce39f2973386e3d384e0 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 14 Jan 2022 18:50:52 +0000 Subject: [PATCH 215/541] Wasm: tweaks from self-review --- compiler/gen_wasm/src/wasm_module/opcodes.rs | 5 ++++- compiler/gen_wasm/src/wasm_module/sections.rs | 20 +++++++++---------- .../gen_wasm/src/wasm_module/serialize.rs | 5 +++-- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/opcodes.rs b/compiler/gen_wasm/src/wasm_module/opcodes.rs index 2dd2d7e3c5..cd6f8b59d9 100644 --- a/compiler/gen_wasm/src/wasm_module/opcodes.rs +++ b/compiler/gen_wasm/src/wasm_module/opcodes.rs @@ -185,6 +185,9 @@ pub enum OpCode { F64REINTERPRETI64 = 0xbf, } +/// The format of the *immediate* operands of an operator +/// Immediates appear directly in the byte stream after the opcode, +/// rather than being popped off the value stack. These are the possible forms. enum OpImmediates { NoImmediate, Byte1, @@ -248,7 +251,7 @@ impl From for OpImmediates { | I64REINTERPRETF64 | F32REINTERPRETI32 | F64REINTERPRETI64 => NoImmediate, // Catch-all in case of an invalid cast from u8 to OpCode while parsing binary - // (rustc keeps this code, it has been verified in Compiler Explorer) + // (rustc keeps this code, I verified in Compiler Explorer) #[allow(unreachable_patterns)] _ => internal_error!("Unknown Wasm instruction 0x{:02x}", op as u8), } diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 213892f5d8..90a3df8d72 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -75,11 +75,7 @@ macro_rules! section_impl { } fn size(&self) -> usize { - let id = 1; - let encoded_length = 5; - let encoded_count = 5; - - id + encoded_length + encoded_count + self.bytes.len() + section_size(self.get_bytes()) } } }; @@ -106,6 +102,14 @@ where } } +fn section_size(bytes: &[u8]) -> usize { + let id = 1; + let encoded_length = 5; + let encoded_count = 5; + + id + encoded_length + encoded_count + bytes.len() +} + fn parse_section<'a>(id: SectionId, module_bytes: &'a [u8], cursor: &mut usize) -> (u32, &'a [u8]) { if module_bytes[*cursor] != id as u8 { return (0, &[]); @@ -661,11 +665,7 @@ impl<'a> ExportSection<'a> { } pub fn size(&self) -> usize { - let id = 1; - let encoded_length = 5; - let encoded_count = 5; - - id + encoded_length + encoded_count + self.bytes.len() + section_size(&self.bytes) } pub fn empty(arena: &'a Bump) -> Self { diff --git a/compiler/gen_wasm/src/wasm_module/serialize.rs b/compiler/gen_wasm/src/wasm_module/serialize.rs index 105eb34cfa..a2ed7f9622 100644 --- a/compiler/gen_wasm/src/wasm_module/serialize.rs +++ b/compiler/gen_wasm/src/wasm_module/serialize.rs @@ -294,6 +294,7 @@ impl SkipBytes for u8 { } } +/// Note: This is just for skipping over Wasm bytes. We don't actually care about String vs str! impl SkipBytes for String { fn skip_bytes(bytes: &[u8], cursor: &mut usize) { let len = parse_u32_or_panic(bytes, cursor); @@ -301,8 +302,8 @@ impl SkipBytes for String { if false { let str_bytes = &bytes[*cursor..(*cursor + len as usize)]; println!( - "Skipping String {:?}", - String::from_utf8(str_bytes.to_vec()).unwrap() + "Skipping string {:?}", + std::str::from_utf8(str_bytes).unwrap() ); } From 91a0b21e703b8992e36e58fdb62cad6cf8464033 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 15 Jan 2022 07:28:38 +0000 Subject: [PATCH 216/541] Wasm: Get rid of some Backend compatibility code --- compiler/gen_wasm/src/backend.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 24f71cf832..cdf38fd1ba 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -586,7 +586,7 @@ impl<'a> WasmBackend<'a> { x => todo!("call type {:?}", x), }, - Expr::Struct(fields) => self.create_struct(sym, layout, fields), + Expr::Struct(fields) => self.create_struct(sym, layout, storage, fields), Expr::StructAtIndex { index, @@ -1430,15 +1430,17 @@ impl<'a> WasmBackend<'a> { (linker_sym_index as u32, elements_addr) } - fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) { - // TODO: we just calculated storage and now we're getting it out of a map - // Not passing it as an argument because I'm trying to match Backend method signatures - let storage = self.storage.get(sym).to_owned(); - + fn create_struct( + &mut self, + sym: &Symbol, + layout: &Layout<'a>, + storage: &StoredValue, + fields: &'a [Symbol], + ) { if matches!(layout, Layout::Struct(_)) { match storage { StoredValue::StackMemory { location, size, .. } => { - if size > 0 { + if *size > 0 { let (local_id, struct_offset) = location.local_and_offset(self.storage.stack_frame_pointer); let mut field_offset = struct_offset; @@ -1461,7 +1463,7 @@ impl<'a> WasmBackend<'a> { // Struct expression but not Struct layout => single element. Copy it. let field_storage = self.storage.get(&fields[0]).to_owned(); self.storage - .clone_value(&mut self.code_builder, &storage, &field_storage, fields[0]); + .clone_value(&mut self.code_builder, storage, &field_storage, fields[0]); } } From bafb6e54d05c96bd5f66af7d61380ba7ebf87af2 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 15 Jan 2022 07:36:52 +0000 Subject: [PATCH 217/541] Wasm: Change panic for literals todo -> internal_error --- compiler/gen_wasm/src/backend.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index cdf38fd1ba..3f5ac51e0a 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -1295,7 +1295,8 @@ impl<'a> WasmBackend<'a> { sym: Symbol, layout: &Layout<'a>, ) { - let not_supported_error = || todo!("Literal value {:?}", lit); + let invalid_error = + || internal_error!("Literal value {:?} has invalid storage {:?}", lit, storage); match storage { StoredValue::VirtualMachineStack { value_type, .. } => { @@ -1306,7 +1307,7 @@ impl<'a> WasmBackend<'a> { (Literal::Int(x), ValueType::I32) => self.code_builder.i32_const(*x as i32), (Literal::Bool(x), ValueType::I32) => self.code_builder.i32_const(*x as i32), (Literal::Byte(x), ValueType::I32) => self.code_builder.i32_const(*x as i32), - _ => not_supported_error(), + _ => invalid_error(), }; } @@ -1369,11 +1370,11 @@ impl<'a> WasmBackend<'a> { self.code_builder.i32_store(Align::Bytes4, offset + 4); }; } - _ => not_supported_error(), + _ => invalid_error(), } } - _ => not_supported_error(), + _ => invalid_error(), }; } From c38134bdc092940d5f52464ff5fbb84cffde5c2e Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 15 Jan 2022 07:56:57 +0000 Subject: [PATCH 218/541] Wasm: remove legacy unused ret_layout --- compiler/gen_wasm/src/backend.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 3f5ac51e0a..f4ba2fb2b5 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -182,7 +182,7 @@ impl<'a> WasmBackend<'a> { self.start_proc(proc); - self.build_stmt(&proc.body, &proc.ret_layout); + self.build_stmt(&proc.body); self.finalize_proc(); self.reset(); @@ -285,7 +285,7 @@ impl<'a> WasmBackend<'a> { } } - fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) { + fn build_stmt(&mut self, stmt: &Stmt<'a>) { match stmt { Stmt::Let(_, _, _, _) => { let mut current_stmt = stmt; @@ -304,7 +304,7 @@ impl<'a> WasmBackend<'a> { current_stmt = *following; } - self.build_stmt(current_stmt, ret_layout); + self.build_stmt(current_stmt); } Stmt::Ret(sym) => { @@ -413,7 +413,7 @@ impl<'a> WasmBackend<'a> { } // if we never jumped because a value matched, we're in the default case - self.build_stmt(default_branch.1, ret_layout); + self.build_stmt(default_branch.1); // now put in the actual body of each branch in order // (the first branch would have broken out of 1 block, @@ -421,7 +421,7 @@ impl<'a> WasmBackend<'a> { for (_, _, branch) in branches.iter() { self.end_block(); - self.build_stmt(branch, ret_layout); + self.build_stmt(branch); } } Stmt::Join { @@ -451,12 +451,12 @@ impl<'a> WasmBackend<'a> { self.joinpoint_label_map .insert(*id, (self.block_depth, jp_param_storages)); - self.build_stmt(remainder, ret_layout); + self.build_stmt(remainder); self.end_block(); self.start_loop(); - self.build_stmt(body, ret_layout); + self.build_stmt(body); // ends the loop self.end_block(); @@ -503,7 +503,7 @@ impl<'a> WasmBackend<'a> { self.register_helper_proc(spec); } - self.build_stmt(rc_stmt, ret_layout); + self.build_stmt(rc_stmt); } x => todo!("statement {:?}", x), From 8d5a1cb661c9a9fcdcc84378e7e57f2c4cba462a Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 15 Jan 2022 08:02:46 +0000 Subject: [PATCH 219/541] Wasm: Replace _ todo with RuntimeError todo --- compiler/gen_wasm/src/backend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index f4ba2fb2b5..f2b2b79922 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -506,7 +506,7 @@ impl<'a> WasmBackend<'a> { self.build_stmt(rc_stmt); } - x => todo!("statement {:?}", x), + Stmt::RuntimeError(msg) => todo!("RuntimeError {:?}", msg), } } From 8c4fd93b072f7a351a42dece6714fc371a010ac3 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 15 Jan 2022 09:50:54 +0000 Subject: [PATCH 220/541] Wasm: Remove old debug code that's never used. We have the HTML debugger now. --- compiler/gen_wasm/src/backend.rs | 20 ------------------- .../gen_wasm/src/wasm_module/code_builder.rs | 13 ------------ 2 files changed, 33 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index f2b2b79922..53d303abf0 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -53,8 +53,6 @@ pub struct WasmBackend<'a> { /// how many blocks deep are we (used for jumps) block_depth: u32, joinpoint_label_map: MutMap)>, - - debug_current_proc_index: usize, } impl<'a> WasmBackend<'a> { @@ -98,8 +96,6 @@ impl<'a> WasmBackend<'a> { joinpoint_label_map: MutMap::default(), code_builder: CodeBuilder::new(env.arena), storage: Storage::new(env.arena), - - debug_current_proc_index: 0, } } @@ -178,8 +174,6 @@ impl<'a> WasmBackend<'a> { println!("\ngenerating procedure {:?}\n", proc.name); } - self.debug_current_proc_index += 1; - self.start_proc(proc); self.build_stmt(&proc.body); @@ -1486,18 +1480,4 @@ impl<'a> WasmBackend<'a> { self.code_builder .call(fn_index, linker_symbol_index, num_wasm_args, has_return_val); } - - /// Debug utility - /// - /// if self._debug_current_proc_is("#UserApp_foo_1") { - /// self.code_builder._debug_assert_i32(0x1234); - /// } - fn _debug_current_proc_is(&self, linker_name: &'static str) -> bool { - let (_, linker_sym_index) = self.proc_symbols[self.debug_current_proc_index]; - let sym_info = &self.module.linking.symbol_table[linker_sym_index as usize]; - match sym_info { - SymInfo::Function(WasmObjectSymbol::Defined { name, .. }) => name == linker_name, - _ => false, - } - } } diff --git a/compiler/gen_wasm/src/wasm_module/code_builder.rs b/compiler/gen_wasm/src/wasm_module/code_builder.rs index db51b2d22e..fc0f73ca60 100644 --- a/compiler/gen_wasm/src/wasm_module/code_builder.rs +++ b/compiler/gen_wasm/src/wasm_module/code_builder.rs @@ -968,17 +968,4 @@ impl<'a> CodeBuilder<'a> { instruction_no_args!(i64_reinterpret_f64, I64REINTERPRETF64, 1, true); instruction_no_args!(f32_reinterpret_i32, F32REINTERPRETI32, 1, true); instruction_no_args!(f64_reinterpret_i64, F64REINTERPRETI64, 1, true); - - /// Generate a debug assertion for an expected i32 value - pub fn _debug_assert_i32(&mut self, expected: i32) { - self.i32_const(expected); - self.i32_eq(); - self.i32_eqz(); - self.if_(); - self.unreachable_(); // Tell Wasm runtime to throw an exception - self.end(); - // It matches. Restore the original value to the VM stack and continue the program. - // We know it matched the expected value, so just use that! - self.i32_const(expected); - } } From 54e820620a1aba57ca418885b36298f136b37c87 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 15 Jan 2022 14:06:20 +0000 Subject: [PATCH 221/541] Wasm: Improve a todo in roc_build --- compiler/build/src/program.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 29cf97ffc6..d8ee99e07c 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -505,12 +505,16 @@ fn gen_from_mono_module_dev_wasm32( exposed_to_host, }; - let todo_platform_and_builtins_object_file_bytes = &[]; + let platform_and_builtins_object_file_bytes: &[u8] = if true { + todo!("The WebAssembly dev backend is a work in progress. Coming soon!") + } else { + &[] // This `if` gets rid of "unreachable code" warnings. When we're ready to use it, we'll notice! + }; let bytes = roc_gen_wasm::build_module( &env, &mut interns, - todo_platform_and_builtins_object_file_bytes, + platform_and_builtins_object_file_bytes, procedures, ) .unwrap(); From e37ca971bd93a6a5a339aeec9fcd08c8dd1e22fb Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 15 Jan 2022 14:38:15 +0000 Subject: [PATCH 222/541] Wasm: Simplify CodeBuilder serialization, based on Richard's suggestion --- .../gen_wasm/src/wasm_module/code_builder.rs | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/code_builder.rs b/compiler/gen_wasm/src/wasm_module/code_builder.rs index fc0f73ca60..ca80469ba7 100644 --- a/compiler/gen_wasm/src/wasm_module/code_builder.rs +++ b/compiler/gen_wasm/src/wasm_module/code_builder.rs @@ -499,26 +499,13 @@ impl<'a> CodeBuilder<'a> { buffer.append_slice(&self.preamble); let mut code_pos = 0; - let mut insert_iter = self.insertions.iter(); - loop { - let next_insert = insert_iter.next(); - let next_pos = match next_insert { - Some(Insertion { at, .. }) => *at, - None => self.code.len(), - }; - - buffer.append_slice(&self.code[code_pos..next_pos]); - - match next_insert { - Some(Insertion { at, start, end }) => { - buffer.append_slice(&self.insert_bytes[*start..*end]); - code_pos = *at; - } - None => { - break; - } - } + for Insertion { at, start, end } in self.insertions.iter() { + buffer.append_slice(&self.code[code_pos..(*at)]); + buffer.append_slice(&self.insert_bytes[*start..*end]); + code_pos = *at; } + + buffer.append_slice(&self.code[code_pos..self.code.len()]); } /// Serialize all byte vectors in the right order From 9290d3ad5c5a17d08b877b9a9ef743adeb29cbbe Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 15 Jan 2022 14:43:02 +0000 Subject: [PATCH 223/541] Wasm: Clarify where magic constant 5 comes from --- compiler/gen_wasm/src/wasm_module/sections.rs | 9 +++--- .../gen_wasm/src/wasm_module/serialize.rs | 32 +++++++++++-------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 90a3df8d72..d9d735fbd4 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -9,7 +9,9 @@ use super::dead_code::{ }; use super::linking::RelocationEntry; use super::opcodes::OpCode; -use super::serialize::{parse_u32_or_panic, SerialBuffer, Serialize, SkipBytes}; +use super::serialize::{ + parse_u32_or_panic, SerialBuffer, Serialize, SkipBytes, MAX_SIZE_ENCODED_U32, +}; use super::{CodeBuilder, ValueType}; /******************************************************************* @@ -38,7 +40,6 @@ pub enum SectionId { DataCount = 12, } -const MAX_SIZE_ENCODED_U32: usize = 5; const MAX_SIZE_SECTION_HEADER: usize = std::mem::size_of::() + 2 * MAX_SIZE_ENCODED_U32; pub trait Section<'a>: Sized { @@ -104,8 +105,8 @@ where fn section_size(bytes: &[u8]) -> usize { let id = 1; - let encoded_length = 5; - let encoded_count = 5; + let encoded_length = MAX_SIZE_ENCODED_U32; + let encoded_count = MAX_SIZE_ENCODED_U32; id + encoded_length + encoded_count + bytes.len() } diff --git a/compiler/gen_wasm/src/wasm_module/serialize.rs b/compiler/gen_wasm/src/wasm_module/serialize.rs index a2ed7f9622..8c2204a04b 100644 --- a/compiler/gen_wasm/src/wasm_module/serialize.rs +++ b/compiler/gen_wasm/src/wasm_module/serialize.rs @@ -3,6 +3,12 @@ use std::{fmt::Debug, iter::FromIterator}; use bumpalo::collections::vec::Vec; use roc_reporting::internal_error; +/// In the WebAssembly binary format, all integers are variable-length encoded (using LEB-128) +/// A small value like 3 or 100 is encoded as 1 byte. The value 128 needs 2 bytes, etc. +/// In practice, this saves space, since small numbers used more often than large numbers. +/// Of course there is a price for this - an encoded U32 can be up to 5 bytes wide. +pub const MAX_SIZE_ENCODED_U32: usize = 5; + pub(super) trait Serialize { fn serialize(&self, buffer: &mut T); } @@ -122,7 +128,7 @@ macro_rules! encode_padded_sleb128 { /// write a maximally-padded SLEB128 integer (only used in relocations) fn $name(&mut self, value: $ty) { let mut x = value; - let size = (std::mem::size_of::<$ty>() / 4) * 5; + let size = (std::mem::size_of::<$ty>() / 4) * MAX_SIZE_ENCODED_U32; for _ in 0..(size - 1) { self.append_u8(0x80 | (x & 0x7f) as u8); x >>= 7; @@ -187,18 +193,18 @@ impl SerialBuffer for std::vec::Vec { } fn reserve_padded_u32(&mut self) -> usize { let index = self.len(); - self.resize(index + 5, 0xff); + self.resize(index + MAX_SIZE_ENCODED_U32, 0xff); index } fn encode_padded_u32(&mut self, value: u32) -> usize { let index = self.len(); - let new_len = index + 5; + let new_len = index + MAX_SIZE_ENCODED_U32; self.resize(new_len, 0); overwrite_padded_u32_help(&mut self[index..new_len], value); index } fn overwrite_padded_u32(&mut self, index: usize, value: u32) { - overwrite_padded_u32_help(&mut self[index..(index + 5)], value); + overwrite_padded_u32_help(&mut self[index..(index + MAX_SIZE_ENCODED_U32)], value); } } @@ -217,18 +223,18 @@ impl<'a> SerialBuffer for Vec<'a, u8> { } fn reserve_padded_u32(&mut self) -> usize { let index = self.len(); - self.resize(index + 5, 0xff); + self.resize(index + MAX_SIZE_ENCODED_U32, 0xff); index } fn encode_padded_u32(&mut self, value: u32) -> usize { let index = self.len(); - let new_len = index + 5; + let new_len = index + MAX_SIZE_ENCODED_U32; self.resize(new_len, 0); overwrite_padded_u32_help(&mut self[index..new_len], value); index } fn overwrite_padded_u32(&mut self, index: usize, value: u32) { - overwrite_padded_u32_help(&mut self[index..(index + 5)], value); + overwrite_padded_u32_help(&mut self[index..(index + MAX_SIZE_ENCODED_U32)], value); } } @@ -237,7 +243,7 @@ impl<'a> SerialBuffer for Vec<'a, u8> { pub fn decode_u32(bytes: &[u8]) -> Result<(u32, usize), String> { let mut value = 0; let mut shift = 0; - for (i, byte) in bytes.iter().take(5).enumerate() { + for (i, byte) in bytes.iter().take(MAX_SIZE_ENCODED_U32).enumerate() { value += ((byte & 0x7f) as u32) << shift; if (byte & 0x80) == 0 { return Ok((value, i + 1)); @@ -246,7 +252,7 @@ pub fn decode_u32(bytes: &[u8]) -> Result<(u32, usize), String> { } Err(format!( "Failed to decode u32 as LEB-128 from bytes: {:2x?}", - std::vec::Vec::from_iter(bytes.iter().take(5)) + std::vec::Vec::from_iter(bytes.iter().take(MAX_SIZE_ENCODED_U32)) )) } @@ -317,7 +323,7 @@ mod tests { use bumpalo::{self, collections::Vec, Bump}; fn help_u32<'a>(arena: &'a Bump, value: u32) -> Vec<'a, u8> { - let mut buffer = Vec::with_capacity_in(5, arena); + let mut buffer = Vec::with_capacity_in(MAX_SIZE_ENCODED_U32, arena); buffer.encode_u32(value); buffer } @@ -356,7 +362,7 @@ mod tests { } fn help_i32<'a>(arena: &'a Bump, value: i32) -> Vec<'a, u8> { - let mut buffer = Vec::with_capacity_in(5, arena); + let mut buffer = Vec::with_capacity_in(MAX_SIZE_ENCODED_U32, arena); buffer.encode_i32(value); buffer } @@ -443,7 +449,7 @@ mod tests { } fn help_pad_i32(val: i32) -> std::vec::Vec { - let mut buffer = std::vec::Vec::with_capacity(5); + let mut buffer = std::vec::Vec::with_capacity(MAX_SIZE_ENCODED_U32); buffer.encode_padded_i32(val); buffer } @@ -497,7 +503,7 @@ mod tests { assert_eq!(decode_u32(&[0x80, 0x80, 0x01]), Ok((0x4000, 3))); assert_eq!( decode_u32(&[0xff, 0xff, 0xff, 0xff, 0x0f]), - Ok((u32::MAX, 5)) + Ok((u32::MAX, MAX_SIZE_ENCODED_U32)) ); assert!(matches!(decode_u32(&[0x80; 6]), Err(_))); assert!(matches!(decode_u32(&[0x80; 2]), Err(_))); From 94ea50f56acd76cdd60807f427023a3465ba408a Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 15 Jan 2022 15:17:24 +0000 Subject: [PATCH 224/541] Wasm: Clarify some more magic numbers --- compiler/gen_wasm/src/wasm_module/sections.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index d9d735fbd4..4a5e370e1f 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -458,15 +458,21 @@ pub enum Limits { MinMax(u32, u32), } +#[repr(u8)] +enum LimitsId { + Min = 0, + MinMax = 1, +} + impl Serialize for Limits { fn serialize(&self, buffer: &mut T) { match self { Self::Min(min) => { - buffer.append_u8(0); + buffer.append_u8(LimitsId::Min as u8); buffer.encode_u32(*min); } Self::MinMax(min, max) => { - buffer.append_u8(1); + buffer.append_u8(LimitsId::MinMax as u8); buffer.encode_u32(*min); buffer.encode_u32(*max); } @@ -476,7 +482,7 @@ impl Serialize for Limits { impl SkipBytes for Limits { fn skip_bytes(bytes: &[u8], cursor: &mut usize) { - if bytes[*cursor] == 0 { + if bytes[*cursor] == LimitsId::Min as u8 { u8::skip_bytes(bytes, cursor); u32::skip_bytes(bytes, cursor); } else { @@ -831,11 +837,11 @@ impl Serialize for DataSegment<'_> { fn serialize(&self, buffer: &mut T) { match &self.mode { DataMode::Active { offset } => { - buffer.append_u8(0); + buffer.append_u8(0); // variant ID offset.serialize(buffer); } DataMode::Passive => { - buffer.append_u8(1); + buffer.append_u8(1); // variant ID } } From f4650654ca8f35bd760ece905a472a4f6155d1fa Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 15 Jan 2022 15:20:46 +0000 Subject: [PATCH 225/541] Wasm: cosmetic changes to fake WASI functions --- .../test_gen/src/helpers/debug-wasm-test.html | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/compiler/test_gen/src/helpers/debug-wasm-test.html b/compiler/test_gen/src/helpers/debug-wasm-test.html index 890aa4cf24..caf1b01901 100644 --- a/compiler/test_gen/src/helpers/debug-wasm-test.html +++ b/compiler/test_gen/src/helpers/debug-wasm-test.html @@ -316,11 +316,6 @@ return { wasi_snapshot_preview1: { - fd_close, - fd_fdstat_get, - fd_seek, - fd_write, - proc_exit, args_get: sig6, args_sizes_get: sig6, environ_get: sig6, @@ -329,9 +324,9 @@ clock_time_get: sig9, fd_advise: sig10, fd_allocate: sig11, - // fd_close: sig12, + fd_close, fd_datasync: sig12, - // fd_fdstat_get: sig6, + fd_fdstat_get, fd_fdstat_set_flags: sig6, fd_fdstat_set_rights: sig11, fd_filestat_get: sig6, @@ -344,10 +339,10 @@ fd_read: sig15, fd_readdir: sig14, fd_renumber: sig6, - // fd_seek: sig16, + fd_seek, fd_sync: sig12, fd_tell: sig6, - // fd_write: sig15, + fd_write, path_create_directory: sig7, path_filestat_get: sig17, path_filestat_set_times: sig18, @@ -359,7 +354,7 @@ path_symlink: sig17, path_unlink_file: sig7, poll_oneoff: sig15, - // proc_exit: sig2, + proc_exit, proc_raise: sig12, sched_yield: sig22, random_get: sig6, From 6bdc27a49ee6721b161df84dac9cc27269922026 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 15 Jan 2022 15:27:31 +0000 Subject: [PATCH 226/541] Wasm: Use Vec instead of MutSet for called preloads --- compiler/gen_wasm/src/backend.rs | 10 +++++----- compiler/gen_wasm/src/lib.rs | 2 +- compiler/gen_wasm/src/wasm_module/dead_code.rs | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 53d303abf0..4d00a21d79 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -2,7 +2,7 @@ use bumpalo::{self, collections::Vec}; use code_builder::Align; use roc_builtins::bitcode::{self, IntWidth}; -use roc_collections::all::{MutMap, MutSet}; +use roc_collections::all::MutMap; use roc_module::ident::Ident; use roc_module::low_level::{LowLevel, LowLevelWrapperType}; use roc_module::symbol::{Interns, Symbol}; @@ -42,7 +42,7 @@ pub struct WasmBackend<'a> { layout_ids: LayoutIds<'a>, next_constant_addr: u32, fn_index_offset: u32, - called_preload_fns: MutSet, + called_preload_fns: Vec<'a, u32>, proc_symbols: Vec<'a, (Symbol, u32)>, helper_proc_gen: CodeGenHelp<'a>, @@ -87,7 +87,7 @@ impl<'a> WasmBackend<'a> { layout_ids, next_constant_addr: CONST_SEGMENT_BASE_ADDR, fn_index_offset, - called_preload_fns: MutSet::default(), + called_preload_fns: Vec::with_capacity_in(2, env.arena), proc_symbols, helper_proc_gen, @@ -122,7 +122,7 @@ impl<'a> WasmBackend<'a> { self.module.linking.symbol_table.push(linker_symbol); } - pub fn finalize(self) -> (WasmModule<'a>, MutSet) { + pub fn finalize(self) -> (WasmModule<'a>, Vec<'a, u32>) { (self.module, self.called_preload_fns) } @@ -1474,7 +1474,7 @@ impl<'a> WasmBackend<'a> { let num_wasm_args = param_types.len(); let has_return_val = ret_type.is_some(); let fn_index = self.module.names.functions[name.as_bytes()]; - self.called_preload_fns.insert(fn_index); + self.called_preload_fns.push(fn_index); let linker_symbol_index = u32::MAX; self.code_builder diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 0849bf2027..b033c66583 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -57,7 +57,7 @@ pub fn build_module_without_test_wrapper<'a>( interns: &'a mut Interns, preload_bytes: &[u8], procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, -) -> (WasmModule<'a>, MutSet, u32) { +) -> (WasmModule<'a>, Vec<'a, u32>, u32) { let mut layout_ids = LayoutIds::default(); let mut procs = Vec::with_capacity_in(procedures.len(), env.arena); let mut proc_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena); diff --git a/compiler/gen_wasm/src/wasm_module/dead_code.rs b/compiler/gen_wasm/src/wasm_module/dead_code.rs index 33950ee42c..382f306322 100644 --- a/compiler/gen_wasm/src/wasm_module/dead_code.rs +++ b/compiler/gen_wasm/src/wasm_module/dead_code.rs @@ -13,17 +13,17 @@ Or, more specifically, "dead function replacement" - On pre-loading the object file: - Analyse its call graph by finding all `call` instructions in the Code section, - and checking which function index they refer to. Store this in a `PreloadsCallGraph` + and checking which function index they refer to. Store this in a `PreloadsCallGraph` - While compiling Roc code: - Run the backend as usual, adding more data into various sections of the Wasm module - - Whenever a call to a builtin or platform function is made, record its index in a Set. - These are the "live" preloaded functions that we are not allowed to eliminate. + - Whenever a call to a builtin or platform function is made, record its index. + These are the "live" preloaded functions that we are not allowed to eliminate. - Call graph analysis: - - Starting with the set of live preloaded functions, trace their call graphs using the info we - collected earlier in `PreloadsCallGraph`. Mark all function indices in the call graph as "live". + - Starting with the live preloaded functions, trace their call graphs using the info we + collected earlier in `PreloadsCallGraph`. Mark all function indices in the call graph as "live". - Dead function replacement: - We actually don't want to just *delete* dead functions, because that would change the indices - of the live functions, invalidating all references to them, such as `call` instructions. + of the live functions, invalidating all references to them, such as `call` instructions. - Instead, during serialization, we replace its body with a single `unreachable` instruction */ From 5e68f298df15c80f4e2b8498f417d13be044f057 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 15 Jan 2022 15:53:41 +0000 Subject: [PATCH 227/541] Wasm: Reorganise Stmt and Expr methods --- compiler/gen_wasm/src/backend.rs | 1480 ++++++++++++++++-------------- 1 file changed, 779 insertions(+), 701 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 4d00a21d79..c68d14529c 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -8,7 +8,8 @@ use roc_module::low_level::{LowLevel, LowLevelWrapperType}; use roc_module::symbol::{Interns, Symbol}; use roc_mono::code_gen_help::{CodeGenHelp, REFCOUNT_MAX}; use roc_mono::ir::{ - CallType, Expr, JoinPointId, ListLiteralElement, Literal, Proc, ProcLayout, Stmt, + BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, ModifyRc, Param, Proc, + ProcLayout, Stmt, }; use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout}; @@ -176,7 +177,7 @@ impl<'a> WasmBackend<'a> { self.start_proc(proc); - self.build_stmt(&proc.body); + self.stmt(&proc.body); self.finalize_proc(); self.reset(); @@ -240,6 +241,35 @@ impl<'a> WasmBackend<'a> { ***********************************************************/ + fn stmt(&mut self, stmt: &Stmt<'a>) { + match stmt { + Stmt::Let(_, _, _, _) => self.stmt_let(stmt), + + Stmt::Ret(sym) => self.stmt_ret(*sym), + + Stmt::Switch { + cond_symbol, + cond_layout, + branches, + default_branch, + ret_layout: _, + } => self.stmt_switch(*cond_symbol, cond_layout, branches, default_branch), + + Stmt::Join { + id, + parameters, + body, + remainder, + } => self.stmt_join(*id, parameters, body, remainder), + + Stmt::Jump(id, arguments) => self.stmt_jump(*id, arguments), + + Stmt::Refcounting(modify, following) => self.stmt_refcounting(modify, following), + + Stmt::RuntimeError(msg) => self.stmt_runtime_error(msg), + } + } + fn start_block(&mut self) { // Wasm blocks can have result types, but we don't use them. // You need the right type on the stack when you jump from an inner block to an outer one. @@ -259,7 +289,27 @@ impl<'a> WasmBackend<'a> { self.code_builder.end(); } - fn store_expr_value( + fn stmt_let(&mut self, stmt: &Stmt<'a>) { + let mut current_stmt = stmt; + while let Stmt::Let(sym, expr, layout, following) = current_stmt { + if DEBUG_LOG_SETTINGS.let_stmt_ir { + println!("let {:?} = {}", sym, expr.to_pretty(200)); // ignore `following`! Too confusing otherwise. + } + + let kind = match following { + Stmt::Ret(ret_sym) if *sym == *ret_sym => StoredValueKind::ReturnValue, + _ => StoredValueKind::Variable, + }; + + self.stmt_let_store_expr(*sym, layout, expr, kind); + + current_stmt = *following; + } + + self.stmt(current_stmt); + } + + fn stmt_let_store_expr( &mut self, sym: Symbol, layout: &Layout<'a>, @@ -268,7 +318,7 @@ impl<'a> WasmBackend<'a> { ) { let sym_storage = self.storage.allocate(*layout, sym, kind); - self.build_expr(&sym, expr, layout, &sym_storage); + self.expr(sym, expr, layout, &sym_storage); // If this value is stored in the VM stack, we need code_builder to track it // (since every instruction can change the VM stack) @@ -279,229 +329,207 @@ impl<'a> WasmBackend<'a> { } } - fn build_stmt(&mut self, stmt: &Stmt<'a>) { - match stmt { - Stmt::Let(_, _, _, _) => { - let mut current_stmt = stmt; - while let Stmt::Let(sym, expr, layout, following) = current_stmt { - if DEBUG_LOG_SETTINGS.let_stmt_ir { - println!("let {:?} = {}", sym, expr.to_pretty(200)); // ignore `following`! Too confusing otherwise. - } + fn stmt_ret(&mut self, sym: Symbol) { + use crate::storage::StoredValue::*; - let kind = match following { - Stmt::Ret(ret_sym) if *sym == *ret_sym => StoredValueKind::ReturnValue, - _ => StoredValueKind::Variable, - }; + let storage = self.storage.symbol_storage_map.get(&sym).unwrap(); - self.store_expr_value(*sym, layout, expr, kind); - - current_stmt = *following; - } - - self.build_stmt(current_stmt); - } - - Stmt::Ret(sym) => { - use crate::storage::StoredValue::*; - - let storage = self.storage.symbol_storage_map.get(sym).unwrap(); - - match storage { - StackMemory { - location, - size, - alignment_bytes, - .. - } => { - let (from_ptr, from_offset) = - location.local_and_offset(self.storage.stack_frame_pointer); - copy_memory( - &mut self.code_builder, - CopyMemoryConfig { - from_ptr, - from_offset, - to_ptr: LocalId(0), - to_offset: 0, - size: *size, - alignment_bytes: *alignment_bytes, - }, - ); - } - - _ => { - self.storage.load_symbols(&mut self.code_builder, &[*sym]); - - // If we have a return value, store it to the return variable - // This avoids complications with block result types when returning from nested blocks - if let Some(ret_var) = self.storage.return_var { - self.code_builder.set_local(ret_var); - } - } - } - // jump to the "stack frame pop" code at the end of the function - self.code_builder.br(self.block_depth - 1); - } - - Stmt::Switch { - cond_symbol, - cond_layout, - branches, - default_branch, - ret_layout: _, + match storage { + StackMemory { + location, + size, + alignment_bytes, + .. } => { - // NOTE currently implemented as a series of conditional jumps - // We may be able to improve this in the future with `Select` - // or `BrTable` - - // Ensure the condition value is not stored only in the VM stack - // Otherwise we can't reach it from inside the block - let cond_storage = self.storage.get(cond_symbol).to_owned(); - self.storage.ensure_value_has_local( + let (from_ptr, from_offset) = + location.local_and_offset(self.storage.stack_frame_pointer); + copy_memory( &mut self.code_builder, - *cond_symbol, - cond_storage, + CopyMemoryConfig { + from_ptr, + from_offset, + to_ptr: LocalId(0), + to_offset: 0, + size: *size, + alignment_bytes: *alignment_bytes, + }, ); - - // create a block for each branch except the default - for _ in 0..branches.len() { - self.start_block() - } - - let is_bool = matches!(cond_layout, Layout::Builtin(Builtin::Bool)); - let cond_type = WasmLayout::new(cond_layout).arg_types(CallConv::C)[0]; - - // then, we jump whenever the value under scrutiny is equal to the value of a branch - for (i, (value, _, _)) in branches.iter().enumerate() { - // put the cond_symbol on the top of the stack - self.storage - .load_symbols(&mut self.code_builder, &[*cond_symbol]); - - if is_bool { - // We already have a bool, don't need to compare against a const to get one - if *value == 0 { - self.code_builder.i32_eqz(); - } - } else { - match cond_type { - ValueType::I32 => { - self.code_builder.i32_const(*value as i32); - self.code_builder.i32_eq(); - } - ValueType::I64 => { - self.code_builder.i64_const(*value as i64); - self.code_builder.i64_eq(); - } - ValueType::F32 => { - self.code_builder.f32_const(f32::from_bits(*value as u32)); - self.code_builder.f32_eq(); - } - ValueType::F64 => { - self.code_builder.f64_const(f64::from_bits(*value as u64)); - self.code_builder.f64_eq(); - } - } - } - - // "break" out of `i` surrounding blocks - self.code_builder.br_if(i as u32); - } - - // if we never jumped because a value matched, we're in the default case - self.build_stmt(default_branch.1); - - // now put in the actual body of each branch in order - // (the first branch would have broken out of 1 block, - // hence we must generate its code first) - for (_, _, branch) in branches.iter() { - self.end_block(); - - self.build_stmt(branch); - } - } - Stmt::Join { - id, - parameters, - body, - remainder, - } => { - // make locals for join pointer parameters - let mut jp_param_storages = Vec::with_capacity_in(parameters.len(), self.env.arena); - for parameter in parameters.iter() { - let mut param_storage = self.storage.allocate( - parameter.layout, - parameter.symbol, - StoredValueKind::Variable, - ); - param_storage = self.storage.ensure_value_has_local( - &mut self.code_builder, - parameter.symbol, - param_storage, - ); - jp_param_storages.push(param_storage); - } - - self.start_block(); - - self.joinpoint_label_map - .insert(*id, (self.block_depth, jp_param_storages)); - - self.build_stmt(remainder); - - self.end_block(); - self.start_loop(); - - self.build_stmt(body); - - // ends the loop - self.end_block(); - } - Stmt::Jump(id, arguments) => { - let (target, param_storages) = self.joinpoint_label_map[id].clone(); - - for (arg_symbol, param_storage) in arguments.iter().zip(param_storages.iter()) { - let arg_storage = self.storage.get(arg_symbol).clone(); - self.storage.clone_value( - &mut self.code_builder, - param_storage, - &arg_storage, - *arg_symbol, - ); - } - - // jump - let levels = self.block_depth - target; - self.code_builder.br(levels); } - Stmt::Refcounting(modify, following) => { - let value = modify.get_symbol(); - let layout = self.storage.symbol_layouts[&value]; + _ => { + self.storage.load_symbols(&mut self.code_builder, &[sym]); - let ident_ids = self - .interns - .all_ident_ids - .get_mut(&self.env.module_id) - .unwrap(); - - let (rc_stmt, new_specializations) = self - .helper_proc_gen - .expand_refcount_stmt(ident_ids, layout, modify, *following); - - if false { - self.register_symbol_debug_names(); - println!("## rc_stmt:\n{}\n{:?}", rc_stmt.to_pretty(200), rc_stmt); + // If we have a return value, store it to the return variable + // This avoids complications with block result types when returning from nested blocks + if let Some(ret_var) = self.storage.return_var { + self.code_builder.set_local(ret_var); } - - // If any new specializations were created, register their symbol data - for spec in new_specializations.into_iter() { - self.register_helper_proc(spec); - } - - self.build_stmt(rc_stmt); } - - Stmt::RuntimeError(msg) => todo!("RuntimeError {:?}", msg), } + // jump to the "stack frame pop" code at the end of the function + self.code_builder.br(self.block_depth - 1); + } + + fn stmt_switch( + &mut self, + cond_symbol: Symbol, + cond_layout: &Layout<'a>, + branches: &'a [(u64, BranchInfo<'a>, Stmt<'a>)], + default_branch: &(BranchInfo<'a>, &'a Stmt<'a>), + ) { + // NOTE currently implemented as a series of conditional jumps + // We may be able to improve this in the future with `Select` + // or `BrTable` + + // Ensure the condition value is not stored only in the VM stack + // Otherwise we can't reach it from inside the block + let cond_storage = self.storage.get(&cond_symbol).to_owned(); + self.storage + .ensure_value_has_local(&mut self.code_builder, cond_symbol, cond_storage); + + // create a block for each branch except the default + for _ in 0..branches.len() { + self.start_block() + } + + let is_bool = matches!(cond_layout, Layout::Builtin(Builtin::Bool)); + let cond_type = WasmLayout::new(cond_layout).arg_types(CallConv::C)[0]; + + // then, we jump whenever the value under scrutiny is equal to the value of a branch + for (i, (value, _, _)) in branches.iter().enumerate() { + // put the cond_symbol on the top of the stack + self.storage + .load_symbols(&mut self.code_builder, &[cond_symbol]); + + if is_bool { + // We already have a bool, don't need to compare against a const to get one + if *value == 0 { + self.code_builder.i32_eqz(); + } + } else { + match cond_type { + ValueType::I32 => { + self.code_builder.i32_const(*value as i32); + self.code_builder.i32_eq(); + } + ValueType::I64 => { + self.code_builder.i64_const(*value as i64); + self.code_builder.i64_eq(); + } + ValueType::F32 => { + self.code_builder.f32_const(f32::from_bits(*value as u32)); + self.code_builder.f32_eq(); + } + ValueType::F64 => { + self.code_builder.f64_const(f64::from_bits(*value as u64)); + self.code_builder.f64_eq(); + } + } + } + + // "break" out of `i` surrounding blocks + self.code_builder.br_if(i as u32); + } + + // if we never jumped because a value matched, we're in the default case + self.stmt(default_branch.1); + + // now put in the actual body of each branch in order + // (the first branch would have broken out of 1 block, + // hence we must generate its code first) + for (_, _, branch) in branches.iter() { + self.end_block(); + + self.stmt(branch); + } + } + + fn stmt_join( + &mut self, + id: JoinPointId, + parameters: &'a [Param<'a>], + body: &'a Stmt<'a>, + remainder: &'a Stmt<'a>, + ) { + // make locals for join pointer parameters + let mut jp_param_storages = Vec::with_capacity_in(parameters.len(), self.env.arena); + for parameter in parameters.iter() { + let mut param_storage = self.storage.allocate( + parameter.layout, + parameter.symbol, + StoredValueKind::Variable, + ); + param_storage = self.storage.ensure_value_has_local( + &mut self.code_builder, + parameter.symbol, + param_storage, + ); + jp_param_storages.push(param_storage); + } + + self.start_block(); + + self.joinpoint_label_map + .insert(id, (self.block_depth, jp_param_storages)); + + self.stmt(remainder); + + self.end_block(); + self.start_loop(); + + self.stmt(body); + + // ends the loop + self.end_block(); + } + + fn stmt_jump(&mut self, id: JoinPointId, arguments: &'a [Symbol]) { + let (target, param_storages) = self.joinpoint_label_map[&id].clone(); + + for (arg_symbol, param_storage) in arguments.iter().zip(param_storages.iter()) { + let arg_storage = self.storage.get(arg_symbol).clone(); + self.storage.clone_value( + &mut self.code_builder, + param_storage, + &arg_storage, + *arg_symbol, + ); + } + + // jump + let levels = self.block_depth - target; + self.code_builder.br(levels); + } + + fn stmt_refcounting(&mut self, modify: &ModifyRc, following: &'a Stmt<'a>) { + let value = modify.get_symbol(); + let layout = self.storage.symbol_layouts[&value]; + + let ident_ids = self + .interns + .all_ident_ids + .get_mut(&self.env.module_id) + .unwrap(); + + let (rc_stmt, new_specializations) = self + .helper_proc_gen + .expand_refcount_stmt(ident_ids, layout, modify, following); + + if false { + self.register_symbol_debug_names(); + println!("## rc_stmt:\n{}\n{:?}", rc_stmt.to_pretty(200), rc_stmt); + } + + // If any new specializations were created, register their symbol data + for spec in new_specializations.into_iter() { + self.register_helper_proc(spec); + } + + self.stmt(rc_stmt); + } + + fn stmt_runtime_error(&mut self, msg: &'a str) { + todo!("RuntimeError {:?}", msg) } /********************************************************** @@ -510,212 +538,567 @@ impl<'a> WasmBackend<'a> { ***********************************************************/ - fn build_expr( - &mut self, - sym: &Symbol, - expr: &Expr<'a>, - layout: &Layout<'a>, - storage: &StoredValue, - ) { - let wasm_layout = WasmLayout::new(layout); + fn expr(&mut self, sym: Symbol, expr: &Expr<'a>, layout: &Layout<'a>, storage: &StoredValue) { match expr { - Expr::Literal(lit) => self.load_literal(lit, storage, *sym, layout), + Expr::Literal(lit) => self.expr_literal(lit, storage, sym, layout), Expr::Call(roc_mono::ir::Call { call_type, arguments, - }) => match call_type { - CallType::ByName { name: func_sym, .. } => { - // If this function is just a lowlevel wrapper, then inline it - if let LowLevelWrapperType::CanBeReplacedBy(lowlevel) = - LowLevelWrapperType::from_symbol(*func_sym) - { - return self.build_low_level( - lowlevel, - arguments, - *sym, - wasm_layout, - layout, - storage, - ); - } + }) => self.expr_call(call_type, arguments, sym, layout, storage), - let (param_types, ret_type) = self.storage.load_symbols_for_call( - self.env.arena, - &mut self.code_builder, - arguments, - *sym, - &wasm_layout, - CallConv::C, - ); - - for (roc_proc_index, (ir_sym, linker_sym_index)) in - self.proc_symbols.iter().enumerate() - { - let wasm_fn_index = self.fn_index_offset + roc_proc_index as u32; - if ir_sym == func_sym { - let num_wasm_args = param_types.len(); - let has_return_val = ret_type.is_some(); - self.code_builder.call( - wasm_fn_index, - *linker_sym_index, - num_wasm_args, - has_return_val, - ); - return; - } - } - - internal_error!( - "Could not find procedure {:?}\nKnown procedures: {:?}", - func_sym, - self.proc_symbols - ); - } - - CallType::LowLevel { op: lowlevel, .. } => { - self.build_low_level(*lowlevel, arguments, *sym, wasm_layout, layout, storage) - } - - x => todo!("call type {:?}", x), - }, - - Expr::Struct(fields) => self.create_struct(sym, layout, storage, fields), + Expr::Struct(fields) => self.expr_struct(sym, layout, storage, fields), Expr::StructAtIndex { index, field_layouts, structure, - } => { - self.storage.ensure_value_has_local( - &mut self.code_builder, - *sym, - storage.to_owned(), - ); - let (local_id, mut offset) = match self.storage.get(structure) { - StoredValue::StackMemory { location, .. } => { - location.local_and_offset(self.storage.stack_frame_pointer) - } + } => self.expr_struct_at_index(sym, storage, *index, field_layouts, *structure), - StoredValue::Local { - value_type, - local_id, - .. - } => { - debug_assert!(matches!(value_type, ValueType::I32)); - (*local_id, 0) - } + Expr::Array { elems, elem_layout } => self.expr_array(sym, storage, elem_layout, elems), - StoredValue::VirtualMachineStack { .. } => { - internal_error!("ensure_value_has_local didn't work") - } - }; - for field in field_layouts.iter().take(*index as usize) { - offset += field.stack_size(PTR_SIZE); - } - self.storage - .copy_value_from_memory(&mut self.code_builder, *sym, local_id, offset); - } - - Expr::Array { elems, elem_layout } => { - if let StoredValue::StackMemory { location, .. } = storage { - let size = elem_layout.stack_size(PTR_SIZE) * (elems.len() as u32); - - // Allocate heap space and store its address in a local variable - let heap_local_id = self.storage.create_anonymous_local(PTR_TYPE); - let heap_alignment = elem_layout.alignment_bytes(PTR_SIZE); - self.allocate_with_refcount(Some(size), heap_alignment, 1); - self.code_builder.set_local(heap_local_id); - - let (stack_local_id, stack_offset) = - location.local_and_offset(self.storage.stack_frame_pointer); - - // elements pointer - self.code_builder.get_local(stack_local_id); - self.code_builder.get_local(heap_local_id); - self.code_builder.i32_store(Align::Bytes4, stack_offset); - - // length of the list - self.code_builder.get_local(stack_local_id); - self.code_builder.i32_const(elems.len() as i32); - self.code_builder.i32_store(Align::Bytes4, stack_offset + 4); - - let mut elem_offset = 0; - - for (i, elem) in elems.iter().enumerate() { - let elem_sym = match elem { - ListLiteralElement::Literal(lit) => { - // This has no Symbol but our storage methods expect one. - // Let's just pretend it was defined in a `Let`. - let debug_name = format!("{:?}_{}", sym, i); - let elem_sym = self.create_symbol(&debug_name); - let expr = Expr::Literal(*lit); - - self.store_expr_value( - elem_sym, - elem_layout, - &expr, - StoredValueKind::Variable, - ); - - elem_sym - } - - ListLiteralElement::Symbol(elem_sym) => *elem_sym, - }; - - elem_offset += self.storage.copy_value_to_memory( - &mut self.code_builder, - heap_local_id, - elem_offset, - elem_sym, - ); - } - } else { - internal_error!("Unexpected storage for Array {:?}: {:?}", sym, storage) - } - } - - Expr::EmptyArray => { - if let StoredValue::StackMemory { location, .. } = storage { - let (local_id, offset) = - location.local_and_offset(self.storage.stack_frame_pointer); - - // This is a minor cheat. - // What we want to write to stack memory is { elements: null, length: 0 } - // But instead of two 32-bit stores, we can do a single 64-bit store. - self.code_builder.get_local(local_id); - self.code_builder.i64_const(0); - self.code_builder.i64_store(Align::Bytes4, offset); - } else { - internal_error!("Unexpected storage for {:?}", sym) - } - } + Expr::EmptyArray => self.expr_empty_array(sym, storage), Expr::Tag { tag_layout: union_layout, tag_id, arguments, .. - } => self.build_tag(union_layout, *tag_id, arguments, *sym, storage), + } => self.expr_tag(union_layout, *tag_id, arguments, sym, storage), Expr::GetTagId { structure, union_layout, - } => self.build_get_tag_id(*structure, union_layout, *sym, storage), + } => self.expr_get_tag_id(*structure, union_layout, sym, storage), Expr::UnionAtIndex { structure, tag_id, union_layout, index, - } => self.build_union_at_index(*structure, *tag_id, union_layout, *index, *sym), + } => self.expr_union_at_index(*structure, *tag_id, union_layout, *index, sym), _ => todo!("Expression `{}`", expr.to_pretty(100)), } } - fn build_tag( + /******************************************************************* + * Literals + *******************************************************************/ + + fn expr_literal( + &mut self, + lit: &Literal<'a>, + storage: &StoredValue, + sym: Symbol, + layout: &Layout<'a>, + ) { + let invalid_error = + || internal_error!("Literal value {:?} has invalid storage {:?}", lit, storage); + + match storage { + StoredValue::VirtualMachineStack { value_type, .. } => { + match (lit, value_type) { + (Literal::Float(x), ValueType::F64) => self.code_builder.f64_const(*x as f64), + (Literal::Float(x), ValueType::F32) => self.code_builder.f32_const(*x as f32), + (Literal::Int(x), ValueType::I64) => self.code_builder.i64_const(*x as i64), + (Literal::Int(x), ValueType::I32) => self.code_builder.i32_const(*x as i32), + (Literal::Bool(x), ValueType::I32) => self.code_builder.i32_const(*x as i32), + (Literal::Byte(x), ValueType::I32) => self.code_builder.i32_const(*x as i32), + _ => invalid_error(), + }; + } + + StoredValue::StackMemory { location, .. } => { + let mut write128 = |lower_bits, upper_bits| { + let (local_id, offset) = + location.local_and_offset(self.storage.stack_frame_pointer); + + self.code_builder.get_local(local_id); + self.code_builder.i64_const(lower_bits); + self.code_builder.i64_store(Align::Bytes8, offset); + + self.code_builder.get_local(local_id); + self.code_builder.i64_const(upper_bits); + self.code_builder.i64_store(Align::Bytes8, offset + 8); + }; + + match lit { + Literal::Decimal(decimal) => { + let lower_bits = (decimal.0 & 0xffff_ffff_ffff_ffff) as i64; + let upper_bits = (decimal.0 >> 64) as i64; + write128(lower_bits, upper_bits); + } + Literal::Int(x) => { + let lower_bits = (*x & 0xffff_ffff_ffff_ffff) as i64; + let upper_bits = (*x >> 64) as i64; + write128(lower_bits, upper_bits); + } + Literal::Float(_) => { + // Also not implemented in LLVM backend (nor in Rust!) + todo!("f128 type"); + } + Literal::Str(string) => { + let (local_id, offset) = + location.local_and_offset(self.storage.stack_frame_pointer); + + let len = string.len(); + if len < 8 { + let mut stack_mem_bytes = [0; 8]; + stack_mem_bytes[0..len].clone_from_slice(string.as_bytes()); + stack_mem_bytes[7] = 0x80 | (len as u8); + let str_as_int = i64::from_le_bytes(stack_mem_bytes); + + // Write all 8 bytes at once using an i64 + // Str is normally two i32's, but in this special case, we can get away with fewer instructions + self.code_builder.get_local(local_id); + self.code_builder.i64_const(str_as_int); + self.code_builder.i64_store(Align::Bytes4, offset); + } else { + let (linker_sym_index, elements_addr) = + self.expr_literal_big_str(string, sym, layout); + + self.code_builder.get_local(local_id); + self.code_builder + .i32_const_mem_addr(elements_addr, linker_sym_index); + self.code_builder.i32_store(Align::Bytes4, offset); + + self.code_builder.get_local(local_id); + self.code_builder.i32_const(string.len() as i32); + self.code_builder.i32_store(Align::Bytes4, offset + 4); + }; + } + _ => invalid_error(), + } + } + + _ => invalid_error(), + }; + } + + /// Create a string constant in the module data section + /// Return the data we need for code gen: linker symbol index and memory address + fn expr_literal_big_str( + &mut self, + string: &'a str, + sym: Symbol, + layout: &Layout<'a>, + ) -> (u32, u32) { + // Place the segment at a 4-byte aligned offset + let segment_addr = round_up_to_alignment!(self.next_constant_addr, PTR_SIZE); + let elements_addr = segment_addr + PTR_SIZE; + let length_with_refcount = 4 + string.len(); + self.next_constant_addr = segment_addr + length_with_refcount as u32; + + let mut segment = DataSegment { + mode: DataMode::active_at(segment_addr), + init: Vec::with_capacity_in(length_with_refcount, self.env.arena), + }; + + // Prefix the string bytes with "infinite" refcount + let refcount_max_bytes: [u8; 4] = (REFCOUNT_MAX as i32).to_le_bytes(); + segment.init.extend_from_slice(&refcount_max_bytes); + segment.init.extend_from_slice(string.as_bytes()); + + let segment_index = self.module.data.append_segment(segment); + + // Generate linker symbol + let name = self + .layout_ids + .get(sym, layout) + .to_symbol_string(sym, self.interns); + + let linker_symbol = SymInfo::Data(DataSymbol::Defined { + flags: 0, + name: name.clone(), + segment_index, + segment_offset: 4, + size: string.len() as u32, + }); + + // Ensure the linker keeps the segment aligned when relocating it + self.module.linking.segment_info.push(LinkingSegment { + name, + alignment: Align::Bytes4, + flags: 0, + }); + + let linker_sym_index = self.module.linking.symbol_table.len(); + self.module.linking.symbol_table.push(linker_symbol); + + (linker_sym_index as u32, elements_addr) + } + + /******************************************************************* + * Call expressions + *******************************************************************/ + + fn expr_call( + &mut self, + call_type: &CallType<'a>, + arguments: &'a [Symbol], + ret_sym: Symbol, + ret_layout: &Layout<'a>, + ret_storage: &StoredValue, + ) { + match call_type { + CallType::ByName { name: func_sym, .. } => { + self.expr_call_by_name(*func_sym, arguments, ret_sym, ret_layout, ret_storage) + } + CallType::LowLevel { op: lowlevel, .. } => { + self.expr_call_low_level(*lowlevel, arguments, ret_sym, ret_layout, ret_storage) + } + + x => todo!("call type {:?}", x), + } + } + + fn expr_call_by_name( + &mut self, + func_sym: Symbol, + arguments: &'a [Symbol], + ret_sym: Symbol, + ret_layout: &Layout<'a>, + ret_storage: &StoredValue, + ) { + let wasm_layout = WasmLayout::new(ret_layout); + + // If this function is just a lowlevel wrapper, then inline it + if let LowLevelWrapperType::CanBeReplacedBy(lowlevel) = + LowLevelWrapperType::from_symbol(func_sym) + { + return self.expr_call_low_level(lowlevel, arguments, ret_sym, ret_layout, ret_storage); + } + + let (param_types, ret_type) = self.storage.load_symbols_for_call( + self.env.arena, + &mut self.code_builder, + arguments, + ret_sym, + &wasm_layout, + CallConv::C, + ); + + for (roc_proc_index, (ir_sym, linker_sym_index)) in self.proc_symbols.iter().enumerate() { + let wasm_fn_index = self.fn_index_offset + roc_proc_index as u32; + if *ir_sym == func_sym { + let num_wasm_args = param_types.len(); + let has_return_val = ret_type.is_some(); + self.code_builder.call( + wasm_fn_index, + *linker_sym_index, + num_wasm_args, + has_return_val, + ); + return; + } + } + + internal_error!( + "Could not find procedure {:?}\nKnown procedures: {:?}", + func_sym, + self.proc_symbols + ); + } + + fn expr_call_low_level( + &mut self, + lowlevel: LowLevel, + arguments: &'a [Symbol], + return_sym: Symbol, + mono_layout: &Layout<'a>, + storage: &StoredValue, + ) { + use LowLevel::*; + let return_layout = WasmLayout::new(mono_layout); + + match lowlevel { + Eq | NotEq => self.build_eq_or_neq( + lowlevel, + arguments, + return_sym, + return_layout, + mono_layout, + storage, + ), + PtrCast => { + // Don't want Zig calling convention when casting pointers. + self.storage.load_symbols(&mut self.code_builder, arguments); + } + Hash => todo!("Generic hash function generation"), + + // Almost all lowlevels take this branch, except for the special cases above + _ => { + // Load the arguments using Zig calling convention + let (param_types, ret_type) = self.storage.load_symbols_for_call( + self.env.arena, + &mut self.code_builder, + arguments, + return_sym, + &return_layout, + CallConv::Zig, + ); + + // Generate instructions OR decide which Zig function to call + let build_result = dispatch_low_level( + &mut self.code_builder, + &mut self.storage, + lowlevel, + arguments, + &return_layout, + mono_layout, + ); + + // Handle the result + use LowlevelBuildResult::*; + match build_result { + Done => {} + BuiltinCall(name) => { + self.expr_call_zig_builtin(name, param_types, ret_type); + } + NotImplemented => { + todo!("Low level operation {:?}", lowlevel) + } + } + } + } + } + + /// Generate a call instruction to a Zig builtin function. + /// And if we haven't seen it before, add an Import and linker data for it. + /// Zig calls use LLVM's "fast" calling convention rather than our usual C ABI. + fn expr_call_zig_builtin( + &mut self, + name: &'a str, + param_types: Vec<'a, ValueType>, + ret_type: Option, + ) { + let num_wasm_args = param_types.len(); + let has_return_val = ret_type.is_some(); + let fn_index = self.module.names.functions[name.as_bytes()]; + self.called_preload_fns.push(fn_index); + let linker_symbol_index = u32::MAX; + + self.code_builder + .call(fn_index, linker_symbol_index, num_wasm_args, has_return_val); + } + + /******************************************************************* + * Structs + *******************************************************************/ + + fn expr_struct( + &mut self, + sym: Symbol, + layout: &Layout<'a>, + storage: &StoredValue, + fields: &'a [Symbol], + ) { + if matches!(layout, Layout::Struct(_)) { + match storage { + StoredValue::StackMemory { location, size, .. } => { + if *size > 0 { + let (local_id, struct_offset) = + location.local_and_offset(self.storage.stack_frame_pointer); + let mut field_offset = struct_offset; + for field in fields.iter() { + field_offset += self.storage.copy_value_to_memory( + &mut self.code_builder, + local_id, + field_offset, + *field, + ); + } + } else { + // Zero-size struct. No code to emit. + // These values are purely conceptual, they only exist internally in the compiler + } + } + _ => internal_error!("Cannot create struct {:?} with storage {:?}", sym, storage), + }; + } else { + // Struct expression but not Struct layout => single element. Copy it. + let field_storage = self.storage.get(&fields[0]).to_owned(); + self.storage + .clone_value(&mut self.code_builder, storage, &field_storage, fields[0]); + } + } + + fn expr_struct_at_index( + &mut self, + sym: Symbol, + storage: &StoredValue, + index: u64, + field_layouts: &'a [Layout<'a>], + structure: Symbol, + ) { + self.storage + .ensure_value_has_local(&mut self.code_builder, sym, storage.to_owned()); + let (local_id, mut offset) = match self.storage.get(&structure) { + StoredValue::StackMemory { location, .. } => { + location.local_and_offset(self.storage.stack_frame_pointer) + } + + StoredValue::Local { + value_type, + local_id, + .. + } => { + debug_assert!(matches!(value_type, ValueType::I32)); + (*local_id, 0) + } + + StoredValue::VirtualMachineStack { .. } => { + internal_error!("ensure_value_has_local didn't work") + } + }; + for field in field_layouts.iter().take(index as usize) { + offset += field.stack_size(PTR_SIZE); + } + self.storage + .copy_value_from_memory(&mut self.code_builder, sym, local_id, offset); + } + + /******************************************************************* + * Heap allocation + *******************************************************************/ + + /// Allocate heap space and write an initial refcount + /// If the data size is known at compile time, pass it in comptime_data_size. + /// If size is only known at runtime, push *data* size to the VM stack first. + /// Leaves the *data* address on the VM stack + fn allocate_with_refcount( + &mut self, + comptime_data_size: Option, + alignment_bytes: u32, + initial_refcount: u32, + ) { + // Add extra bytes for the refcount + let extra_bytes = alignment_bytes.max(PTR_SIZE); + + if let Some(data_size) = comptime_data_size { + // Data size known at compile time and passed as an argument + self.code_builder + .i32_const((data_size + extra_bytes) as i32); + } else { + // Data size known only at runtime and is on top of VM stack + self.code_builder.i32_const(extra_bytes as i32); + self.code_builder.i32_add(); + } + + // Provide a constant for the alignment argument + self.code_builder.i32_const(alignment_bytes as i32); + + // Call the foreign function. (Zig and C calling conventions are the same for this signature) + let param_types = bumpalo::vec![in self.env.arena; ValueType::I32, ValueType::I32]; + let ret_type = Some(ValueType::I32); + self.expr_call_zig_builtin("roc_alloc", param_types, ret_type); + + // Save the allocation address to a temporary local variable + let local_id = self.storage.create_anonymous_local(ValueType::I32); + self.code_builder.tee_local(local_id); + + // Write the initial refcount + let refcount_offset = extra_bytes - PTR_SIZE; + let encoded_refcount = (initial_refcount as i32) - 1 + i32::MIN; + self.code_builder.i32_const(encoded_refcount); + self.code_builder.i32_store(Align::Bytes4, refcount_offset); + + // Put the data address on the VM stack + self.code_builder.get_local(local_id); + self.code_builder.i32_const(extra_bytes as i32); + self.code_builder.i32_add(); + } + + /******************************************************************* + * Arrays + *******************************************************************/ + + fn expr_array( + &mut self, + sym: Symbol, + storage: &StoredValue, + elem_layout: &Layout<'a>, + elems: &'a [ListLiteralElement<'a>], + ) { + if let StoredValue::StackMemory { location, .. } = storage { + let size = elem_layout.stack_size(PTR_SIZE) * (elems.len() as u32); + + // Allocate heap space and store its address in a local variable + let heap_local_id = self.storage.create_anonymous_local(PTR_TYPE); + let heap_alignment = elem_layout.alignment_bytes(PTR_SIZE); + self.allocate_with_refcount(Some(size), heap_alignment, 1); + self.code_builder.set_local(heap_local_id); + + let (stack_local_id, stack_offset) = + location.local_and_offset(self.storage.stack_frame_pointer); + + // elements pointer + self.code_builder.get_local(stack_local_id); + self.code_builder.get_local(heap_local_id); + self.code_builder.i32_store(Align::Bytes4, stack_offset); + + // length of the list + self.code_builder.get_local(stack_local_id); + self.code_builder.i32_const(elems.len() as i32); + self.code_builder.i32_store(Align::Bytes4, stack_offset + 4); + + let mut elem_offset = 0; + + for (i, elem) in elems.iter().enumerate() { + let elem_sym = match elem { + ListLiteralElement::Literal(lit) => { + // This has no Symbol but our storage methods expect one. + // Let's just pretend it was defined in a `Let`. + let debug_name = format!("{:?}_{}", sym, i); + let elem_sym = self.create_symbol(&debug_name); + let expr = Expr::Literal(*lit); + + self.stmt_let_store_expr( + elem_sym, + elem_layout, + &expr, + StoredValueKind::Variable, + ); + + elem_sym + } + + ListLiteralElement::Symbol(elem_sym) => *elem_sym, + }; + + elem_offset += self.storage.copy_value_to_memory( + &mut self.code_builder, + heap_local_id, + elem_offset, + elem_sym, + ); + } + } else { + internal_error!("Unexpected storage for Array {:?}: {:?}", sym, storage) + } + } + + fn expr_empty_array(&mut self, sym: Symbol, storage: &StoredValue) { + if let StoredValue::StackMemory { location, .. } = storage { + let (local_id, offset) = location.local_and_offset(self.storage.stack_frame_pointer); + + // This is a minor cheat. + // What we want to write to stack memory is { elements: null, length: 0 } + // But instead of two 32-bit stores, we can do a single 64-bit store. + self.code_builder.get_local(local_id); + self.code_builder.i64_const(0); + self.code_builder.i64_store(Align::Bytes4, offset); + } else { + internal_error!("Unexpected storage for {:?}", sym) + } + } + + /******************************************************************* + * Tag Unions + *******************************************************************/ + + fn expr_tag( &mut self, union_layout: &UnionLayout<'a>, tag_id: TagIdIntType, @@ -798,7 +1181,7 @@ impl<'a> WasmBackend<'a> { } } - fn build_get_tag_id( + fn expr_get_tag_id( &mut self, structure: Symbol, union_layout: &UnionLayout<'a>, @@ -879,7 +1262,7 @@ impl<'a> WasmBackend<'a> { } } - fn build_union_at_index( + fn expr_union_at_index( &mut self, structure: Symbol, tag_id: TagIdIntType, @@ -951,115 +1334,9 @@ impl<'a> WasmBackend<'a> { .copy_value_from_memory(&mut self.code_builder, symbol, from_ptr, from_offset); } - /// Allocate heap space and write an initial refcount - /// If the data size is known at compile time, pass it in comptime_data_size. - /// If size is only known at runtime, push *data* size to the VM stack first. - /// Leaves the *data* address on the VM stack - fn allocate_with_refcount( - &mut self, - comptime_data_size: Option, - alignment_bytes: u32, - initial_refcount: u32, - ) { - // Add extra bytes for the refcount - let extra_bytes = alignment_bytes.max(PTR_SIZE); - - if let Some(data_size) = comptime_data_size { - // Data size known at compile time and passed as an argument - self.code_builder - .i32_const((data_size + extra_bytes) as i32); - } else { - // Data size known only at runtime and is on top of VM stack - self.code_builder.i32_const(extra_bytes as i32); - self.code_builder.i32_add(); - } - - // Provide a constant for the alignment argument - self.code_builder.i32_const(alignment_bytes as i32); - - // Call the foreign function. (Zig and C calling conventions are the same for this signature) - let param_types = bumpalo::vec![in self.env.arena; ValueType::I32, ValueType::I32]; - let ret_type = Some(ValueType::I32); - self.call_zig_builtin("roc_alloc", param_types, ret_type); - - // Save the allocation address to a temporary local variable - let local_id = self.storage.create_anonymous_local(ValueType::I32); - self.code_builder.tee_local(local_id); - - // Write the initial refcount - let refcount_offset = extra_bytes - PTR_SIZE; - let encoded_refcount = (initial_refcount as i32) - 1 + i32::MIN; - self.code_builder.i32_const(encoded_refcount); - self.code_builder.i32_store(Align::Bytes4, refcount_offset); - - // Put the data address on the VM stack - self.code_builder.get_local(local_id); - self.code_builder.i32_const(extra_bytes as i32); - self.code_builder.i32_add(); - } - - fn build_low_level( - &mut self, - lowlevel: LowLevel, - arguments: &'a [Symbol], - return_sym: Symbol, - return_layout: WasmLayout, - mono_layout: &Layout<'a>, - storage: &StoredValue, - ) { - use LowLevel::*; - - match lowlevel { - Eq | NotEq => self.build_eq_or_neq( - lowlevel, - arguments, - return_sym, - return_layout, - mono_layout, - storage, - ), - PtrCast => { - // Don't want Zig calling convention when casting pointers. - self.storage.load_symbols(&mut self.code_builder, arguments); - } - Hash => todo!("Generic hash function generation"), - - // Almost all lowlevels take this branch, except for the special cases above - _ => { - // Load the arguments using Zig calling convention - let (param_types, ret_type) = self.storage.load_symbols_for_call( - self.env.arena, - &mut self.code_builder, - arguments, - return_sym, - &return_layout, - CallConv::Zig, - ); - - // Generate instructions OR decide which Zig function to call - let build_result = dispatch_low_level( - &mut self.code_builder, - &mut self.storage, - lowlevel, - arguments, - &return_layout, - mono_layout, - ); - - // Handle the result - use LowlevelBuildResult::*; - match build_result { - Done => {} - BuiltinCall(name) => { - self.call_zig_builtin(name, param_types, ret_type); - } - NotImplemented => { - todo!("Low level operation {:?}", lowlevel) - } - } - } - } - } + /******************************************************************* + * Equality + *******************************************************************/ fn build_eq_or_neq( &mut self, @@ -1091,7 +1368,7 @@ impl<'a> WasmBackend<'a> { &return_layout, CallConv::Zig, ); - self.call_zig_builtin(bitcode::STR_EQUAL, param_types, ret_type); + self.expr_call_zig_builtin(bitcode::STR_EQUAL, param_types, ret_type); if matches!(lowlevel, LowLevel::NotEq) { self.code_builder.i32_eqz(); } @@ -1274,210 +1551,11 @@ impl<'a> WasmBackend<'a> { // Generate Wasm code for the IR call expression let bool_layout = Layout::Builtin(Builtin::Bool); - self.build_expr( - &return_sym, + self.expr( + return_sym, self.env.arena.alloc(specialized_call_expr), &bool_layout, storage, ); } - - fn load_literal( - &mut self, - lit: &Literal<'a>, - storage: &StoredValue, - sym: Symbol, - layout: &Layout<'a>, - ) { - let invalid_error = - || internal_error!("Literal value {:?} has invalid storage {:?}", lit, storage); - - match storage { - StoredValue::VirtualMachineStack { value_type, .. } => { - match (lit, value_type) { - (Literal::Float(x), ValueType::F64) => self.code_builder.f64_const(*x as f64), - (Literal::Float(x), ValueType::F32) => self.code_builder.f32_const(*x as f32), - (Literal::Int(x), ValueType::I64) => self.code_builder.i64_const(*x as i64), - (Literal::Int(x), ValueType::I32) => self.code_builder.i32_const(*x as i32), - (Literal::Bool(x), ValueType::I32) => self.code_builder.i32_const(*x as i32), - (Literal::Byte(x), ValueType::I32) => self.code_builder.i32_const(*x as i32), - _ => invalid_error(), - }; - } - - StoredValue::StackMemory { location, .. } => { - let mut write128 = |lower_bits, upper_bits| { - let (local_id, offset) = - location.local_and_offset(self.storage.stack_frame_pointer); - - self.code_builder.get_local(local_id); - self.code_builder.i64_const(lower_bits); - self.code_builder.i64_store(Align::Bytes8, offset); - - self.code_builder.get_local(local_id); - self.code_builder.i64_const(upper_bits); - self.code_builder.i64_store(Align::Bytes8, offset + 8); - }; - - match lit { - Literal::Decimal(decimal) => { - let lower_bits = (decimal.0 & 0xffff_ffff_ffff_ffff) as i64; - let upper_bits = (decimal.0 >> 64) as i64; - write128(lower_bits, upper_bits); - } - Literal::Int(x) => { - let lower_bits = (*x & 0xffff_ffff_ffff_ffff) as i64; - let upper_bits = (*x >> 64) as i64; - write128(lower_bits, upper_bits); - } - Literal::Float(_) => { - // Also not implemented in LLVM backend (nor in Rust!) - todo!("f128 type"); - } - Literal::Str(string) => { - let (local_id, offset) = - location.local_and_offset(self.storage.stack_frame_pointer); - - let len = string.len(); - if len < 8 { - let mut stack_mem_bytes = [0; 8]; - stack_mem_bytes[0..len].clone_from_slice(string.as_bytes()); - stack_mem_bytes[7] = 0x80 | (len as u8); - let str_as_int = i64::from_le_bytes(stack_mem_bytes); - - // Write all 8 bytes at once using an i64 - // Str is normally two i32's, but in this special case, we can get away with fewer instructions - self.code_builder.get_local(local_id); - self.code_builder.i64_const(str_as_int); - self.code_builder.i64_store(Align::Bytes4, offset); - } else { - let (linker_sym_index, elements_addr) = - self.create_string_constant(string, sym, layout); - - self.code_builder.get_local(local_id); - self.code_builder - .i32_const_mem_addr(elements_addr, linker_sym_index); - self.code_builder.i32_store(Align::Bytes4, offset); - - self.code_builder.get_local(local_id); - self.code_builder.i32_const(string.len() as i32); - self.code_builder.i32_store(Align::Bytes4, offset + 4); - }; - } - _ => invalid_error(), - } - } - - _ => invalid_error(), - }; - } - - /// Create a string constant in the module data section - /// Return the data we need for code gen: linker symbol index and memory address - fn create_string_constant( - &mut self, - string: &'a str, - sym: Symbol, - layout: &Layout<'a>, - ) -> (u32, u32) { - // Place the segment at a 4-byte aligned offset - let segment_addr = round_up_to_alignment!(self.next_constant_addr, PTR_SIZE); - let elements_addr = segment_addr + PTR_SIZE; - let length_with_refcount = 4 + string.len(); - self.next_constant_addr = segment_addr + length_with_refcount as u32; - - let mut segment = DataSegment { - mode: DataMode::active_at(segment_addr), - init: Vec::with_capacity_in(length_with_refcount, self.env.arena), - }; - - // Prefix the string bytes with "infinite" refcount - let refcount_max_bytes: [u8; 4] = (REFCOUNT_MAX as i32).to_le_bytes(); - segment.init.extend_from_slice(&refcount_max_bytes); - segment.init.extend_from_slice(string.as_bytes()); - - let segment_index = self.module.data.append_segment(segment); - - // Generate linker symbol - let name = self - .layout_ids - .get(sym, layout) - .to_symbol_string(sym, self.interns); - - let linker_symbol = SymInfo::Data(DataSymbol::Defined { - flags: 0, - name: name.clone(), - segment_index, - segment_offset: 4, - size: string.len() as u32, - }); - - // Ensure the linker keeps the segment aligned when relocating it - self.module.linking.segment_info.push(LinkingSegment { - name, - alignment: Align::Bytes4, - flags: 0, - }); - - let linker_sym_index = self.module.linking.symbol_table.len(); - self.module.linking.symbol_table.push(linker_symbol); - - (linker_sym_index as u32, elements_addr) - } - - fn create_struct( - &mut self, - sym: &Symbol, - layout: &Layout<'a>, - storage: &StoredValue, - fields: &'a [Symbol], - ) { - if matches!(layout, Layout::Struct(_)) { - match storage { - StoredValue::StackMemory { location, size, .. } => { - if *size > 0 { - let (local_id, struct_offset) = - location.local_and_offset(self.storage.stack_frame_pointer); - let mut field_offset = struct_offset; - for field in fields.iter() { - field_offset += self.storage.copy_value_to_memory( - &mut self.code_builder, - local_id, - field_offset, - *field, - ); - } - } else { - // Zero-size struct. No code to emit. - // These values are purely conceptual, they only exist internally in the compiler - } - } - _ => internal_error!("Cannot create struct {:?} with storage {:?}", sym, storage), - }; - } else { - // Struct expression but not Struct layout => single element. Copy it. - let field_storage = self.storage.get(&fields[0]).to_owned(); - self.storage - .clone_value(&mut self.code_builder, storage, &field_storage, fields[0]); - } - } - - /// Generate a call instruction to a Zig builtin function. - /// And if we haven't seen it before, add an Import and linker data for it. - /// Zig calls use LLVM's "fast" calling convention rather than our usual C ABI. - fn call_zig_builtin( - &mut self, - name: &'a str, - param_types: Vec<'a, ValueType>, - ret_type: Option, - ) { - let num_wasm_args = param_types.len(); - let has_return_val = ret_type.is_some(); - let fn_index = self.module.names.functions[name.as_bytes()]; - self.called_preload_fns.push(fn_index); - let linker_symbol_index = u32::MAX; - - self.code_builder - .call(fn_index, linker_symbol_index, num_wasm_args, has_return_val); - } } From d2c2064782a68d0d0c4cb0a8f80eb5ddbf7e396f Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Sat, 15 Jan 2022 17:48:19 -0700 Subject: [PATCH 228/541] Add missing `Num.maxI128` docs --- compiler/builtins/docs/Num.roc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index df39d4849d..5724bc4b06 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -822,6 +822,16 @@ maxU32 : U32 ## and zero is the lowest unsigned number. Unsigned numbers cannot be negative. minU32 : U32 +## The highest number that can be stored in an #I128 without overflowing its +## available memory and crashing. +## +## For reference, this number is `170_141_183_460_469_231_731_687_303_715_884_105_727`, +## which is over 2 million. +## +## Note that this is smaller than the positive version of #Int.minI128, +## which means if you call #Num.abs on #Int.minI128, it will overflow and crash! +maxI128 : I128 + ## The highest supported #F64 value you can have, which is approximately 1.8 × 10^308. ## ## If you go higher than this, your running Roc code will crash - so be careful not to! From d7e2be306f6767a9bf5fa6465539ff41a1522335 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Sat, 15 Jan 2022 14:05:31 -0700 Subject: [PATCH 229/541] WIP: Add `Num.minI128` builtin (TODOs remain) --- compiler/builtins/docs/Num.roc | 10 ++++++++++ compiler/builtins/src/std.rs | 3 +++ compiler/can/src/builtins.rs | 30 ++++++++++++++++++++++++++++++ compiler/module/src/symbol.rs | 1 + compiler/solve/tests/solve_expr.rs | 12 ++++++++++++ compiler/test_gen/src/gen_num.rs | 14 ++++++++++++++ 6 files changed, 70 insertions(+) diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index 5724bc4b06..34fb2ca201 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -65,6 +65,7 @@ interface Num maxI128, maxInt, minFloat, + minI128, minInt, modInt, modFloat, @@ -832,6 +833,15 @@ minU32 : U32 ## which means if you call #Num.abs on #Int.minI128, it will overflow and crash! maxI128 : I128 +## The min number that can be stored in an #I128 without underflowing its +## available memory and crashing. +## +## For reference, this number is `-170_141_183_460_469_231_731_687_303_715_884_105_728`. +## +## Note that the positive version of this number is larger than #Int.maxI128, +## which means if you call #Num.abs on #Int.minI128, it will overflow and crash! +minI128 : I128 + ## The highest supported #F64 value you can have, which is approximately 1.8 × 10^308. ## ## If you go higher than this, your running Roc code will crash - so be careful not to! diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index db6d917a7c..d338d34959 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -397,6 +397,9 @@ pub fn types() -> MutMap { Box::new(bool_type()), ); + // minI128 : I128 + add_type!(Symbol::NUM_MIN_I128, i128_type()); + // maxI128 : I128 add_type!(Symbol::NUM_MAX_I128, i128_type()); diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 04a1a1d9c8..696df17538 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -206,6 +206,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_SHIFT_RIGHT => num_shift_right_by, NUM_SHIFT_RIGHT_ZERO_FILL => num_shift_right_zf_by, NUM_INT_CAST=> num_int_cast, + NUM_MIN_I128=> num_min_i128, NUM_MAX_I128=> num_max_i128, NUM_TO_STR => num_to_str, RESULT_MAP => result_map, @@ -1237,6 +1238,35 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_1(symbol, LowLevel::NumIntCast, var_store) } +/// Num.minI128: I128 +fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { + let int_var = var_store.fresh(); + let int_precision_var = var_store.fresh(); + // TODO: or `i128::MIN.into()` ? + let body = int(int_var, int_precision_var, i128::MIN); + + let std = roc_builtins::std::types(); + let solved = std.get(&symbol).unwrap(); + let mut free_vars = roc_types::solved_types::FreeVars::default(); + let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); + + let annotation = crate::def::Annotation { + signature, + introduced_variables: Default::default(), + region: Region::zero(), + aliases: Default::default(), + }; + + Def { + // TODO: or `None` ? + annotation: Some(annotation), + expr_var: int_var, + loc_expr: Loc::at_zero(body), + loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), + pattern_vars: SendMap::default(), + } +} + /// Num.maxI128: I128 fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { let int_var = var_store.fresh(); diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 237f72b199..f2e4c31059 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -995,6 +995,7 @@ define_builtins! { 107 NUM_CAST_TO_NAT: "#castToNat" 108 NUM_DIV_CEIL: "divCeil" 109 NUM_TO_STR: "toStr" + 110 NUM_MIN_I128: "minI128" } 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 7ada9982f1..2a4e285971 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -3341,6 +3341,18 @@ mod solve_expr { ); } + #[test] + fn min_i128() { + infer_eq_without_problem( + indoc!( + r#" + Num.minI128 + "# + ), + "I128", + ); + } + #[test] fn max_i128() { infer_eq_without_problem( diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index 3fc1227f70..88096f5b39 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -1829,6 +1829,20 @@ fn shift_right_zf_by() { assert_evals_to!("Num.shiftRightBy 3 0b0000_1100u8", 0b0000_0011, i64); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_i128() { + assert_evals_to!( + indoc!( + r#" + Num.minI128 + "# + ), + i128::MIN, + i128 + ); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn max_i128() { From a61b8e402cdc2568b3b2dfe9b06d74dbe0022800 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Sat, 15 Jan 2022 18:37:07 -0700 Subject: [PATCH 230/541] Fix directory name typo --- compiler/builtins/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/builtins/README.md b/compiler/builtins/README.md index a83e924cf7..3809bb1b34 100644 --- a/compiler/builtins/README.md +++ b/compiler/builtins/README.md @@ -80,7 +80,7 @@ fn atan() { ``` But replace `Num.atan` and the type signature with the new builtin. -### gen/test/*.rs +### test_gen/test/*.rs In this directory, there are a couple files like `gen_num.rs`, `gen_str.rs`, etc. For the `Str` module builtins, put the test in `gen_str.rs`, etc. Find the one for the new builtin, and add a test like: ``` #[test] From c1dc0226f691114b38e867d19fb170295aa6c662 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Sat, 15 Jan 2022 17:19:32 -0700 Subject: [PATCH 231/541] Fix a lint error by simplifying an if/else block From `cargo clippy -- -D warnings`: ``` error: all if blocks contain the same code at the start --> compiler/gen_wasm/src/wasm_module/sections.rs:478:9 | 478 | / if bytes[*cursor] == 0 { 479 | | u8::skip_bytes(bytes, cursor); 480 | | u32::skip_bytes(bytes, cursor); | |___________________________________________^ | = note: `-D clippy::branches-sharing-code` implied by `-D warnings` = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#branches_sharing_code help: consider moving the start statements out like this | 478 ~ u8::skip_bytes(bytes, cursor); 479 + u32::skip_bytes(bytes, cursor); 480 + if bytes[*cursor] == 0 { | error: could not compile `roc_gen_wasm` due to previous error warning: build failed, waiting for other jobs to finish... error: build failed ``` --- compiler/gen_wasm/src/wasm_module/sections.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 4a5e370e1f..117d705a07 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -482,13 +482,11 @@ impl Serialize for Limits { impl SkipBytes for Limits { fn skip_bytes(bytes: &[u8], cursor: &mut usize) { - if bytes[*cursor] == LimitsId::Min as u8 { - u8::skip_bytes(bytes, cursor); - u32::skip_bytes(bytes, cursor); - } else { - u8::skip_bytes(bytes, cursor); - u32::skip_bytes(bytes, cursor); - u32::skip_bytes(bytes, cursor); + let variant_id = bytes[*cursor]; + u8::skip_bytes(bytes, cursor); // advance past the variant byte + u32::skip_bytes(bytes, cursor); // skip "min" + if variant_id == LimitsId::MinMax as u8 { + u32::skip_bytes(bytes, cursor); // skip "max" } } } From 697a65e77c95b501462894b6dc14a9a0738cd565 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 16 Jan 2022 22:28:36 +0100 Subject: [PATCH 232/541] builtins can use builtins now --- compiler/can/src/builtins.rs | 95 +++++++++++++++++------------------ compiler/can/src/module.rs | 11 ++++ compiler/module/src/symbol.rs | 58 +++++++++++---------- 3 files changed, 85 insertions(+), 79 deletions(-) diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 04a1a1d9c8..fdbd2dd396 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -44,6 +44,22 @@ macro_rules! macro_magic { /// delegates to the compiler-internal List.getUnsafe function to do the actual /// lookup (if the bounds check passed). That internal function is hardcoded in code gen, /// which works fine because it doesn't involve any open tag unions. + +/// Does a builtin depend on any other builtins? +/// +/// NOTE: you are supposed to give all symbols that are relied on, +/// even those that are relied on transitively! +pub fn builtin_dependencies(symbol: Symbol) -> &'static [Symbol] { + match symbol { + // Symbol::LIST_SORT_ASC => &[Symbol::LIST_SORT_WITH, Symbol::NUM_COMPARE], + Symbol::LIST_PRODUCT => &[Symbol::LIST_WALK, Symbol::NUM_MUL], + Symbol::LIST_SUM => &[Symbol::LIST_WALK, Symbol::NUM_ADD], + Symbol::LIST_JOIN_MAP => &[Symbol::LIST_WALK, Symbol::LIST_CONCAT], + _ => &[], + } +} + +/// Implementation for a builtin pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option { debug_assert!(symbol.is_builtin()); @@ -3045,15 +3061,22 @@ fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); let closure_var = var_store.fresh(); - let body = RunLowLevel { - op: LowLevel::ListWalk, - args: vec![ - (list_var, Var(Symbol::ARG_1)), - (num_var, num(var_store.fresh(), 0)), - (closure_var, list_sum_add(num_var, var_store)), - ], + let function = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(Symbol::LIST_WALK)), + var_store.fresh(), ret_var, - }; + ); + + let body = Expr::Call( + Box::new(function), + vec![ + (list_var, Loc::at_zero(Var(Symbol::ARG_1))), + (num_var, Loc::at_zero(num(var_store.fresh(), 0))), + (closure_var, Loc::at_zero(Var(Symbol::NUM_ADD))), + ], + CalledVia::Space, + ); defn( symbol, @@ -3064,60 +3087,34 @@ fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } -fn list_sum_add(num_var: Variable, var_store: &mut VarStore) -> Expr { - let body = RunLowLevel { - op: LowLevel::NumAdd, - args: vec![(num_var, Var(Symbol::ARG_3)), (num_var, Var(Symbol::ARG_4))], - ret_var: num_var, - }; - - defn_help( - Symbol::LIST_SUM_ADD, - vec![(num_var, Symbol::ARG_3), (num_var, Symbol::ARG_4)], - var_store, - body, - num_var, - ) -} - /// List.product : List (Num a) -> Num a fn list_product(symbol: Symbol, var_store: &mut VarStore) -> Def { let num_var = var_store.fresh(); - let ret_var = num_var; let list_var = var_store.fresh(); let closure_var = var_store.fresh(); - let body = RunLowLevel { - op: LowLevel::ListWalk, - args: vec![ - (list_var, Var(Symbol::ARG_1)), - (num_var, num(var_store.fresh(), 1)), - (closure_var, list_product_mul(num_var, var_store)), + let function = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(Symbol::LIST_WALK)), + var_store.fresh(), + num_var, + ); + + let body = Expr::Call( + Box::new(function), + vec![ + (list_var, Loc::at_zero(Var(Symbol::ARG_1))), + (num_var, Loc::at_zero(num(var_store.fresh(), 1))), + (closure_var, Loc::at_zero(Var(Symbol::NUM_MUL))), ], - ret_var, - }; + CalledVia::Space, + ); defn( symbol, vec![(list_var, Symbol::ARG_1)], var_store, body, - ret_var, - ) -} - -fn list_product_mul(num_var: Variable, var_store: &mut VarStore) -> Expr { - let body = RunLowLevel { - op: LowLevel::NumMul, - args: vec![(num_var, Var(Symbol::ARG_3)), (num_var, Var(Symbol::ARG_4))], - ret_var: num_var, - }; - - defn_help( - Symbol::LIST_PRODUCT_MUL, - vec![(num_var, Symbol::ARG_3), (num_var, Symbol::ARG_4)], - var_store, - body, num_var, ) } diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 07a19cc654..1f57cc8734 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -181,6 +181,17 @@ where references.insert(*symbol); } + // add any builtins used by other builtins + let transitive_builtins: Vec = references + .iter() + .filter(|s| s.is_builtin()) + .map(|s| crate::builtins::builtin_dependencies(*s)) + .flatten() + .copied() + .collect(); + + references.extend(transitive_builtins); + // NOTE previously we inserted builtin defs into the list of defs here // this is now done later, in file.rs. diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 237f72b199..eaab9a9af8 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1075,36 +1075,34 @@ define_builtins! { 24 LIST_MAP2: "map2" 25 LIST_MAP3: "map3" 26 LIST_PRODUCT: "product" - 27 LIST_SUM_ADD: "#sumadd" - 28 LIST_PRODUCT_MUL: "#productmul" - 29 LIST_WALK_UNTIL: "walkUntil" - 30 LIST_RANGE: "range" - 31 LIST_SORT_WITH: "sortWith" - 32 LIST_DROP: "drop" - 33 LIST_SWAP: "swap" - 34 LIST_DROP_AT: "dropAt" - 35 LIST_DROP_LAST: "dropLast" - 36 LIST_MIN: "min" - 37 LIST_MIN_LT: "#minlt" - 38 LIST_MAX: "max" - 39 LIST_MAX_GT: "#maxGt" - 40 LIST_MAP4: "map4" - 41 LIST_DROP_FIRST: "dropFirst" - 42 LIST_JOIN_MAP: "joinMap" - 43 LIST_JOIN_MAP_CONCAT: "#joinMapConcat" - 44 LIST_ANY: "any" - 45 LIST_TAKE_FIRST: "takeFirst" - 46 LIST_TAKE_LAST: "takeLast" - 47 LIST_FIND: "find" - 48 LIST_FIND_RESULT: "#find_result" // symbol used in the definition of List.find - 49 LIST_SUBLIST: "sublist" - 50 LIST_INTERSPERSE: "intersperse" - 51 LIST_INTERSPERSE_CLOS: "#intersperseClos" - 52 LIST_SPLIT: "split" - 53 LIST_SPLIT_CLOS: "#splitClos" - 54 LIST_ALL: "all" - 55 LIST_DROP_IF: "dropIf" - 56 LIST_DROP_IF_PREDICATE: "#dropIfPred" + 27 LIST_WALK_UNTIL: "walkUntil" + 28 LIST_RANGE: "range" + 29 LIST_SORT_WITH: "sortWith" + 30 LIST_DROP: "drop" + 31 LIST_SWAP: "swap" + 32 LIST_DROP_AT: "dropAt" + 33 LIST_DROP_LAST: "dropLast" + 34 LIST_MIN: "min" + 35 LIST_MIN_LT: "#minlt" + 36 LIST_MAX: "max" + 37 LIST_MAX_GT: "#maxGt" + 38 LIST_MAP4: "map4" + 39 LIST_DROP_FIRST: "dropFirst" + 40 LIST_JOIN_MAP: "joinMap" + 41 LIST_JOIN_MAP_CONCAT: "#joinMapConcat" + 42 LIST_ANY: "any" + 43 LIST_TAKE_FIRST: "takeFirst" + 44 LIST_TAKE_LAST: "takeLast" + 45 LIST_FIND: "find" + 46 LIST_FIND_RESULT: "#find_result" // symbol used in the definition of List.find + 47 LIST_SUBLIST: "sublist" + 48 LIST_INTERSPERSE: "intersperse" + 49 LIST_INTERSPERSE_CLOS: "#intersperseClos" + 50 LIST_SPLIT: "split" + 51 LIST_SPLIT_CLOS: "#splitClos" + 52 LIST_ALL: "all" + 53 LIST_DROP_IF: "dropIf" + 54 LIST_DROP_IF_PREDICATE: "#dropIfPred" } 5 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias From be14dd49546d030d0c9e85660b43757afa955c29 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 17 Jan 2022 00:11:58 +0100 Subject: [PATCH 233/541] write big comment on why this _should_ work --- compiler/load/src/effect_module.rs | 59 ++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/compiler/load/src/effect_module.rs b/compiler/load/src/effect_module.rs index 738330faf4..43b35a2df6 100644 --- a/compiler/load/src/effect_module.rs +++ b/compiler/load/src/effect_module.rs @@ -28,6 +28,8 @@ pub const BUILTIN_EFFECT_FUNCTIONS: [(&str, Builder); 3] = [ ("map", build_effect_map), // Effect.always : a -> Effect a ("always", build_effect_always), + // Effect.forever : Effect a -> Effect b + ("forever", build_effect_forever), ]; // the Effects alias & associated functions @@ -588,6 +590,63 @@ fn build_effect_after( (after_symbol, def) } +fn build_effect_forever( + env: &mut Env, + scope: &mut Scope, + effect_symbol: Symbol, + effect_tag_name: TagName, + var_store: &mut VarStore, +) -> (Symbol, Def) { + // morally + // + // Effect.forever = \effect -> Effect.after effect (\_ -> Effect.forever effect) + // + // Here we inline the `Effect.after`, and get + // + // Effect.forever : Effect a -> Effect b + // Effect.forever = \effect -> + // \{} -> + // @Effect thunk1 = effect + // _ = thunk1 {} + // @Effect thunk2 = Effect.forever effect + // thunk2 {} + // + // We then rely on our defunctionalization to turn this into a tail-recursive loop. + // First the `@Effect` wrapper melts away + // + // Effect.forever : ({} -> a) -> ({} -> b) + // Effect.forever = \effect -> + // \{} -> + // thunk1 = effect + // _ = thunk1 {} + // thunk2 = Effect.forever effect + // thunk2 {} + // + // Then we defunctionalize + // + // foreverInner = \{}, { effect } -> + // thunk1 = effect + // _ = thunk1 {} + // thunk2 = Effect.forever effect + // thunk2 {} + // + // Effect.forever : [ C foreverInner { effect : T } ] + // Effect.forever = \effect -> + // C { effect } + // + // And we have to adjust the call + // + // foreverInner = \{}, { effect } -> + // thunk1 = effect + // _ = thunk1 {} + // thunk2 = Effect.forever effect + // when thunk2 is + // C env -> foreverInner {} env.effect + // + // Making `foreverInner` perfectly tail-call optimizable + todo!() +} + pub fn build_host_exposed_def( env: &mut Env, scope: &mut Scope, From 085c02ffee868fb8e17e2b56691436d88831a094 Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Sun, 16 Jan 2022 22:01:17 -0600 Subject: [PATCH 234/541] Infrastructure to test and implement the replacement of an 'expect' failure with an error production Last command run was 'cargo test expect_fail' --- compiler/builtins/bitcode/src/utils.zig | 9 ++++- compiler/builtins/src/bitcode.rs | 3 ++ compiler/gen_llvm/src/llvm/build.rs | 43 ++++++---------------- compiler/gen_llvm/src/run_roc.rs | 9 ++++- compiler/test_gen/src/gen_primitives.rs | 9 ++++- compiler/test_gen/src/helpers/dev.rs | 22 +++++++++++ compiler/test_gen/src/helpers/llvm.rs | 49 +++++++++++++++++++++++++ 7 files changed, 108 insertions(+), 36 deletions(-) diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 09861e4e22..44ca582deb 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -305,10 +305,15 @@ pub fn getExpectFailures() []Failure { return failures[0..failure_length]; } -pub fn getExpectFailuresC() callconv(.C) *c_void { +const CSlice = extern struct { + pointer: *c_void, + len: usize, +}; +pub fn getExpectFailuresC() callconv(.C) CSlice { + var bytes = @ptrCast(*c_void, failures); - return bytes; + return .{.pointer = bytes, .len = failure_length}; } pub fn deinitFailures() void { diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 6746068a66..5b026968e3 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -316,3 +316,6 @@ pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic"; pub const UTILS_INCREF: &str = "roc_builtins.utils.incref"; pub const UTILS_DECREF: &str = "roc_builtins.utils.decref"; pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null"; +pub const UTILS_EXPECT_FAILED: &str = "roc_builtins.utils.expect_failed"; +pub const UTILS_GET_EXPECT_FAILURES: &str = "roc_builtins.utils.get_expect_failures"; +pub const UTILS_DEINIT_FAILURES: &str = "roc_builtins.utils.deinit_failures"; diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index a5284f4c55..0d230a6bb1 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -5259,11 +5259,6 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( } } -// TODO: Fix me! I should be different in tests vs. user code! -fn expect_failed() { - panic!("An expectation failed!"); -} - #[allow(clippy::too_many_arguments)] fn run_low_level<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, @@ -5274,7 +5269,6 @@ fn run_low_level<'a, 'ctx, 'env>( op: LowLevel, args: &[Symbol], update_mode: UpdateMode, - // expect_failed: *const (), ) -> BasicValueEnum<'ctx> { use LowLevel::*; @@ -6036,33 +6030,20 @@ fn run_low_level<'a, 'ctx, 'env>( { bd.position_at_end(throw_block); - match env.ptr_bytes { - 8 => { - let test = env.module.get_function("get_expect_failed").unwrap(); - let fn_ptr_type = context - .void_type() - .fn_type(&[], false) - .ptr_type(AddressSpace::Generic); - let fn_addr = env - .ptr_int() - .const_int(expect_failed as *const () as u64, false); - let func: PointerValue<'ctx> = bd.build_int_to_ptr( - fn_addr, - fn_ptr_type, - "cast_expect_failed_addr_to_ptr", - ); - let callable = CallableValue::try_from(func).unwrap(); + let func = env.module.get_function("roc_builtins.utils.expect_failed").unwrap(); + let callable = CallableValue::try_from(func).unwrap(); + let start_line = context.i32_type().const_int(0, false); + let end_line = context.i32_type().const_int(0, false); + let start_col = context.i16_type().const_int(0, false); + let end_col = context.i16_type().const_int(0, false); - bd.build_call(callable, &[], "call_expect_failed"); + bd.build_call( + callable, + &[start_line.into(), end_line.into(), start_col.into(), end_col.into()], + "call_expect_failed", + ); - bd.build_unconditional_branch(then_block); - } - 4 => { - // temporary WASM implementation - throw_exception(env, "An expectation failed!"); - } - _ => unreachable!(), - } + bd.build_unconditional_branch(then_block); } bd.position_at_end(then_block); diff --git a/compiler/gen_llvm/src/run_roc.rs b/compiler/gen_llvm/src/run_roc.rs index 4428f68fbf..b607a6a281 100644 --- a/compiler/gen_llvm/src/run_roc.rs +++ b/compiler/gen_llvm/src/run_roc.rs @@ -38,6 +38,9 @@ macro_rules! run_jit_function { }}; ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr) => {{ + run_jit_function!($lib, $main_fn_name, $ty, $transform, $errors, &[]) + }}; + ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr, $expect_failures:expr) => {{ use inkwell::context::Context; use roc_gen_llvm::run_roc::RocCallResult; use std::mem::MaybeUninit; @@ -48,7 +51,11 @@ macro_rules! run_jit_function { .ok() .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) .expect("errored"); - + let get_expect_failures: libloading::Symbol (usize, usize)> = + $lib.get("roc_builtins.utils.get_expect_failures".as_bytes()) + .ok() + .ok_or(format!("Unable to JIT compile `{}`", "roc_builtins.utils.get_expect_failures")) + .expect("errored"); let mut result = MaybeUninit::uninit(); main(result.as_mut_ptr()); diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index d6dc7efeb6..f79a27cc77 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -1,6 +1,8 @@ #[cfg(feature = "gen-llvm")] use crate::helpers::llvm::assert_evals_to; #[cfg(feature = "gen-llvm")] +use crate::helpers::llvm::assert_expect_failed; +#[cfg(feature = "gen-llvm")] use crate::helpers::llvm::assert_llvm_evals_to; #[cfg(feature = "gen-llvm")] use crate::helpers::llvm::assert_non_opt_evals_to; @@ -8,6 +10,8 @@ use crate::helpers::llvm::assert_non_opt_evals_to; #[cfg(feature = "gen-dev")] use crate::helpers::dev::assert_evals_to; // #[cfg(feature = "gen-dev")] +// use crate::helpers::dev::assert_expect_failed; +// #[cfg(feature = "gen-dev")] // use crate::helpers::dev::assert_evals_to as assert_llvm_evals_to; // #[cfg(feature = "gen-dev")] // use crate::helpers::dev::assert_evals_to as assert_non_opt_evals_to; @@ -15,6 +19,8 @@ use crate::helpers::dev::assert_evals_to; #[cfg(feature = "gen-wasm")] use crate::helpers::wasm::assert_evals_to; // #[cfg(feature = "gen-wasm")] +// use crate::helpers::dev::assert_expect_failed; +// #[cfg(feature = "gen-wasm")] // use crate::helpers::wasm::assert_evals_to as assert_llvm_evals_to; // #[cfg(feature = "gen-wasm")] // use crate::helpers::wasm::assert_evals_to as assert_non_opt_evals_to; @@ -2507,9 +2513,8 @@ fn call_invalid_layout() { #[test] #[cfg(any(feature = "gen-llvm"))] -#[should_panic(expected = "An expectation failed!")] fn expect_fail() { - assert_evals_to!( + assert_expect_failed!( indoc!( r#" expect 1 == 2 diff --git a/compiler/test_gen/src/helpers/dev.rs b/compiler/test_gen/src/helpers/dev.rs index 522dc6ed51..867c2c6cf3 100644 --- a/compiler/test_gen/src/helpers/dev.rs +++ b/compiler/test_gen/src/helpers/dev.rs @@ -255,5 +255,27 @@ macro_rules! assert_evals_to { }; } +#[allow(unused_macros)] +macro_rules! assert_expect_failed { + ($src:expr, $expected:expr, $ty:ty, $failures:expr) => {{ + use bumpalo::Bump; + use roc_gen_dev::run_jit_function_raw; + let stdlib = roc_builtins::std::standard_stdlib(); + + let arena = Bump::new(); + let (main_fn_name, errors, lib) = + $crate::helpers::dev::helper(&arena, $src, stdlib, true, true); + + let transform = |success| { + let expected = $expected; + assert_eq!(&success, &expected); + }; + run_jit_function_raw!(lib, main_fn_name, $ty, transform, errors); + todo!("Actually look up the failures and check them") + }; +} + + #[allow(unused_imports)] pub(crate) use assert_evals_to; +pub(crate) use assert_expect_failed; diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index a74f64eaba..edea6be011 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -599,6 +599,52 @@ macro_rules! assert_evals_to { }; } +#[allow(unused_macros)] +macro_rules! assert_expect_failed { + ($src:expr, $expected:expr, $ty:ty) => { + use bumpalo::Bump; + use inkwell::context::Context; + use roc_gen_llvm::run_jit_function; + + let arena = Bump::new(); + let context = Context::create(); + + // 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 (main_fn_name, errors, lib) = $crate::helpers::llvm::helper( + &arena, + $src, + stdlib, + is_gen_test, + false, + &context, + ); + + let transform = |success| { + let expected = $expected; + assert_eq!(&success, &expected, "LLVM test failed"); + }; + run_jit_function!(lib, main_fn_name, $ty, transform, errors) + }; + + ($src:expr, $expected:expr, $ty:ty) => { + $crate::helpers::llvm::assert_llvm_evals_to!( + $src, + $expected, + $ty, + $crate::helpers::llvm::identity, + false + ); + }; + + ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { + $crate::helpers::llvm::assert_llvm_evals_to!($src, $expected, $ty, $transform, false); + }; +} + + #[allow(dead_code)] pub fn identity(value: T) -> T { value @@ -633,3 +679,6 @@ pub(crate) use assert_llvm_evals_to; pub(crate) use assert_non_opt_evals_to; #[allow(unused_imports)] pub(crate) use assert_wasm_evals_to; +#[allow(unused_imports)] +pub(crate) use assert_expect_failed; + From 05a6a9c7ce195e9ac2d392477ca5ae91be4fb91e Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 16 Jan 2022 08:25:20 +0000 Subject: [PATCH 235/541] Wasm: update comments on lowlevels --- compiler/gen_wasm/src/low_level.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index 8ae585238a..d0d029ce9f 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -40,11 +40,12 @@ pub fn dispatch_low_level<'a>( StrStartsWithCodePt => return BuiltinCall(bitcode::STR_STARTS_WITH_CODE_PT), StrEndsWith => return BuiltinCall(bitcode::STR_ENDS_WITH), StrSplit => { - // Roughly we need to: - // 1. count segments - // 2. make a new pointer - // 3. split that pointer in place - // see: build_str.rs line 31 + // LLVM implementation (build_str.rs) does the following + // 1. Call bitcode::STR_COUNT_SEGMENTS + // 2. Allocate a `List Str` + // 3. Call bitcode::STR_STR_SPLIT_IN_PLACE + // 4. Write the elements and length of the List + // To do this here, we need full access to WasmBackend, or we could make a Zig wrapper return NotImplemented; } StrCountGraphemes => return BuiltinCall(bitcode::STR_COUNT_GRAPEHEME_CLUSTERS), @@ -78,8 +79,8 @@ pub fn dispatch_low_level<'a>( StrFromUtf8 => return BuiltinCall(bitcode::STR_FROM_UTF8), StrTrimLeft => return BuiltinCall(bitcode::STR_TRIM_LEFT), StrTrimRight => return BuiltinCall(bitcode::STR_TRIM_RIGHT), - StrFromUtf8Range => return BuiltinCall(bitcode::STR_FROM_UTF8_RANGE), // refcounting errors - StrToUtf8 => return BuiltinCall(bitcode::STR_TO_UTF8), // refcounting errors + StrFromUtf8Range => return BuiltinCall(bitcode::STR_FROM_UTF8_RANGE), + StrToUtf8 => return BuiltinCall(bitcode::STR_TO_UTF8), StrRepeat => return BuiltinCall(bitcode::STR_REPEAT), StrTrim => return BuiltinCall(bitcode::STR_TRIM), From 88b779c3ffa927659d2bf6950b8805069566f1da Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 16 Jan 2022 08:25:36 +0000 Subject: [PATCH 236/541] Wasm: Create CodeGenNumber --- compiler/gen_wasm/src/low_level.rs | 106 ++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 3 deletions(-) diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index d0d029ce9f..66947d71fb 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -1,12 +1,18 @@ -use roc_builtins::bitcode::{self, FloatWidth}; +use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_module::low_level::{LowLevel, LowLevel::*}; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout}; use roc_reporting::internal_error; -use crate::layout::{StackMemoryFormat::*, WasmLayout}; +use crate::layout::{ + StackMemoryFormat::{self, *}, + WasmLayout, +}; use crate::storage::{Storage, StoredValue}; -use crate::wasm_module::{Align, CodeBuilder, ValueType::*}; +use crate::wasm_module::{ + Align, CodeBuilder, + ValueType::{self, *}, +}; #[derive(Debug)] pub enum LowlevelBuildResult { @@ -15,6 +21,100 @@ pub enum LowlevelBuildResult { NotImplemented, } +/// Number types used for Wasm code gen +/// Unlike other enums, this contains no details about layout or storage. +/// Its purpose is to help simplify the arms of the main lowlevel `match` below. +/// +/// Note: Wasm I32 is used for Roc I8, I16, I32, U8, U16, and U32, since it's +/// the smallest integer supported in the Wasm instruction set. +/// We may choose different instructions for signed and unsigned integers, +/// but they share the same Wasm value type. +enum CodeGenNumber { + I32, // Supported in Wasm instruction set + I64, // Supported in Wasm instruction set + F32, // Supported in Wasm instruction set + F64, // Supported in Wasm instruction set + I128, // Bytes in memory, needs Zig builtins + F128, // Bytes in memory, needs Zig builtins + Decimal, // Bytes in memory, needs Zig builtins +} + +impl From> for CodeGenNumber { + fn from(layout: Layout) -> CodeGenNumber { + use CodeGenNumber::*; + + let not_num_error = + || internal_error!("Tried to perform a Num low-level operation on {:?}", layout); + match layout { + Layout::Builtin(builtin) => match builtin { + Builtin::Int(int_width) => match int_width { + IntWidth::U8 => I32, + IntWidth::U16 => I32, + IntWidth::U32 => I32, + IntWidth::U64 => I64, + IntWidth::U128 => I128, + IntWidth::I8 => I32, + IntWidth::I16 => I32, + IntWidth::I32 => I32, + IntWidth::I64 => I64, + IntWidth::I128 => I128, + }, + Builtin::Float(float_width) => match float_width { + FloatWidth::F32 => F32, + FloatWidth::F64 => F64, + FloatWidth::F128 => F128, + }, + Builtin::Decimal => Decimal, + _ => not_num_error(), + }, + _ => not_num_error(), + } + } +} + +impl From for CodeGenNumber { + fn from(value_type: ValueType) -> CodeGenNumber { + match value_type { + ValueType::I32 => CodeGenNumber::I32, + ValueType::I64 => CodeGenNumber::I64, + ValueType::F32 => CodeGenNumber::F32, + ValueType::F64 => CodeGenNumber::F64, + } + } +} + +impl From for CodeGenNumber { + fn from(format: StackMemoryFormat) -> CodeGenNumber { + match format { + StackMemoryFormat::Int128 => CodeGenNumber::I128, + StackMemoryFormat::Float128 => CodeGenNumber::F128, + StackMemoryFormat::Decimal => CodeGenNumber::Decimal, + StackMemoryFormat::DataStructure => { + internal_error!("Tried to perform a Num low-level operation on a data structure") + } + } + } +} + +impl From for CodeGenNumber { + fn from(wasm_layout: WasmLayout) -> CodeGenNumber { + match wasm_layout { + WasmLayout::Primitive(value_type, _) => CodeGenNumber::from(value_type), + WasmLayout::StackMemory { format, .. } => CodeGenNumber::from(format), + } + } +} + +impl From for CodeGenNumber { + fn from(stored: StoredValue) -> CodeGenNumber { + match stored { + StoredValue::VirtualMachineStack { value_type, .. } => CodeGenNumber::from(value_type), + StoredValue::Local { value_type, .. } => CodeGenNumber::from(value_type), + StoredValue::StackMemory { format, .. } => CodeGenNumber::from(format), + } + } +} + pub fn dispatch_low_level<'a>( code_builder: &mut CodeBuilder<'a>, storage: &mut Storage<'a>, From f635dd87767cfcdc42e8bcc87b2ae44d32ade471 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 16 Jan 2022 14:45:26 +0000 Subject: [PATCH 237/541] Wasm: Refactor lowlevels to get more flexibility without increasing boilerplate. --- compiler/gen_wasm/src/backend.rs | 100 +-- compiler/gen_wasm/src/low_level.rs | 1211 +++++++++++++++------------- 2 files changed, 669 insertions(+), 642 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index c68d14529c..d9cc9316d8 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -16,7 +16,7 @@ use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout}; use roc_reporting::internal_error; use crate::layout::{CallConv, ReturnMethod, StackMemoryFormat, WasmLayout}; -use crate::low_level::{dispatch_low_level, LowlevelBuildResult}; +use crate::low_level::LowLevelCall; use crate::storage::{StackMemoryLocation, Storage, StoredValue, StoredValueKind}; use crate::wasm_module::linking::{DataSymbol, LinkingSegment, WasmObjectSymbol}; use crate::wasm_module::sections::{DataMode, DataSegment}; @@ -35,7 +35,7 @@ use crate::{ const CONST_SEGMENT_BASE_ADDR: u32 = 1024; pub struct WasmBackend<'a> { - env: &'a Env<'a>, + pub env: &'a Env<'a>, interns: &'a mut Interns, // Module-level data @@ -48,8 +48,8 @@ pub struct WasmBackend<'a> { helper_proc_gen: CodeGenHelp<'a>, // Function-level data - code_builder: CodeBuilder<'a>, - storage: Storage<'a>, + pub code_builder: CodeBuilder<'a>, + pub storage: Storage<'a>, /// how many blocks deep are we (used for jumps) block_depth: u32, @@ -805,21 +805,21 @@ impl<'a> WasmBackend<'a> { &mut self, lowlevel: LowLevel, arguments: &'a [Symbol], - return_sym: Symbol, - mono_layout: &Layout<'a>, - storage: &StoredValue, + ret_symbol: Symbol, + ret_layout: &Layout<'a>, + ret_storage: &StoredValue, ) { use LowLevel::*; - let return_layout = WasmLayout::new(mono_layout); + let wasm_layout = WasmLayout::new(ret_layout); match lowlevel { Eq | NotEq => self.build_eq_or_neq( lowlevel, arguments, - return_sym, - return_layout, - mono_layout, - storage, + ret_symbol, + wasm_layout, + ret_layout, + ret_storage, ), PtrCast => { // Don't want Zig calling convention when casting pointers. @@ -829,37 +829,14 @@ impl<'a> WasmBackend<'a> { // Almost all lowlevels take this branch, except for the special cases above _ => { - // Load the arguments using Zig calling convention - let (param_types, ret_type) = self.storage.load_symbols_for_call( - self.env.arena, - &mut self.code_builder, - arguments, - return_sym, - &return_layout, - CallConv::Zig, - ); - - // Generate instructions OR decide which Zig function to call - let build_result = dispatch_low_level( - &mut self.code_builder, - &mut self.storage, + let low_level_call = LowLevelCall { lowlevel, arguments, - &return_layout, - mono_layout, - ); - - // Handle the result - use LowlevelBuildResult::*; - match build_result { - Done => {} - BuiltinCall(name) => { - self.expr_call_zig_builtin(name, param_types, ret_type); - } - NotImplemented => { - todo!("Low level operation {:?}", lowlevel) - } - } + ret_symbol, + ret_layout: ret_layout.to_owned(), + ret_storage: ret_storage.to_owned(), + }; + low_level_call.generate(self); } } } @@ -867,7 +844,7 @@ impl<'a> WasmBackend<'a> { /// Generate a call instruction to a Zig builtin function. /// And if we haven't seen it before, add an Import and linker data for it. /// Zig calls use LLVM's "fast" calling convention rather than our usual C ABI. - fn expr_call_zig_builtin( + pub fn call_zig_builtin_after_loading_args( &mut self, name: &'a str, param_types: Vec<'a, ValueType>, @@ -992,7 +969,7 @@ impl<'a> WasmBackend<'a> { // Call the foreign function. (Zig and C calling conventions are the same for this signature) let param_types = bumpalo::vec![in self.env.arena; ValueType::I32, ValueType::I32]; let ret_type = Some(ValueType::I32); - self.expr_call_zig_builtin("roc_alloc", param_types, ret_type); + self.call_zig_builtin_after_loading_args("roc_alloc", param_types, ret_type); // Save the allocation address to a temporary local variable let local_id = self.storage.create_anonymous_local(ValueType::I32); @@ -1368,7 +1345,7 @@ impl<'a> WasmBackend<'a> { &return_layout, CallConv::Zig, ); - self.expr_call_zig_builtin(bitcode::STR_EQUAL, param_types, ret_type); + self.call_zig_builtin_after_loading_args(bitcode::STR_EQUAL, param_types, ret_type); if matches!(lowlevel, LowLevel::NotEq) { self.code_builder.i32_eqz(); } @@ -1472,22 +1449,25 @@ impl<'a> WasmBackend<'a> { // Both args are finite let first = [arguments[0]]; let second = [arguments[1]]; - dispatch_low_level( - &mut self.code_builder, - &mut self.storage, - LowLevel::NumIsFinite, - &first, - &return_layout, - mono_layout, - ); - dispatch_low_level( - &mut self.code_builder, - &mut self.storage, - LowLevel::NumIsFinite, - &second, - &return_layout, - mono_layout, - ); + + // TODO! + // + // dispatch_low_level( + // &mut self.code_builder, + // &mut self.storage, + // LowLevel::NumIsFinite, + // &first, + // &return_layout, + // mono_layout, + // ); + // dispatch_low_level( + // &mut self.code_builder, + // &mut self.storage, + // LowLevel::NumIsFinite, + // &second, + // &return_layout, + // mono_layout, + // ); self.code_builder.i32_and(); // AND they have the same bytes diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index 66947d71fb..4195e05261 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -1,25 +1,15 @@ +use bumpalo::collections::Vec; use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_module::low_level::{LowLevel, LowLevel::*}; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout}; use roc_reporting::internal_error; -use crate::layout::{ - StackMemoryFormat::{self, *}, - WasmLayout, -}; -use crate::storage::{Storage, StoredValue}; -use crate::wasm_module::{ - Align, CodeBuilder, - ValueType::{self, *}, -}; - -#[derive(Debug)] -pub enum LowlevelBuildResult { - Done, - BuiltinCall(&'static str), - NotImplemented, -} +use crate::backend::WasmBackend; +use crate::layout::CallConv; +use crate::layout::{StackMemoryFormat, WasmLayout}; +use crate::storage::StoredValue; +use crate::wasm_module::{Align, ValueType}; /// Number types used for Wasm code gen /// Unlike other enums, this contains no details about layout or storage. @@ -29,7 +19,8 @@ pub enum LowlevelBuildResult { /// the smallest integer supported in the Wasm instruction set. /// We may choose different instructions for signed and unsigned integers, /// but they share the same Wasm value type. -enum CodeGenNumber { +#[derive(Clone, Copy, Debug)] +enum CodeGenNumType { I32, // Supported in Wasm instruction set I64, // Supported in Wasm instruction set F32, // Supported in Wasm instruction set @@ -39,9 +30,15 @@ enum CodeGenNumber { Decimal, // Bytes in memory, needs Zig builtins } -impl From> for CodeGenNumber { - fn from(layout: Layout) -> CodeGenNumber { - use CodeGenNumber::*; +impl CodeGenNumType { + pub fn for_symbol<'a>(backend: &WasmBackend<'a>, symbol: Symbol) -> Self { + Self::from(backend.storage.get(&symbol)) + } +} + +impl From> for CodeGenNumType { + fn from(layout: Layout) -> CodeGenNumType { + use CodeGenNumType::*; let not_num_error = || internal_error!("Tried to perform a Num low-level operation on {:?}", layout); @@ -72,23 +69,23 @@ impl From> for CodeGenNumber { } } -impl From for CodeGenNumber { - fn from(value_type: ValueType) -> CodeGenNumber { +impl From for CodeGenNumType { + fn from(value_type: ValueType) -> CodeGenNumType { match value_type { - ValueType::I32 => CodeGenNumber::I32, - ValueType::I64 => CodeGenNumber::I64, - ValueType::F32 => CodeGenNumber::F32, - ValueType::F64 => CodeGenNumber::F64, + ValueType::I32 => CodeGenNumType::I32, + ValueType::I64 => CodeGenNumType::I64, + ValueType::F32 => CodeGenNumType::F32, + ValueType::F64 => CodeGenNumType::F64, } } } -impl From for CodeGenNumber { - fn from(format: StackMemoryFormat) -> CodeGenNumber { +impl From for CodeGenNumType { + fn from(format: StackMemoryFormat) -> CodeGenNumType { match format { - StackMemoryFormat::Int128 => CodeGenNumber::I128, - StackMemoryFormat::Float128 => CodeGenNumber::F128, - StackMemoryFormat::Decimal => CodeGenNumber::Decimal, + StackMemoryFormat::Int128 => CodeGenNumType::I128, + StackMemoryFormat::Float128 => CodeGenNumType::F128, + StackMemoryFormat::Decimal => CodeGenNumType::Decimal, StackMemoryFormat::DataStructure => { internal_error!("Tried to perform a Num low-level operation on a data structure") } @@ -96,586 +93,636 @@ impl From for CodeGenNumber { } } -impl From for CodeGenNumber { - fn from(wasm_layout: WasmLayout) -> CodeGenNumber { +impl From for CodeGenNumType { + fn from(wasm_layout: WasmLayout) -> CodeGenNumType { match wasm_layout { - WasmLayout::Primitive(value_type, _) => CodeGenNumber::from(value_type), - WasmLayout::StackMemory { format, .. } => CodeGenNumber::from(format), + WasmLayout::Primitive(value_type, _) => CodeGenNumType::from(value_type), + WasmLayout::StackMemory { format, .. } => CodeGenNumType::from(format), } } } -impl From for CodeGenNumber { - fn from(stored: StoredValue) -> CodeGenNumber { +impl From<&StoredValue> for CodeGenNumType { + fn from(stored: &StoredValue) -> CodeGenNumType { + use StoredValue::*; match stored { - StoredValue::VirtualMachineStack { value_type, .. } => CodeGenNumber::from(value_type), - StoredValue::Local { value_type, .. } => CodeGenNumber::from(value_type), - StoredValue::StackMemory { format, .. } => CodeGenNumber::from(format), + VirtualMachineStack { value_type, .. } => CodeGenNumType::from(*value_type), + Local { value_type, .. } => CodeGenNumType::from(*value_type), + StackMemory { format, .. } => CodeGenNumType::from(*format), } } } -pub fn dispatch_low_level<'a>( - code_builder: &mut CodeBuilder<'a>, - storage: &mut Storage<'a>, - lowlevel: LowLevel, - args: &[Symbol], - ret_layout: &WasmLayout, - mono_layout: &Layout<'a>, -) -> LowlevelBuildResult { - use LowlevelBuildResult::*; +pub struct LowLevelCall<'a> { + pub lowlevel: LowLevel, + pub arguments: &'a [Symbol], + pub ret_symbol: Symbol, + pub ret_layout: Layout<'a>, + pub ret_storage: StoredValue, +} - let panic_ret_type = - || internal_error!("Invalid return layout for {:?}: {:?}", lowlevel, ret_layout); +impl<'a> LowLevelCall<'a> { + /// Load symbol values for a Zig call or numerical operation + /// For numerical ops, this just pushes the arguments to the Wasm VM's value stack + /// It implements the calling convention used by Zig for both numbers and structs + /// When returning structs, it also loads a stack frame pointer for the return value + fn load_args(&self, backend: &mut WasmBackend<'a>) -> (Vec<'a, ValueType>, Option) { + let fn_signature = backend.storage.load_symbols_for_call( + backend.env.arena, + &mut backend.code_builder, + self.arguments, + self.ret_symbol, + &WasmLayout::new(&self.ret_layout), + CallConv::Zig, + ); + fn_signature + } - match lowlevel { - // Str - StrConcat => return BuiltinCall(bitcode::STR_CONCAT), - StrJoinWith => return BuiltinCall(bitcode::STR_JOIN_WITH), - StrIsEmpty => { - code_builder.i64_const(i64::MIN); - code_builder.i64_eq(); + fn load_args_and_call_zig(&self, backend: &mut WasmBackend<'a>, name: &'a str) { + let (param_types, ret_type) = self.load_args(backend); + backend.call_zig_builtin_after_loading_args(name, param_types, ret_type); + } + + /// Wrap an integer whose Wasm representation is i32 + /// This may seem like deliberately introducing an error! + /// But we want all targets to behave the same, and hash algos rely on wrapping. + /// Discussion: https://github.com/rtfeldman/roc/pull/2117#discussion_r760723063 + fn wrap_i32(&self, backend: &mut WasmBackend<'a>) { + let invalid = + || internal_error!("Expected integer <= 32 bits, found {:?}", self.ret_layout); + + let (shift, is_signed) = match self.ret_layout { + Layout::Builtin(Builtin::Int(int_width)) => match int_width { + IntWidth::U8 => (24, false), + IntWidth::U16 => (16, false), + IntWidth::I8 => (24, true), + IntWidth::I16 => (16, true), + IntWidth::I32 | IntWidth::U32 => return, + _ => invalid(), + }, + _ => invalid(), + }; + + backend.code_builder.i32_const(shift); + backend.code_builder.i32_shl(); + backend.code_builder.i32_const(shift); + if is_signed { + backend.code_builder.i32_shr_s(); + } else { + backend.code_builder.i32_shr_u(); } - StrStartsWith => return BuiltinCall(bitcode::STR_STARTS_WITH), - StrStartsWithCodePt => return BuiltinCall(bitcode::STR_STARTS_WITH_CODE_PT), - StrEndsWith => return BuiltinCall(bitcode::STR_ENDS_WITH), - StrSplit => { - // LLVM implementation (build_str.rs) does the following - // 1. Call bitcode::STR_COUNT_SEGMENTS - // 2. Allocate a `List Str` - // 3. Call bitcode::STR_STR_SPLIT_IN_PLACE - // 4. Write the elements and length of the List - // To do this here, we need full access to WasmBackend, or we could make a Zig wrapper - return NotImplemented; - } - StrCountGraphemes => return BuiltinCall(bitcode::STR_COUNT_GRAPEHEME_CLUSTERS), - StrToNum => { - let number_layout = match mono_layout { - Layout::Struct(fields) => fields[0], - _ => internal_error!("Unexpected mono layout {:?} for StrToNum", mono_layout), - }; - // match on the return layout to figure out which zig builtin we need - let intrinsic = match number_layout { - Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width], - Layout::Builtin(Builtin::Float(float_width)) => &bitcode::STR_TO_FLOAT[float_width], - Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR, - rest => internal_error!("Unexpected builtin {:?} for StrToNum", rest), - }; + } - return BuiltinCall(intrinsic); - } - StrFromInt => { - // This does not get exposed in user space. We switched to NumToStr instead. - // We can probably just leave this as NotImplemented. We may want remove this LowLevel. - // see: https://github.com/rtfeldman/roc/pull/2108 - return NotImplemented; - } - StrFromFloat => { - // linker errors for __ashlti3, __fixunsdfti, __multi3, __udivti3, __umodti3 - // https://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html - // https://gcc.gnu.org/onlinedocs/gccint/Soft-float-library-routines.html - return NotImplemented; - } - StrFromUtf8 => return BuiltinCall(bitcode::STR_FROM_UTF8), - StrTrimLeft => return BuiltinCall(bitcode::STR_TRIM_LEFT), - StrTrimRight => return BuiltinCall(bitcode::STR_TRIM_RIGHT), - StrFromUtf8Range => return BuiltinCall(bitcode::STR_FROM_UTF8_RANGE), - StrToUtf8 => return BuiltinCall(bitcode::STR_TO_UTF8), - StrRepeat => return BuiltinCall(bitcode::STR_REPEAT), - StrTrim => return BuiltinCall(bitcode::STR_TRIM), + pub fn generate(&self, backend: &mut WasmBackend<'a>) { + use CodeGenNumType::*; - // List - ListLen => { - // List structure has already been loaded as i64 (Zig calling convention) - // We want the second (more significant) 32 bits. Shift and convert to i32. - code_builder.i64_const(32); - code_builder.i64_shr_u(); - code_builder.i32_wrap_i64(); - } + let panic_ret_type = || { + internal_error!( + "Invalid return layout for {:?}: {:?}", + self.lowlevel, + self.ret_layout + ) + }; - ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat - | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap | ListMap2 - | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil - | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListSublist - | ListDropAt | ListSwap | ListAny | ListAll | ListFindUnsafe | DictSize | DictEmpty - | DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues - | DictUnion | DictIntersection | DictDifference | DictWalk | SetFromList => { - return NotImplemented; - } - - // Num - NumAdd => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_add(), - I64 => code_builder.i64_add(), - F32 => code_builder.f32_add(), - F64 => code_builder.f64_add(), - }, - WasmLayout::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_ADD_WITH_OVERFLOW), - }, - }, - NumAddWrap => match ret_layout { - WasmLayout::Primitive(value_type, size) => match value_type { - I32 => { - code_builder.i32_add(); - wrap_i32(code_builder, *size); - } - I64 => code_builder.i64_add(), - F32 => code_builder.f32_add(), - F64 => code_builder.f64_add(), - }, - WasmLayout::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_ADD_WITH_OVERFLOW), - }, - }, - NumToStr => return NotImplemented, - NumAddChecked => return NotImplemented, - NumAddSaturated => return NotImplemented, - NumSub => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_sub(), - I64 => code_builder.i64_sub(), - F32 => code_builder.f32_sub(), - F64 => code_builder.f64_sub(), - }, - WasmLayout::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_SUB_WITH_OVERFLOW), - }, - }, - NumSubWrap => match ret_layout { - WasmLayout::Primitive(value_type, size) => match value_type { - I32 => { - code_builder.i32_sub(); - wrap_i32(code_builder, *size); - } - I64 => code_builder.i64_sub(), - F32 => code_builder.f32_sub(), - F64 => code_builder.f64_sub(), - }, - WasmLayout::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_SUB_WITH_OVERFLOW), - }, - }, - NumSubChecked => return NotImplemented, - NumSubSaturated => return NotImplemented, - NumMul => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_mul(), - I64 => code_builder.i64_mul(), - F32 => code_builder.f32_mul(), - F64 => code_builder.f64_mul(), - }, - WasmLayout::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_MUL_WITH_OVERFLOW), - }, - }, - NumMulWrap => match ret_layout { - WasmLayout::Primitive(value_type, size) => match value_type { - I32 => { - code_builder.i32_mul(); - wrap_i32(code_builder, *size); - } - I64 => code_builder.i64_mul(), - F32 => code_builder.f32_mul(), - F64 => code_builder.f64_mul(), - }, - WasmLayout::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_MUL_WITH_OVERFLOW), - }, - }, - NumMulChecked => return NotImplemented, - NumGt => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => code_builder.i32_gt_s(), - I64 => code_builder.i64_gt_s(), - F32 => code_builder.f32_gt(), - F64 => code_builder.f64_gt(), - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumGte => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => code_builder.i32_ge_s(), - I64 => code_builder.i64_ge_s(), - F32 => code_builder.f32_ge(), - F64 => code_builder.f64_ge(), - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumLt => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => code_builder.i32_lt_s(), - I64 => code_builder.i64_lt_s(), - F32 => code_builder.f32_lt(), - F64 => code_builder.f64_lt(), - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumLte => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => code_builder.i32_le_s(), - I64 => code_builder.i64_le_s(), - F32 => code_builder.f32_le(), - F64 => code_builder.f64_le(), - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumCompare => return NotImplemented, - NumDivUnchecked => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => code_builder.i32_div_s(), - I64 => code_builder.i64_div_s(), - F32 => code_builder.f32_div(), - F64 => code_builder.f64_div(), - }, - StoredValue::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_DIV), - }, - }, - NumDivCeilUnchecked => return NotImplemented, - NumRemUnchecked => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => code_builder.i32_rem_s(), - I64 => code_builder.i64_rem_s(), - F32 => return NotImplemented, - F64 => return NotImplemented, - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumIsMultipleOf => return NotImplemented, - NumAbs => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => { - let arg_storage = storage.get(&args[0]).to_owned(); - storage.ensure_value_has_local(code_builder, args[0], arg_storage); - storage.load_symbols(code_builder, args); - code_builder.i32_const(0); - storage.load_symbols(code_builder, args); - code_builder.i32_sub(); - storage.load_symbols(code_builder, args); - code_builder.i32_const(0); - code_builder.i32_ge_s(); - code_builder.select(); - } - I64 => { - let arg_storage = storage.get(&args[0]).to_owned(); - storage.ensure_value_has_local(code_builder, args[0], arg_storage); - storage.load_symbols(code_builder, args); - code_builder.i64_const(0); - storage.load_symbols(code_builder, args); - code_builder.i64_sub(); - storage.load_symbols(code_builder, args); - code_builder.i64_const(0); - code_builder.i64_ge_s(); - code_builder.select(); - } - F32 => code_builder.f32_abs(), - F64 => code_builder.f64_abs(), - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumNeg => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => { - code_builder.i32_const(0); - storage.load_symbols(code_builder, args); - code_builder.i32_sub(); - } - I64 => { - code_builder.i64_const(0); - storage.load_symbols(code_builder, args); - code_builder.i64_sub(); - } - F32 => code_builder.f32_neg(), - F64 => code_builder.f64_neg(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumSin => return NotImplemented, - NumCos => return NotImplemented, - NumSqrtUnchecked => return NotImplemented, - NumLogUnchecked => return NotImplemented, - NumRound => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - F32 => return BuiltinCall(&bitcode::NUM_ROUND[FloatWidth::F32]), - F64 => return BuiltinCall(&bitcode::NUM_ROUND[FloatWidth::F64]), - _ => return NotImplemented, - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumToFloat => { - use StoredValue::*; - let stored = storage.get(&args[0]); - match ret_layout { - WasmLayout::Primitive(ret_type, _) => match stored { - VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { - match (ret_type, value_type) { - (F32, I32) => code_builder.f32_convert_s_i32(), - (F32, I64) => code_builder.f32_convert_s_i64(), - (F32, F32) => {} - (F32, F64) => code_builder.f32_demote_f64(), - - (F64, I32) => code_builder.f64_convert_s_i32(), - (F64, I64) => code_builder.f64_convert_s_i64(), - (F64, F32) => code_builder.f64_promote_f32(), - (F64, F64) => {} - - _ => panic_ret_type(), - } - } - StackMemory { .. } => return NotImplemented, - }, - WasmLayout::StackMemory { .. } => return NotImplemented, + match self.lowlevel { + // Str + StrConcat => self.load_args_and_call_zig(backend, bitcode::STR_CONCAT), + StrJoinWith => self.load_args_and_call_zig(backend, bitcode::STR_JOIN_WITH), + StrIsEmpty => { + self.load_args(backend); + backend.code_builder.i64_const(i64::MIN); + backend.code_builder.i64_eq(); } - } - NumPow => return NotImplemented, - NumCeiling => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => { - code_builder.f32_ceil(); - code_builder.i32_trunc_s_f32() - } - I64 => { - code_builder.f64_ceil(); - code_builder.i64_trunc_s_f64() - } - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumPowInt => return NotImplemented, - NumFloor => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => { - code_builder.f32_floor(); - code_builder.i32_trunc_s_f32() - } - I64 => { - code_builder.f64_floor(); - code_builder.i64_trunc_s_f64() - } - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumIsFinite => { - use StoredValue::*; - match storage.get(&args[0]) { - VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { - match value_type { - I32 | I64 => code_builder.i32_const(1), // always true for integers - F32 => { - code_builder.i32_reinterpret_f32(); - code_builder.i32_const(0x7f80_0000); - code_builder.i32_and(); - code_builder.i32_const(0x7f80_0000); - code_builder.i32_ne(); - } - F64 => { - code_builder.i64_reinterpret_f64(); - code_builder.i64_const(0x7ff0_0000_0000_0000); - code_builder.i64_and(); - code_builder.i64_const(0x7ff0_0000_0000_0000); - code_builder.i64_ne(); - } + StrStartsWith => self.load_args_and_call_zig(backend, bitcode::STR_STARTS_WITH), + StrStartsWithCodePt => { + self.load_args_and_call_zig(backend, bitcode::STR_STARTS_WITH_CODE_PT) + } + StrEndsWith => self.load_args_and_call_zig(backend, bitcode::STR_ENDS_WITH), + StrSplit => { + // LLVM implementation (build_str.rs) does the following + // 1. Call bitcode::STR_COUNT_SEGMENTS + // 2. Allocate a `List Str` + // 3. Call bitcode::STR_STR_SPLIT_IN_PLACE + // 4. Write the elements and length of the List + // To do this here, we need full access to WasmBackend, or we could make a Zig wrapper + todo!("{:?}", self.lowlevel); + } + StrCountGraphemes => { + self.load_args_and_call_zig(backend, bitcode::STR_COUNT_GRAPEHEME_CLUSTERS) + } + StrToNum => { + let number_layout = match self.ret_layout { + Layout::Struct(fields) => fields[0], + _ => { + internal_error!("Unexpected mono layout {:?} for StrToNum", self.ret_layout) } - } - StackMemory { - format, location, .. - } => { - let (local_id, offset) = location.local_and_offset(storage.stack_frame_pointer); + }; + // match on the return layout to figure out which zig builtin we need + let intrinsic = match number_layout { + Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width], + Layout::Builtin(Builtin::Float(float_width)) => { + &bitcode::STR_TO_FLOAT[float_width] + } + Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR, + rest => internal_error!("Unexpected builtin {:?} for StrToNum", rest), + }; - match format { - Int128 => code_builder.i32_const(1), - Float128 => { - code_builder.get_local(local_id); - code_builder.i64_load(Align::Bytes4, offset + 8); - code_builder.i64_const(0x7fff_0000_0000_0000); - code_builder.i64_and(); - code_builder.i64_const(0x7fff_0000_0000_0000); - code_builder.i64_ne(); - } - Decimal => { - code_builder.get_local(local_id); - code_builder.i64_load(Align::Bytes4, offset + 8); - code_builder.i64_const(0x7100_0000_0000_0000); - code_builder.i64_and(); - code_builder.i64_const(0x7100_0000_0000_0000); - code_builder.i64_ne(); - } - DataStructure => return NotImplemented, + self.load_args_and_call_zig(backend, intrinsic); + } + StrFromInt => { + // This does not get exposed in user space. We switched to NumToStr instead. + // We can probably just leave this as NotImplemented. We may want remove this LowLevel. + // see: https://github.com/rtfeldman/roc/pull/2108 + todo!("{:?}", self.lowlevel); + } + StrFromFloat => { + // linker errors for __ashlti3, __fixunsdfti, __multi3, __udivti3, __umodti3 + // https://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html + // https://gcc.gnu.org/onlinedocs/gccint/Soft-float-library-routines.html + todo!("{:?}", self.lowlevel); + } + StrFromUtf8 => self.load_args_and_call_zig(backend, bitcode::STR_FROM_UTF8), + StrTrimLeft => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_LEFT), + StrTrimRight => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_RIGHT), + StrFromUtf8Range => self.load_args_and_call_zig(backend, bitcode::STR_FROM_UTF8_RANGE), + StrToUtf8 => self.load_args_and_call_zig(backend, bitcode::STR_TO_UTF8), + StrRepeat => self.load_args_and_call_zig(backend, bitcode::STR_REPEAT), + StrTrim => self.load_args_and_call_zig(backend, bitcode::STR_TRIM), + + // List + ListLen => match backend.storage.get(&self.arguments[0]) { + StoredValue::StackMemory { location, .. } => { + let (local_id, offset) = + location.local_and_offset(backend.storage.stack_frame_pointer); + backend.code_builder.get_local(local_id); + backend.code_builder.i32_load(Align::Bytes4, offset + 4); + } + _ => internal_error!("invalid storage for List"), + }, + + ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat + | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap + | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk + | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith + | ListSublist | ListDropAt | ListSwap | ListAny | ListAll | ListFindUnsafe + | DictSize | DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe + | DictKeys | DictValues | DictUnion | DictIntersection | DictDifference | DictWalk + | SetFromList => { + todo!("{:?}", self.lowlevel); + } + + // Num + NumAdd => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + CodeGenNumType::I32 => backend.code_builder.i32_add(), + CodeGenNumType::I64 => backend.code_builder.i64_add(), + CodeGenNumType::F32 => backend.code_builder.f32_add(), + CodeGenNumType::F64 => backend.code_builder.f64_add(), + CodeGenNumType::I128 => todo!("{:?}", self.lowlevel), + CodeGenNumType::F128 => todo!("{:?}", self.lowlevel), + CodeGenNumType::Decimal => { + self.load_args_and_call_zig(backend, bitcode::DEC_ADD_WITH_OVERFLOW) } } } - } - NumAtan => { - let width = float_width_from_layout(ret_layout); - return BuiltinCall(&bitcode::NUM_ATAN[width]); - } - NumAcos => { - let width = float_width_from_layout(ret_layout); - return BuiltinCall(&bitcode::NUM_ACOS[width]); - } - NumAsin => { - let width = float_width_from_layout(ret_layout); - return BuiltinCall(&bitcode::NUM_ASIN[width]); - } - NumBytesToU16 => return NotImplemented, - NumBytesToU32 => return NotImplemented, - NumBitwiseAnd => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_and(), - I64 => code_builder.i64_and(), - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumBitwiseXor => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_xor(), - I64 => code_builder.i64_xor(), - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumBitwiseOr => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_or(), - I64 => code_builder.i64_or(), - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumShiftLeftBy => { - // Swap order of arguments - storage.load_symbols(code_builder, &[args[1], args[0]]); - match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_shl(), - I64 => code_builder.i64_shl(), + + NumAddWrap => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => { + backend.code_builder.i32_add(); + self.wrap_i32(backend); + } + I64 => backend.code_builder.i64_add(), + F32 => backend.code_builder.f32_add(), + F64 => backend.code_builder.f64_add(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_ADD_WITH_OVERFLOW), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumToStr => todo!("{:?}", self.lowlevel), + NumAddChecked => todo!("{:?}", self.lowlevel), + NumAddSaturated => todo!("{:?}", self.lowlevel), + NumSub => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_sub(), + I64 => backend.code_builder.i64_sub(), + F32 => backend.code_builder.f32_sub(), + F64 => backend.code_builder.f64_sub(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_SUB_WITH_OVERFLOW), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumSubWrap => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => { + backend.code_builder.i32_sub(); + self.wrap_i32(backend); + } + I64 => backend.code_builder.i64_sub(), + F32 => backend.code_builder.f32_sub(), + F64 => backend.code_builder.f64_sub(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_SUB_WITH_OVERFLOW), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumSubChecked => todo!("{:?}", self.lowlevel), + NumSubSaturated => todo!("{:?}", self.lowlevel), + NumMul => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_mul(), + I64 => backend.code_builder.i64_mul(), + F32 => backend.code_builder.f32_mul(), + F64 => backend.code_builder.f64_mul(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_MUL_WITH_OVERFLOW), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumMulWrap => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => { + backend.code_builder.i32_mul(); + self.wrap_i32(backend); + } + I64 => backend.code_builder.i64_mul(), + F32 => backend.code_builder.f32_mul(), + F64 => backend.code_builder.f64_mul(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_MUL_WITH_OVERFLOW), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumMulChecked => todo!("{:?}", self.lowlevel), + NumGt => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => backend.code_builder.i32_gt_s(), + I64 => backend.code_builder.i64_gt_s(), + F32 => backend.code_builder.f32_gt(), + F64 => backend.code_builder.f64_gt(), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumGte => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => backend.code_builder.i32_ge_s(), + I64 => backend.code_builder.i64_ge_s(), + F32 => backend.code_builder.f32_ge(), + F64 => backend.code_builder.f64_ge(), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumLt => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => backend.code_builder.i32_lt_s(), + I64 => backend.code_builder.i64_lt_s(), + F32 => backend.code_builder.f32_lt(), + F64 => backend.code_builder.f64_lt(), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumLte => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => backend.code_builder.i32_le_s(), + I64 => backend.code_builder.i64_le_s(), + F32 => backend.code_builder.f32_le(), + F64 => backend.code_builder.f64_le(), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumCompare => todo!("{:?}", self.lowlevel), + NumDivUnchecked => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => backend.code_builder.i32_div_s(), + I64 => backend.code_builder.i64_div_s(), + F32 => backend.code_builder.f32_div(), + F64 => backend.code_builder.f64_div(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_DIV), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumDivCeilUnchecked => todo!("{:?}", self.lowlevel), + NumRemUnchecked => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => backend.code_builder.i32_rem_s(), + I64 => backend.code_builder.i64_rem_s(), + _ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout), + } + } + NumIsMultipleOf => todo!("{:?}", self.lowlevel), + NumAbs => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => { + let code_builder = &mut backend.code_builder; + let arg_storage = backend.storage.get(&self.arguments[0]).to_owned(); + backend.storage.ensure_value_has_local( + code_builder, + self.arguments[0], + arg_storage, + ); + backend.storage.load_symbols(code_builder, self.arguments); + code_builder.i32_const(0); + backend.storage.load_symbols(code_builder, self.arguments); + code_builder.i32_sub(); + backend.storage.load_symbols(code_builder, self.arguments); + code_builder.i32_const(0); + code_builder.i32_ge_s(); + code_builder.select(); + } + I64 => { + let code_builder = &mut backend.code_builder; + let arg_storage = backend.storage.get(&self.arguments[0]).to_owned(); + backend.storage.ensure_value_has_local( + code_builder, + self.arguments[0], + arg_storage, + ); + backend.storage.load_symbols(code_builder, self.arguments); + code_builder.i64_const(0); + backend.storage.load_symbols(code_builder, self.arguments); + code_builder.i64_sub(); + backend.storage.load_symbols(code_builder, self.arguments); + code_builder.i64_const(0); + code_builder.i64_ge_s(); + code_builder.select(); + } + F32 => backend.code_builder.f32_abs(), + F64 => backend.code_builder.f64_abs(), + _ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout), + } + } + NumNeg => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => { + backend.code_builder.i32_const(0); + backend + .storage + .load_symbols(&mut backend.code_builder, self.arguments); + backend.code_builder.i32_sub(); + } + I64 => { + backend.code_builder.i64_const(0); + backend + .storage + .load_symbols(&mut backend.code_builder, self.arguments); + backend.code_builder.i64_sub(); + } + F32 => backend.code_builder.f32_neg(), + F64 => backend.code_builder.f64_neg(), + _ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout), + } + } + NumSin => todo!("{:?}", self.lowlevel), + NumCos => todo!("{:?}", self.lowlevel), + NumSqrtUnchecked => todo!("{:?}", self.lowlevel), + NumLogUnchecked => todo!("{:?}", self.lowlevel), + NumRound => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + F32 => { + self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND[FloatWidth::F32]) + } + F64 => { + self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND[FloatWidth::F64]) + } + _ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout), + } + } + NumToFloat => { + self.load_args(backend); + let ret_type = CodeGenNumType::from(self.ret_layout); + let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]); + match (ret_type, arg_type) { + (F32, I32) => backend.code_builder.f32_convert_s_i32(), + (F32, I64) => backend.code_builder.f32_convert_s_i64(), + (F32, F32) => {} + (F32, F64) => backend.code_builder.f32_demote_f64(), + + (F64, I32) => backend.code_builder.f64_convert_s_i32(), + (F64, I64) => backend.code_builder.f64_convert_s_i64(), + (F64, F32) => backend.code_builder.f64_promote_f32(), + (F64, F64) => {} + + _ => todo!("{:?}: {:?} -> {:?}", self.lowlevel, arg_type, ret_type), + } + } + NumPow => todo!("{:?}", self.lowlevel), + NumCeiling => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => { + backend.code_builder.f32_ceil(); + backend.code_builder.i32_trunc_s_f32() + } + I64 => { + backend.code_builder.f64_ceil(); + backend.code_builder.i64_trunc_s_f64() + } _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, + } } - } - NumShiftRightBy => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_shr_s(), - I64 => code_builder.i64_shr_s(), - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumShiftRightZfBy => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_shr_u(), - I64 => code_builder.i64_shr_u(), - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumIntCast => { - use StoredValue::*; - let stored = storage.get(&args[0]); - match ret_layout { - WasmLayout::Primitive(ret_type, _) => match stored { + NumPowInt => todo!("{:?}", self.lowlevel), + NumFloor => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => { + backend.code_builder.f32_floor(); + backend.code_builder.i32_trunc_s_f32() + } + I64 => { + backend.code_builder.f64_floor(); + backend.code_builder.i64_trunc_s_f64() + } + _ => panic_ret_type(), + } + } + NumIsFinite => { + use StoredValue::*; + self.load_args(backend); + match backend.storage.get(&self.arguments[0]) { VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { - match (ret_type, value_type) { - (I32, I32) => {} - (I32, I64) => code_builder.i32_wrap_i64(), - (I32, F32) => code_builder.i32_trunc_s_f32(), - (I32, F64) => code_builder.i32_trunc_s_f64(), - - (I64, I32) => code_builder.i64_extend_s_i32(), - (I64, I64) => {} - (I64, F32) => code_builder.i64_trunc_s_f32(), - (I64, F64) => code_builder.i64_trunc_s_f64(), - - (F32, I32) => code_builder.f32_convert_s_i32(), - (F32, I64) => code_builder.f32_convert_s_i64(), - (F32, F32) => {} - (F32, F64) => code_builder.f32_demote_f64(), - - (F64, I32) => code_builder.f64_convert_s_i32(), - (F64, I64) => code_builder.f64_convert_s_i64(), - (F64, F32) => code_builder.f64_promote_f32(), - (F64, F64) => {} + match value_type { + ValueType::I32 | ValueType::I64 => backend.code_builder.i32_const(1), // always true for integers + ValueType::F32 => { + backend.code_builder.i32_reinterpret_f32(); + backend.code_builder.i32_const(0x7f80_0000); + backend.code_builder.i32_and(); + backend.code_builder.i32_const(0x7f80_0000); + backend.code_builder.i32_ne(); + } + ValueType::F64 => { + backend.code_builder.i64_reinterpret_f64(); + backend.code_builder.i64_const(0x7ff0_0000_0000_0000); + backend.code_builder.i64_and(); + backend.code_builder.i64_const(0x7ff0_0000_0000_0000); + backend.code_builder.i64_ne(); + } } } + StackMemory { + format, location, .. + } => { + let (local_id, offset) = + location.local_and_offset(backend.storage.stack_frame_pointer); - StackMemory { .. } => return NotImplemented, - }, - WasmLayout::StackMemory { .. } => return NotImplemented, + match format { + StackMemoryFormat::Int128 => backend.code_builder.i32_const(1), + StackMemoryFormat::Float128 => { + backend.code_builder.get_local(local_id); + backend.code_builder.i64_load(Align::Bytes4, offset + 8); + backend.code_builder.i64_const(0x7fff_0000_0000_0000); + backend.code_builder.i64_and(); + backend.code_builder.i64_const(0x7fff_0000_0000_0000); + backend.code_builder.i64_ne(); + } + StackMemoryFormat::Decimal => { + backend.code_builder.get_local(local_id); + backend.code_builder.i64_load(Align::Bytes4, offset + 8); + backend.code_builder.i64_const(0x7100_0000_0000_0000); + backend.code_builder.i64_and(); + backend.code_builder.i64_const(0x7100_0000_0000_0000); + backend.code_builder.i64_ne(); + } + StackMemoryFormat::DataStructure => panic_ret_type(), + } + } + } + } + NumAtan => match self.ret_layout { + Layout::Builtin(Builtin::Float(width)) => { + self.load_args_and_call_zig(backend, &bitcode::NUM_ATAN[width]); + } + _ => panic_ret_type(), + }, + NumAcos => match self.ret_layout { + Layout::Builtin(Builtin::Float(width)) => { + self.load_args_and_call_zig(backend, &bitcode::NUM_ACOS[width]); + } + _ => panic_ret_type(), + }, + NumAsin => match self.ret_layout { + Layout::Builtin(Builtin::Float(width)) => { + self.load_args_and_call_zig(backend, &bitcode::NUM_ASIN[width]); + } + _ => panic_ret_type(), + }, + NumBytesToU16 => todo!("{:?}", self.lowlevel), + NumBytesToU32 => todo!("{:?}", self.lowlevel), + NumBitwiseAnd => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_and(), + I64 => backend.code_builder.i64_and(), + I128 => todo!("{:?} for I128", self.lowlevel), + _ => panic_ret_type(), + } + } + NumBitwiseXor => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_xor(), + I64 => backend.code_builder.i64_xor(), + I128 => todo!("{:?} for I128", self.lowlevel), + _ => panic_ret_type(), + } + } + NumBitwiseOr => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_or(), + I64 => backend.code_builder.i64_or(), + I128 => todo!("{:?} for I128", self.lowlevel), + _ => panic_ret_type(), + } + } + NumShiftLeftBy => { + // Swap order of arguments + backend.storage.load_symbols( + &mut backend.code_builder, + &[self.arguments[1], self.arguments[0]], + ); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_shl(), + I64 => backend.code_builder.i64_shl(), + I128 => todo!("{:?} for I128", self.lowlevel), + _ => panic_ret_type(), + } + } + NumShiftRightBy => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_shr_s(), + I64 => backend.code_builder.i64_shr_s(), + I128 => todo!("{:?} for I128", self.lowlevel), + _ => panic_ret_type(), + } + } + NumShiftRightZfBy => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_shr_u(), + I64 => backend.code_builder.i64_shr_u(), + I128 => todo!("{:?} for I128", self.lowlevel), + _ => panic_ret_type(), + } + } + NumIntCast => { + self.load_args(backend); + let ret_type = CodeGenNumType::from(self.ret_layout); + let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]); + match (ret_type, arg_type) { + (I32, I32) => {} + (I32, I64) => backend.code_builder.i32_wrap_i64(), + (I32, F32) => backend.code_builder.i32_trunc_s_f32(), + (I32, F64) => backend.code_builder.i32_trunc_s_f64(), + + (I64, I32) => backend.code_builder.i64_extend_s_i32(), + (I64, I64) => {} + (I64, F32) => backend.code_builder.i64_trunc_s_f32(), + (I64, F64) => backend.code_builder.i64_trunc_s_f64(), + + (F32, I32) => backend.code_builder.f32_convert_s_i32(), + (F32, I64) => backend.code_builder.f32_convert_s_i64(), + (F32, F32) => {} + (F32, F64) => backend.code_builder.f32_demote_f64(), + + (F64, I32) => backend.code_builder.f64_convert_s_i32(), + (F64, I64) => backend.code_builder.f64_convert_s_i64(), + (F64, F32) => backend.code_builder.f64_promote_f32(), + (F64, F64) => {} + + _ => todo!("{:?}: {:?} -> {:?}", self.lowlevel, arg_type, ret_type), + } + } + And => { + self.load_args(backend); + backend.code_builder.i32_and(); + } + Or => { + self.load_args(backend); + backend.code_builder.i32_or(); + } + Not => { + self.load_args(backend); + backend.code_builder.i32_eqz(); + } + ExpectTrue => todo!("{:?}", self.lowlevel), + RefCountInc => self.load_args_and_call_zig(backend, bitcode::UTILS_INCREF), + RefCountDec => self.load_args_and_call_zig(backend, bitcode::UTILS_DECREF), + Eq | NotEq | Hash | PtrCast => { + internal_error!("{:?} should be handled in backend.rs", self.lowlevel) } } - And => code_builder.i32_and(), - Or => code_builder.i32_or(), - Not => code_builder.i32_eqz(), - ExpectTrue => return NotImplemented, - RefCountInc => return BuiltinCall(bitcode::UTILS_INCREF), - RefCountDec => return BuiltinCall(bitcode::UTILS_DECREF), - Eq | NotEq | Hash | PtrCast => { - internal_error!("{:?} should be handled in backend.rs", lowlevel) - } - } - Done -} - -/// Wrap an integer whose Wasm representation is i32 -fn wrap_i32(code_builder: &mut CodeBuilder, size: u32) { - match size { - 1 => { - // Underlying Roc value is i8 - code_builder.i32_const(24); - code_builder.i32_shl(); - code_builder.i32_const(24); - code_builder.i32_shr_s(); - } - 2 => { - // Underlying Roc value is i16 - code_builder.i32_const(16); - code_builder.i32_shl(); - code_builder.i32_const(16); - code_builder.i32_shr_s(); - } - _ => {} // the only other possible value is 4, and i32 wraps natively - } -} - -fn float_width_from_layout(wasm_layout: &WasmLayout) -> FloatWidth { - match wasm_layout { - WasmLayout::Primitive(F32, _) => FloatWidth::F32, - WasmLayout::Primitive(F64, _) => FloatWidth::F64, - _ => internal_error!("{:?} does not have a FloatWidth", wasm_layout), } } From f354b4842be0a357b9646af9f6f83d0e773cf478 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 17 Jan 2022 09:07:12 +0000 Subject: [PATCH 238/541] Wasm: Move Eq/NotEq into LowLevelCall --- compiler/gen_wasm/src/backend.rs | 319 +++++------------------------ compiler/gen_wasm/src/low_level.rs | 302 +++++++++++++++++++++------ 2 files changed, 292 insertions(+), 329 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index d9cc9316d8..97533c074b 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -1,7 +1,7 @@ use bumpalo::{self, collections::Vec}; use code_builder::Align; -use roc_builtins::bitcode::{self, IntWidth}; +use roc_builtins::bitcode::IntWidth; use roc_collections::all::MutMap; use roc_module::ident::Ident; use roc_module::low_level::{LowLevel, LowLevelWrapperType}; @@ -15,9 +15,9 @@ use roc_mono::ir::{ use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout}; use roc_reporting::internal_error; -use crate::layout::{CallConv, ReturnMethod, StackMemoryFormat, WasmLayout}; +use crate::layout::{CallConv, ReturnMethod, WasmLayout}; use crate::low_level::LowLevelCall; -use crate::storage::{StackMemoryLocation, Storage, StoredValue, StoredValueKind}; +use crate::storage::{Storage, StoredValue, StoredValueKind}; use crate::wasm_module::linking::{DataSymbol, LinkingSegment, WasmObjectSymbol}; use crate::wasm_module::sections::{DataMode, DataSegment}; use crate::wasm_module::{ @@ -809,36 +809,14 @@ impl<'a> WasmBackend<'a> { ret_layout: &Layout<'a>, ret_storage: &StoredValue, ) { - use LowLevel::*; - let wasm_layout = WasmLayout::new(ret_layout); - - match lowlevel { - Eq | NotEq => self.build_eq_or_neq( - lowlevel, - arguments, - ret_symbol, - wasm_layout, - ret_layout, - ret_storage, - ), - PtrCast => { - // Don't want Zig calling convention when casting pointers. - self.storage.load_symbols(&mut self.code_builder, arguments); - } - Hash => todo!("Generic hash function generation"), - - // Almost all lowlevels take this branch, except for the special cases above - _ => { - let low_level_call = LowLevelCall { - lowlevel, - arguments, - ret_symbol, - ret_layout: ret_layout.to_owned(), - ret_storage: ret_storage.to_owned(), - }; - low_level_call.generate(self); - } - } + let low_level_call = LowLevelCall { + lowlevel, + arguments, + ret_symbol, + ret_layout: ret_layout.to_owned(), + ret_storage: ret_storage.to_owned(), + }; + low_level_call.generate(self); } /// Generate a call instruction to a Zig builtin function. @@ -847,11 +825,9 @@ impl<'a> WasmBackend<'a> { pub fn call_zig_builtin_after_loading_args( &mut self, name: &'a str, - param_types: Vec<'a, ValueType>, - ret_type: Option, + num_wasm_args: usize, + has_return_val: bool, ) { - let num_wasm_args = param_types.len(); - let has_return_val = ret_type.is_some(); let fn_index = self.module.names.functions[name.as_bytes()]; self.called_preload_fns.push(fn_index); let linker_symbol_index = u32::MAX; @@ -860,6 +836,43 @@ impl<'a> WasmBackend<'a> { .call(fn_index, linker_symbol_index, num_wasm_args, has_return_val); } + /// Call a helper procedure that implements `==` for a data structure (not numbers or Str) + /// If this is the first call for this Layout, it will generate the IR for the procedure. + /// Call stack is expr_call_low_level -> LowLevelCall::generate -> call_eq_specialized + /// It's a bit circuitous, but the alternative is to give low_level.rs `pub` access to + /// interns, helper_proc_gen, and expr(). That just seemed all wrong. + pub fn call_eq_specialized( + &mut self, + arguments: &'a [Symbol], + arg_layout: &Layout<'a>, + ret_symbol: Symbol, + ret_storage: &StoredValue, + ) { + let ident_ids = self + .interns + .all_ident_ids + .get_mut(&self.env.module_id) + .unwrap(); + + // Get an IR expression for the call to the specialized procedure + let (specialized_call_expr, new_specializations) = self + .helper_proc_gen + .call_specialized_equals(ident_ids, arg_layout, arguments); + + // If any new specializations were created, register their symbol data + for spec in new_specializations.into_iter() { + self.register_helper_proc(spec); + } + + // Generate Wasm code for the IR call expression + self.expr( + ret_symbol, + self.env.arena.alloc(specialized_call_expr), + &Layout::Builtin(Builtin::Bool), + ret_storage, + ); + } + /******************************************************************* * Structs *******************************************************************/ @@ -967,9 +980,7 @@ impl<'a> WasmBackend<'a> { self.code_builder.i32_const(alignment_bytes as i32); // Call the foreign function. (Zig and C calling conventions are the same for this signature) - let param_types = bumpalo::vec![in self.env.arena; ValueType::I32, ValueType::I32]; - let ret_type = Some(ValueType::I32); - self.call_zig_builtin_after_loading_args("roc_alloc", param_types, ret_type); + self.call_zig_builtin_after_loading_args("roc_alloc", 2, true); // Save the allocation address to a temporary local variable let local_id = self.storage.create_anonymous_local(ValueType::I32); @@ -1310,232 +1321,4 @@ impl<'a> WasmBackend<'a> { self.storage .copy_value_from_memory(&mut self.code_builder, symbol, from_ptr, from_offset); } - - /******************************************************************* - * Equality - *******************************************************************/ - - fn build_eq_or_neq( - &mut self, - lowlevel: LowLevel, - arguments: &'a [Symbol], - return_sym: Symbol, - return_layout: WasmLayout, - mono_layout: &Layout<'a>, - storage: &StoredValue, - ) { - let arg_layout = self.storage.symbol_layouts[&arguments[0]]; - let other_arg_layout = self.storage.symbol_layouts[&arguments[1]]; - debug_assert!( - arg_layout == other_arg_layout, - "Cannot do `==` comparison on different types" - ); - - match arg_layout { - Layout::Builtin( - Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal, - ) => self.build_eq_or_neq_number(lowlevel, arguments, return_layout, mono_layout), - - Layout::Builtin(Builtin::Str) => { - let (param_types, ret_type) = self.storage.load_symbols_for_call( - self.env.arena, - &mut self.code_builder, - arguments, - return_sym, - &return_layout, - CallConv::Zig, - ); - self.call_zig_builtin_after_loading_args(bitcode::STR_EQUAL, param_types, ret_type); - if matches!(lowlevel, LowLevel::NotEq) { - self.code_builder.i32_eqz(); - } - } - - // Empty record is always equal to empty record. - // There are no runtime arguments to check, so just emit true or false. - Layout::Struct(fields) if fields.is_empty() => { - self.code_builder - .i32_const(if lowlevel == LowLevel::Eq { 1 } else { 0 }); - } - - // Void is always equal to void. This is the type for the contents of the empty list in `[] == []` - // This code will never execute, but we need a true or false value to type-check - Layout::Union(UnionLayout::NonRecursive(tags)) if tags.is_empty() => { - self.code_builder - .i32_const(if lowlevel == LowLevel::Eq { 1 } else { 0 }); - } - - Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_)) - | Layout::Struct(_) - | Layout::Union(_) - | Layout::LambdaSet(_) => { - self.build_eq_specialized(&arg_layout, arguments, return_sym, storage); - if matches!(lowlevel, LowLevel::NotEq) { - self.code_builder.i32_eqz(); - } - } - - Layout::RecursivePointer => { - internal_error!( - "Tried to apply `==` to RecursivePointer values {:?}", - arguments, - ) - } - } - } - - fn build_eq_or_neq_number( - &mut self, - lowlevel: LowLevel, - arguments: &'a [Symbol], - return_layout: WasmLayout, - mono_layout: &Layout<'a>, - ) { - use StoredValue::*; - match self.storage.get(&arguments[0]).to_owned() { - VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { - self.storage.load_symbols(&mut self.code_builder, arguments); - match lowlevel { - LowLevel::Eq => match value_type { - ValueType::I32 => self.code_builder.i32_eq(), - ValueType::I64 => self.code_builder.i64_eq(), - ValueType::F32 => self.code_builder.f32_eq(), - ValueType::F64 => self.code_builder.f64_eq(), - }, - LowLevel::NotEq => match value_type { - ValueType::I32 => self.code_builder.i32_ne(), - ValueType::I64 => self.code_builder.i64_ne(), - ValueType::F32 => self.code_builder.f32_ne(), - ValueType::F64 => self.code_builder.f64_ne(), - }, - _ => internal_error!("Low-level op {:?} handled in the wrong place", lowlevel), - } - } - StackMemory { - format, - location: location0, - .. - } => { - if let StackMemory { - location: location1, - .. - } = self.storage.get(&arguments[1]).to_owned() - { - self.build_eq_num128( - format, - [location0, location1], - arguments, - return_layout, - mono_layout, - ); - if matches!(lowlevel, LowLevel::NotEq) { - self.code_builder.i32_eqz(); - } - } - } - } - } - - fn build_eq_num128( - &mut self, - format: StackMemoryFormat, - locations: [StackMemoryLocation; 2], - arguments: &'a [Symbol], - return_layout: WasmLayout, - mono_layout: &Layout<'a>, - ) { - match format { - StackMemoryFormat::Decimal => { - // Both args are finite - let first = [arguments[0]]; - let second = [arguments[1]]; - - // TODO! - // - // dispatch_low_level( - // &mut self.code_builder, - // &mut self.storage, - // LowLevel::NumIsFinite, - // &first, - // &return_layout, - // mono_layout, - // ); - // dispatch_low_level( - // &mut self.code_builder, - // &mut self.storage, - // LowLevel::NumIsFinite, - // &second, - // &return_layout, - // mono_layout, - // ); - self.code_builder.i32_and(); - - // AND they have the same bytes - self.build_eq_num128_bytes(locations); - self.code_builder.i32_and(); - } - - StackMemoryFormat::Int128 => self.build_eq_num128_bytes(locations), - - StackMemoryFormat::Float128 => todo!("equality for f128"), - - StackMemoryFormat::DataStructure => { - internal_error!("Data structure equality is handled elsewhere") - } - } - } - - /// Check that two 128-bit numbers contain the same bytes - fn build_eq_num128_bytes(&mut self, locations: [StackMemoryLocation; 2]) { - let (local0, offset0) = locations[0].local_and_offset(self.storage.stack_frame_pointer); - let (local1, offset1) = locations[1].local_and_offset(self.storage.stack_frame_pointer); - - self.code_builder.get_local(local0); - self.code_builder.i64_load(Align::Bytes8, offset0); - self.code_builder.get_local(local1); - self.code_builder.i64_load(Align::Bytes8, offset1); - self.code_builder.i64_eq(); - - self.code_builder.get_local(local0); - self.code_builder.i64_load(Align::Bytes8, offset0 + 8); - self.code_builder.get_local(local1); - self.code_builder.i64_load(Align::Bytes8, offset1 + 8); - self.code_builder.i64_eq(); - - self.code_builder.i32_and(); - } - - /// Call a helper procedure that implements `==` for a specific data structure - fn build_eq_specialized( - &mut self, - arg_layout: &Layout<'a>, - arguments: &'a [Symbol], - return_sym: Symbol, - storage: &StoredValue, - ) { - let ident_ids = self - .interns - .all_ident_ids - .get_mut(&self.env.module_id) - .unwrap(); - - // Get an IR expression for the call to the specialized procedure - let (specialized_call_expr, new_specializations) = self - .helper_proc_gen - .call_specialized_equals(ident_ids, arg_layout, arguments); - - // If any new specializations were created, register their symbol data - for spec in new_specializations.into_iter() { - self.register_helper_proc(spec); - } - - // Generate Wasm code for the IR call expression - let bool_layout = Layout::Builtin(Builtin::Bool); - self.expr( - return_sym, - self.env.arena.alloc(specialized_call_expr), - &bool_layout, - storage, - ); - } } diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index 4195e05261..1ea652d999 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -2,13 +2,13 @@ use bumpalo::collections::Vec; use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_module::low_level::{LowLevel, LowLevel::*}; use roc_module::symbol::Symbol; -use roc_mono::layout::{Builtin, Layout}; +use roc_mono::layout::{Builtin, Layout, UnionLayout}; use roc_reporting::internal_error; use crate::backend::WasmBackend; use crate::layout::CallConv; use crate::layout::{StackMemoryFormat, WasmLayout}; -use crate::storage::StoredValue; +use crate::storage::{StackMemoryLocation, StoredValue}; use crate::wasm_module::{Align, ValueType}; /// Number types used for Wasm code gen @@ -31,7 +31,7 @@ enum CodeGenNumType { } impl CodeGenNumType { - pub fn for_symbol<'a>(backend: &WasmBackend<'a>, symbol: Symbol) -> Self { + pub fn for_symbol(backend: &WasmBackend<'_>, symbol: Symbol) -> Self { Self::from(backend.storage.get(&symbol)) } } @@ -125,22 +125,21 @@ impl<'a> LowLevelCall<'a> { /// Load symbol values for a Zig call or numerical operation /// For numerical ops, this just pushes the arguments to the Wasm VM's value stack /// It implements the calling convention used by Zig for both numbers and structs - /// When returning structs, it also loads a stack frame pointer for the return value + /// Result is the type signature of the call fn load_args(&self, backend: &mut WasmBackend<'a>) -> (Vec<'a, ValueType>, Option) { - let fn_signature = backend.storage.load_symbols_for_call( + backend.storage.load_symbols_for_call( backend.env.arena, &mut backend.code_builder, self.arguments, self.ret_symbol, &WasmLayout::new(&self.ret_layout), CallConv::Zig, - ); - fn_signature + ) } fn load_args_and_call_zig(&self, backend: &mut WasmBackend<'a>, name: &'a str) { let (param_types, ret_type) = self.load_args(backend); - backend.call_zig_builtin_after_loading_args(name, param_types, ret_type); + backend.call_zig_builtin_after_loading_args(name, param_types.len(), ret_type.is_some()); } /// Wrap an integer whose Wasm representation is i32 @@ -173,6 +172,7 @@ impl<'a> LowLevelCall<'a> { } } + /// Main entrypoint from WasmBackend pub fn generate(&self, backend: &mut WasmBackend<'a>) { use CodeGenNumType::*; @@ -547,58 +547,8 @@ impl<'a> LowLevelCall<'a> { _ => panic_ret_type(), } } - NumIsFinite => { - use StoredValue::*; - self.load_args(backend); - match backend.storage.get(&self.arguments[0]) { - VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { - match value_type { - ValueType::I32 | ValueType::I64 => backend.code_builder.i32_const(1), // always true for integers - ValueType::F32 => { - backend.code_builder.i32_reinterpret_f32(); - backend.code_builder.i32_const(0x7f80_0000); - backend.code_builder.i32_and(); - backend.code_builder.i32_const(0x7f80_0000); - backend.code_builder.i32_ne(); - } - ValueType::F64 => { - backend.code_builder.i64_reinterpret_f64(); - backend.code_builder.i64_const(0x7ff0_0000_0000_0000); - backend.code_builder.i64_and(); - backend.code_builder.i64_const(0x7ff0_0000_0000_0000); - backend.code_builder.i64_ne(); - } - } - } - StackMemory { - format, location, .. - } => { - let (local_id, offset) = - location.local_and_offset(backend.storage.stack_frame_pointer); + NumIsFinite => num_is_finite(backend, self.arguments[0]), - match format { - StackMemoryFormat::Int128 => backend.code_builder.i32_const(1), - StackMemoryFormat::Float128 => { - backend.code_builder.get_local(local_id); - backend.code_builder.i64_load(Align::Bytes4, offset + 8); - backend.code_builder.i64_const(0x7fff_0000_0000_0000); - backend.code_builder.i64_and(); - backend.code_builder.i64_const(0x7fff_0000_0000_0000); - backend.code_builder.i64_ne(); - } - StackMemoryFormat::Decimal => { - backend.code_builder.get_local(local_id); - backend.code_builder.i64_load(Align::Bytes4, offset + 8); - backend.code_builder.i64_const(0x7100_0000_0000_0000); - backend.code_builder.i64_and(); - backend.code_builder.i64_const(0x7100_0000_0000_0000); - backend.code_builder.i64_ne(); - } - StackMemoryFormat::DataStructure => panic_ret_type(), - } - } - } - } NumAtan => match self.ret_layout { Layout::Builtin(Builtin::Float(width)) => { self.load_args_and_call_zig(backend, &bitcode::NUM_ATAN[width]); @@ -720,8 +670,238 @@ impl<'a> LowLevelCall<'a> { ExpectTrue => todo!("{:?}", self.lowlevel), RefCountInc => self.load_args_and_call_zig(backend, bitcode::UTILS_INCREF), RefCountDec => self.load_args_and_call_zig(backend, bitcode::UTILS_DECREF), - Eq | NotEq | Hash | PtrCast => { - internal_error!("{:?} should be handled in backend.rs", self.lowlevel) + + PtrCast => { + let code_builder = &mut backend.code_builder; + backend.storage.load_symbols(code_builder, self.arguments); + } + + Hash => todo!("{:?}", self.lowlevel), + + Eq | NotEq => self.eq_or_neq(backend), + } + } + + /// Equality and inequality + /// These can operate on any data type (except functions) so they're more complex than other operators. + fn eq_or_neq(&self, backend: &mut WasmBackend<'a>) { + let arg_layout = backend.storage.symbol_layouts[&self.arguments[0]]; + let other_arg_layout = backend.storage.symbol_layouts[&self.arguments[1]]; + debug_assert!( + arg_layout == other_arg_layout, + "Cannot do `==` comparison on different types" + ); + + let invert_result = matches!(self.lowlevel, NotEq); + + match arg_layout { + Layout::Builtin( + Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal, + ) => self.eq_or_neq_number(backend), + + Layout::Builtin(Builtin::Str) => { + self.load_args_and_call_zig(backend, bitcode::STR_EQUAL); + if invert_result { + backend.code_builder.i32_eqz(); + } + } + + // Empty record is always equal to empty record. + // There are no runtime arguments to check, so just emit true or false. + Layout::Struct(fields) if fields.is_empty() => { + backend.code_builder.i32_const(!invert_result as i32); + } + + // Void is always equal to void. This is the type for the contents of the empty list in `[] == []` + // This instruction will never execute, but we need an i32 for module validation + Layout::Union(UnionLayout::NonRecursive(tags)) if tags.is_empty() => { + backend.code_builder.i32_const(!invert_result as i32); + } + + Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_)) + | Layout::Struct(_) + | Layout::Union(_) + | Layout::LambdaSet(_) => { + // Don't want Zig calling convention here, we're calling internal Roc functions + backend + .storage + .load_symbols(&mut backend.code_builder, self.arguments); + + backend.call_eq_specialized( + self.arguments, + &arg_layout, + self.ret_symbol, + &self.ret_storage, + ); + + if invert_result { + backend.code_builder.i32_eqz(); + } + } + + Layout::RecursivePointer => { + internal_error!( + "Tried to apply `==` to RecursivePointer values {:?}", + self.arguments, + ) + } + } + } + + fn eq_or_neq_number(&self, backend: &mut WasmBackend<'a>) { + use StoredValue::*; + + match backend.storage.get(&self.arguments[0]).to_owned() { + VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { + self.load_args(backend); + match self.lowlevel { + LowLevel::Eq => match value_type { + ValueType::I32 => backend.code_builder.i32_eq(), + ValueType::I64 => backend.code_builder.i64_eq(), + ValueType::F32 => backend.code_builder.f32_eq(), + ValueType::F64 => backend.code_builder.f64_eq(), + }, + LowLevel::NotEq => match value_type { + ValueType::I32 => backend.code_builder.i32_ne(), + ValueType::I64 => backend.code_builder.i64_ne(), + ValueType::F32 => backend.code_builder.f32_ne(), + ValueType::F64 => backend.code_builder.f64_ne(), + }, + _ => internal_error!("{:?} ended up in Equality code", self.lowlevel), + } + } + StackMemory { + format, + location: location0, + .. + } => { + if let StackMemory { + location: location1, + .. + } = backend.storage.get(&self.arguments[1]).to_owned() + { + self.eq_num128(backend, format, [location0, location1]); + if matches!(self.lowlevel, LowLevel::NotEq) { + backend.code_builder.i32_eqz(); + } + } + } + } + } + + /// Equality for 12-bit numbers. Checks if they're finite and contain the same bytes + /// Takes care of loading the arguments + fn eq_num128( + &self, + backend: &mut WasmBackend<'a>, + format: StackMemoryFormat, + locations: [StackMemoryLocation; 2], + ) { + match format { + StackMemoryFormat::Decimal => { + // Both args are finite + num_is_finite(backend, self.arguments[0]); + num_is_finite(backend, self.arguments[1]); + backend.code_builder.i32_and(); + + // AND they have the same bytes + Self::eq_num128_bytes(backend, locations); + backend.code_builder.i32_and(); + } + + StackMemoryFormat::Int128 => Self::eq_num128_bytes(backend, locations), + + StackMemoryFormat::Float128 => todo!("equality for f128"), + + StackMemoryFormat::DataStructure => { + internal_error!("Data structure equality is handled elsewhere") + } + } + } + + /// Check that two 128-bit numbers contain the same bytes + /// Loads *half* an argument at a time + /// (Don't call "load arguments" or "load symbols" helpers before this, it'll just waste instructions) + fn eq_num128_bytes(backend: &mut WasmBackend<'a>, locations: [StackMemoryLocation; 2]) { + let (local0, offset0) = locations[0].local_and_offset(backend.storage.stack_frame_pointer); + let (local1, offset1) = locations[1].local_and_offset(backend.storage.stack_frame_pointer); + + // Load & compare the first half of each argument + backend.code_builder.get_local(local0); + backend.code_builder.i64_load(Align::Bytes8, offset0); + backend.code_builder.get_local(local1); + backend.code_builder.i64_load(Align::Bytes8, offset1); + backend.code_builder.i64_eq(); + + // Load & compare the second half of each argument + backend.code_builder.get_local(local0); + backend.code_builder.i64_load(Align::Bytes8, offset0 + 8); + backend.code_builder.get_local(local1); + backend.code_builder.i64_load(Align::Bytes8, offset1 + 8); + backend.code_builder.i64_eq(); + + // First half matches AND second half matches + backend.code_builder.i32_and(); + } +} + +/// Helper for NumIsFinite op, and also part of Eq/NotEq +fn num_is_finite(backend: &mut WasmBackend<'_>, argument: Symbol) { + use StoredValue::*; + let stored = backend.storage.get(&argument).to_owned(); + match stored { + VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { + backend + .storage + .load_symbols(&mut backend.code_builder, &[argument]); + match value_type { + ValueType::I32 | ValueType::I64 => backend.code_builder.i32_const(1), // always true for integers + ValueType::F32 => { + backend.code_builder.i32_reinterpret_f32(); + backend.code_builder.i32_const(0x7f80_0000); + backend.code_builder.i32_and(); + backend.code_builder.i32_const(0x7f80_0000); + backend.code_builder.i32_ne(); + } + ValueType::F64 => { + backend.code_builder.i64_reinterpret_f64(); + backend.code_builder.i64_const(0x7ff0_0000_0000_0000); + backend.code_builder.i64_and(); + backend.code_builder.i64_const(0x7ff0_0000_0000_0000); + backend.code_builder.i64_ne(); + } + } + } + StackMemory { + format, location, .. + } => { + let (local_id, offset) = location.local_and_offset(backend.storage.stack_frame_pointer); + + match format { + StackMemoryFormat::Int128 => backend.code_builder.i32_const(1), + + // f128 is not supported anywhere else but it's easy to support it here, so why not... + StackMemoryFormat::Float128 => { + backend.code_builder.get_local(local_id); + backend.code_builder.i64_load(Align::Bytes4, offset + 8); + backend.code_builder.i64_const(0x7fff_0000_0000_0000); + backend.code_builder.i64_and(); + backend.code_builder.i64_const(0x7fff_0000_0000_0000); + backend.code_builder.i64_ne(); + } + + StackMemoryFormat::Decimal => { + backend.code_builder.get_local(local_id); + backend.code_builder.i64_load(Align::Bytes4, offset + 8); + backend.code_builder.i64_const(0x7100_0000_0000_0000); + backend.code_builder.i64_and(); + backend.code_builder.i64_const(0x7100_0000_0000_0000); + backend.code_builder.i64_ne(); + } + + StackMemoryFormat::DataStructure => { + internal_error!("Tried to perform NumIsFinite on a data structure") + } } } } From bcdbbd1a5d098ac63b6dd2b3ef09a73fbbb9dd90 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 17 Jan 2022 11:15:24 +0100 Subject: [PATCH 239/541] added comment to trigger CI --- ast/src/lang/rigids.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ast/src/lang/rigids.rs b/ast/src/lang/rigids.rs index c629904dbd..fc65866479 100644 --- a/ast/src/lang/rigids.rs +++ b/ast/src/lang/rigids.rs @@ -11,7 +11,7 @@ use roc_module::ident::Lowercase; use roc_types::subs::Variable; #[derive(Debug)] -pub struct Rigids { +pub struct Rigids { // Rigid type variable = type variable where type is specified by the programmar pub names: PoolVec<(Option, Variable)>, // 8B padding: [u8; 1], } From fc0047a452f4e51b7c6a9f5e811324b0bbc4c0f0 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 17 Jan 2022 11:17:25 +0100 Subject: [PATCH 240/541] typo --- ast/src/lang/rigids.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ast/src/lang/rigids.rs b/ast/src/lang/rigids.rs index fc65866479..b27ad254b0 100644 --- a/ast/src/lang/rigids.rs +++ b/ast/src/lang/rigids.rs @@ -11,7 +11,7 @@ use roc_module::ident::Lowercase; use roc_types::subs::Variable; #[derive(Debug)] -pub struct Rigids { // Rigid type variable = type variable where type is specified by the programmar +pub struct Rigids { // Rigid type variable = type variable where type is specified by the programmer pub names: PoolVec<(Option, Variable)>, // 8B padding: [u8; 1], } From 791434a88bb1611c1d3f1e67fe4c94e37f769b09 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 17 Jan 2022 11:33:35 +0100 Subject: [PATCH 241/541] fmt --- ast/src/lang/rigids.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ast/src/lang/rigids.rs b/ast/src/lang/rigids.rs index b27ad254b0..d86aa5ec88 100644 --- a/ast/src/lang/rigids.rs +++ b/ast/src/lang/rigids.rs @@ -11,7 +11,8 @@ use roc_module::ident::Lowercase; use roc_types::subs::Variable; #[derive(Debug)] -pub struct Rigids { // Rigid type variable = type variable where type is specified by the programmer +pub struct Rigids { + // Rigid type variable = type variable where type is specified by the programmer pub names: PoolVec<(Option, Variable)>, // 8B padding: [u8; 1], } From 79241adf8e6ad5a95bdf0c5c27f752ab2b0bcb1f Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Sat, 15 Jan 2022 13:33:00 -0700 Subject: [PATCH 242/541] Reorder Num.min/max docs --- compiler/builtins/docs/Num.roc | 88 +++++++++++++++++----------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index 34fb2ca201..269917446b 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -768,6 +768,13 @@ not : Int a -> Int a ## Limits +## The number zero. +## +## #Num.minNat is the lowest number that can be stored in a #Nat, which is zero +## because #Nat is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), +## and zero is the lowest unsigned number. Unsigned numbers cannot be negative. +minNat : Nat + ## The highest number that can be stored in a #Nat without overflowing its ## available memory and crashing. ## @@ -776,12 +783,12 @@ not : Int a -> Int a ## 32-bit system, this will be equal to #Num.maxU32. maxNat : Nat -## The number zero. +## The min number that can be stored in an #I32 without overflowing its +## available memory and crashing. ## -## #Num.minNat is the lowest number that can be stored in a #Nat, which is zero -## because #Nat is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), -## and zero is the lowest unsigned number. Unsigned numbers cannot be negative. -minNat : Nat +## Note that the positive version of this number is this is larger than +## #Int.maxI32, which means if you call #Num.abs on #Int.minI32, it will overflow and crash! +minI32 : I32 ## The highest number that can be stored in an #I32 without overflowing its ## available memory and crashing. @@ -790,25 +797,12 @@ minNat : Nat ## which means if you call #Num.abs on #Int.minI32, it will overflow and crash! maxI32 : I32 -## The min number that can be stored in an #I32 without overflowing its -## available memory and crashing. -## -## Note that the positive version of this number is this is larger than -## #Int.maxI32, which means if you call #Num.abs on #Int.minI32, it will overflow and crash! -minI32 : I32 - -## The highest number that can be stored in a #U64 without overflowing its -## available memory and crashing. -## -## For reference, that number is `18_446_744_073_709_551_615`, which is over 18 quintillion. -maxU64 : U64 - ## The number zero. ## -## #Num.minU64 is the lowest number that can be stored in a #U64, which is zero -## because #U64 is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), +## #Num.minU32 is the lowest number that can be stored in a #U32, which is zero +## because #U32 is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), ## and zero is the lowest unsigned number. Unsigned numbers cannot be negative. -minU64 : U64 +minU32 : U32 ## The highest number that can be stored in a #U32 without overflowing its ## available memory and crashing. @@ -818,10 +812,25 @@ maxU32 : U32 ## The number zero. ## -## #Num.minU32 is the lowest number that can be stored in a #U32, which is zero -## because #U32 is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), +## #Num.minU64 is the lowest number that can be stored in a #U64, which is zero +## because #U64 is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), ## and zero is the lowest unsigned number. Unsigned numbers cannot be negative. -minU32 : U32 +minU64 : U64 + +## The highest number that can be stored in a #U64 without overflowing its +## available memory and crashing. +## +## For reference, that number is `18_446_744_073_709_551_615`, which is over 18 quintillion. +maxU64 : U64 + +## The min number that can be stored in an #I128 without underflowing its +## available memory and crashing. +## +## For reference, this number is `-170_141_183_460_469_231_731_687_303_715_884_105_728`. +## +## Note that the positive version of this number is larger than #Int.maxI128, +## which means if you call #Num.abs on #Int.minI128, it will overflow and crash! +minI128 : I128 ## The highest number that can be stored in an #I128 without overflowing its ## available memory and crashing. @@ -833,45 +842,36 @@ minU32 : U32 ## which means if you call #Num.abs on #Int.minI128, it will overflow and crash! maxI128 : I128 -## The min number that can be stored in an #I128 without underflowing its -## available memory and crashing. -## -## For reference, this number is `-170_141_183_460_469_231_731_687_303_715_884_105_728`. -## -## Note that the positive version of this number is larger than #Int.maxI128, -## which means if you call #Num.abs on #Int.minI128, it will overflow and crash! -minI128 : I128 - -## The highest supported #F64 value you can have, which is approximately 1.8 × 10^308. -## -## If you go higher than this, your running Roc code will crash - so be careful not to! -maxF64 : F64 - -## The lowest supported #F64 value you can have, which is approximately -1.8 × 10^308. +## The lowest supported #F32 value you can have, which is approximately -1.8 × 10^308. ## ## If you go lower than this, your running Roc code will crash - so be careful not to! -minF64 : F64 +minF32 : F32 ## The highest supported #F32 value you can have, which is approximately 1.8 × 10^308. ## ## If you go higher than this, your running Roc code will crash - so be careful not to! maxF32 : F32 -## The lowest supported #F32 value you can have, which is approximately -1.8 × 10^308. +## The lowest supported #F64 value you can have, which is approximately -1.8 × 10^308. ## ## If you go lower than this, your running Roc code will crash - so be careful not to! -minF32 : F32 +minF64 : F64 -## The highest supported #Dec value you can have, which is precisely 170_141_183_460_469_231_731.687303715884105727. +## The highest supported #F64 value you can have, which is approximately 1.8 × 10^308. ## ## If you go higher than this, your running Roc code will crash - so be careful not to! -maxDec : Dec +maxF64 : F64 ## The lowest supported #Dec value you can have, which is precisely -170_141_183_460_469_231_731.687303715884105728. ## ## If you go lower than this, your running Roc code will crash - so be careful not to! minDec : Dec +## The highest supported #Dec value you can have, which is precisely 170_141_183_460_469_231_731.687303715884105727. +## +## If you go higher than this, your running Roc code will crash - so be careful not to! +maxDec : Dec + ## Constants ## An approximation of e, specifically 2.718281828459045. From cdf705941ee0cb2d2a4d12296295e91ca54e439a Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Sat, 15 Jan 2022 13:46:37 -0700 Subject: [PATCH 243/541] Improve Num.min/max docstrings --- compiler/builtins/docs/Num.roc | 54 +++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index 269917446b..4b491a3bc8 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -768,11 +768,13 @@ not : Int a -> Int a ## Limits -## The number zero. +## The lowest number that can be stored in a #Nat without underflowing its +## available memory and crashing. ## -## #Num.minNat is the lowest number that can be stored in a #Nat, which is zero -## because #Nat is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), -## and zero is the lowest unsigned number. Unsigned numbers cannot be negative. +## For reference, this is the number zero, because #Nat is +## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), +## and zero is the lowest unsigned number. +## Unsigned numbers cannot be negative. minNat : Nat ## The highest number that can be stored in a #Nat without overflowing its @@ -783,47 +785,57 @@ minNat : Nat ## 32-bit system, this will be equal to #Num.maxU32. maxNat : Nat -## The min number that can be stored in an #I32 without overflowing its +## The lowest number that can be stored in an #I32 without underflowing its ## available memory and crashing. ## -## Note that the positive version of this number is this is larger than -## #Int.maxI32, which means if you call #Num.abs on #Int.minI32, it will overflow and crash! +## For reference, this number is `-2_147_483_648`. +## +## Note that the positive version of this number is larger than #Int.maxI32, +## which means if you call #Num.abs on #Int.minI32, it will overflow and crash! minI32 : I32 ## The highest number that can be stored in an #I32 without overflowing its ## available memory and crashing. ## -## Note that this is smaller than the positive version of #Int.minI32 +## For reference, this number is `2_147_483_647`, +## which is over 2 million. +## +## Note that this is smaller than the positive version of #Int.minI32, ## which means if you call #Num.abs on #Int.minI32, it will overflow and crash! maxI32 : I32 -## The number zero. +## The lowest number that can be stored in a #U32 without underflowing its +## available memory and crashing. ## -## #Num.minU32 is the lowest number that can be stored in a #U32, which is zero -## because #U32 is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), -## and zero is the lowest unsigned number. Unsigned numbers cannot be negative. +## For reference, this number is zero, because #U32 is +## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), +## and zero is the lowest unsigned number. +## Unsigned numbers cannot be negative. minU32 : U32 ## The highest number that can be stored in a #U32 without overflowing its ## available memory and crashing. ## -## For reference, that number is `4_294_967_295`, which is over 4 million. +## For reference, this number is `4_294_967_295`, +## which is over 4 million. maxU32 : U32 -## The number zero. +## The lowest number that can be stored in a #U64 without underflowing its +## available memory and crashing. ## -## #Num.minU64 is the lowest number that can be stored in a #U64, which is zero -## because #U64 is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), +## For reference, this number is zero because #U64 is +## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), ## and zero is the lowest unsigned number. Unsigned numbers cannot be negative. minU64 : U64 ## The highest number that can be stored in a #U64 without overflowing its ## available memory and crashing. ## -## For reference, that number is `18_446_744_073_709_551_615`, which is over 18 quintillion. +## For reference, this number is `18_446_744_073_709_551_615`, +## which is over 18 quintillion. maxU64 : U64 -## The min number that can be stored in an #I128 without underflowing its +## The lowest number that can be stored in an #I128 without underflowing its ## available memory and crashing. ## ## For reference, this number is `-170_141_183_460_469_231_731_687_303_715_884_105_728`. @@ -862,12 +874,14 @@ minF64 : F64 ## If you go higher than this, your running Roc code will crash - so be careful not to! maxF64 : F64 -## The lowest supported #Dec value you can have, which is precisely -170_141_183_460_469_231_731.687303715884105728. +## The lowest supported #Dec value you can have, +## which is precisely -170_141_183_460_469_231_731.687303715884105728. ## ## If you go lower than this, your running Roc code will crash - so be careful not to! minDec : Dec -## The highest supported #Dec value you can have, which is precisely 170_141_183_460_469_231_731.687303715884105727. +## The highest supported #Dec value you can have, +## which is precisely 170_141_183_460_469_231_731.687303715884105727. ## ## If you go higher than this, your running Roc code will crash - so be careful not to! maxDec : Dec From 865dcd5507e7de62f7fd84db84c5299609b02574 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Sat, 15 Jan 2022 13:54:03 -0700 Subject: [PATCH 244/541] Expose existing `Num.min/max` builtin docs --- compiler/builtins/docs/Num.roc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index 4b491a3bc8..9624e1f387 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -62,9 +62,15 @@ interface Num isZero, log, maxFloat, + maxI32, + maxU32, + maxU64, maxI128, maxInt, minFloat, + minI32, + minU32, + minU64, minI128, minInt, modInt, From 591477e77b6bf7388c1e04ba3912fb2e4af4dae2 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Sat, 15 Jan 2022 14:05:10 -0700 Subject: [PATCH 245/541] Add most remaining `Num.min/max*` builtins This skips `min/maxU128`, as they require a subtle change to the `I128`-centric implementation of `Int`s. --- compiler/builtins/docs/Num.roc | 21 +++ compiler/builtins/src/std.rs | 24 +++ compiler/can/src/builtins.rs | 282 ++++++++++++++++++++++++++--- compiler/module/src/symbol.rs | 8 + compiler/solve/tests/solve_expr.rs | 96 ++++++++++ compiler/test_gen/src/gen_num.rs | 112 ++++++++++++ 6 files changed, 519 insertions(+), 24 deletions(-) diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index 9624e1f387..ed4e2f9e9d 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -64,12 +64,14 @@ interface Num maxFloat, maxI32, maxU32, + maxI64, maxU64, maxI128, maxInt, minFloat, minI32, minU32, + minI64, minU64, minI128, minInt, @@ -826,6 +828,25 @@ minU32 : U32 ## which is over 4 million. maxU32 : U32 +## The min number that can be stored in an #I64 without underflowing its +## available memory and crashing. +## +## For reference, this number is `-`. +## +## Note that the positive version of this number is larger than #Int.maxI64, +## which means if you call #Num.abs on #Int.minI64, it will overflow and crash! +minI64 : I64 + +## The highest number that can be stored in an #I64 without overflowing its +## available memory and crashing. +## +## For reference, this number is ``, +## which is over 2 million. +## +## Note that this is smaller than the positive version of #Int.minI64, +## which means if you call #Num.abs on #Int.minI64, it will overflow and crash! +maxI64 : I64 + ## The lowest number that can be stored in a #U64 without underflowing its ## available memory and crashing. ## diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index d338d34959..35a8ea86fb 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -397,6 +397,30 @@ pub fn types() -> MutMap { Box::new(bool_type()), ); + // minI32 : I32 + add_type!(Symbol::NUM_MIN_I32, i32_type()); + + // maxI32 : I32 + add_type!(Symbol::NUM_MAX_I32, i32_type()); + + // minU32 : U32 + add_type!(Symbol::NUM_MIN_U32, u32_type()); + + // maxU32 : U32 + add_type!(Symbol::NUM_MAX_U32, u32_type()); + + // minI64 : I64 + add_type!(Symbol::NUM_MIN_I64, i64_type()); + + // maxI64 : I64 + add_type!(Symbol::NUM_MAX_I64, i64_type()); + + // minU64 : U64 + add_type!(Symbol::NUM_MIN_U64, u64_type()); + + // maxU64 : U64 + add_type!(Symbol::NUM_MAX_U64, u64_type()); + // minI128 : I128 add_type!(Symbol::NUM_MIN_I128, i128_type()); diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 696df17538..df75271330 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -206,6 +206,14 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_SHIFT_RIGHT => num_shift_right_by, NUM_SHIFT_RIGHT_ZERO_FILL => num_shift_right_zf_by, NUM_INT_CAST=> num_int_cast, + NUM_MIN_I32=> num_min_i32, + NUM_MAX_I32=> num_max_i32, + NUM_MIN_U32=> num_min_u32, + NUM_MAX_U32=> num_max_u32, + NUM_MIN_I64=> num_min_i64, + NUM_MAX_I64=> num_max_i64, + NUM_MIN_U64=> num_min_u64, + NUM_MAX_U64=> num_max_u64, NUM_MIN_I128=> num_min_i128, NUM_MAX_I128=> num_max_i128, NUM_TO_STR => num_to_str, @@ -360,7 +368,7 @@ fn lowlevel_5(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def { fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def { let int_var = var_store.fresh(); let int_precision_var = var_store.fresh(); - let body = int(int_var, int_precision_var, i64::MAX.into()); + let body = int::(int_var, int_precision_var, i64::MAX); Def { annotation: None, @@ -375,7 +383,7 @@ fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def { let int_var = var_store.fresh(); let int_precision_var = var_store.fresh(); - let body = int(int_var, int_precision_var, i64::MIN.into()); + let body = int::(int_var, int_precision_var, i64::MIN); Def { annotation: None, @@ -861,7 +869,10 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def { let body = RunLowLevel { op: LowLevel::Eq, args: vec![ - (arg_var, int(var_store.fresh(), var_store.fresh(), 1)), + ( + arg_var, + int::(var_store.fresh(), var_store.fresh(), 1), + ), ( arg_var, RunLowLevel { @@ -1238,12 +1249,228 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_1(symbol, LowLevel::NumIntCast, var_store) } +/// Num.minI32: I32 +fn num_min_i32(symbol: Symbol, var_store: &mut VarStore) -> Def { + let int_var = var_store.fresh(); + let int_precision_var = var_store.fresh(); + let body = int::(int_var, int_precision_var, i32::MIN); + + let std = roc_builtins::std::types(); + let solved = std.get(&symbol).unwrap(); + let mut free_vars = roc_types::solved_types::FreeVars::default(); + let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); + + let annotation = crate::def::Annotation { + signature, + introduced_variables: Default::default(), + region: Region::zero(), + aliases: Default::default(), + }; + + Def { + annotation: Some(annotation), + expr_var: int_var, + loc_expr: Loc::at_zero(body), + loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), + pattern_vars: SendMap::default(), + } +} + +/// Num.maxI32: I32 +fn num_max_i32(symbol: Symbol, var_store: &mut VarStore) -> Def { + let int_var = var_store.fresh(); + let int_precision_var = var_store.fresh(); + let body = int::(int_var, int_precision_var, i32::MAX); + + let std = roc_builtins::std::types(); + let solved = std.get(&symbol).unwrap(); + let mut free_vars = roc_types::solved_types::FreeVars::default(); + let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); + + let annotation = crate::def::Annotation { + signature, + introduced_variables: Default::default(), + region: Region::zero(), + aliases: Default::default(), + }; + + Def { + annotation: Some(annotation), + expr_var: int_var, + loc_expr: Loc::at_zero(body), + loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), + pattern_vars: SendMap::default(), + } +} + +/// Num.minU32: U32 +fn num_min_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { + let int_var = var_store.fresh(); + let int_precision_var = var_store.fresh(); + let body = int::(int_var, int_precision_var, u32::MIN); + + let std = roc_builtins::std::types(); + let solved = std.get(&symbol).unwrap(); + let mut free_vars = roc_types::solved_types::FreeVars::default(); + let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); + + let annotation = crate::def::Annotation { + signature, + introduced_variables: Default::default(), + region: Region::zero(), + aliases: Default::default(), + }; + + Def { + annotation: Some(annotation), + expr_var: int_var, + loc_expr: Loc::at_zero(body), + loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), + pattern_vars: SendMap::default(), + } +} + +/// Num.maxU32: U32 +fn num_max_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { + let int_var = var_store.fresh(); + let int_precision_var = var_store.fresh(); + let body = int::(int_var, int_precision_var, u32::MAX); + + let std = roc_builtins::std::types(); + let solved = std.get(&symbol).unwrap(); + let mut free_vars = roc_types::solved_types::FreeVars::default(); + let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); + + let annotation = crate::def::Annotation { + signature, + introduced_variables: Default::default(), + region: Region::zero(), + aliases: Default::default(), + }; + + Def { + annotation: Some(annotation), + expr_var: int_var, + loc_expr: Loc::at_zero(body), + loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), + pattern_vars: SendMap::default(), + } +} + +/// Num.minI64: I64 +fn num_min_i64(symbol: Symbol, var_store: &mut VarStore) -> Def { + let int_var = var_store.fresh(); + let int_precision_var = var_store.fresh(); + let body = int::(int_var, int_precision_var, i64::MIN); + + let std = roc_builtins::std::types(); + let solved = std.get(&symbol).unwrap(); + let mut free_vars = roc_types::solved_types::FreeVars::default(); + let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); + + let annotation = crate::def::Annotation { + signature, + introduced_variables: Default::default(), + region: Region::zero(), + aliases: Default::default(), + }; + + Def { + annotation: Some(annotation), + expr_var: int_var, + loc_expr: Loc::at_zero(body), + loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), + pattern_vars: SendMap::default(), + } +} + +/// Num.maxI64: I64 +fn num_max_i64(symbol: Symbol, var_store: &mut VarStore) -> Def { + let int_var = var_store.fresh(); + let int_precision_var = var_store.fresh(); + let body = int::(int_var, int_precision_var, i64::MAX); + + let std = roc_builtins::std::types(); + let solved = std.get(&symbol).unwrap(); + let mut free_vars = roc_types::solved_types::FreeVars::default(); + let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); + + let annotation = crate::def::Annotation { + signature, + introduced_variables: Default::default(), + region: Region::zero(), + aliases: Default::default(), + }; + + Def { + annotation: Some(annotation), + expr_var: int_var, + loc_expr: Loc::at_zero(body), + loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), + pattern_vars: SendMap::default(), + } +} + +/// Num.minU64: U64 +fn num_min_u64(symbol: Symbol, var_store: &mut VarStore) -> Def { + let int_var = var_store.fresh(); + let int_precision_var = var_store.fresh(); + let body = int::(int_var, int_precision_var, u64::MIN); + + let std = roc_builtins::std::types(); + let solved = std.get(&symbol).unwrap(); + let mut free_vars = roc_types::solved_types::FreeVars::default(); + let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); + + let annotation = crate::def::Annotation { + signature, + introduced_variables: Default::default(), + region: Region::zero(), + aliases: Default::default(), + }; + + Def { + annotation: Some(annotation), + expr_var: int_var, + loc_expr: Loc::at_zero(body), + loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), + pattern_vars: SendMap::default(), + } +} + +/// Num.maxU64: U64 +fn num_max_u64(symbol: Symbol, var_store: &mut VarStore) -> Def { + let int_var = var_store.fresh(); + let int_precision_var = var_store.fresh(); + let body = int::(int_var, int_precision_var, u64::MAX); + + let std = roc_builtins::std::types(); + let solved = std.get(&symbol).unwrap(); + let mut free_vars = roc_types::solved_types::FreeVars::default(); + let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); + + let annotation = crate::def::Annotation { + signature, + introduced_variables: Default::default(), + region: Region::zero(), + aliases: Default::default(), + }; + + Def { + annotation: Some(annotation), + expr_var: int_var, + loc_expr: Loc::at_zero(body), + loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), + pattern_vars: SendMap::default(), + } +} + /// Num.minI128: I128 fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { let int_var = var_store.fresh(); let int_precision_var = var_store.fresh(); // TODO: or `i128::MIN.into()` ? - let body = int(int_var, int_precision_var, i128::MIN); + let body = int::(int_var, int_precision_var, i128::MIN); let std = roc_builtins::std::types(); let solved = std.get(&symbol).unwrap(); @@ -1271,7 +1498,7 @@ fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { let int_var = var_store.fresh(); let int_precision_var = var_store.fresh(); - let body = int(int_var, int_precision_var, i128::MAX); + let body = int::(int_var, int_precision_var, i128::MAX); let std = roc_builtins::std::types(); let solved = std.get(&symbol).unwrap(); @@ -1416,7 +1643,10 @@ fn str_to_num(symbol: Symbol, var_store: &mut VarStore) -> Def { loc_expr: Box::new(no_region(Var(Symbol::ARG_2))), }, ), - (errorcode_var, int(errorcode_var, Variable::UNSIGNED8, 0)), + ( + errorcode_var, + int::(errorcode_var, Variable::UNSIGNED8, 0), + ), ], ret_var: bool_var, }), @@ -2158,7 +2388,7 @@ fn list_swap(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_take_first(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); let len_var = var_store.fresh(); - let zero = int(len_var, Variable::NATURAL, 0); + let zero = int::(len_var, Variable::NATURAL, 0); let body = RunLowLevel { op: LowLevel::ListSublist, @@ -2184,7 +2414,7 @@ fn list_take_last(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); let len_var = var_store.fresh(); - let zero = int(len_var, Variable::NATURAL, 0); + let zero = int::(len_var, Variable::NATURAL, 0); let bool_var = var_store.fresh(); let get_list_len = RunLowLevel { @@ -2294,7 +2524,7 @@ fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def { let clos_elem_sym = Symbol::ARG_4; let int_var = var_store.fresh(); - let zero = int(int_var, Variable::NATURAL, 0); + let zero = int::(int_var, Variable::NATURAL, 0); // \acc, elem -> acc |> List.append sep |> List.append elem let clos = Closure(ClosureData { @@ -2374,7 +2604,7 @@ fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def { let clos_ret_var = var_store.fresh(); let ret_var = var_store.fresh(); - let zero = int(index_var, Variable::NATURAL, 0); + let zero = int::(index_var, Variable::NATURAL, 0); let clos = Closure(ClosureData { function_type: clos_fun_var, @@ -2536,7 +2766,7 @@ fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::ListDropAt, args: vec![ (list_var, Var(Symbol::ARG_1)), - (index_var, int(num_var, num_precision_var, 0)), + (index_var, int::(num_var, num_precision_var, 0)), ], ret_var: list_var, }; @@ -2633,7 +2863,7 @@ fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def { ret_var: len_var, }, ), - (arg_var, int(num_var, num_precision_var, 1)), + (arg_var, int::(num_var, num_precision_var, 1)), ], ret_var: len_var, }, @@ -2831,7 +3061,7 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def { RunLowLevel { op: LowLevel::NotEq, args: vec![ - (len_var, int(num_var, num_precision_var, 0)), + (len_var, int::(num_var, num_precision_var, 0)), ( len_var, RunLowLevel { @@ -2862,7 +3092,7 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::ListGetUnsafe, args: vec![ (list_var, Var(Symbol::ARG_1)), - (arg_var, int(num_var, num_precision_var, 0)), + (arg_var, int::(num_var, num_precision_var, 0)), ], ret_var: list_elem_var, }, @@ -2961,7 +3191,7 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def { RunLowLevel { op: LowLevel::NotEq, args: vec![ - (len_var, int(num_var, num_precision_var, 0)), + (len_var, int::(num_var, num_precision_var, 0)), ( len_var, RunLowLevel { @@ -2992,7 +3222,7 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::ListGetUnsafe, args: vec![ (list_var, Var(Symbol::ARG_1)), - (arg_var, int(num_var, num_precision_var, 0)), + (arg_var, int::(num_var, num_precision_var, 0)), ], ret_var: list_elem_var, }, @@ -3874,7 +4104,7 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def { (num_var, Var(Symbol::ARG_2)), ( num_var, - int(unbound_zero_var, unbound_zero_precision_var, 0), + int::(unbound_zero_var, unbound_zero_precision_var, 0), ), ], ret_var: bool_var, @@ -3940,7 +4170,7 @@ fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def { (num_var, Var(Symbol::ARG_2)), ( num_var, - int(unbound_zero_var, unbound_zero_precision_var, 0), + int::(unbound_zero_var, unbound_zero_precision_var, 0), ), ], ret_var: bool_var, @@ -4010,7 +4240,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def { RunLowLevel { op: LowLevel::NotEq, args: vec![ - (len_var, int(zero_var, zero_precision_var, 0)), + (len_var, int::(zero_var, zero_precision_var, 0)), ( len_var, RunLowLevel { @@ -4034,7 +4264,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::ListGetUnsafe, args: vec![ (list_var, Var(Symbol::ARG_1)), - (len_var, int(zero_var, zero_precision_var, 0)), + (len_var, int::(zero_var, zero_precision_var, 0)), ], ret_var: list_elem_var, }, @@ -4091,7 +4321,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def { RunLowLevel { op: LowLevel::NotEq, args: vec![ - (len_var, int(num_var, num_precision_var, 0)), + (len_var, int::(num_var, num_precision_var, 0)), ( len_var, RunLowLevel { @@ -4130,7 +4360,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def { ret_var: len_var, }, ), - (arg_var, int(num_var, num_precision_var, 1)), + (arg_var, int::(num_var, num_precision_var, 1)), ], ret_var: len_var, }, @@ -4846,8 +5076,12 @@ fn defn_help( } #[inline(always)] -fn int(num_var: Variable, precision_var: Variable, i: i128) -> Expr { - Int(num_var, precision_var, i.to_string().into_boxed_str(), i) +fn int(num_var: Variable, precision_var: Variable, i: I128) -> Expr +where + I128: Into, +{ + let ii = i.into(); + Int(num_var, precision_var, ii.to_string().into_boxed_str(), ii) } #[inline(always)] diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index f2e4c31059..d39352cfba 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -996,6 +996,14 @@ define_builtins! { 108 NUM_DIV_CEIL: "divCeil" 109 NUM_TO_STR: "toStr" 110 NUM_MIN_I128: "minI128" + 111 NUM_MIN_I32: "minI32" + 112 NUM_MAX_I32: "maxI32" + 113 NUM_MIN_U32: "minU32" + 114 NUM_MAX_U32: "maxU32" + 115 NUM_MIN_I64: "minI64" + 116 NUM_MAX_I64: "maxI64" + 117 NUM_MIN_U64: "minU64" + 118 NUM_MAX_U64: "maxU64" } 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 2a4e285971..df5d36580f 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -3365,6 +3365,102 @@ mod solve_expr { ); } + #[test] + fn min_i64() { + infer_eq_without_problem( + indoc!( + r#" + Num.minI64 + "# + ), + "I64", + ); + } + + #[test] + fn max_i64() { + infer_eq_without_problem( + indoc!( + r#" + Num.maxI64 + "# + ), + "I64", + ); + } + + #[test] + fn min_u64() { + infer_eq_without_problem( + indoc!( + r#" + Num.minU64 + "# + ), + "U64", + ); + } + + #[test] + fn max_u64() { + infer_eq_without_problem( + indoc!( + r#" + Num.maxU64 + "# + ), + "U64", + ); + } + + #[test] + fn min_i32() { + infer_eq_without_problem( + indoc!( + r#" + Num.minI32 + "# + ), + "I32", + ); + } + + #[test] + fn max_i32() { + infer_eq_without_problem( + indoc!( + r#" + Num.maxI32 + "# + ), + "I32", + ); + } + + #[test] + fn min_u32() { + infer_eq_without_problem( + indoc!( + r#" + Num.minU32 + "# + ), + "U32", + ); + } + + #[test] + fn max_u32() { + infer_eq_without_problem( + indoc!( + r#" + Num.maxU32 + "# + ), + "U32", + ); + } + #[test] fn reconstruct_path() { infer_eq_without_problem( diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index 88096f5b39..e026c4b292 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -1857,6 +1857,118 @@ fn max_i128() { ); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_i64() { + assert_evals_to!( + indoc!( + r#" + Num.minI64 + "# + ), + i64::MIN, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn max_i64() { + assert_evals_to!( + indoc!( + r#" + Num.maxI64 + "# + ), + i64::MAX, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_u64() { + assert_evals_to!( + indoc!( + r#" + Num.minU64 + "# + ), + u64::MIN, + u64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn max_u64() { + assert_evals_to!( + indoc!( + r#" + Num.maxU64 + "# + ), + u64::MAX, + u64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_i32() { + assert_evals_to!( + indoc!( + r#" + Num.minI32 + "# + ), + i32::MIN, + i32 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn max_i32() { + assert_evals_to!( + indoc!( + r#" + Num.maxI32 + "# + ), + i32::MAX, + i32 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_u32() { + assert_evals_to!( + indoc!( + r#" + Num.minU32 + "# + ), + u32::MIN, + u32 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn max_u32() { + assert_evals_to!( + indoc!( + r#" + Num.maxU32 + "# + ), + u32::MAX, + u32 + ); +} + #[test] #[cfg(any(feature = "gen-llvm"))] fn is_multiple_of() { From 93d65a1601626b43680437e83fd25a30d69e53e1 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Sun, 16 Jan 2022 20:51:10 -0700 Subject: [PATCH 246/541] Remove leftover TODOs --- compiler/can/src/builtins.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index df75271330..7124ca5739 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1469,7 +1469,6 @@ fn num_max_u64(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { let int_var = var_store.fresh(); let int_precision_var = var_store.fresh(); - // TODO: or `i128::MIN.into()` ? let body = int::(int_var, int_precision_var, i128::MIN); let std = roc_builtins::std::types(); @@ -1485,7 +1484,6 @@ fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { }; Def { - // TODO: or `None` ? annotation: Some(annotation), expr_var: int_var, loc_expr: Loc::at_zero(body), From 21c59bd621ffffbd635a3a445901aa4478419cf5 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Tue, 18 Jan 2022 20:16:27 +0100 Subject: [PATCH 247/541] added editor meeting notes --- meeting_notes/editor_ui_ux_18-01-2022.md | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 meeting_notes/editor_ui_ux_18-01-2022.md diff --git a/meeting_notes/editor_ui_ux_18-01-2022.md b/meeting_notes/editor_ui_ux_18-01-2022.md new file mode 100644 index 0000000000..7fa24d0489 --- /dev/null +++ b/meeting_notes/editor_ui_ux_18-01-2022.md @@ -0,0 +1,32 @@ + +# Meeting notes + +- 18/1/2022 2pm GMT + +## issue 2368 + +- How is AST data accessible to plugins? +- How does plugin UI work (UI components, vector level, pixel-level)? +- Given a selected expression, how do we show all plugins available that can visualize this + expression or have an interactive widget to alter the expression (color picker). What does + this API look like? +- use type driven UX? https://pchiusano.github.io/2013-09-10/type-systems-and-ux-example.html + +## ideas + +- Several "zoom levels" in the editor should show/hide context-appropriate views/buttons/functionality: + + zoomed out view should show type defs and function defs with folded body + + zooming in on function should unfold/show function body + + Traditional IDE's like ecplise can show an overwhelming amount of possible buttons/actions and views. Zoom levels can be used to prevent this excess of available options. + +- There should be a single editable text field to alter AST. This could be the same text field for entering commands, pressing a certain key could switch between command/plain text input into AST. Current part of AST that is being edited is highlighted. + +- Hovering over expression should show button on left sidebar that allows you to pop out a pinned view of the expression. + This pinned view could for example show all variants in a large type definition. + Hovering over a type in a place other than the definition could show all variants, this hover view should also be capable of being pinned and moved around as desired. + +- UI interaction specification from which we can generate both e.g. a window with clickable buttons 'previuous' and `next` that also supports the voice commands `previuous` and `next`. + +Next actions to take: +- Zeljko: draft UI interaction in figma +- Anton: draft plugin API in roc From 1e9d2d12390471994169e059c74d62edd03a683d Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Tue, 18 Jan 2022 10:11:13 -0700 Subject: [PATCH 248/541] Remove accidental trailing spaces --- .github/workflows/benchmarks.yml | 4 +-- .gitignore | 2 +- BUILDING_FROM_SOURCE.md | 2 +- ast/src/constrain.rs | 8 ++--- ast/src/lang/core/def/def.rs | 4 +-- ast/src/roc_file.rs | 6 ++-- ci/bench-runner/src/main.rs | 2 +- cli/tests/cli_run.rs | 10 +++--- cli/tests/repl_eval.rs | 20 +++++------ compiler/README.md | 2 +- compiler/builtins/README.md | 2 +- compiler/builtins/bitcode/README.md | 2 +- compiler/can/tests/test_can.rs | 12 +++---- compiler/fmt/tests/test_fmt.rs | 28 +++++++-------- .../build/app_with_deps/ManualAttr.roc | 2 +- .../build/interface_with_deps/ManualAttr.roc | 2 +- compiler/load/tests/test_load.rs | 8 ++--- compiler/mono/src/exhaustive.rs | 2 +- compiler/parse/src/expr.rs | 2 +- compiler/str/README.md | 2 +- compiler/test_gen/src/gen_compare.rs | 12 +++---- compiler/test_gen/src/gen_records.rs | 2 +- compiler/test_gen/src/gen_result.rs | 8 ++--- compiler/test_gen/src/gen_set.rs | 6 ++-- .../test_mono/generated/ir_when_these.txt | 6 ++-- .../generated/somehow_drops_definitions.txt | 4 +-- .../generated/specialize_closures.txt | 4 +-- .../generated/specialize_lowlevel.txt | 4 +-- .../test_mono/generated/when_joinpoint.txt | 6 ++-- compiler/test_mono/src/tests.rs | 14 ++++---- editor/README.md | 2 +- editor/editor-ideas.md | 36 +++++++++---------- editor/snippet-ideas.md | 10 +++--- editor/src/graphics/shaders/shader.wgsl | 4 +-- editor/src/window/keyboard_input.rs | 2 +- .../false-interpreter/examples/cksum.false | 4 +-- examples/hello-swift/platform/host.swift | 4 +-- examples/hello-world/README.md | 14 ++++---- examples/hello-zig/README.md | 14 ++++---- getting_started/linux_x86.md | 2 +- meeting_notes/editor_ui_ux_18-01-2022.md | 8 ++--- name-and-logo.md | 14 ++++---- nightly_benches/README.me | 4 +-- reporting/tests/test_reporting.rs | 32 ++++++++--------- 44 files changed, 169 insertions(+), 169 deletions(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 63e7fc9638..c5f2a8d123 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -17,13 +17,13 @@ jobs: - uses: actions/checkout@v2 with: ref: "trunk" - clean: "true" + clean: "true" - name: Earthly version run: earthly --version - name: on trunk; prepare a self-contained benchmark folder - run: ./ci/safe-earthly.sh --build-arg BENCH_SUFFIX=trunk +prep-bench-folder + run: ./ci/safe-earthly.sh --build-arg BENCH_SUFFIX=trunk +prep-bench-folder - uses: actions/checkout@v2 with: diff --git a/.gitignore b/.gitignore index 4efe9d201e..87635559c0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ zig-cache *.o *.tmp -# llvm human-readable output +# llvm human-readable output *.ll *.bc diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index 86373b26c0..dc2740021f 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -15,7 +15,7 @@ To run the test suite (via `cargo test`), you additionally need to install: * [`valgrind`](https://www.valgrind.org/) (needs special treatment to [install on macOS](https://stackoverflow.com/a/61359781) Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specific_tests` to skip over the valgrind failures & tests. -For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it. +For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it. ### libcxb libraries diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index 5ea08e4fcb..f26eddd841 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -2325,7 +2325,7 @@ pub mod test_constrain { indoc!( r#" person = { name: "roc" } - + person "# ), @@ -2339,8 +2339,8 @@ pub mod test_constrain { indoc!( r#" person = { name: "roc" } - - { person & name: "bird" } + + { person & name: "bird" } "# ), "{ name : Str }", @@ -2462,7 +2462,7 @@ pub mod test_constrain { indoc!( r#" x = 1 - + \{} -> x "# ), diff --git a/ast/src/lang/core/def/def.rs b/ast/src/lang/core/def/def.rs index 8cd83f4257..aeea6e6a73 100644 --- a/ast/src/lang/core/def/def.rs +++ b/ast/src/lang/core/def/def.rs @@ -511,7 +511,7 @@ fn canonicalize_pending_def<'a>( // remove its generated name from the closure map. let references = env.closures.remove(&closure_symbol).unwrap_or_else(|| { - panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures) + panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures) }); // TODO should we re-insert this function into env.closures? @@ -680,7 +680,7 @@ fn canonicalize_pending_def<'a>( // remove its generated name from the closure map. let references = env.closures.remove(&closure_symbol).unwrap_or_else(|| { - panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures) + panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures) }); // TODO should we re-insert this function into env.closures? diff --git a/ast/src/roc_file.rs b/ast/src/roc_file.rs index 5379e9097a..5299921c62 100644 --- a/ast/src/roc_file.rs +++ b/ast/src/roc_file.rs @@ -116,13 +116,13 @@ mod test_file { indoc!( r#" interface Simple - exposes [ + exposes [ v, x ] imports [] - + v : Str - + v = "Value!" x : Int diff --git a/ci/bench-runner/src/main.rs b/ci/bench-runner/src/main.rs index b30e692541..efccc69497 100644 --- a/ci/bench-runner/src/main.rs +++ b/ci/bench-runner/src/main.rs @@ -65,7 +65,7 @@ fn finish(all_regressed_benches: HashSet, nr_repeat_benchmarks: usize) { r#" FAILED: The following benchmarks have shown a regression {:?} times: {:?} - + "#, nr_repeat_benchmarks, all_regressed_benches ); diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 3cfdd7f0a1..be905da812 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -774,19 +774,19 @@ mod cli_run { indoc!( r#" ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── - + I cannot find a `d` value - + 10│ _ <- await (line d) ^ - + Did you mean one of these? - + U8 Ok I8 F64 - + ────────────────────────────────────────────────────────────────────────────────"# ), ); diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index af13cb5ac5..5b79f87769 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -872,15 +872,15 @@ mod repl_eval { indoc!( r#" ── ARGUMENTS BEFORE EQUALS ───────────────────────────────────────────────────── - + I am partway through parsing a definition, but I got stuck here: - + 1│ app "app" provides [ replOutput ] to "./platform" 2│ 3│ replOutput = 4│ add m n = m + n ^^^ - + Looks like you are trying to define a function. In roc, functions are always written as a lambda, like increment = \n -> n + 1. "# @@ -901,20 +901,20 @@ mod repl_eval { indoc!( r#" ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── - + This when does not cover all the possibilities: - + 7│> when t is 8│> A -> "a" - + Other possibilities include: - + B C - + I would have to crash if I saw one of those! Add branches for them! - - + + Enter an expression, or :help, or :exit/:q."# ), ); diff --git a/compiler/README.md b/compiler/README.md index b78867e9f2..e57984562d 100644 --- a/compiler/README.md +++ b/compiler/README.md @@ -147,7 +147,7 @@ The compiler contains a lot of code! If you're new to the project it can be hard After you get into the details, you'll discover that some parts of the compiler have more than one entry point. And things can be interwoven together in subtle and complex ways, for reasons to do with performance, edge case handling, etc. But if this is "day one" for you, and you're just trying to get familiar with things, this should be "good enough". -The compiler is invoked from the CLI via `build_file` in cli/src/build.rs +The compiler is invoked from the CLI via `build_file` in cli/src/build.rs | Phase | Entry point / main functions | | ------------------------------------- | ------------------------------------------------ | diff --git a/compiler/builtins/README.md b/compiler/builtins/README.md index 3809bb1b34..ceaf3007e3 100644 --- a/compiler/builtins/README.md +++ b/compiler/builtins/README.md @@ -4,7 +4,7 @@ Builtins are the functions and modules that are implicitly imported into every m ### module/src/symbol.rs -Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `mod` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones). +Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `mod` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones). Some of these have `#` inside their name (`first#list`, `#lt` ..). This is a trick we are doing to hide implementation details from Roc programmers. To a Roc programmer, a name with `#` in it is invalid, because `#` means everything after it is parsed to a comment. We are constructing these functions manually, so we are circumventing the parsing step and dont have such restrictions. We get to make functions and values with `#` which as a consequence are not accessible to Roc programmers. Roc programmers simply cannot reference them. diff --git a/compiler/builtins/bitcode/README.md b/compiler/builtins/bitcode/README.md index 1d9fbb225b..e7a03e341f 100644 --- a/compiler/builtins/bitcode/README.md +++ b/compiler/builtins/bitcode/README.md @@ -27,7 +27,7 @@ There will be two directories like `roc_builtins-[some random characters]`, look `out` directory as a child. > The bitcode is a bunch of bytes that aren't particularly human-readable. -> If you want to take a look at the human-readable LLVM IR, look at +> If you want to take a look at the human-readable LLVM IR, look at > `target/debug/build/roc_builtins-[some random characters]/out/builtins.ll` ## Calling bitcode functions diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index 2fa00c5f5e..26ec355746 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -524,7 +524,7 @@ mod test_can { fn annotation_followed_with_unrelated_affectation() { let src = indoc!( r#" - F : Str + F : Str x = 1 @@ -545,10 +545,10 @@ mod test_can { fn two_annotations_followed_with_unrelated_affectation() { let src = indoc!( r#" - G : Str + G : Str + + F : {} - F : {} - x = 1 x @@ -629,7 +629,7 @@ mod test_can { fn incorrect_optional_value() { let src = indoc!( r#" - { x ? 42 } + { x ? 42 } "# ); let arena = Bump::new(); @@ -1004,7 +1004,7 @@ mod test_can { let src = indoc!( r#" x = Dict.empty - + Dict.len x "# ); diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index 2b5561fdce..82609329a3 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -410,19 +410,19 @@ mod test_fmt { indoc!( r#" x = (5) - - + + y = ((10)) - + 42 "# ), indoc!( r#" x = 5 - + y = 10 - + 42 "# ), @@ -766,9 +766,9 @@ mod test_fmt { # comment 2 x: 42 - + # comment 3 - + # comment 4 }"# ), @@ -819,7 +819,7 @@ mod test_fmt { f: { y : Int *, x : Int * , } - + f"# ), indoc!( @@ -910,7 +910,7 @@ mod test_fmt { { # comment } - + f"# ), ); @@ -935,7 +935,7 @@ mod test_fmt { indoc!( r#" f : - { + { x: Int * # comment 1 , # comment 2 @@ -951,7 +951,7 @@ mod test_fmt { # comment 1 # comment 2 } - + f"# ), ); @@ -1007,7 +1007,7 @@ mod test_fmt { indoc!( r#" identity = \a - -> + -> a + b identity 4010 @@ -1387,7 +1387,7 @@ mod test_fmt { expr_formats_to( indoc!( r#" - { + { }"# ), "{}", @@ -2763,7 +2763,7 @@ mod test_fmt { # comment 2 # comment 3 ] - + b "# ), diff --git a/compiler/load/tests/fixtures/build/app_with_deps/ManualAttr.roc b/compiler/load/tests/fixtures/build/app_with_deps/ManualAttr.roc index 7fd1e010c8..5e51189cbb 100644 --- a/compiler/load/tests/fixtures/build/app_with_deps/ManualAttr.roc +++ b/compiler/load/tests/fixtures/build/app_with_deps/ManualAttr.roc @@ -3,7 +3,7 @@ interface ManualAttr imports [] # manually replicates the Attr wrapping that uniqueness inference uses, to try and find out why they are different -# It is very important that there are no signatures here! elm uses an optimization that leads to less copying when +# It is very important that there are no signatures here! elm uses an optimization that leads to less copying when # signatures are given. map = diff --git a/compiler/load/tests/fixtures/build/interface_with_deps/ManualAttr.roc b/compiler/load/tests/fixtures/build/interface_with_deps/ManualAttr.roc index 7fd1e010c8..5e51189cbb 100644 --- a/compiler/load/tests/fixtures/build/interface_with_deps/ManualAttr.roc +++ b/compiler/load/tests/fixtures/build/interface_with_deps/ManualAttr.roc @@ -3,7 +3,7 @@ interface ManualAttr imports [] # manually replicates the Attr wrapping that uniqueness inference uses, to try and find out why they are different -# It is very important that there are no signatures here! elm uses an optimization that leads to less copying when +# It is very important that there are no signatures here! elm uses an optimization that leads to less copying when # signatures are given. map = diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index 373446c3a9..84c9780366 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -276,9 +276,9 @@ mod test_load { "Main", indoc!( r#" - app "test-app" - packages { blah: "./blah" } - imports [ RBTree ] + app "test-app" + packages { blah: "./blah" } + imports [ RBTree ] provides [ main ] to blah empty : RBTree.RedBlackTree I64 I64 @@ -541,7 +541,7 @@ mod test_load { indoc!( " \u{1b}[36m── UNFINISHED LIST ─────────────────────────────────────────────────────────────\u{1b}[0m - + I cannot find the end of this list: \u{1b}[36m3\u{1b}[0m\u{1b}[36m│\u{1b}[0m \u{1b}[37mmain = [\u{1b}[0m diff --git a/compiler/mono/src/exhaustive.rs b/compiler/mono/src/exhaustive.rs index 58ddf05064..a29ca6cf12 100644 --- a/compiler/mono/src/exhaustive.rs +++ b/compiler/mono/src/exhaustive.rs @@ -498,7 +498,7 @@ fn specialize_row_by_ctor2( patterns.extend(args); matrix.push(patterns); } else { - // do nothing + // do nothing } Some(Anything) => { // TODO order! diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 47b68f680f..c56e6bb5da 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -61,7 +61,7 @@ pub struct ExprParseOptions { /// Check for the `->` token, and raise an error if found /// This is usually true, but false in if-guards - /// + /// /// > Just foo if foo == 2 -> ... check_for_arrow: bool, } diff --git a/compiler/str/README.md b/compiler/str/README.md index 04af2f5e9f..be3a82df20 100644 --- a/compiler/str/README.md +++ b/compiler/str/README.md @@ -59,7 +59,7 @@ In some cases, the compiler can detect that no reference counting is necessary. The fact that the reference count may or may not be present could creat a tricky situation for some `List` operations. -For example, should `List.get 0` return the first 16B of the heap-allocated bytes, or the second 16B? If there's a reference count in the first 16B, it should return the second 16B. If there's no refcount, it should return the first 16B. +For example, should `List.get 0` return the first 16B of the heap-allocated bytes, or the second 16B? If there's a reference count in the first 16B, it should return the second 16B. If there's no refcount, it should return the first 16B. To solve this, the pointer in the List struct *always* points to the first element in the list. That means to access the reference count, it does negative pointer arithmetic to get the address at 16B *preceding* the memory address it has stored in its pointer field. diff --git a/compiler/test_gen/src/gen_compare.rs b/compiler/test_gen/src/gen_compare.rs index 38610b8cb2..edaf68dbb8 100644 --- a/compiler/test_gen/src/gen_compare.rs +++ b/compiler/test_gen/src/gen_compare.rs @@ -551,10 +551,10 @@ fn rosetree_with_tag() { r#" Rose a : [ Rose (Result (List (Rose a)) I64) ] - x : Rose I64 + x : Rose I64 x = (Rose (Ok [])) - y : Rose I64 + y : Rose I64 y = (Rose (Ok [])) x == y @@ -637,10 +637,10 @@ fn compare_recursive_union_same_content() { r#" Expr : [ Add Expr Expr, Mul Expr Expr, Val1 I64, Val2 I64 ] - v1 : Expr + v1 : Expr v1 = Val1 42 - v2 : Expr + v2 : Expr v2 = Val2 42 v1 == v2 @@ -659,10 +659,10 @@ fn compare_nullable_recursive_union_same_content() { r#" Expr : [ Add Expr Expr, Mul Expr Expr, Val1 I64, Val2 I64, Empty ] - v1 : Expr + v1 : Expr v1 = Val1 42 - v2 : Expr + v2 : Expr v2 = Val2 42 v1 == v2 diff --git a/compiler/test_gen/src/gen_records.rs b/compiler/test_gen/src/gen_records.rs index f194de0939..5377da5afc 100644 --- a/compiler/test_gen/src/gen_records.rs +++ b/compiler/test_gen/src/gen_records.rs @@ -997,7 +997,7 @@ fn both_have_unique_fields() { b = { x: 42, z: 44 } f : { x : I64 }a, { x : I64 }b -> I64 - f = \{ x: x1}, { x: x2 } -> x1 + x2 + f = \{ x: x1}, { x: x2 } -> x1 + x2 f a b "# diff --git a/compiler/test_gen/src/gen_result.rs b/compiler/test_gen/src/gen_result.rs index cf466665ca..d919bf89ef 100644 --- a/compiler/test_gen/src/gen_result.rs +++ b/compiler/test_gen/src/gen_result.rs @@ -34,7 +34,7 @@ fn with_default() { indoc!( r#" result : Result I64 {} - result = Err {} + result = Err {} Result.withDefault result 0 "# @@ -66,7 +66,7 @@ fn result_map() { indoc!( r#" result : Result I64 {} - result = Err {} + result = Err {} result |> Result.map (\x -> x + 1) @@ -230,7 +230,7 @@ fn roc_result_ok() { indoc!( r#" result : Result I64 {} - result = Ok 42 + result = Ok 42 result "# @@ -246,7 +246,7 @@ fn roc_result_err() { assert_evals_to!( indoc!( r#" - result : Result I64 Str + result : Result I64 Str result = Err "foo" result diff --git a/compiler/test_gen/src/gen_set.rs b/compiler/test_gen/src/gen_set.rs index b1607176f6..77503c105e 100644 --- a/compiler/test_gen/src/gen_set.rs +++ b/compiler/test_gen/src/gen_set.rs @@ -121,7 +121,7 @@ fn union() { set1 = Set.fromList [1,2] set2 : Set I64 - set2 = Set.fromList [1,3,4] + set2 = Set.fromList [1,3,4] Set.union set1 set2 |> Set.toList @@ -142,7 +142,7 @@ fn difference() { set1 = Set.fromList [1,2] set2 : Set I64 - set2 = Set.fromList [1,3,4] + set2 = Set.fromList [1,3,4] Set.difference set1 set2 |> Set.toList @@ -163,7 +163,7 @@ fn intersection() { set1 = Set.fromList [1,2] set2 : Set I64 - set2 = Set.fromList [1,3,4] + set2 = Set.fromList [1,3,4] Set.intersection set1 set2 |> Set.toList diff --git a/compiler/test_mono/generated/ir_when_these.txt b/compiler/test_mono/generated/ir_when_these.txt index 08c3614351..5b557e1473 100644 --- a/compiler/test_mono/generated/ir_when_these.txt +++ b/compiler/test_mono/generated/ir_when_these.txt @@ -7,12 +7,12 @@ procedure Test.0 (): case 2: let Test.2 : Builtin(Int(I64)) = UnionAtIndex (Id 2) (Index 0) Test.5; ret Test.2; - + case 0: let Test.3 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.5; ret Test.3; - + default: let Test.4 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.5; ret Test.4; - + diff --git a/compiler/test_mono/generated/somehow_drops_definitions.txt b/compiler/test_mono/generated/somehow_drops_definitions.txt index aed6376bbd..84d70d01cb 100644 --- a/compiler/test_mono/generated/somehow_drops_definitions.txt +++ b/compiler/test_mono/generated/somehow_drops_definitions.txt @@ -32,11 +32,11 @@ procedure Test.5 (Test.8, Test.9): case 0: let Test.16 : Builtin(Int(I64)) = CallByName Test.3 Test.9; jump Test.15 Test.16; - + default: let Test.17 : Builtin(Int(I64)) = CallByName Test.4 Test.9; jump Test.15 Test.17; - + procedure Test.0 (): joinpoint Test.19 Test.12: diff --git a/compiler/test_mono/generated/specialize_closures.txt b/compiler/test_mono/generated/specialize_closures.txt index 42428a6f41..7360e25d30 100644 --- a/compiler/test_mono/generated/specialize_closures.txt +++ b/compiler/test_mono/generated/specialize_closures.txt @@ -15,11 +15,11 @@ procedure Test.1 (Test.2, Test.3): case 0: let Test.19 : Builtin(Int(I64)) = CallByName Test.7 Test.3 Test.2; jump Test.18 Test.19; - + default: let Test.20 : Builtin(Int(I64)) = CallByName Test.8 Test.3 Test.2; jump Test.18 Test.20; - + procedure Test.7 (Test.10, #Attr.12): let Test.4 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) #Attr.12; diff --git a/compiler/test_mono/generated/specialize_lowlevel.txt b/compiler/test_mono/generated/specialize_lowlevel.txt index 1bb68683ce..0a95adfb01 100644 --- a/compiler/test_mono/generated/specialize_lowlevel.txt +++ b/compiler/test_mono/generated/specialize_lowlevel.txt @@ -29,11 +29,11 @@ procedure Test.0 (): case 0: let Test.16 : Builtin(Int(I64)) = CallByName Test.6 Test.12 Test.13; jump Test.15 Test.16; - + default: let Test.17 : Builtin(Int(I64)) = CallByName Test.7 Test.12 Test.13; jump Test.15 Test.17; - + in let Test.22 : Builtin(Bool) = true; if Test.22 then diff --git a/compiler/test_mono/generated/when_joinpoint.txt b/compiler/test_mono/generated/when_joinpoint.txt index 3366717fca..c4b993a044 100644 --- a/compiler/test_mono/generated/when_joinpoint.txt +++ b/compiler/test_mono/generated/when_joinpoint.txt @@ -7,15 +7,15 @@ procedure Test.1 (Test.5): case 1: let Test.10 : Builtin(Int(I64)) = 1i64; jump Test.9 Test.10; - + case 2: let Test.11 : Builtin(Int(I64)) = 2i64; jump Test.9 Test.11; - + default: let Test.12 : Builtin(Int(I64)) = 3i64; jump Test.9 Test.12; - + procedure Test.0 (): let Test.7 : Struct([]) = Struct {}; diff --git a/compiler/test_mono/src/tests.rs b/compiler/test_mono/src/tests.rs index cbcdbed066..75f3c19201 100644 --- a/compiler/test_mono/src/tests.rs +++ b/compiler/test_mono/src/tests.rs @@ -982,17 +982,17 @@ fn closure_in_list() { indoc!( r#" app "test" provides [ main ] to "./platform" - + foo = \{} -> x = 41 - + f = \{} -> x - + [ f ] - + main = items = foo {} - + List.len items "# ) @@ -1005,7 +1005,7 @@ fn somehow_drops_definitions() { r#" app "test" provides [ main ] to "./platform" - one : I64 + one : I64 one = 1 two : I64 @@ -1037,7 +1037,7 @@ fn specialize_closures() { apply = \f, x -> f x main = - one : I64 + one : I64 one = 1 two : I64 diff --git a/editor/README.md b/editor/README.md index 24ca818e9c..48ec92969f 100644 --- a/editor/README.md +++ b/editor/README.md @@ -42,7 +42,7 @@ From roc to render: - The `ed_model` is filled in part with data obtained by loading and typechecking the roc file with the same function (`load_and_typecheck`) that is used by the compiler. - `ed_model` also contains an `EdModule`, which holds the parsed abstract syntax tree (AST). - In the `init_model` function: - + The AST is converted into a tree of `MarkupNode`. The different types of `MarkupNode` are similar to the elements/nodes in HTML. A line of roc code is represented as a nested `MarkupNode` containing mostly text `MarkupNode`s. The line `foo = "bar"` is represented as + + The AST is converted into a tree of `MarkupNode`. The different types of `MarkupNode` are similar to the elements/nodes in HTML. A line of roc code is represented as a nested `MarkupNode` containing mostly text `MarkupNode`s. The line `foo = "bar"` is represented as three text `MarkupNode` representing `foo`, ` = ` and `bar`. Multiple lines of roc code are represented as nested `MarkupNode` that contain other nested `MarkupNode`. + `CodeLines` holds a `Vec` of `String`, each line of code is a `String`. When saving the file, the content of `CodeLines` is written to disk. + `GridNodeMap` maps every position of a char of roc code to a `MarkNodeId`, for easy interaction with the caret. diff --git a/editor/editor-ideas.md b/editor/editor-ideas.md index b69b2f4ce3..4a227d6c96 100644 --- a/editor/editor-ideas.md +++ b/editor/editor-ideas.md @@ -48,7 +48,7 @@ Nice collection of research on innovative editors, [link](https://futureofcoding * [Hest](https://ivanish.ca/hest-time-travel/) tool for making highly interactive simulations. * [replit](https://replit.com/) collaborative browser based IDE. * [paper](https://openreview.net/pdf?id=SJeqs6EFvB) on finding and fixing bugs automatically. -* [specialized editors that can be embedded in main editor](https://elliot.website/editor/) +* [specialized editors that can be embedded in main editor](https://elliot.website/editor/) * Say you have a failing test that used to work, it would be very valuable to see all code that was changed that was used only by that test. e.g. you have a test `calculate_sum_test` that only uses the function `add`, when the test fails you should be able to see a diff showing only what changed for the function `add`. It would also be great to have a diff of [expression values](https://homepages.cwi.nl/~storm/livelit/images/bret.png) Bret Victor style. An ambitious project would be to suggest or automatically try fixes based on these diffs. * I think it could be possible to create a minimal reproduction of a program / block of code / code used by a single test. So for a failing unit test I would expect it to extract imports, the platform, types and functions that are necessary to run only that unit test and put them in a standalone roc project. This would be useful for sharing bugs with library+application authors and colleagues, for profiling or debugging with all "clutter" removed. @@ -138,7 +138,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe * "complex" filtered search: search for all occurrences of `"#` but ignore all like `"#,` * color this debug print orange * remove unused imports - + #### Inspiration * Voice control and eye tracking with [Talon](https://github.com/Gauteab/talon-tree-sitter-service) @@ -157,13 +157,13 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe ### Productivity features -* When refactoring; +* When refactoring; - Cutting and pasting code to a new file should automatically add imports to the new file and delete them from the old file. - Ability to link e.g. variable name in comments to actual variable name. Comment is automatically updated when variable name is changed. - When updating dependencies with breaking changes; show similar diffs from github projects that have successfully updated that dependency. - - AST backed renaming, changing variable/function/type name should change it all over the codebase. + - AST backed renaming, changing variable/function/type name should change it all over the codebase. * Automatically create all "arms" when pattern matching after entering `when var is` based on the type. - - All `when ... is` should be updated if the type is changed, e.g. adding Indigo to the Color type should add an arm everywhere where `when color is` is used. + - All `when ... is` should be updated if the type is changed, e.g. adding Indigo to the Color type should add an arm everywhere where `when color is` is used. * When a function is called like `foo(false)`, the name of the boolean argument should be shown automatically; `foo(`*is_active:*`false)`. This should be done for booleans and numbers. * Suggest automatically creating a function if the compiler says it does not exist. * Integrated search: @@ -171,7 +171,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe * Show productivity/feature tips on startup. Show link to page with all tips. Allow not seeing tips next time. * Search friendly editor docs inside the editor. Offer to send search string to Roc maintainers when no results, or if no results were clicked. * File history timeline view. Show timeline with commits that changed this file, the number of lines added and deleted as well as which user made the changes. Arrow navigation should allow you to quickly view different versions of the file. -* Suggested quick fixes should be directly visible and clickable. Not like in vs code where you put the caret on an error until a lightbulb appears in the margin which you have to click for the fixes to appear, after which you click to apply the fix you want :( . You should be able to apply suggestions in rapid succession. e.g. if you copy some roc code from the internet you should be able to apply 5 import suggestions quickly. +* Suggested quick fixes should be directly visible and clickable. Not like in vs code where you put the caret on an error until a lightbulb appears in the margin which you have to click for the fixes to appear, after which you click to apply the fix you want :( . You should be able to apply suggestions in rapid succession. e.g. if you copy some roc code from the internet you should be able to apply 5 import suggestions quickly. * Regex-like find and substitution based on plain english description and example (replacement). i.e. replace all `[` between double quotes with `{`. [Inspiration](https://alexmoltzau.medium.com/english-to-regex-thanks-to-gpt-3-13f03b68236e). * Show productivity tips based on behavior. i.e. if the user is scrolling through the error bar and clicking on the next error several times, show a tip with "go to next error" shortcut. * Command to "benchmark this function" or "benchmark this test" with flamegraph and execution time per line. @@ -201,7 +201,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe * Intelligent search: "search this folder for ", "search all tests for " * Show some kind of warning if path str in code does not exist locally. * repl on panic/error: ability to inspect all values and try executing some things at the location of the error. -* show values in memory on panic/error +* show values in memory on panic/error * automatic clustering of (text) search results in groups by similarity * fill screen with little windows of clustered search results * clustering of examples similar to current code @@ -222,7 +222,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe * GPT-3 can generate correct python functions based on a comment describing the functionality, video [here](https://www.youtube.com/watch?v=utuz7wBGjKM). It's possible that training a model using ast's may lead to better results than text based models. - Current autocomplete lacks flow, moving through suggestions with arrows is slow. Being able to code by weaving together autocomplete suggestions laid out in rows using eye tracking, that could flow. - It's possible that with strong static types, pure functions and a good search algorithm we can develop a more reliable autocomplete than one with machine learning. -- When ranking autocomplete suggestions, take into account how new a function is. Newly created functions are likely to be used soon. +- When ranking autocomplete suggestions, take into account how new a function is. Newly created functions are likely to be used soon. #### Productivity Inspiration @@ -239,7 +239,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe * [Codesee](https://www.codesee.io/) code base visualization. * [Loopy](https://dl.acm.org/doi/10.1145/3485530?sid=SCITRUS) interactive program synthesis. * [bracket guides](https://mobile.twitter.com/elyktrix/status/1461380028609048576) - + ### Non-Code Related Inspiration * [Scrivner](https://www.literatureandlatte.com/scrivener/overview) writing app for novelists, screenwriters, and more @@ -266,7 +266,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe ## Testing - + * From Google Docs' comments, adding tests in a similar manner, where they exists in the same "document" but parallel to the code being written * Makes sense for unit tests, keeps the test close to the source * Doesn't necessarily make sense for integration or e2e testing @@ -295,7 +295,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe * Library should have cheat sheet with most used/important docs summarized. * With explicit user permission, anonymously track viewing statistics for documentation. Can be used to show most important documentation, report pain points to library authors. * Easy side-by-side docs for multiple versions of library. -* ability to add questions and answers to library documentation +* ability to add questions and answers to library documentation ## Tutorials @@ -350,28 +350,28 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe Thoughts and ideas possibly taken from above inspirations or separate. * ACCESSIBILITY === EMPATHY - * Visual Imapirments + * Visual Imapirments No Animation is most benign form of cognitive disabity but really important base line of people with tense nerve system. Insensitivity to certain or all colors. Need of highcontrast - Or Everything Magnified for me with no glasses. + Or Everything Magnified for me with no glasses. Or Total blindness where we need to trough sound to communicate to the user Screen readers read trees of labeled elements. Each platform has different apis, but I think they are horrible. Just close your eyes and imagine listening to screen reader all day while you are using this majectic machines called computers. But blind people walk with a tool and they can react much better to sound/space relations than full on visal majority does. They are acute to sound as a spatial hint. And a hand for most of them is a very sensitive tool that can make sounds in space. Imagine if everytime for the user doesnt want to rely on shining rendered pixels on the screen for a feedback from machine, we make a acoustic room simulation, where with moving the "stick", either with mouse or with key arrows, we bump into one of the objects and that produces certain contextually appropriate sound (clean)*ding* - + On the each level of abstraction they can make sounds more deeper, so then when you type letters you feel like you are playing with the sand (soft)*shh*. We would need help from some sound engineer about it, but imagine moving down, which can be voice triggered command for motion impaired, you hear (soft)*pup* and the name of the module, and then you have options and commands appropriate for the module, they could map to those basic 4 buttons that we trained user on, and he would shortcut all the soft talk with click of a button. Think of the satisfaction when you can skip the dialog of the game and get straight into action. (X) Open functions! each function would make a sound and say its name, unless you press search and start searching for a specific function inside module, if you want one you select or move to next. - Related idea: Playing sounds in rapid succession for different expressions in your program might be a high throughput alternative to stepping through your code line by line. I'd bet you quickly learn what your program should sound like. The difference in throughput would be even larger for those who need to rely on voice transcription. - + * Motor impariments [rant]BACKS OF CODERS ARE NOT HEALTHY! We need to change that![/neverstop] Too much mouse waving and sitting for too long is bad for humans. - Keyboard is basic accessability tool but - Keyboard is also optional, some people have too shaky hands even for keyboard. + Keyboard is basic accessability tool but + Keyboard is also optional, some people have too shaky hands even for keyboard. They rely on eye tracking to move mouse cursor arond. If we employ _some_ voice recognition functions we could make same interface as we could do for consoles where 4+2 buttons and directional pad would suffice. That is 10 phrases that need to be pulled trough as many possible translations so people don't have to pretend that they are from Maine or Texas so they get voice recognition to work. Believe me I was there with Apple's Siri :D That is why we have 10 phrases for movement and management and most basic syntax. - * Builtin fonts that can be read more easily by those with dyslexia. + * Builtin fonts that can be read more easily by those with dyslexia. * Nice backtraces that highlight important information * Ability to show import connection within project visually diff --git a/editor/snippet-ideas.md b/editor/snippet-ideas.md index ea701cea0f..16014f7ba3 100644 --- a/editor/snippet-ideas.md +++ b/editor/snippet-ideas.md @@ -67,10 +67,10 @@ Snippets are inserted based on type of value on which the cursor is located. - command: sort ^List *^ (by ^Record Field^) {ascending/descending} + example: sort people by age descending >> ... -- command: escape url +- command: escape url + example: >> `percEncodedString = Url.percentEncode ^String^` - command: list files in directory - + example: >> + + example: >> ``` path <- File.pathFromStr ^String^ dirContents <- File.enumerateDir path @@ -90,9 +90,9 @@ Snippets are inserted based on type of value on which the cursor is located. * repeat list > List.repeat ^elem^ ^Nat^ * len list (fuzzy matches should be length of list) - append element to list - + # fuzzy matching - + some pairs for fuzzy matching unit tests: - hashmap > Dict - map > map (function), Dict @@ -108,6 +108,6 @@ Snippets are inserted based on type of value on which the cursor is located. - [grepper](https://www.codegrepper.com/) snippet collection that embeds in google search results. See also this [collection of common questions](https://www.codegrepper.com/code-examples/rust). - [github copilot](https://copilot.github.com/) snippet generation with machine learning -- [stackoverflow](https://stackoverflow.com) +- [stackoverflow](https://stackoverflow.com) - [rosetta code](http://www.rosettacode.org/wiki/Rosetta_Code) snippets in many different programming languages. Many [snippets](https://www.rosettacode.org/wiki/Category:Programming_Tasks) are programming contest style problems, but there also problems that demonstrate the use of JSON, SHA-256, read a file line by line... - check docs of popular languages to cross reference function/snippet names for fuzzy matching diff --git a/editor/src/graphics/shaders/shader.wgsl b/editor/src/graphics/shaders/shader.wgsl index 645032ecbf..343f8b0d14 100644 --- a/editor/src/graphics/shaders/shader.wgsl +++ b/editor/src/graphics/shaders/shader.wgsl @@ -18,10 +18,10 @@ fn vs_main( [[location(1)]] in_color: vec4, ) -> VertexOutput { var out: VertexOutput; - + out.position = u_globals.ortho * vec4(in_position, 0.0, 1.0); out.color = in_color; - + return out; } diff --git a/editor/src/window/keyboard_input.rs b/editor/src/window/keyboard_input.rs index 71addc230a..1f06293d1c 100644 --- a/editor/src/window/keyboard_input.rs +++ b/editor/src/window/keyboard_input.rs @@ -21,7 +21,7 @@ impl Modifiers { // returns true if modifiers are active that can be active when the user wants to insert a new char; e.g.: shift+a to make A pub fn new_char_modifiers(&self) -> bool { self.no_modifiers() - || (self.shift && !self.ctrl && !self.alt && !self.logo) // e.g.: shift+a to make A + || (self.shift && !self.ctrl && !self.alt && !self.logo) // e.g.: shift+a to make A || (self.cmd_or_ctrl() && self.alt) // e.g.: ctrl+alt+2 to make @ on azerty keyboard } diff --git a/examples/false-interpreter/examples/cksum.false b/examples/false-interpreter/examples/cksum.false index 0711b2da1d..49e63ef392 100644 --- a/examples/false-interpreter/examples/cksum.false +++ b/examples/false-interpreter/examples/cksum.false @@ -8,7 +8,7 @@ With the interpreter, it currently runs about 350x slower though and requires ex Load 256 constants from https://github.com/wertarbyte/coreutils/blob/f70c7b785b93dd436788d34827b209453157a6f2/src/cksum.c#L117 To support the original false interpreter, numbers must be less than 32000. To deal with loading, just split all the numbers in two chunks. -First chunk is lower 16 bits, second chunk is higher 16 bits shift to the right. +First chunk is lower 16 bits, second chunk is higher 16 bits shift to the right. Its values are then shifted back and merged together. } 16564 45559 65536*| 23811 46390 65536*| 31706 47221 65536*| 26221 48308 65536*| @@ -199,7 +199,7 @@ i; {left shift it by 8 (multiply by 0x100)} 256* - + {xor with the loaded constant} x;! diff --git a/examples/hello-swift/platform/host.swift b/examples/hello-swift/platform/host.swift index 747f62b6ee..74edc494d2 100644 --- a/examples/hello-swift/platform/host.swift +++ b/examples/hello-swift/platform/host.swift @@ -25,7 +25,7 @@ extension RocStr { var isSmallString: Bool { len < 0 } - + var length: Int { if isSmallString { var len = len @@ -37,7 +37,7 @@ extension RocStr { return len } } - + var string: String { if isSmallString { let data: Data = withUnsafePointer(to: self) { ptr in diff --git a/examples/hello-world/README.md b/examples/hello-world/README.md index 4f0ec8dfd1..26d4c8e7d9 100644 --- a/examples/hello-world/README.md +++ b/examples/hello-world/README.md @@ -14,17 +14,17 @@ $ cargo run --release Hello.roc ## Design Notes -This demonstrates the basic design of hosts: Roc code gets compiled into a pure +This demonstrates the basic design of hosts: Roc code gets compiled into a pure function (in this case, a thunk that always returns `"Hello, World!"`) and then the host calls that function. Fundamentally, that's the whole idea! The host might not even have a `main` - it could be a library, a plugin, anything. Everything else is built on this basic "hosts calling linked pure functions" design. For example, things get more interesting when the compiled Roc function returns -a `Task` - that is, a tagged union data structure containing function pointers -to callback closures. This lets the Roc pure function describe arbitrary -chainable effects, which the host can interpret to perform I/O as requested by -the Roc program. (The tagged union `Task` would have a variant for each supported +a `Task` - that is, a tagged union data structure containing function pointers +to callback closures. This lets the Roc pure function describe arbitrary +chainable effects, which the host can interpret to perform I/O as requested by +the Roc program. (The tagged union `Task` would have a variant for each supported I/O operation.) In this trivial example, it's very easy to line up the API between the host and @@ -39,6 +39,6 @@ Roc application authors only care about the Roc-host/Roc-app portion, and the host author only cares about the Roc-host/C boundary when implementing the host. Using this glue code, the Roc compiler can generate C header files describing the -boundary. This not only gets us host compatibility with C compilers, but also -Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen) +boundary. This not only gets us host compatibility with C compilers, but also +Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen) generates correct Rust FFI bindings from C headers. diff --git a/examples/hello-zig/README.md b/examples/hello-zig/README.md index 4f0ec8dfd1..26d4c8e7d9 100644 --- a/examples/hello-zig/README.md +++ b/examples/hello-zig/README.md @@ -14,17 +14,17 @@ $ cargo run --release Hello.roc ## Design Notes -This demonstrates the basic design of hosts: Roc code gets compiled into a pure +This demonstrates the basic design of hosts: Roc code gets compiled into a pure function (in this case, a thunk that always returns `"Hello, World!"`) and then the host calls that function. Fundamentally, that's the whole idea! The host might not even have a `main` - it could be a library, a plugin, anything. Everything else is built on this basic "hosts calling linked pure functions" design. For example, things get more interesting when the compiled Roc function returns -a `Task` - that is, a tagged union data structure containing function pointers -to callback closures. This lets the Roc pure function describe arbitrary -chainable effects, which the host can interpret to perform I/O as requested by -the Roc program. (The tagged union `Task` would have a variant for each supported +a `Task` - that is, a tagged union data structure containing function pointers +to callback closures. This lets the Roc pure function describe arbitrary +chainable effects, which the host can interpret to perform I/O as requested by +the Roc program. (The tagged union `Task` would have a variant for each supported I/O operation.) In this trivial example, it's very easy to line up the API between the host and @@ -39,6 +39,6 @@ Roc application authors only care about the Roc-host/Roc-app portion, and the host author only cares about the Roc-host/C boundary when implementing the host. Using this glue code, the Roc compiler can generate C header files describing the -boundary. This not only gets us host compatibility with C compilers, but also -Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen) +boundary. This not only gets us host compatibility with C compilers, but also +Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen) generates correct Rust FFI bindings from C headers. diff --git a/getting_started/linux_x86.md b/getting_started/linux_x86.md index 1e6218bd7c..5cb46e360b 100644 --- a/getting_started/linux_x86.md +++ b/getting_started/linux_x86.md @@ -30,4 +30,4 @@ ``` 0. See [here](../README.md#examples) for the other examples. -**Tip:** when programming in roc, we recommend to execute `./roc check myproject/Foo.roc` before `./roc myproject/Foo.roc` or `./roc build myproject/Foo.roc`. `./roc check` can produce clear error messages in cases where building/running may panic. +**Tip:** when programming in roc, we recommend to execute `./roc check myproject/Foo.roc` before `./roc myproject/Foo.roc` or `./roc build myproject/Foo.roc`. `./roc check` can produce clear error messages in cases where building/running may panic. diff --git a/meeting_notes/editor_ui_ux_18-01-2022.md b/meeting_notes/editor_ui_ux_18-01-2022.md index 7fa24d0489..55d0d5032f 100644 --- a/meeting_notes/editor_ui_ux_18-01-2022.md +++ b/meeting_notes/editor_ui_ux_18-01-2022.md @@ -7,8 +7,8 @@ - How is AST data accessible to plugins? - How does plugin UI work (UI components, vector level, pixel-level)? -- Given a selected expression, how do we show all plugins available that can visualize this - expression or have an interactive widget to alter the expression (color picker). What does +- Given a selected expression, how do we show all plugins available that can visualize this + expression or have an interactive widget to alter the expression (color picker). What does this API look like? - use type driven UX? https://pchiusano.github.io/2013-09-10/type-systems-and-ux-example.html @@ -18,13 +18,13 @@ + zoomed out view should show type defs and function defs with folded body + zooming in on function should unfold/show function body + Traditional IDE's like ecplise can show an overwhelming amount of possible buttons/actions and views. Zoom levels can be used to prevent this excess of available options. - + - There should be a single editable text field to alter AST. This could be the same text field for entering commands, pressing a certain key could switch between command/plain text input into AST. Current part of AST that is being edited is highlighted. - Hovering over expression should show button on left sidebar that allows you to pop out a pinned view of the expression. This pinned view could for example show all variants in a large type definition. Hovering over a type in a place other than the definition could show all variants, this hover view should also be capable of being pinned and moved around as desired. - + - UI interaction specification from which we can generate both e.g. a window with clickable buttons 'previuous' and `next` that also supports the voice commands `previuous` and `next`. Next actions to take: diff --git a/name-and-logo.md b/name-and-logo.md index 39584990ee..9465fab6bf 100644 --- a/name-and-logo.md +++ b/name-and-logo.md @@ -8,16 +8,16 @@ That’s why the logo is a bird. It’s specifically an [*origami* bird](https:/ to [Elm](https://elm-lang.org/)’s tangram logo. Roc is a direct descendant of Elm. The languages are similar, but not the same. -[Origami](https://en.wikipedia.org/wiki/Origami) likewise has similarities to [tangrams](https://en.wikipedia.org/wiki/Tangram), although they are not the same. -Both involve making a surprising variety of things -from simple primitives. [*Folds*](https://en.wikipedia.org/wiki/Fold_(higher-order_function)) +[Origami](https://en.wikipedia.org/wiki/Origami) likewise has similarities to [tangrams](https://en.wikipedia.org/wiki/Tangram), although they are not the same. +Both involve making a surprising variety of things +from simple primitives. [*Folds*](https://en.wikipedia.org/wiki/Fold_(higher-order_function)) are also common in functional programming. -The logo was made by tracing triangles onto a photo of a physical origami bird. -It’s made of triangles because triangles are a foundational primitive in +The logo was made by tracing triangles onto a photo of a physical origami bird. +It’s made of triangles because triangles are a foundational primitive in computer graphics. -The name was chosen because it makes for a three-letter file extension, it means +The name was chosen because it makes for a three-letter file extension, it means something fantastical, and it has incredible potential for puns. # Different Ways to Spell Roc @@ -29,4 +29,4 @@ something fantastical, and it has incredible potential for puns. # Fun Facts -Roc translates to 鹏 in Chinese, [which means](https://www.mdbg.net/chinese/dictionary?page=worddict&wdrst=0&wdqb=%E9%B9%8F) "a large fabulous bird." +Roc translates to 鹏 in Chinese, [which means](https://www.mdbg.net/chinese/dictionary?page=worddict&wdrst=0&wdqb=%E9%B9%8F) "a large fabulous bird." diff --git a/nightly_benches/README.me b/nightly_benches/README.me index fb6013bb8f..1e66e8951f 100644 --- a/nightly_benches/README.me +++ b/nightly_benches/README.me @@ -1,6 +1,6 @@ # Running benchmarks - Install cargo criterion: + Install cargo criterion: ``` cargo install --git https://github.com/Anton-4/cargo-criterion --branch main ``` @@ -8,7 +8,7 @@ ``` sudo sh -c 'echo 1 >/proc/sys/kernel/perf_event_paranoid' ``` - run: + run: ``` cargo criterion ``` \ No newline at end of file diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 62b7e8dc2c..3a004094ec 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -576,14 +576,14 @@ mod test_reporting { indoc!( r#" ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── - + I cannot find a `true` value - + 1│ if true then 1 else 2 ^^^^ - + Did you mean one of these? - + True Str Num @@ -630,12 +630,12 @@ mod test_reporting { indoc!( r#" y = 9 - + box = \class, htmlChildren -> div [ class ] [] - + div = \_, _ -> 4 - + box "wizard" [] "# ), @@ -644,7 +644,7 @@ mod test_reporting { ── UNUSED ARGUMENT ───────────────────────────────────────────────────────────── `box` doesn't use `htmlChildren`. - + 3│ box = \class, htmlChildren -> ^^^^^^^^^^^^ @@ -7042,7 +7042,7 @@ I need all branches in an `if` to have the same type! r#" C a b : a -> D a b D a b : { a, b } - + f : C a Nat -> D a Nat f = \c -> c 6 f @@ -7051,20 +7051,20 @@ I need all branches in an `if` to have the same type! indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── - + The 1st argument to `c` is not what I expect: - + 5│ f = \c -> c 6 ^ - + This argument is a number of type: - + Num a - + But `c` needs the 1st argument to be: - + a - + Tip: The type annotation uses the type variable `a` to say that this definition can produce any type of value. But in the body I see that it will only produce a `Num` value of a single specific type. Maybe From fb66467343279b6194bc401e5146ee0123a95a14 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Tue, 18 Jan 2022 22:33:24 -0700 Subject: [PATCH 249/541] Remove trailing spaces from test snapshots --- .../snapshots/pass/newline_before_add.expr.result-ast | 4 ++-- .../tests/snapshots/pass/newline_before_add.expr.roc | 2 +- .../snapshots/pass/newline_before_sub.expr.result-ast | 4 ++-- .../tests/snapshots/pass/newline_before_sub.expr.roc | 2 +- .../snapshots/pass/ops_with_newlines.expr.result-ast | 4 ++-- .../tests/snapshots/pass/ops_with_newlines.expr.roc | 4 ++-- .../snapshots/pass/two_backpassing.expr.result-ast | 4 ++-- .../tests/snapshots/pass/two_backpassing.expr.roc | 2 +- .../tests/snapshots/pass/when_if_guard.expr.result-ast | 10 +++++----- .../parse/tests/snapshots/pass/when_if_guard.expr.roc | 6 +++--- .../snapshots/pass/when_in_parens_indented.expr.roc | 2 +- .../when_with_function_application.expr.result-ast | 4 ++-- .../pass/when_with_function_application.expr.roc | 2 +- 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast index b5c5538c1f..57dac2f56f 100644 --- a/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast @@ -9,10 +9,10 @@ BinOps( Newline, ], ), - @4-5 Plus, + @2-3 Plus, ), ], - @6-7 Num( + @4-5 Num( "4", ), ) diff --git a/compiler/parse/tests/snapshots/pass/newline_before_add.expr.roc b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.roc index 2aef041bd9..73999f176e 100644 --- a/compiler/parse/tests/snapshots/pass/newline_before_add.expr.roc +++ b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.roc @@ -1,2 +1,2 @@ -3 +3 + 4 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast index 4ce4377f5e..c01422cca9 100644 --- a/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast @@ -9,10 +9,10 @@ BinOps( Newline, ], ), - @4-5 Minus, + @2-3 Minus, ), ], - @6-7 Num( + @4-5 Num( "4", ), ) diff --git a/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.roc b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.roc index 19ab26bdea..dfab8da599 100644 --- a/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.roc +++ b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.roc @@ -1,2 +1,2 @@ -3 +3 - 4 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast index 2905c1e745..28005999cb 100644 --- a/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast @@ -9,10 +9,10 @@ BinOps( Newline, ], ), - @4-5 Plus, + @2-3 Plus, ), ], - @10-11 SpaceBefore( + @7-8 SpaceBefore( Num( "4", ), diff --git a/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.roc b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.roc index 518b92423c..e2985d8af2 100644 --- a/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.roc +++ b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.roc @@ -1,4 +1,4 @@ -3 -+ +3 ++ 4 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/two_backpassing.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_backpassing.expr.result-ast index 0bfd1dacf9..4792ed7351 100644 --- a/compiler/parse/tests/snapshots/pass/two_backpassing.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/two_backpassing.expr.result-ast @@ -18,7 +18,7 @@ SpaceBefore( }, ), ), - @33-44 SpaceBefore( + @33-43 SpaceBefore( Backpassing( [ @33-34 Identifier( @@ -28,7 +28,7 @@ SpaceBefore( @38-40 Record( [], ), - @43-44 SpaceBefore( + @42-43 SpaceBefore( Var { module_name: "", ident: "x", diff --git a/compiler/parse/tests/snapshots/pass/two_backpassing.expr.roc b/compiler/parse/tests/snapshots/pass/two_backpassing.expr.roc index f36a289c6c..051228bc5e 100644 --- a/compiler/parse/tests/snapshots/pass/two_backpassing.expr.roc +++ b/compiler/parse/tests/snapshots/pass/two_backpassing.expr.roc @@ -1,5 +1,5 @@ # leading comment x <- (\y -> y) -z <- {} +z <- {} x diff --git a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast index b641b19efc..e0f8d41ece 100644 --- a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast @@ -15,7 +15,7 @@ When( ], ), ], - value: @28-29 SpaceBefore( + value: @27-28 SpaceBefore( Num( "1", ), @@ -27,7 +27,7 @@ When( }, WhenBranch { patterns: [ - @35-36 SpaceBefore( + @34-35 SpaceBefore( Underscore( "", ), @@ -37,7 +37,7 @@ When( ], ), ], - value: @49-50 SpaceBefore( + value: @47-48 SpaceBefore( Num( "2", ), @@ -49,7 +49,7 @@ When( }, WhenBranch { patterns: [ - @56-58 SpaceBefore( + @54-56 SpaceBefore( GlobalTag( "Ok", ), @@ -59,7 +59,7 @@ When( ], ), ], - value: @71-72 SpaceBefore( + value: @68-69 SpaceBefore( Num( "3", ), diff --git a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.roc b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.roc index 392b1fe99f..e696ac51ae 100644 --- a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.roc +++ b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.roc @@ -1,9 +1,9 @@ when x is - _ -> + _ -> 1 - _ -> + _ -> 2 - Ok -> + Ok -> 3 diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.roc b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.roc index 21bd43313f..a599c446fc 100644 --- a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.roc +++ b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.roc @@ -1,3 +1,3 @@ (when x is - Ok -> 3 + Ok -> 3 ) diff --git a/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast index c01c55e1f9..1822dfa962 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast @@ -36,7 +36,7 @@ When( }, WhenBranch { patterns: [ - @39-40 SpaceBefore( + @38-39 SpaceBefore( Underscore( "", ), @@ -45,7 +45,7 @@ When( ], ), ], - value: @44-45 Num( + value: @43-44 Num( "4", ), guard: None, diff --git a/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.roc b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.roc index fd5da76dec..647ce0d14a 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.roc +++ b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.roc @@ -1,4 +1,4 @@ when x is 1 -> Num.neg - 2 + 2 _ -> 4 From 8e4b6f0cabbd894249a5a8c0bf27ccace1e29103 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Mon, 17 Jan 2022 21:19:34 -0700 Subject: [PATCH 250/541] Remove `Num.(min/max)Int` Replace all uses with `Num.(min/max)I64`, since all uses expect an `I64`. --- cli/tests/repl_eval.rs | 18 ++++--- compiler/builtins/docs/Num.roc | 2 - compiler/builtins/src/std.rs | 6 --- compiler/can/src/builtins.rs | 32 ------------ .../build/app_with_deps/WithBuiltins.roc | 2 +- .../interface_with_deps/WithBuiltins.roc | 2 +- compiler/load/tests/test_load.rs | 2 +- compiler/module/src/symbol.rs | 4 +- compiler/test_gen/src/gen_list.rs | 2 +- compiler/test_gen/src/gen_num.rs | 52 +++++-------------- 10 files changed, 30 insertions(+), 92 deletions(-) diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index af13cb5ac5..49be11c618 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -342,24 +342,30 @@ mod repl_eval { #[test] fn num_add_wrap() { - expect_success("Num.addWrap Num.maxInt 1", "-9223372036854775808 : Int *"); + expect_success( + "Num.addWrap Num.maxI64 1", + "-9223372036854775808 : Int Signed64", + ); } #[test] fn num_sub_wrap() { - expect_success("Num.subWrap Num.minInt 1", "9223372036854775807 : Int *"); + expect_success( + "Num.subWrap Num.minI64 1", + "9223372036854775807 : Int Signed64", + ); } #[test] fn num_mul_wrap() { - expect_success("Num.mulWrap Num.maxInt 2", "-2 : Int *"); + expect_success("Num.mulWrap Num.maxI64 2", "-2 : Int Signed64"); } #[test] fn num_add_checked() { expect_success("Num.addChecked 1 1", "Ok 2 : Result (Num *) [ Overflow ]*"); expect_success( - "Num.addChecked Num.maxInt 1", + "Num.addChecked Num.maxI64 1", "Err Overflow : Result I64 [ Overflow ]*", ); } @@ -368,7 +374,7 @@ mod repl_eval { fn num_sub_checked() { expect_success("Num.subChecked 1 1", "Ok 0 : Result (Num *) [ Overflow ]*"); expect_success( - "Num.subChecked Num.minInt 1", + "Num.subChecked Num.minI64 1", "Err Overflow : Result I64 [ Overflow ]*", ); } @@ -380,7 +386,7 @@ mod repl_eval { "Ok 40 : Result (Num *) [ Overflow ]*", ); expect_success( - "Num.mulChecked Num.maxInt 2", + "Num.mulChecked Num.maxI64 2", "Err Overflow : Result I64 [ Overflow ]*", ); } diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index ed4e2f9e9d..792d688030 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -67,14 +67,12 @@ interface Num maxI64, maxU64, maxI128, - maxInt, minFloat, minI32, minU32, minI64, minU64, minI128, - minInt, modInt, modFloat, mul, diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 35a8ea86fb..53bd661fe4 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -302,12 +302,6 @@ pub fn types() -> MutMap { Box::new(bool_type()) ); - // maxInt : Int range - add_type!(Symbol::NUM_MAX_INT, int_type(flex(TVAR1))); - - // minInt : Int range - add_type!(Symbol::NUM_MIN_INT, int_type(flex(TVAR1))); - let div_by_zero = SolvedType::TagUnion( vec![(TagName::Global("DivByZero".into()), vec![])], Box::new(SolvedType::Wildcard), diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index df75271330..0b5856911c 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -197,8 +197,6 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_ASIN => num_asin, NUM_BYTES_TO_U16 => num_bytes_to_u16, NUM_BYTES_TO_U32 => num_bytes_to_u32, - NUM_MAX_INT => num_max_int, - NUM_MIN_INT => num_min_int, NUM_BITWISE_AND => num_bitwise_and, NUM_BITWISE_XOR => num_bitwise_xor, NUM_BITWISE_OR => num_bitwise_or, @@ -364,36 +362,6 @@ fn lowlevel_5(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def { ) } -/// Num.maxInt : Int -fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def { - let int_var = var_store.fresh(); - let int_precision_var = var_store.fresh(); - let body = int::(int_var, int_precision_var, i64::MAX); - - Def { - annotation: None, - expr_var: int_var, - loc_expr: Loc::at_zero(body), - loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), - pattern_vars: SendMap::default(), - } -} - -/// Num.minInt : Int -fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def { - let int_var = var_store.fresh(); - let int_precision_var = var_store.fresh(); - let body = int::(int_var, int_precision_var, i64::MIN); - - Def { - annotation: None, - expr_var: int_var, - loc_expr: Loc::at_zero(body), - loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), - pattern_vars: SendMap::default(), - } -} - // Num.toStr : Num a -> Str fn num_to_str(symbol: Symbol, var_store: &mut VarStore) -> Def { let num_var = var_store.fresh(); diff --git a/compiler/load/tests/fixtures/build/app_with_deps/WithBuiltins.roc b/compiler/load/tests/fixtures/build/app_with_deps/WithBuiltins.roc index 23ba8842f4..4082739f17 100644 --- a/compiler/load/tests/fixtures/build/app_with_deps/WithBuiltins.roc +++ b/compiler/load/tests/fixtures/build/app_with_deps/WithBuiltins.roc @@ -10,7 +10,7 @@ x = 5.0 divisionTest = Num.maxFloat / x -intTest = Num.maxInt +intTest = Num.maxI64 constantNum = 5 diff --git a/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc b/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc index 23ba8842f4..4082739f17 100644 --- a/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc +++ b/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc @@ -10,7 +10,7 @@ x = 5.0 divisionTest = Num.maxFloat / x -intTest = Num.maxInt +intTest = Num.maxI64 constantNum = 5 diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index 373446c3a9..a12a95f617 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -379,7 +379,7 @@ mod test_load { "floatTest" => "Float *", "divisionFn" => "Float a, Float a -> Result (Float a) [ DivByZero ]*", "divisionTest" => "Result (Float *) [ DivByZero ]*", - "intTest" => "Int *", + "intTest" => "I64", "x" => "Float *", "constantNum" => "Num *", "divDep1ByDep2" => "Result (Float *) [ DivByZero ]*", diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index d39352cfba..50056ea268 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -903,8 +903,8 @@ define_builtins! { 15 NUM_F32: "F32" imported // the Num.F32 type alias 16 NUM_FLOATINGPOINT: "FloatingPoint" imported // Float : Num FloatingPoint 17 NUM_AT_FLOATINGPOINT: "@FloatingPoint" // the Float.@FloatingPoint private tag - 18 NUM_MAX_INT: "maxInt" - 19 NUM_MIN_INT: "minInt" + 18 NUM_MAX_INT: "" // removed (replaced functionally by NUM_MAX_I128) + 19 NUM_MIN_INT: "" // removed (replaced functionally by NUM_MIN_I128) 20 NUM_MAX_FLOAT: "maxFloat" 21 NUM_MIN_FLOAT: "minFloat" 22 NUM_ABS: "abs" diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 55845c777e..bccc2ecee2 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -2381,7 +2381,7 @@ fn cleanup_because_exception() { five : I64 five = 5 - five + Num.maxInt + 3 + (Num.intCast (List.len x)) + five + Num.maxI64 + 3 + (Num.intCast (List.len x)) "# ), 9, diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index e026c4b292..6da8458881 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -549,8 +549,8 @@ fn i64_abs() { 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); - assert_evals_to!("Num.abs Num.maxInt", i64::MAX, i64); - assert_evals_to!("Num.abs (Num.minInt + 1)", -(i64::MIN + 1), i64); + assert_evals_to!("Num.abs Num.maxI64", i64::MAX, i64); + assert_evals_to!("Num.abs (Num.minI64 + 1)", -(i64::MIN + 1), i64); } #[test] @@ -562,7 +562,7 @@ fn abs_min_int_overflow() { assert_evals_to!( indoc!( r#" - Num.abs Num.minInt + Num.abs Num.minI64 "# ), 0, @@ -1231,7 +1231,7 @@ fn tail_call_elimination() { #[cfg(any(feature = "gen-dev"))] fn int_negate_dev() { // TODO - // dev backend yet to have `Num.maxInt` or `Num.minInt`. + // dev backend yet to have `Num.maxI64` or `Num.minI64`. // add the "gen-dev" feature to the test below after implementing them both. assert_evals_to!("Num.neg 123", -123, i64); assert_evals_to!("Num.neg -123", 123, i64); @@ -1242,8 +1242,8 @@ fn int_negate_dev() { #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn int_negate() { assert_evals_to!("Num.neg 123", -123, i64); - assert_evals_to!("Num.neg Num.maxInt", -i64::MAX, i64); - assert_evals_to!("Num.neg (Num.minInt + 1)", i64::MAX, i64); + assert_evals_to!("Num.neg Num.maxI64", -i64::MAX, i64); + assert_evals_to!("Num.neg (Num.minI64 + 1)", i64::MAX, i64); } #[test] @@ -1255,7 +1255,7 @@ fn neg_min_int_overflow() { assert_evals_to!( indoc!( r#" - Num.neg Num.minInt + Num.neg Num.minI64 "# ), 0, @@ -1543,34 +1543,6 @@ fn float_add_overflow() { ); } -#[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -fn num_max_int() { - assert_evals_to!( - indoc!( - r#" - Num.maxInt - "# - ), - i64::MAX, - i64 - ); -} - -#[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -fn num_min_int() { - assert_evals_to!( - indoc!( - r#" - Num.minInt - "# - ), - i64::MIN, - i64 - ); -} - #[test] #[cfg(any(feature = "gen-llvm"))] #[should_panic(expected = r#"Roc failed with message: "integer subtraction overflowed!"#)] @@ -1633,7 +1605,7 @@ fn int_sub_checked() { assert_evals_to!( indoc!( r#" - when Num.subChecked Num.minInt 1 is + when Num.subChecked Num.minI64 1 is Err Overflow -> -1 Ok v -> v "# @@ -1737,7 +1709,7 @@ fn int_mul_wrap() { assert_evals_to!( indoc!( r#" - Num.mulWrap Num.maxInt 2 + Num.mulWrap Num.maxI64 2 "# ), -2, @@ -1763,7 +1735,7 @@ fn int_mul_checked() { assert_evals_to!( indoc!( r#" - when Num.mulChecked Num.maxInt 2 is + when Num.mulChecked Num.maxI64 2 is Err Overflow -> -1 Ok v -> v "# @@ -2215,14 +2187,14 @@ fn num_to_str() { let max = format!("{}", i64::MAX); assert_evals_to!( - r#"Num.toStr Num.maxInt"#, + r#"Num.toStr Num.maxI64"#, RocStr::from_slice(max.as_bytes()), RocStr ); let min = format!("{}", i64::MIN); assert_evals_to!( - r#"Num.toStr Num.minInt"#, + r#"Num.toStr Num.minI64"#, RocStr::from_slice(min.as_bytes()), RocStr ); From 359956220e8fe4c8ffba2340ad6f66673911be78 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 19 Jan 2022 19:11:37 +0100 Subject: [PATCH 251/541] working definition of Effect.forever --- compiler/load/src/effect_module.rs | 374 ++++++++++++++++++++++++++++- 1 file changed, 369 insertions(+), 5 deletions(-) diff --git a/compiler/load/src/effect_module.rs b/compiler/load/src/effect_module.rs index 43b35a2df6..c06fb85d5c 100644 --- a/compiler/load/src/effect_module.rs +++ b/compiler/load/src/effect_module.rs @@ -21,7 +21,7 @@ type Builder = for<'r, 's, 't0, 't1> fn( &'t1 mut VarStore, ) -> (Symbol, Def); -pub const BUILTIN_EFFECT_FUNCTIONS: [(&str, Builder); 3] = [ +pub const BUILTIN_EFFECT_FUNCTIONS: &[(&str, Builder)] = &[ // Effect.after : Effect a, (a -> Effect b) -> Effect b ("after", build_effect_after), // Effect.map : Effect a, (a -> b) -> Effect b @@ -32,6 +32,8 @@ pub const BUILTIN_EFFECT_FUNCTIONS: [(&str, Builder); 3] = [ ("forever", build_effect_forever), ]; +const RECURSIVE_BUILTIN_EFFECT_FUNCTIONS: &[&str] = &["forever"]; + // the Effects alias & associated functions // // A platform can define an Effect type in its header. It can have an arbitrary name @@ -54,7 +56,7 @@ pub fn build_effect_builtins( exposed_symbols: &mut MutSet, declarations: &mut Vec, ) { - for (_, f) in BUILTIN_EFFECT_FUNCTIONS.iter() { + for (name, f) in BUILTIN_EFFECT_FUNCTIONS.iter() { let (symbol, def) = f( env, scope, @@ -64,7 +66,20 @@ pub fn build_effect_builtins( ); exposed_symbols.insert(symbol); - declarations.push(Declaration::Declare(def)); + + let is_recursive = RECURSIVE_BUILTIN_EFFECT_FUNCTIONS.iter().any(|n| n == name); + if is_recursive { + declarations.push(Declaration::DeclareRec(vec![def])); + } else { + declarations.push(Declaration::Declare(def)); + } + } + + // Useful when working on functions in this module. By default symbols that we named do now + // show up with their name. We have to register them like below to make the names show up in + // debug prints + if false { + env.home.register_debug_idents(&env.ident_ids); } } @@ -590,6 +605,98 @@ fn build_effect_after( (after_symbol, def) } +/// turn `value` into `@Effect \{} -> value` +fn wrap_in_effect_thunk( + body: Expr, + effect_tag_name: TagName, + closure_name: Symbol, + captured_symbols: Vec, + var_store: &mut VarStore, +) -> Expr { + let captured_symbols: Vec<_> = captured_symbols + .into_iter() + .map(|x| (x, var_store.fresh())) + .collect(); + + // \{} -> body + let const_closure = { + let arguments = vec![( + var_store.fresh(), + Loc::at_zero(empty_record_pattern(var_store)), + )]; + + Expr::Closure(ClosureData { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: var_store.fresh(), + name: closure_name, + // captured_symbols: vec![(value_symbol, var_store.fresh())], + captured_symbols, + recursive: Recursive::NotRecursive, + arguments, + loc_body: Box::new(Loc::at_zero(body)), + }) + }; + + // `@Effect \{} -> value` + Expr::Tag { + variant_var: var_store.fresh(), + ext_var: var_store.fresh(), + name: effect_tag_name, + arguments: vec![(var_store.fresh(), Loc::at_zero(const_closure))], + } +} + +/// given `effect : Effect a`, unwrap the thunk and force it, giving a value of type `a` +fn force_effect( + effect: Expr, + effect_tag_name: TagName, + thunk_symbol: Symbol, + var_store: &mut VarStore, +) -> Expr { + let whole_var = var_store.fresh(); + let ext_var = var_store.fresh(); + + let thunk_var = var_store.fresh(); + + let pattern = Pattern::AppliedTag { + ext_var, + whole_var, + tag_name: effect_tag_name, + arguments: vec![(thunk_var, Loc::at_zero(Pattern::Identifier(thunk_symbol)))], + }; + + let pattern_vars = SendMap::default(); + // pattern_vars.insert(thunk_symbol, thunk_var); + + let def = Def { + loc_pattern: Loc::at_zero(pattern), + loc_expr: Loc::at_zero(effect), + expr_var: var_store.fresh(), + pattern_vars, + annotation: None, + }; + + let ret_var = var_store.fresh(); + + let force_thunk_call = { + let boxed = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(thunk_symbol)), + var_store.fresh(), + ret_var, + ); + + let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::EmptyRecord))]; + let call = Expr::Call(Box::new(boxed), arguments, CalledVia::Space); + + Loc::at_zero(call) + }; + + Expr::LetNonRec(Box::new(def), Box::new(force_thunk_call), var_store.fresh()) +} + fn build_effect_forever( env: &mut Env, scope: &mut Scope, @@ -605,7 +712,7 @@ fn build_effect_forever( // // Effect.forever : Effect a -> Effect b // Effect.forever = \effect -> - // \{} -> + // @Effect \{} -> // @Effect thunk1 = effect // _ = thunk1 {} // @Effect thunk2 = Effect.forever effect @@ -644,7 +751,264 @@ fn build_effect_forever( // C env -> foreverInner {} env.effect // // Making `foreverInner` perfectly tail-call optimizable - todo!() + + let forever_symbol = { + scope + .introduce( + "forever".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let effect = { + scope + .introduce( + "effect".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let body = build_effect_forever_body( + env, + scope, + effect_tag_name.clone(), + forever_symbol, + effect, + var_store, + ); + + let arguments = vec![(var_store.fresh(), Loc::at_zero(Pattern::Identifier(effect)))]; + + let function_var = var_store.fresh(); + let after_closure = Expr::Closure(ClosureData { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: var_store.fresh(), + name: forever_symbol, + captured_symbols: Vec::new(), + recursive: Recursive::Recursive, + arguments, + loc_body: Box::new(Loc::at_zero(body)), + }); + + let mut introduced_variables = IntroducedVariables::default(); + + let signature = { + let var_a = var_store.fresh(); + + introduced_variables.insert_named("a".into(), var_a); + + let effect_a_1 = build_effect_alias( + effect_symbol, + effect_tag_name.clone(), + "a", + var_a, + Type::Variable(var_a), + var_store, + &mut introduced_variables, + ); + + // We need this second variable (instead of cloning the one above) + // so we get a new fresh variable for the lambda set + let effect_a_2 = build_effect_alias( + effect_symbol, + effect_tag_name, + "a", + var_a, + Type::Variable(var_a), + var_store, + &mut introduced_variables, + ); + + let closure_var = var_store.fresh(); + introduced_variables.insert_wildcard(closure_var); + + Type::Function( + vec![effect_a_1], + Box::new(Type::Variable(closure_var)), + Box::new(effect_a_2), + ) + }; + + let def_annotation = roc_can::def::Annotation { + signature, + introduced_variables, + aliases: SendMap::default(), + region: Region::zero(), + }; + + let pattern = Pattern::Identifier(forever_symbol); + let mut pattern_vars = SendMap::default(); + pattern_vars.insert(forever_symbol, function_var); + let def = Def { + loc_pattern: Loc::at_zero(pattern), + loc_expr: Loc::at_zero(after_closure), + expr_var: function_var, + pattern_vars, + annotation: Some(def_annotation), + }; + + (forever_symbol, def) +} + +fn build_effect_forever_body( + env: &mut Env, + scope: &mut Scope, + effect_tag_name: TagName, + forever_symbol: Symbol, + effect: Symbol, + var_store: &mut VarStore, +) -> Expr { + let closure_name = { + scope + .introduce( + "forever_inner".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let inner_body = build_effect_forever_inner_body( + env, + scope, + effect_tag_name.clone(), + forever_symbol, + effect, + var_store, + ); + + let captured_symbols = vec![effect]; + wrap_in_effect_thunk( + inner_body, + effect_tag_name, + closure_name, + captured_symbols, + var_store, + ) +} + +fn build_effect_forever_inner_body( + env: &mut Env, + scope: &mut Scope, + effect_tag_name: TagName, + forever_symbol: Symbol, + effect: Symbol, + var_store: &mut VarStore, +) -> Expr { + let thunk1_symbol = { + scope + .introduce( + "thunk1".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let thunk2_symbol = { + scope + .introduce( + "thunk2".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + // Effect thunk1 = effect + let thunk_from_effect = { + let whole_var = var_store.fresh(); + let ext_var = var_store.fresh(); + + let thunk_var = var_store.fresh(); + + let pattern = Pattern::AppliedTag { + ext_var, + whole_var, + tag_name: effect_tag_name.clone(), + arguments: vec![(thunk_var, Loc::at_zero(Pattern::Identifier(thunk1_symbol)))], + }; + + let pattern_vars = SendMap::default(); + + Def { + loc_pattern: Loc::at_zero(pattern), + loc_expr: Loc::at_zero(Expr::Var(effect)), + expr_var: var_store.fresh(), + pattern_vars, + annotation: None, + } + }; + + // thunk1 {} + let force_thunk_call = { + let ret_var = var_store.fresh(); + let boxed = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(thunk1_symbol)), + var_store.fresh(), + ret_var, + ); + + let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::EmptyRecord))]; + let call = Expr::Call(Box::new(boxed), arguments, CalledVia::Space); + + Loc::at_zero(call) + }; + + // _ = thunk1 {} + let force_thunk1 = Def { + loc_pattern: Loc::at_zero(Pattern::Underscore), + loc_expr: force_thunk_call, + expr_var: var_store.fresh(), + pattern_vars: Default::default(), + annotation: None, + }; + + // recursive call `forever effect` + let forever_effect = { + let boxed = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(forever_symbol)), + var_store.fresh(), + var_store.fresh(), + ); + + let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::Var(effect)))]; + Expr::Call(Box::new(boxed), arguments, CalledVia::Space) + }; + + // ``` + // Effect thunk2 = forever effect + // thunk2 {} + // ``` + let force_thunk2 = Loc::at_zero(force_effect( + forever_effect, + effect_tag_name, + thunk2_symbol, + var_store, + )); + + Expr::LetNonRec( + Box::new(thunk_from_effect), + Box::new(Loc::at_zero(Expr::LetNonRec( + Box::new(force_thunk1), + Box::new(force_thunk2), + var_store.fresh(), + ))), + var_store.fresh(), + ) } pub fn build_host_exposed_def( From 389681ee909f82ca255ba14f9bad8f885339a824 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 19 Jan 2022 19:19:43 +0100 Subject: [PATCH 252/541] return forall b. Effect b --- compiler/load/src/effect_module.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/load/src/effect_module.rs b/compiler/load/src/effect_module.rs index c06fb85d5c..42622db425 100644 --- a/compiler/load/src/effect_module.rs +++ b/compiler/load/src/effect_module.rs @@ -802,10 +802,12 @@ fn build_effect_forever( let signature = { let var_a = var_store.fresh(); + let var_b = var_store.fresh(); introduced_variables.insert_named("a".into(), var_a); + introduced_variables.insert_named("b".into(), var_b); - let effect_a_1 = build_effect_alias( + let effect_a = build_effect_alias( effect_symbol, effect_tag_name.clone(), "a", @@ -815,14 +817,12 @@ fn build_effect_forever( &mut introduced_variables, ); - // We need this second variable (instead of cloning the one above) - // so we get a new fresh variable for the lambda set - let effect_a_2 = build_effect_alias( + let effect_b = build_effect_alias( effect_symbol, effect_tag_name, - "a", - var_a, - Type::Variable(var_a), + "b", + var_b, + Type::Variable(var_b), var_store, &mut introduced_variables, ); @@ -831,9 +831,9 @@ fn build_effect_forever( introduced_variables.insert_wildcard(closure_var); Type::Function( - vec![effect_a_1], + vec![effect_a], Box::new(Type::Variable(closure_var)), - Box::new(effect_a_2), + Box::new(effect_b), ) }; From aecafe5c801d0156eb91b68f021214d7d88ecf07 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 19 Jan 2022 19:32:18 +0100 Subject: [PATCH 253/541] add Task.forever to platforms --- examples/benchmarks/platform/Task.roc | 5 ++++- examples/cli/platform/Task.roc | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/examples/benchmarks/platform/Task.roc b/examples/benchmarks/platform/Task.roc index 603abb83f1..7589213b34 100644 --- a/examples/benchmarks/platform/Task.roc +++ b/examples/benchmarks/platform/Task.roc @@ -1,9 +1,12 @@ interface Task - exposes [ Task, succeed, fail, after, map, putLine, putInt, getInt ] + exposes [ Task, succeed, fail, after, map, putLine, putInt, getInt, forever ] imports [ fx.Effect ] Task ok err : Effect.Effect (Result ok err) +forever : Task val err -> Task c d +forever = \task -> Effect.forever task + succeed : val -> Task val * succeed = \val -> Effect.always (Ok val) diff --git a/examples/cli/platform/Task.roc b/examples/cli/platform/Task.roc index f12060cd5e..70e7125c94 100644 --- a/examples/cli/platform/Task.roc +++ b/examples/cli/platform/Task.roc @@ -1,9 +1,13 @@ interface Task - exposes [ Task, succeed, fail, await, map, onFail, attempt ] + exposes [ Task, succeed, fail, await, map, onFail, attempt, forever ] imports [ fx.Effect ] Task ok err : Effect.Effect (Result ok err) +# Run a task forever. Note that even when we hit an error, we don't stop +forever : Task val err -> Task c d +forever = \task -> Effect.forever task + succeed : val -> Task val * succeed = \val -> Effect.always (Ok val) From 4c445f9f24fe2f98eab33080c351759d0256da00 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 19 Jan 2022 20:09:20 +0100 Subject: [PATCH 254/541] recognize functions that become tail-recursive after closure conversion --- compiler/mono/src/ir.rs | 10 +++++++--- compiler/mono/src/layout.rs | 5 +++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index a2431df636..e250343d91 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -738,15 +738,19 @@ impl<'a> Procs<'a> { ret_var: Variable, layout_cache: &mut LayoutCache<'a>, ) -> Result, RuntimeError> { - // anonymous functions cannot reference themselves, therefore cannot be tail-recursive - let is_self_recursive = false; - let raw_layout = layout_cache .raw_from_var(env.arena, annotation, env.subs) .unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err)); let top_level = ProcLayout::from_raw(env.arena, raw_layout); + // anonymous functions cannot reference themselves, therefore cannot be tail-recursive + // EXCEPT when the closure conversion makes it tail-recursive. + let is_self_recursive = match top_level.arguments.last() { + Some(Layout::LambdaSet(lambda_set)) => lambda_set.contains(symbol), + _ => false, + }; + match patterns_to_when(env, layout_cache, loc_args, ret_var, loc_body) { Ok((_, pattern_symbols, body)) => { // an anonymous closure. These will always be specialized already diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 2e28822023..2ece967011 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -538,6 +538,11 @@ impl<'a> LambdaSet<'a> { *self.representation } + /// Does the lambda set contain the given symbol? + pub fn contains(&self, symbol: Symbol) -> bool { + self.set.iter().any(|(s, _)| *s == symbol) + } + pub fn is_represented(&self) -> Option> { if let Layout::Struct(&[]) = self.representation { None From fbab19a937f86b1d71ce5e5545187fd760603bca Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 19 Jan 2022 20:57:36 +0100 Subject: [PATCH 255/541] custom debug instance for LambdaSet so that symbols are printed as their numbers; makes mono tests reliable --- compiler/mono/src/layout.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 2e28822023..4b94b2bdab 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -506,7 +506,40 @@ impl<'a> UnionLayout<'a> { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +/// Custom type so we can get the numeric representation of a symbol in tests (so `#UserApp.3` +/// instead of `UserApp.foo`). The pretty name is not reliable when running many tests +/// concurrently. The number does not change and will give a reliable output. +struct SetElement<'a> { + symbol: Symbol, + layout: &'a [Layout<'a>], +} + +impl std::fmt::Debug for SetElement<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if crate::ir::pretty_print_ir_symbols() { + write!(f, "( {:?}, {:?})", self.symbol, self.layout) + } else { + write!(f, "( {}, {:?})", self.symbol, self.layout) + } + } +} + +impl std::fmt::Debug for LambdaSet<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let entries = self.set.iter().map(|x| SetElement { + symbol: x.0, + layout: x.1, + }); + let set = f.debug_list().entries(entries).finish(); + + f.debug_struct("LambdaSet") + .field("set", &set) + .field("representation", &self.representation) + .finish() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct LambdaSet<'a> { /// collection of function names and their closure arguments pub set: &'a [(Symbol, &'a [Layout<'a>])], From 2adcbecf8a2517bfae3ad4347d66247c1785de02 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 19 Jan 2022 21:01:18 +0100 Subject: [PATCH 256/541] update mono tests --- compiler/test_mono/generated/closure_in_list.txt | 6 +++--- .../generated/empty_list_of_function_type.txt | 16 ++++++++-------- compiler/test_mono/generated/ir_when_these.txt | 6 +++--- compiler/test_mono/generated/nested_closure.txt | 4 ++-- .../generated/somehow_drops_definitions.txt | 8 ++++---- .../test_mono/generated/specialize_closures.txt | 8 ++++---- .../test_mono/generated/specialize_lowlevel.txt | 8 ++++---- compiler/test_mono/generated/when_joinpoint.txt | 6 +++--- 8 files changed, 31 insertions(+), 31 deletions(-) diff --git a/compiler/test_mono/generated/closure_in_list.txt b/compiler/test_mono/generated/closure_in_list.txt index 390ee4acdc..a4df74e7f6 100644 --- a/compiler/test_mono/generated/closure_in_list.txt +++ b/compiler/test_mono/generated/closure_in_list.txt @@ -4,8 +4,8 @@ procedure List.7 (#Attr.2): procedure Test.1 (Test.5): let Test.2 : Builtin(Int(I64)) = 41i64; - let Test.11 : LambdaSet(LambdaSet { set: [(`#UserApp.choose`, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; - let Test.10 : Builtin(List(LambdaSet(LambdaSet { set: [(`#UserApp.choose`, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }))) = Array [Test.11]; + let Test.11 : LambdaSet([( #UserApp.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; + let Test.10 : Builtin(List(LambdaSet([( #UserApp.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }))) = Array [Test.11]; ret Test.10; procedure Test.3 (Test.9, #Attr.12): @@ -14,7 +14,7 @@ procedure Test.3 (Test.9, #Attr.12): procedure Test.0 (): let Test.8 : Struct([]) = Struct {}; - let Test.4 : Builtin(List(LambdaSet(LambdaSet { set: [(`#UserApp.choose`, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }))) = CallByName Test.1 Test.8; + let Test.4 : Builtin(List(LambdaSet([( #UserApp.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }))) = CallByName Test.1 Test.8; let Test.6 : Builtin(Int(U64)) = CallByName List.7 Test.4; dec Test.4; ret Test.6; diff --git a/compiler/test_mono/generated/empty_list_of_function_type.txt b/compiler/test_mono/generated/empty_list_of_function_type.txt index 3789ce311c..5023ac42e8 100644 --- a/compiler/test_mono/generated/empty_list_of_function_type.txt +++ b/compiler/test_mono/generated/empty_list_of_function_type.txt @@ -2,12 +2,12 @@ procedure List.3 (#Attr.2, #Attr.3): let Test.20 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; let Test.17 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.20; if Test.17 then - let Test.19 : LambdaSet(LambdaSet { set: [(`#UserApp.myClosure`, [])], representation: Struct([]) }) = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.18 : Union(NonRecursive([[Struct([])], [LambdaSet(LambdaSet { set: [(`#UserApp.myClosure`, [])], representation: Struct([]) })]])) = Ok Test.19; + let Test.19 : LambdaSet([( #UserApp.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.18 : Union(NonRecursive([[Struct([])], [LambdaSet([( #UserApp.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) })]])) = Ok Test.19; ret Test.18; else let Test.16 : Struct([]) = Struct {}; - let Test.15 : Union(NonRecursive([[Struct([])], [LambdaSet(LambdaSet { set: [(`#UserApp.myClosure`, [])], representation: Struct([]) })]])) = Err Test.16; + let Test.15 : Union(NonRecursive([[Struct([])], [LambdaSet([( #UserApp.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) })]])) = Err Test.16; ret Test.15; procedure Test.2 (Test.6): @@ -15,16 +15,16 @@ procedure Test.2 (Test.6): ret Test.24; procedure Test.0 (): - let Test.1 : Builtin(List(LambdaSet(LambdaSet { set: [], representation: Struct([]) }))) = Array []; + let Test.1 : Builtin(List(LambdaSet([]LambdaSet { set: Ok(()), representation: Struct([]) }))) = Array []; joinpoint Test.22 Test.3: let Test.14 : Builtin(Int(U64)) = 0i64; - let Test.7 : Union(NonRecursive([[Struct([])], [LambdaSet(LambdaSet { set: [(`#UserApp.myClosure`, [])], representation: Struct([]) })]])) = CallByName List.3 Test.3 Test.14; + let Test.7 : Union(NonRecursive([[Struct([])], [LambdaSet([( #UserApp.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) })]])) = CallByName List.3 Test.3 Test.14; dec Test.3; let Test.11 : Builtin(Int(U8)) = 1i64; let Test.12 : Builtin(Int(U8)) = GetTagId Test.7; let Test.13 : Builtin(Bool) = lowlevel Eq Test.11 Test.12; if Test.13 then - let Test.5 : LambdaSet(LambdaSet { set: [(`#UserApp.myClosure`, [])], representation: Struct([]) }) = UnionAtIndex (Id 1) (Index 0) Test.7; + let Test.5 : LambdaSet([( #UserApp.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = UnionAtIndex (Id 1) (Index 0) Test.7; let Test.9 : Builtin(Str) = "foo"; let Test.8 : Builtin(Str) = CallByName Test.2 Test.9; dec Test.9; @@ -38,6 +38,6 @@ procedure Test.0 (): jump Test.22 Test.1; else dec Test.1; - let Test.23 : LambdaSet(LambdaSet { set: [(`#UserApp.myClosure`, [])], representation: Struct([]) }) = Struct {}; - let Test.21 : Builtin(List(LambdaSet(LambdaSet { set: [(`#UserApp.myClosure`, [])], representation: Struct([]) }))) = Array [Test.23]; + let Test.23 : LambdaSet([( #UserApp.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = Struct {}; + let Test.21 : Builtin(List(LambdaSet([( #UserApp.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }))) = Array [Test.23]; jump Test.22 Test.21; diff --git a/compiler/test_mono/generated/ir_when_these.txt b/compiler/test_mono/generated/ir_when_these.txt index 5b557e1473..08c3614351 100644 --- a/compiler/test_mono/generated/ir_when_these.txt +++ b/compiler/test_mono/generated/ir_when_these.txt @@ -7,12 +7,12 @@ procedure Test.0 (): case 2: let Test.2 : Builtin(Int(I64)) = UnionAtIndex (Id 2) (Index 0) Test.5; ret Test.2; - + case 0: let Test.3 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.5; ret Test.3; - + default: let Test.4 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.5; ret Test.4; - + diff --git a/compiler/test_mono/generated/nested_closure.txt b/compiler/test_mono/generated/nested_closure.txt index 0966a26cc8..bac3467a25 100644 --- a/compiler/test_mono/generated/nested_closure.txt +++ b/compiler/test_mono/generated/nested_closure.txt @@ -1,6 +1,6 @@ procedure Test.1 (Test.5): let Test.2 : Builtin(Int(I64)) = 42i64; - let Test.3 : LambdaSet(LambdaSet { set: [(`#UserApp.1`, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; + let Test.3 : LambdaSet([( #UserApp.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; ret Test.3; procedure Test.3 (Test.9, #Attr.12): @@ -9,7 +9,7 @@ procedure Test.3 (Test.9, #Attr.12): procedure Test.0 (): let Test.8 : Struct([]) = Struct {}; - let Test.4 : LambdaSet(LambdaSet { set: [(`#UserApp.1`, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = CallByName Test.1 Test.8; + let Test.4 : LambdaSet([( #UserApp.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }) = CallByName Test.1 Test.8; let Test.7 : Struct([]) = Struct {}; let Test.6 : Builtin(Int(I64)) = CallByName Test.3 Test.7 Test.4; ret Test.6; diff --git a/compiler/test_mono/generated/somehow_drops_definitions.txt b/compiler/test_mono/generated/somehow_drops_definitions.txt index 84d70d01cb..0302de94be 100644 --- a/compiler/test_mono/generated/somehow_drops_definitions.txt +++ b/compiler/test_mono/generated/somehow_drops_definitions.txt @@ -32,11 +32,11 @@ procedure Test.5 (Test.8, Test.9): case 0: let Test.16 : Builtin(Int(I64)) = CallByName Test.3 Test.9; jump Test.15 Test.16; - + default: let Test.17 : Builtin(Int(I64)) = CallByName Test.4 Test.9; jump Test.15 Test.17; - + procedure Test.0 (): joinpoint Test.19 Test.12: @@ -46,8 +46,8 @@ procedure Test.0 (): in let Test.24 : Builtin(Bool) = true; if Test.24 then - let Test.3 : LambdaSet(LambdaSet { set: [(`#UserApp.x`, []), (`#UserApp.one`, [])], representation: Builtin(Bool) }) = false; + let Test.3 : LambdaSet([( #UserApp.3, []), ( #UserApp.4, [])]LambdaSet { set: Ok(()), representation: Builtin(Bool) }) = false; jump Test.19 Test.3; else - let Test.4 : LambdaSet(LambdaSet { set: [(`#UserApp.x`, []), (`#UserApp.one`, [])], representation: Builtin(Bool) }) = true; + let Test.4 : LambdaSet([( #UserApp.3, []), ( #UserApp.4, [])]LambdaSet { set: Ok(()), representation: Builtin(Bool) }) = true; jump Test.19 Test.4; diff --git a/compiler/test_mono/generated/specialize_closures.txt b/compiler/test_mono/generated/specialize_closures.txt index 7360e25d30..091e079b52 100644 --- a/compiler/test_mono/generated/specialize_closures.txt +++ b/compiler/test_mono/generated/specialize_closures.txt @@ -15,11 +15,11 @@ procedure Test.1 (Test.2, Test.3): case 0: let Test.19 : Builtin(Int(I64)) = CallByName Test.7 Test.3 Test.2; jump Test.18 Test.19; - + default: let Test.20 : Builtin(Int(I64)) = CallByName Test.8 Test.3 Test.2; jump Test.18 Test.20; - + procedure Test.7 (Test.10, #Attr.12): let Test.4 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) #Attr.12; @@ -46,8 +46,8 @@ procedure Test.0 (): in let Test.26 : Builtin(Bool) = true; if Test.26 then - let Test.7 : LambdaSet(LambdaSet { set: [(`#UserApp.increment`, [Builtin(Int(I64))]), (`#UserApp.double`, [Builtin(Int(I64)), Builtin(Bool)])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Bool)]])) }) = ClosureTag(Test.7) Test.4; + let Test.7 : LambdaSet([( #UserApp.7, [Builtin(Int(I64))]), ( #UserApp.8, [Builtin(Int(I64)), Builtin(Bool)])]LambdaSet { set: Ok(()), representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Bool)]])) }) = ClosureTag(Test.7) Test.4; jump Test.22 Test.7; else - let Test.8 : LambdaSet(LambdaSet { set: [(`#UserApp.increment`, [Builtin(Int(I64))]), (`#UserApp.double`, [Builtin(Int(I64)), Builtin(Bool)])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Bool)]])) }) = ClosureTag(Test.8) Test.5 Test.6; + let Test.8 : LambdaSet([( #UserApp.7, [Builtin(Int(I64))]), ( #UserApp.8, [Builtin(Int(I64)), Builtin(Bool)])]LambdaSet { set: Ok(()), representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Bool)]])) }) = ClosureTag(Test.8) Test.5 Test.6; jump Test.22 Test.8; diff --git a/compiler/test_mono/generated/specialize_lowlevel.txt b/compiler/test_mono/generated/specialize_lowlevel.txt index 0a95adfb01..96459272bb 100644 --- a/compiler/test_mono/generated/specialize_lowlevel.txt +++ b/compiler/test_mono/generated/specialize_lowlevel.txt @@ -29,16 +29,16 @@ procedure Test.0 (): case 0: let Test.16 : Builtin(Int(I64)) = CallByName Test.6 Test.12 Test.13; jump Test.15 Test.16; - + default: let Test.17 : Builtin(Int(I64)) = CallByName Test.7 Test.12 Test.13; jump Test.15 Test.17; - + in let Test.22 : Builtin(Bool) = true; if Test.22 then - let Test.6 : LambdaSet(LambdaSet { set: [(`#UserApp.b`, [Builtin(Int(I64))]), (`#UserApp.increment`, [Builtin(Int(I64))])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) }) = ClosureTag(Test.6) Test.4; + let Test.6 : LambdaSet([( #UserApp.6, [Builtin(Int(I64))]), ( #UserApp.7, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) }) = ClosureTag(Test.6) Test.4; jump Test.19 Test.6; else - let Test.7 : LambdaSet(LambdaSet { set: [(`#UserApp.b`, [Builtin(Int(I64))]), (`#UserApp.increment`, [Builtin(Int(I64))])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) }) = ClosureTag(Test.7) Test.5; + let Test.7 : LambdaSet([( #UserApp.6, [Builtin(Int(I64))]), ( #UserApp.7, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) }) = ClosureTag(Test.7) Test.5; jump Test.19 Test.7; diff --git a/compiler/test_mono/generated/when_joinpoint.txt b/compiler/test_mono/generated/when_joinpoint.txt index c4b993a044..3366717fca 100644 --- a/compiler/test_mono/generated/when_joinpoint.txt +++ b/compiler/test_mono/generated/when_joinpoint.txt @@ -7,15 +7,15 @@ procedure Test.1 (Test.5): case 1: let Test.10 : Builtin(Int(I64)) = 1i64; jump Test.9 Test.10; - + case 2: let Test.11 : Builtin(Int(I64)) = 2i64; jump Test.9 Test.11; - + default: let Test.12 : Builtin(Int(I64)) = 3i64; jump Test.9 Test.12; - + procedure Test.0 (): let Test.7 : Struct([]) = Struct {}; From 5230c6f6f355dd59b39194f54479c3b7861f0ef4 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 19 Jan 2022 23:06:32 +0100 Subject: [PATCH 257/541] implement Effect.loop --- compiler/load/src/effect_module.rs | 361 +++++++++++++++++++++++++- examples/benchmarks/platform/Task.roc | 26 +- examples/cli/platform/Task.roc | 27 +- 3 files changed, 406 insertions(+), 8 deletions(-) diff --git a/compiler/load/src/effect_module.rs b/compiler/load/src/effect_module.rs index 42622db425..32920ee2aa 100644 --- a/compiler/load/src/effect_module.rs +++ b/compiler/load/src/effect_module.rs @@ -30,9 +30,11 @@ pub const BUILTIN_EFFECT_FUNCTIONS: &[(&str, Builder)] = &[ ("always", build_effect_always), // Effect.forever : Effect a -> Effect b ("forever", build_effect_forever), + // Effect.loop : a, (a -> Effect [ Step a, Done b ]) -> Effect b + ("loop", build_effect_loop), ]; -const RECURSIVE_BUILTIN_EFFECT_FUNCTIONS: &[&str] = &["forever"]; +const RECURSIVE_BUILTIN_EFFECT_FUNCTIONS: &[&str] = &["forever", "loop"]; // the Effects alias & associated functions // @@ -83,6 +85,19 @@ pub fn build_effect_builtins( } } +macro_rules! new_symbol { + ($scope:expr, $env:expr, $name:expr) => {{ + $scope + .introduce( + $name.into(), + &$env.exposed_ident_ids, + &mut $env.ident_ids, + Region::zero(), + ) + .unwrap() + }}; +} + fn build_effect_always( env: &mut Env, scope: &mut Scope, @@ -1011,6 +1026,350 @@ fn build_effect_forever_inner_body( ) } +fn build_effect_loop( + env: &mut Env, + scope: &mut Scope, + effect_symbol: Symbol, + effect_tag_name: TagName, + var_store: &mut VarStore, +) -> (Symbol, Def) { + let loop_symbol = new_symbol!(scope, env, "loop"); + let state_symbol = new_symbol!(scope, env, "state"); + let step_symbol = new_symbol!(scope, env, "step"); + + let body = build_effect_loop_body( + env, + scope, + effect_tag_name.clone(), + loop_symbol, + state_symbol, + step_symbol, + var_store, + ); + + let arguments = vec![ + ( + var_store.fresh(), + Loc::at_zero(Pattern::Identifier(state_symbol)), + ), + ( + var_store.fresh(), + Loc::at_zero(Pattern::Identifier(step_symbol)), + ), + ]; + + let function_var = var_store.fresh(); + let after_closure = Expr::Closure(ClosureData { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: var_store.fresh(), + name: loop_symbol, + captured_symbols: Vec::new(), + recursive: Recursive::Recursive, + arguments, + loc_body: Box::new(Loc::at_zero(body)), + }); + + let mut introduced_variables = IntroducedVariables::default(); + + let signature = { + let var_a = var_store.fresh(); + let var_b = var_store.fresh(); + + introduced_variables.insert_named("a".into(), var_a); + introduced_variables.insert_named("b".into(), var_b); + + let effect_b = build_effect_alias( + effect_symbol, + effect_tag_name.clone(), + "b", + var_b, + Type::Variable(var_b), + var_store, + &mut introduced_variables, + ); + + let state_type = { + let step_tag_name = TagName::Global("Step".into()); + let done_tag_name = TagName::Global("Done".into()); + + Type::TagUnion( + vec![ + (step_tag_name, vec![Type::Variable(var_a)]), + (done_tag_name, vec![Type::Variable(var_b)]), + ], + Box::new(Type::EmptyTagUnion), + ) + }; + + let effect_state_type = { + let closure_var = var_store.fresh(); + introduced_variables.insert_wildcard(closure_var); + + let actual = { + Type::TagUnion( + vec![( + effect_tag_name, + vec![Type::Function( + vec![Type::EmptyRec], + Box::new(Type::Variable(closure_var)), + Box::new(state_type.clone()), + )], + )], + Box::new(Type::EmptyTagUnion), + ) + }; + + Type::Alias { + symbol: effect_symbol, + type_arguments: vec![("a".into(), state_type)], + lambda_set_variables: vec![roc_types::types::LambdaSet(Type::Variable( + closure_var, + ))], + actual: Box::new(actual), + } + }; + + let closure_var = var_store.fresh(); + introduced_variables.insert_wildcard(closure_var); + + let step_type = Type::Function( + vec![Type::Variable(var_a)], + Box::new(Type::Variable(closure_var)), + Box::new(effect_state_type), + ); + + let closure_var = var_store.fresh(); + introduced_variables.insert_wildcard(closure_var); + + Type::Function( + vec![Type::Variable(var_a), step_type], + Box::new(Type::Variable(closure_var)), + Box::new(effect_b), + ) + }; + + let def_annotation = roc_can::def::Annotation { + signature, + introduced_variables, + aliases: SendMap::default(), + region: Region::zero(), + }; + + let pattern = Pattern::Identifier(loop_symbol); + let mut pattern_vars = SendMap::default(); + pattern_vars.insert(loop_symbol, function_var); + let def = Def { + loc_pattern: Loc::at_zero(pattern), + loc_expr: Loc::at_zero(after_closure), + expr_var: function_var, + pattern_vars, + // annotation: Some(def_annotation), + annotation: None, + }; + + (loop_symbol, def) +} + +fn build_effect_loop_body( + env: &mut Env, + scope: &mut Scope, + effect_tag_name: TagName, + loop_symbol: Symbol, + state_symbol: Symbol, + step_symbol: Symbol, + var_store: &mut VarStore, +) -> Expr { + let closure_name = { + scope + .introduce( + "loop_inner".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let inner_body = build_effect_loop_inner_body( + env, + scope, + effect_tag_name.clone(), + loop_symbol, + state_symbol, + step_symbol, + var_store, + ); + + let captured_symbols = vec![state_symbol, step_symbol]; + wrap_in_effect_thunk( + inner_body, + effect_tag_name, + closure_name, + captured_symbols, + var_store, + ) +} + +fn applied_tag_pattern( + tag_name: TagName, + argument_symbols: &[Symbol], + var_store: &mut VarStore, +) -> Pattern { + let arguments = argument_symbols + .iter() + .map(|s| { + let pattern = Pattern::Identifier(*s); + + (var_store.fresh(), Loc::at_zero(pattern)) + }) + .collect(); + + Pattern::AppliedTag { + ext_var: var_store.fresh(), + whole_var: var_store.fresh(), + tag_name, + arguments, + } +} + +fn build_effect_loop_inner_body( + env: &mut Env, + scope: &mut Scope, + effect_tag_name: TagName, + loop_symbol: Symbol, + state_symbol: Symbol, + step_symbol: Symbol, + var_store: &mut VarStore, +) -> Expr { + let thunk1_symbol = new_symbol!(scope, env, "thunk3"); + let thunk2_symbol = new_symbol!(scope, env, "thunk4"); + + let new_state_symbol = new_symbol!(scope, env, "newState"); + let done_symbol = new_symbol!(scope, env, "done"); + + // Effect thunk1 = step state + let thunk_from_effect = { + let whole_var = var_store.fresh(); + let ext_var = var_store.fresh(); + + let thunk_var = var_store.fresh(); + + let pattern = Pattern::AppliedTag { + ext_var, + whole_var, + tag_name: effect_tag_name.clone(), + arguments: vec![(thunk_var, Loc::at_zero(Pattern::Identifier(thunk1_symbol)))], + }; + + let pattern_vars = SendMap::default(); + + // `step state` + let rhs = { + let boxed = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(step_symbol)), + var_store.fresh(), + var_store.fresh(), + ); + + let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::Var(state_symbol)))]; + Expr::Call(Box::new(boxed), arguments, CalledVia::Space) + }; + + Def { + loc_pattern: Loc::at_zero(pattern), + loc_expr: Loc::at_zero(rhs), + expr_var: var_store.fresh(), + pattern_vars, + annotation: None, + } + }; + + // thunk1 {} + let force_thunk_call = { + let ret_var = var_store.fresh(); + let boxed = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(thunk1_symbol)), + var_store.fresh(), + ret_var, + ); + + let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::EmptyRecord))]; + let call = Expr::Call(Box::new(boxed), arguments, CalledVia::Space); + + Loc::at_zero(call) + }; + + // recursive call `loop newState step` + let loop_new_state_step = { + let boxed = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(loop_symbol)), + var_store.fresh(), + var_store.fresh(), + ); + + let arguments = vec![ + (var_store.fresh(), Loc::at_zero(Expr::Var(new_state_symbol))), + (var_store.fresh(), Loc::at_zero(Expr::Var(step_symbol))), + ]; + Expr::Call(Box::new(boxed), arguments, CalledVia::Space) + }; + + // ``` + // Effect thunk2 = loop effect + // thunk2 {} + // ``` + let force_thunk2 = force_effect( + loop_new_state_step, + effect_tag_name, + thunk2_symbol, + var_store, + ); + + let step_branch = { + let step_tag_name = TagName::Global("Step".into()); + + let step_pattern = applied_tag_pattern(step_tag_name, &[new_state_symbol], var_store); + + roc_can::expr::WhenBranch { + patterns: vec![Loc::at_zero(step_pattern)], + value: Loc::at_zero(force_thunk2), + guard: None, + } + }; + + let done_branch = { + let done_tag_name = TagName::Global("Done".into()); + let done_pattern = applied_tag_pattern(done_tag_name, &[done_symbol], var_store); + + roc_can::expr::WhenBranch { + patterns: vec![Loc::at_zero(done_pattern)], + value: Loc::at_zero(Expr::Var(done_symbol)), + guard: None, + } + }; + + let branches = vec![step_branch, done_branch]; + + let match_on_force_thunk1 = Expr::When { + cond_var: var_store.fresh(), + expr_var: var_store.fresh(), + region: Region::zero(), + loc_cond: Box::new(force_thunk_call), + branches, + }; + + Expr::LetNonRec( + Box::new(thunk_from_effect), + Box::new(Loc::at_zero(match_on_force_thunk1)), + var_store.fresh(), + ) +} + pub fn build_host_exposed_def( env: &mut Env, scope: &mut Scope, diff --git a/examples/benchmarks/platform/Task.roc b/examples/benchmarks/platform/Task.roc index 7589213b34..ba266c9dd5 100644 --- a/examples/benchmarks/platform/Task.roc +++ b/examples/benchmarks/platform/Task.roc @@ -1,11 +1,31 @@ interface Task - exposes [ Task, succeed, fail, after, map, putLine, putInt, getInt, forever ] + exposes [ Task, succeed, fail, after, map, putLine, putInt, getInt, forever, loop ] imports [ fx.Effect ] Task ok err : Effect.Effect (Result ok err) -forever : Task val err -> Task c d -forever = \task -> Effect.forever task +forever : Task val err -> Task * err +forever = \task -> + looper = \{} -> + task + |> Effect.map \res -> + when res is + Ok _ -> Step {} + Err e -> Done (Err e) + + Effect.loop {} looper + +loop : state, (state -> Task [ Step state, Done done ] err) -> Task done err +loop = \state, step -> + looper = \current -> + step current + |> Effect.map \res -> + when res is + Ok (Step newState) -> Step newState + Ok (Done result) -> Done (Ok result) + Err e -> Done (Err e) + + Effect.loop state looper succeed : val -> Task val * succeed = \val -> diff --git a/examples/cli/platform/Task.roc b/examples/cli/platform/Task.roc index 70e7125c94..f3f46e8fe0 100644 --- a/examples/cli/platform/Task.roc +++ b/examples/cli/platform/Task.roc @@ -1,12 +1,31 @@ interface Task - exposes [ Task, succeed, fail, await, map, onFail, attempt, forever ] + exposes [ Task, succeed, fail, await, map, onFail, attempt, forever, loop ] imports [ fx.Effect ] Task ok err : Effect.Effect (Result ok err) -# Run a task forever. Note that even when we hit an error, we don't stop -forever : Task val err -> Task c d -forever = \task -> Effect.forever task +forever : Task val err -> Task * err +forever = \task -> + looper = \{} -> + task + |> Effect.map \res -> + when res is + Ok _ -> Step {} + Err e -> Done (Err e) + + Effect.loop {} looper + +loop : state, (state -> Task [ Step state, Done done ] err) -> Task done err +loop = \state, step -> + looper = \current -> + step current + |> Effect.map \res -> + when res is + Ok (Step newState) -> Step newState + Ok (Done result) -> Done (Ok result) + Err e -> Done (Err e) + + Effect.loop state looper succeed : val -> Task val * succeed = \val -> From ee4c2177c03b361511c4527a2ac0d37a741be9ab Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 19 Jan 2022 23:13:34 +0100 Subject: [PATCH 258/541] add annotation for Effect.loop --- compiler/load/src/effect_module.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/load/src/effect_module.rs b/compiler/load/src/effect_module.rs index 32920ee2aa..28bc2f22f4 100644 --- a/compiler/load/src/effect_module.rs +++ b/compiler/load/src/effect_module.rs @@ -1165,8 +1165,7 @@ fn build_effect_loop( loc_expr: Loc::at_zero(after_closure), expr_var: function_var, pattern_vars, - // annotation: Some(def_annotation), - annotation: None, + annotation: Some(def_annotation), }; (loop_symbol, def) From 72e883a20b1e1023d7a632c8f7f50099e409606b Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 19 Jan 2022 23:21:18 +0100 Subject: [PATCH 259/541] make symbol debug formatting consistent --- compiler/mono/src/ir.rs | 32 ++++++++++++++++++-------------- compiler/mono/src/layout.rs | 8 +++----- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index a2431df636..cfd0ef026a 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -1461,26 +1461,30 @@ impl<'a> Literal<'a> { } } +pub(crate) fn symbol_to_doc_string(symbol: Symbol) -> String { + use roc_module::ident::ModuleName; + + if pretty_print_ir_symbols() { + format!("{:?}", symbol) + } else { + let text = format!("{}", symbol); + + if text.starts_with(ModuleName::APP) { + let name: String = text.trim_start_matches(ModuleName::APP).into(); + format!("Test{}", name) + } else { + text + } + } +} + fn symbol_to_doc<'b, D, A>(alloc: &'b D, symbol: Symbol) -> DocBuilder<'b, D, A> where D: DocAllocator<'b, A>, D::Doc: Clone, A: Clone, { - use roc_module::ident::ModuleName; - - if pretty_print_ir_symbols() { - alloc.text(format!("{:?}", symbol)) - } else { - let text = format!("{}", symbol); - - if text.starts_with(ModuleName::APP) { - let name: String = text.trim_start_matches(ModuleName::APP).into(); - alloc.text("Test").append(name) - } else { - alloc.text(text) - } - } + alloc.text(symbol_to_doc_string(symbol)) } fn join_point_to_doc<'b, D, A>(alloc: &'b D, symbol: JoinPointId) -> DocBuilder<'b, D, A> diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 4b94b2bdab..43ebc77874 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -516,11 +516,9 @@ struct SetElement<'a> { impl std::fmt::Debug for SetElement<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if crate::ir::pretty_print_ir_symbols() { - write!(f, "( {:?}, {:?})", self.symbol, self.layout) - } else { - write!(f, "( {}, {:?})", self.symbol, self.layout) - } + let symbol_string = crate::ir::symbol_to_doc_string(self.symbol); + + write!(f, "( {}, {:?})", symbol_string, self.layout) } } From 202a8438ce38c0f028310c59378ff929cd248c8c Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 19 Jan 2022 23:22:19 +0100 Subject: [PATCH 260/541] change UserApp -> Test in mono tests --- compiler/test_mono/generated/closure_in_list.txt | 6 +++--- .../generated/empty_list_of_function_type.txt | 14 +++++++------- compiler/test_mono/generated/nested_closure.txt | 4 ++-- .../generated/somehow_drops_definitions.txt | 4 ++-- .../test_mono/generated/specialize_closures.txt | 4 ++-- .../test_mono/generated/specialize_lowlevel.txt | 4 ++-- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/compiler/test_mono/generated/closure_in_list.txt b/compiler/test_mono/generated/closure_in_list.txt index a4df74e7f6..9fa9aaeae9 100644 --- a/compiler/test_mono/generated/closure_in_list.txt +++ b/compiler/test_mono/generated/closure_in_list.txt @@ -4,8 +4,8 @@ procedure List.7 (#Attr.2): procedure Test.1 (Test.5): let Test.2 : Builtin(Int(I64)) = 41i64; - let Test.11 : LambdaSet([( #UserApp.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; - let Test.10 : Builtin(List(LambdaSet([( #UserApp.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }))) = Array [Test.11]; + let Test.11 : LambdaSet([( Test.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; + let Test.10 : Builtin(List(LambdaSet([( Test.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }))) = Array [Test.11]; ret Test.10; procedure Test.3 (Test.9, #Attr.12): @@ -14,7 +14,7 @@ procedure Test.3 (Test.9, #Attr.12): procedure Test.0 (): let Test.8 : Struct([]) = Struct {}; - let Test.4 : Builtin(List(LambdaSet([( #UserApp.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }))) = CallByName Test.1 Test.8; + let Test.4 : Builtin(List(LambdaSet([( Test.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }))) = CallByName Test.1 Test.8; let Test.6 : Builtin(Int(U64)) = CallByName List.7 Test.4; dec Test.4; ret Test.6; diff --git a/compiler/test_mono/generated/empty_list_of_function_type.txt b/compiler/test_mono/generated/empty_list_of_function_type.txt index 5023ac42e8..e68e0bf85d 100644 --- a/compiler/test_mono/generated/empty_list_of_function_type.txt +++ b/compiler/test_mono/generated/empty_list_of_function_type.txt @@ -2,12 +2,12 @@ procedure List.3 (#Attr.2, #Attr.3): let Test.20 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; let Test.17 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.20; if Test.17 then - let Test.19 : LambdaSet([( #UserApp.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.18 : Union(NonRecursive([[Struct([])], [LambdaSet([( #UserApp.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) })]])) = Ok Test.19; + let Test.19 : LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.18 : Union(NonRecursive([[Struct([])], [LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) })]])) = Ok Test.19; ret Test.18; else let Test.16 : Struct([]) = Struct {}; - let Test.15 : Union(NonRecursive([[Struct([])], [LambdaSet([( #UserApp.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) })]])) = Err Test.16; + let Test.15 : Union(NonRecursive([[Struct([])], [LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) })]])) = Err Test.16; ret Test.15; procedure Test.2 (Test.6): @@ -18,13 +18,13 @@ procedure Test.0 (): let Test.1 : Builtin(List(LambdaSet([]LambdaSet { set: Ok(()), representation: Struct([]) }))) = Array []; joinpoint Test.22 Test.3: let Test.14 : Builtin(Int(U64)) = 0i64; - let Test.7 : Union(NonRecursive([[Struct([])], [LambdaSet([( #UserApp.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) })]])) = CallByName List.3 Test.3 Test.14; + let Test.7 : Union(NonRecursive([[Struct([])], [LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) })]])) = CallByName List.3 Test.3 Test.14; dec Test.3; let Test.11 : Builtin(Int(U8)) = 1i64; let Test.12 : Builtin(Int(U8)) = GetTagId Test.7; let Test.13 : Builtin(Bool) = lowlevel Eq Test.11 Test.12; if Test.13 then - let Test.5 : LambdaSet([( #UserApp.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = UnionAtIndex (Id 1) (Index 0) Test.7; + let Test.5 : LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = UnionAtIndex (Id 1) (Index 0) Test.7; let Test.9 : Builtin(Str) = "foo"; let Test.8 : Builtin(Str) = CallByName Test.2 Test.9; dec Test.9; @@ -38,6 +38,6 @@ procedure Test.0 (): jump Test.22 Test.1; else dec Test.1; - let Test.23 : LambdaSet([( #UserApp.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = Struct {}; - let Test.21 : Builtin(List(LambdaSet([( #UserApp.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }))) = Array [Test.23]; + let Test.23 : LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = Struct {}; + let Test.21 : Builtin(List(LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }))) = Array [Test.23]; jump Test.22 Test.21; diff --git a/compiler/test_mono/generated/nested_closure.txt b/compiler/test_mono/generated/nested_closure.txt index bac3467a25..f8413a0265 100644 --- a/compiler/test_mono/generated/nested_closure.txt +++ b/compiler/test_mono/generated/nested_closure.txt @@ -1,6 +1,6 @@ procedure Test.1 (Test.5): let Test.2 : Builtin(Int(I64)) = 42i64; - let Test.3 : LambdaSet([( #UserApp.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; + let Test.3 : LambdaSet([( Test.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; ret Test.3; procedure Test.3 (Test.9, #Attr.12): @@ -9,7 +9,7 @@ procedure Test.3 (Test.9, #Attr.12): procedure Test.0 (): let Test.8 : Struct([]) = Struct {}; - let Test.4 : LambdaSet([( #UserApp.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }) = CallByName Test.1 Test.8; + let Test.4 : LambdaSet([( Test.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }) = CallByName Test.1 Test.8; let Test.7 : Struct([]) = Struct {}; let Test.6 : Builtin(Int(I64)) = CallByName Test.3 Test.7 Test.4; ret Test.6; diff --git a/compiler/test_mono/generated/somehow_drops_definitions.txt b/compiler/test_mono/generated/somehow_drops_definitions.txt index 0302de94be..0c92ef232e 100644 --- a/compiler/test_mono/generated/somehow_drops_definitions.txt +++ b/compiler/test_mono/generated/somehow_drops_definitions.txt @@ -46,8 +46,8 @@ procedure Test.0 (): in let Test.24 : Builtin(Bool) = true; if Test.24 then - let Test.3 : LambdaSet([( #UserApp.3, []), ( #UserApp.4, [])]LambdaSet { set: Ok(()), representation: Builtin(Bool) }) = false; + let Test.3 : LambdaSet([( Test.3, []), ( Test.4, [])]LambdaSet { set: Ok(()), representation: Builtin(Bool) }) = false; jump Test.19 Test.3; else - let Test.4 : LambdaSet([( #UserApp.3, []), ( #UserApp.4, [])]LambdaSet { set: Ok(()), representation: Builtin(Bool) }) = true; + let Test.4 : LambdaSet([( Test.3, []), ( Test.4, [])]LambdaSet { set: Ok(()), representation: Builtin(Bool) }) = true; jump Test.19 Test.4; diff --git a/compiler/test_mono/generated/specialize_closures.txt b/compiler/test_mono/generated/specialize_closures.txt index 091e079b52..73d49133ce 100644 --- a/compiler/test_mono/generated/specialize_closures.txt +++ b/compiler/test_mono/generated/specialize_closures.txt @@ -46,8 +46,8 @@ procedure Test.0 (): in let Test.26 : Builtin(Bool) = true; if Test.26 then - let Test.7 : LambdaSet([( #UserApp.7, [Builtin(Int(I64))]), ( #UserApp.8, [Builtin(Int(I64)), Builtin(Bool)])]LambdaSet { set: Ok(()), representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Bool)]])) }) = ClosureTag(Test.7) Test.4; + let Test.7 : LambdaSet([( Test.7, [Builtin(Int(I64))]), ( Test.8, [Builtin(Int(I64)), Builtin(Bool)])]LambdaSet { set: Ok(()), representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Bool)]])) }) = ClosureTag(Test.7) Test.4; jump Test.22 Test.7; else - let Test.8 : LambdaSet([( #UserApp.7, [Builtin(Int(I64))]), ( #UserApp.8, [Builtin(Int(I64)), Builtin(Bool)])]LambdaSet { set: Ok(()), representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Bool)]])) }) = ClosureTag(Test.8) Test.5 Test.6; + let Test.8 : LambdaSet([( Test.7, [Builtin(Int(I64))]), ( Test.8, [Builtin(Int(I64)), Builtin(Bool)])]LambdaSet { set: Ok(()), representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Bool)]])) }) = ClosureTag(Test.8) Test.5 Test.6; jump Test.22 Test.8; diff --git a/compiler/test_mono/generated/specialize_lowlevel.txt b/compiler/test_mono/generated/specialize_lowlevel.txt index 96459272bb..3271b1fd41 100644 --- a/compiler/test_mono/generated/specialize_lowlevel.txt +++ b/compiler/test_mono/generated/specialize_lowlevel.txt @@ -37,8 +37,8 @@ procedure Test.0 (): in let Test.22 : Builtin(Bool) = true; if Test.22 then - let Test.6 : LambdaSet([( #UserApp.6, [Builtin(Int(I64))]), ( #UserApp.7, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) }) = ClosureTag(Test.6) Test.4; + let Test.6 : LambdaSet([( Test.6, [Builtin(Int(I64))]), ( Test.7, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) }) = ClosureTag(Test.6) Test.4; jump Test.19 Test.6; else - let Test.7 : LambdaSet([( #UserApp.6, [Builtin(Int(I64))]), ( #UserApp.7, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) }) = ClosureTag(Test.7) Test.5; + let Test.7 : LambdaSet([( Test.6, [Builtin(Int(I64))]), ( Test.7, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) }) = ClosureTag(Test.7) Test.5; jump Test.19 Test.7; From a2cb9035c45fb07d89d5658ba8842207656db39f Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 19 Jan 2022 23:56:14 +0100 Subject: [PATCH 261/541] formatting --- examples/benchmarks/platform/Task.roc | 26 ++++++++++++++++++-------- examples/cli/platform/Task.roc | 26 ++++++++++++++++++-------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/examples/benchmarks/platform/Task.roc b/examples/benchmarks/platform/Task.roc index ba266c9dd5..61b67e321c 100644 --- a/examples/benchmarks/platform/Task.roc +++ b/examples/benchmarks/platform/Task.roc @@ -6,12 +6,16 @@ Task ok err : Effect.Effect (Result ok err) forever : Task val err -> Task * err forever = \task -> - looper = \{} -> + looper = \{ } -> task - |> Effect.map \res -> + |> Effect.map + \res -> when res is - Ok _ -> Step {} - Err e -> Done (Err e) + Ok _ -> + Step {} + + Err e -> + Done (Err e) Effect.loop {} looper @@ -19,11 +23,17 @@ loop : state, (state -> Task [ Step state, Done done ] err) -> Task done err loop = \state, step -> looper = \current -> step current - |> Effect.map \res -> + |> Effect.map + \res -> when res is - Ok (Step newState) -> Step newState - Ok (Done result) -> Done (Ok result) - Err e -> Done (Err e) + Ok (Step newState) -> + Step newState + + Ok (Done result) -> + Done (Ok result) + + Err e -> + Done (Err e) Effect.loop state looper diff --git a/examples/cli/platform/Task.roc b/examples/cli/platform/Task.roc index f3f46e8fe0..c61a74bba1 100644 --- a/examples/cli/platform/Task.roc +++ b/examples/cli/platform/Task.roc @@ -6,12 +6,16 @@ Task ok err : Effect.Effect (Result ok err) forever : Task val err -> Task * err forever = \task -> - looper = \{} -> + looper = \{ } -> task - |> Effect.map \res -> + |> Effect.map + \res -> when res is - Ok _ -> Step {} - Err e -> Done (Err e) + Ok _ -> + Step {} + + Err e -> + Done (Err e) Effect.loop {} looper @@ -19,11 +23,17 @@ loop : state, (state -> Task [ Step state, Done done ] err) -> Task done err loop = \state, step -> looper = \current -> step current - |> Effect.map \res -> + |> Effect.map + \res -> when res is - Ok (Step newState) -> Step newState - Ok (Done result) -> Done (Ok result) - Err e -> Done (Err e) + Ok (Step newState) -> + Step newState + + Ok (Done result) -> + Done (Ok result) + + Err e -> + Done (Err e) Effect.loop state looper From 3342090e7b8c067dcd66c9509bd6e89f36b5a95f Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Wed, 19 Jan 2022 22:51:15 -0500 Subject: [PATCH 262/541] Add `var_contains_content` conditional function to `Subs` This function checks for the existence of a content of a certain shape on a content, or any its deep children. --- compiler/types/src/subs.rs | 93 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 0f758d1f13..6f4a67cacb 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -1364,6 +1364,15 @@ impl Subs { pub fn commit_snapshot(&mut self, snapshot: Snapshot>) { self.utable.commit(snapshot) } + + /// Checks whether the content of `var`, or any nested content, satisfies the `predicate`. + pub fn var_contains_content

(&self, var: Variable, predicate: P) -> bool + where + P: Fn(&Content) -> bool + Copy, + { + let mut seen_recursion_vars = MutSet::default(); + var_contains_content_help(self, var, predicate, &mut seen_recursion_vars) + } } #[inline(always)] @@ -3464,3 +3473,87 @@ fn deep_copy_var_to_help<'a>( } } } + +fn var_contains_content_help

( + subs: &Subs, + var: Variable, + predicate: P, + seen_recursion_vars: &mut MutSet, +) -> bool +where + P: Fn(&Content) -> bool + Copy, +{ + if seen_recursion_vars.contains(&var) { + return false; + } + + let content = subs.get_content_without_compacting(var); + + if predicate(content) { + return true; + } + + macro_rules! check_var { + ($v:expr) => { + var_contains_content_help(subs, $v, predicate, seen_recursion_vars) + }; + } + + macro_rules! check_var_slice { + ($slice:expr) => { + subs.get_subs_slice($slice) + .into_iter() + .any(|v| check_var!(*v)) + }; + } + + use Content::*; + use FlatType::*; + match content { + FlexVar(_) => false, + RigidVar(_) => false, + RecursionVar { + structure, + opt_name: _, + } => { + seen_recursion_vars.insert(var); + var_contains_content_help(subs, *structure, predicate, seen_recursion_vars) + } + Structure(flat_type) => match flat_type { + Apply(_, vars) => check_var_slice!(*vars), + Func(args, clos, ret) => { + check_var_slice!(*args) || check_var!(*clos) || check_var!(*ret) + } + Record(fields, var) => check_var_slice!(fields.variables()) || check_var!(*var), + TagUnion(tags, ext_var) => { + for i in tags.variables() { + if check_var_slice!(subs[i]) { + return true; + } + } + if check_var!(*ext_var) { + return true; + } + false + } + FunctionOrTagUnion(_, _, var) => check_var!(*var), + RecursiveTagUnion(rec_var, tags, ext_var) => { + seen_recursion_vars.insert(*rec_var); + for i in tags.variables() { + if check_var_slice!(subs[i]) { + return true; + } + } + if check_var!(*ext_var) { + return true; + } + false + } + Erroneous(_) | EmptyRecord | EmptyTagUnion => false, + }, + Alias(_, arguments, real_type_var) => { + check_var_slice!(arguments.variables()) || check_var!(*real_type_var) + } + Error => false, + } +} From a5de224626c4895072d14d165b6ab005d619b1a1 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Wed, 19 Jan 2022 22:52:15 -0500 Subject: [PATCH 263/541] Specialize polymorphic non-function expressions This commit fixes a long-standing bug wherein bindings to polymorphic, non-function expressions would be lowered at binding site, rather than being specialized at the call site. Concretely, consider the program ``` main = n = 1 idU8 : U8 -> U8 idU8 = \m -> m idU8 n ``` Prior to this commit, we would lower `n = 1` as part of the IR, and the `n` at the call site `idU8 n` would reference the lowered definition. However, at the definition site, `1` has the polymorphic type `Num *` - it is not until the the call site that we are able to refine the type bound by `n`, but at that point it's too late. Since the default layout for `Num *` is a signed 64-bit int, we would generate IR like ``` procedure main(): let App.n : Builtin(Int(I64)) = 1i64; ... let App.5 : Builtin(Int(U8)) = CallByName Add.idU8 App.n; ret App.5; ``` But we know `idU8` expects a `u8`; giving it an `i64` is nonsense. Indeed this would trigger LLVM miscompilations later on. To remedy this, we now keep a sidecar table that maps symbols to the polymorphic expression they reference, when they do so. We then specialize references to symbols on the fly at usage sites, similar to how we specialize function usages. Looking at our example, the definition `n = 1` is now never lowered to the IR directly. We only generate code for `1` at each place `n` is referenced. As a larger example, you can imagine that ``` main = n = 1 asU8 : U8 -> U8 asU32 : U32 -> U8 asU8 n + asU32 n ``` is lowered to the moral equivalent of ``` main = asU8 : U8 -> U8 asU32 : U32 -> U8 asU8 1 + asU32 1 ``` Moreover, transient usages of polymorphic expressions are lowered successfully with this approach. See for example the `monomorphized_tag_with_polymorphic_arg_and_monomorphic_arg` test in this commit, which checks that ``` main = mono : U8 mono = 15 poly = A wrap = Wrapped poly mono useWrap1 : [Wrapped [A] U8, Other] -> U8 useWrap1 = \w -> when w is Wrapped A n -> n Other -> 0 useWrap2 : [Wrapped [A, B] U8] -> U8 useWrap2 = \w -> when w is Wrapped A n -> n Wrapped B _ -> 0 useWrap1 wrap * useWrap2 wrap ``` has proper code generated for it, in the presence of the polymorphic `wrap` which references the polymorphic `poly`. https://github.com/rtfeldman/roc/pull/2347 had a different approach to this - polymorphic expressions would be converted to (possibly capturing) thunks. This has the benefit of reducing code size if there are many polymorphic usages, but may make the generated code slower and makes integration with the existing IR implementation harder. In practice I think the average number of polymorphic usages of an expression will be very small. Closes https://github.com/rtfeldman/roc/issues/2336 Closes https://github.com/rtfeldman/roc/issues/2254 Closes https://github.com/rtfeldman/roc/issues/2344 --- compiler/mono/src/ir.rs | 216 +++++++++++++++--- compiler/test_gen/src/gen_list.rs | 19 ++ compiler/test_gen/src/gen_num.rs | 89 ++++++++ compiler/test_gen/src/gen_tags.rs | 105 +++++++++ .../test_mono/generated/alias_variable.txt | 1 - .../alias_variable_and_return_it.txt | 4 +- .../test_mono/generated/closure_in_list.txt | 2 +- .../generated/empty_list_of_function_type.txt | 3 +- compiler/test_mono/generated/ir_int_add.txt | 20 +- compiler/test_mono/generated/ir_two_defs.txt | 10 +- compiler/test_mono/generated/let_x_in_x.txt | 2 - .../generated/let_x_in_x_indirect.txt | 6 +- compiler/test_mono/generated/list_len.txt | 16 +- .../generated/monomorphized_applied_tag.txt | 20 ++ .../generated/monomorphized_floats.txt | 9 + .../generated/monomorphized_ints.txt | 9 + .../generated/monomorphized_ints_aliased.txt | 23 ++ .../generated/monomorphized_list.txt | 11 + .../test_mono/generated/monomorphized_tag.txt | 9 + .../monomorphized_tag_with_aliased_args.txt | 10 + .../test_mono/generated/nested_closure.txt | 2 +- ...rd_optional_field_function_use_default.txt | 12 +- compiler/test_mono/src/tests.rs | 125 ++++++++++ 23 files changed, 655 insertions(+), 68 deletions(-) create mode 100644 compiler/test_mono/generated/monomorphized_applied_tag.txt create mode 100644 compiler/test_mono/generated/monomorphized_floats.txt create mode 100644 compiler/test_mono/generated/monomorphized_ints.txt create mode 100644 compiler/test_mono/generated/monomorphized_ints_aliased.txt create mode 100644 compiler/test_mono/generated/monomorphized_list.txt create mode 100644 compiler/test_mono/generated/monomorphized_tag.txt create mode 100644 compiler/test_mono/generated/monomorphized_tag_with_aliased_args.txt diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index cfd0ef026a..e6597c0114 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -207,6 +207,67 @@ impl<'a> PartialProc<'a> { } } +#[derive(Clone, Debug)] +enum PartialExprLink { + Aliases(Symbol), + Expr(roc_can::expr::Expr, Variable), +} + +/// A table of symbols to polymorphic expressions. For example, in the program +/// +/// n = 1 +/// +/// asU8 : U8 -> U8 +/// asU8 = \_ -> 1 +/// +/// asU32 : U32 -> U8 +/// asU32 = \_ -> 1 +/// +/// asU8 n + asU32 n +/// +/// The expression bound by `n` doesn't have a definite layout until it is used +/// at the call sites `asU8 n`, `asU32 n`. +/// +/// Polymorphic *functions* are stored in `PartialProc`s, since functions are +/// non longer first-class once we finish lowering to the IR. +#[derive(Clone, Debug)] +pub struct PartialExprs(BumpMap); + +impl PartialExprs { + pub fn new_in<'a>(arena: &'a Bump) -> Self { + Self(BumpMap::new_in(arena)) + } + + pub fn insert(&mut self, symbol: Symbol, expr: roc_can::expr::Expr, expr_var: Variable) { + self.0.insert(symbol, PartialExprLink::Expr(expr, expr_var)); + } + + pub fn insert_alias(&mut self, symbol: Symbol, aliases: Symbol) { + self.0.insert(symbol, PartialExprLink::Aliases(aliases)); + } + + pub fn contains(&self, symbol: Symbol) -> bool { + self.0.contains_key(&symbol) + } + + pub fn get(&mut self, mut symbol: Symbol) -> Option<(&roc_can::expr::Expr, Variable)> { + // In practice the alias chain is very short + loop { + match self.0.get(&symbol) { + None => { + return None; + } + Some(&PartialExprLink::Aliases(real_symbol)) => { + symbol = real_symbol; + } + Some(PartialExprLink::Expr(expr, var)) => { + return Some((expr, *var)); + } + } + } + } +} + #[derive(Clone, Copy, Debug, PartialEq)] pub enum CapturedSymbols<'a> { None, @@ -668,6 +729,7 @@ impl<'a> Specialized<'a> { #[derive(Clone, Debug)] pub struct Procs<'a> { pub partial_procs: PartialProcs<'a>, + pub partial_exprs: PartialExprs, pub imported_module_thunks: &'a [Symbol], pub module_thunks: &'a [Symbol], pending_specializations: PendingSpecializations<'a>, @@ -680,6 +742,7 @@ impl<'a> Procs<'a> { pub fn new_in(arena: &'a Bump) -> Self { Self { partial_procs: PartialProcs::new_in(arena), + partial_exprs: PartialExprs::new_in(arena), imported_module_thunks: &[], module_thunks: &[], pending_specializations: PendingSpecializations::Finding(Suspended::new_in(arena)), @@ -3103,16 +3166,20 @@ pub fn with_hole<'a>( _ => {} } - // continue with the default path - let mut stmt = with_hole( - env, - cont.value, - variable, - procs, - layout_cache, - assigned, - hole, - ); + let build_rest = + |env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>| { + with_hole( + env, + cont.value, + variable, + procs, + layout_cache, + assigned, + hole, + ) + }; // a variable is aliased if let roc_can::expr::Expr::Var(original) = def.loc_expr.value { @@ -3124,18 +3191,17 @@ pub fn with_hole<'a>( // // foo = RBTRee.empty - stmt = handle_variable_aliasing( + handle_variable_aliasing( env, procs, layout_cache, def.expr_var, symbol, original, - stmt, - ); - - stmt + build_rest, + ) } else { + let rest = build_rest(env, procs, layout_cache); with_hole( env, def.loc_expr.value, @@ -3143,7 +3209,7 @@ pub fn with_hole<'a>( procs, layout_cache, symbol, - env.arena.alloc(stmt), + env.arena.alloc(rest), ) } } else { @@ -3328,6 +3394,7 @@ pub fn with_hole<'a>( let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena); enum Field { + // TODO: rename this since it can handle unspecialized expressions now too Function(Symbol, Variable), ValueSymbol, Field(roc_can::expr::Field), @@ -3338,7 +3405,7 @@ pub fn with_hole<'a>( use ReuseSymbol::*; match fields.remove(&label) { Some(field) => match can_reuse_symbol(env, procs, &field.loc_expr.value) { - Imported(symbol) | LocalFunction(symbol) => { + Imported(symbol) | LocalFunction(symbol) | UnspecializedExpr(symbol) => { field_symbols.push(symbol); can_fields.push(Field::Function(symbol, variable)); } @@ -4064,6 +4131,9 @@ pub fn with_hole<'a>( LocalFunction(_) => { unreachable!("if this was known to be a function, we would not be here") } + UnspecializedExpr(_) => { + unreachable!("if this was known to be an unspecialized expression, we would not be here") + } Imported(thunk_name) => { debug_assert!(procs.is_imported_module_thunk(thunk_name)); @@ -5023,6 +5093,31 @@ fn register_capturing_closure<'a>( } } +fn is_literal_like(expr: &roc_can::expr::Expr) -> bool { + use roc_can::expr::Expr::*; + matches!( + expr, + Num(..) + | Int(..) + | Float(..) + | List { .. } + | Str(_) + | ZeroArgumentTag { .. } + | Tag { .. } + | Record { .. } + ) +} + +fn expr_is_polymorphic<'a>( + env: &mut Env<'a, '_>, + expr: &roc_can::expr::Expr, + expr_var: Variable, +) -> bool { + // TODO: I don't think we need the `is_literal_like` check, but taking it slow for now... + let is_flex_or_rigid = |c: &Content| matches!(c, Content::FlexVar(_) | Content::RigidVar(_)); + is_literal_like(expr) && env.subs.var_contains_content(expr_var, is_flex_or_rigid) +} + pub fn from_can<'a>( env: &mut Env<'a, '_>, variable: Variable, @@ -5177,19 +5272,26 @@ pub fn from_can<'a>( // or // // foo = RBTRee.empty - let mut rest = from_can(env, def.expr_var, cont.value, procs, layout_cache); - rest = handle_variable_aliasing( + // TODO: right now we need help out rustc with the closure types; + // it isn't able to infer the right lifetime bounds. See if we + // can remove the annotations in the future. + let build_rest = + |env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>| { + from_can(env, def.expr_var, cont.value, procs, layout_cache) + }; + + return handle_variable_aliasing( env, procs, layout_cache, def.expr_var, *symbol, original, - rest, + build_rest, ); - - return rest; } roc_can::expr::Expr::LetNonRec(nested_def, nested_cont, nested_annotation) => { use roc_can::expr::Expr::*; @@ -5273,6 +5375,21 @@ pub fn from_can<'a>( return from_can(env, variable, new_outer, procs, layout_cache); } + ref body if expr_is_polymorphic(env, body, def.expr_var) => { + // This is a pattern like + // + // n = 1 + // asU8 n + // + // At the definition site `n = 1` we only know `1` to have the type `[Int *]`, + // which won't be refined until the call `asU8 n`. Add it as a partial expression + // that will be specialized at each concrete usage site. + procs + .partial_exprs + .insert(*symbol, def.loc_expr.value, def.expr_var); + + return from_can(env, variable, cont.value, procs, layout_cache); + } _ => { let rest = from_can(env, variable, cont.value, procs, layout_cache); return with_hole( @@ -6290,6 +6407,7 @@ enum ReuseSymbol { Imported(Symbol), LocalFunction(Symbol), Value(Symbol), + UnspecializedExpr(Symbol), NotASymbol, } @@ -6307,6 +6425,8 @@ fn can_reuse_symbol<'a>( Imported(symbol) } else if procs.partial_procs.contains_key(symbol) { LocalFunction(symbol) + } else if procs.partial_exprs.contains(symbol) { + UnspecializedExpr(symbol) } else { Value(symbol) } @@ -6326,15 +6446,29 @@ fn possible_reuse_symbol<'a>( } } -fn handle_variable_aliasing<'a>( +fn handle_variable_aliasing<'a, BuildRest>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, layout_cache: &mut LayoutCache<'a>, variable: Variable, left: Symbol, right: Symbol, - mut result: Stmt<'a>, -) -> Stmt<'a> { + build_rest: BuildRest, +) -> Stmt<'a> +where + BuildRest: FnOnce(&mut Env<'a, '_>, &mut Procs<'a>, &mut LayoutCache<'a>) -> Stmt<'a>, +{ + if procs.partial_exprs.contains(right) { + // If `right` links to a partial expression, make sure we link `left` to it as well, so + // that usages of it will be specialized when building the rest of the program. + procs.partial_exprs.insert_alias(left, right); + return build_rest(env, procs, layout_cache); + } + + // Otherwise we're dealing with an alias to something that doesn't need to be specialized, or + // whose usages will already be specialized in the rest of the program. Let's just build the + // rest of the program now to get our hole. + let mut result = build_rest(env, procs, layout_cache); if procs.is_imported_module_thunk(right) { // if this is an imported symbol, then we must make sure it is // specialized, and wrap the original in a function pointer. @@ -6392,6 +6526,7 @@ fn let_empty_struct<'a>(assigned: Symbol, hole: &'a Stmt<'a>) -> Stmt<'a> { } /// If the symbol is a function, make sure it is properly specialized +// TODO: rename this now that we handle polymorphic non-function expressions too fn reuse_function_symbol<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, @@ -6401,6 +6536,35 @@ fn reuse_function_symbol<'a>( result: Stmt<'a>, original: Symbol, ) -> Stmt<'a> { + if let Some((expr, expr_var)) = procs.partial_exprs.get(original) { + // Specialize the expression type now, based off the `arg_var` we've been given. + // TODO: cache the specialized result + let snapshot = env.subs.snapshot(); + let cache_snapshot = layout_cache.snapshot(); + let _unified = roc_unify::unify::unify( + env.subs, + arg_var.unwrap(), + expr_var, + roc_unify::unify::Mode::Eq, + ); + + let result = with_hole( + env, + expr.clone(), + expr_var, + procs, + layout_cache, + symbol, + env.arena.alloc(result), + ); + + // Restore the prior state so as not to interfere with future specializations. + env.subs.rollback_to(snapshot); + layout_cache.rollback_to(cache_snapshot); + + return result; + } + match procs.get_partial_proc(original) { None => { match arg_var { @@ -6566,7 +6730,7 @@ fn assign_to_symbol<'a>( ) -> Stmt<'a> { use ReuseSymbol::*; match can_reuse_symbol(env, procs, &loc_arg.value) { - Imported(original) | LocalFunction(original) => { + Imported(original) | LocalFunction(original) | UnspecializedExpr(original) => { // for functions we must make sure they are specialized correctly reuse_function_symbol( env, diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 55845c777e..db050da940 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -2631,3 +2631,22 @@ fn list_find_empty_layout() { i64 ); } + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn monomorphized_lists() { + assert_evals_to!( + indoc!( + r#" + l = [1, 2, 3] + + f : List U8, List U16 -> Nat + f = \_, _ -> 18 + + f l l + "# + ), + 18, + u64 + ) +} diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index e026c4b292..c6c33b94e6 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -2317,3 +2317,92 @@ fn sub_saturated() { u8 ) } + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn monomorphized_ints() { + assert_evals_to!( + indoc!( + r#" + x = 100 + + f : U8, U32 -> Nat + f = \_, _ -> 18 + + f x x + "# + ), + 18, + u64 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn monomorphized_floats() { + assert_evals_to!( + indoc!( + r#" + x = 100.0 + + f : F32, F64 -> Nat + f = \_, _ -> 18 + + f x x + "# + ), + 18, + u64 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn monomorphized_ints_names_dont_conflict() { + assert_evals_to!( + indoc!( + r#" + f : U8 -> Nat + f = \_ -> 9 + x = + n = 100 + f n + + y = + n = 100 + f n + + x + y + "# + ), + 18, + u64 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn monomorphized_ints_aliased() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + y = 100 + w1 = y + w2 = y + + f1 : U8, U32 -> U8 + f1 = \_, _ -> 1 + + f2 : U32, U8 -> U8 + f2 = \_, _ -> 2 + + f1 w1 w2 + f2 w1 w2 + "# + ), + 3, + u8 + ) +} diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index f396534be8..b8062dbde2 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -1262,3 +1262,108 @@ fn recursive_tag_union_into_flat_tag_union() { |_| 0 ) } + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn monomorphized_tag() { + assert_evals_to!( + indoc!( + r#" + b = False + f : Bool, [True, False, Idk] -> U8 + f = \_, _ -> 18 + f b b + "# + ), + 18, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn monomorphized_applied_tag() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + main = + a = A "abc" + f = \x -> + when x is + A y -> y + B y -> y + f a + "# + ), + RocStr::from_slice(b"abc"), + RocStr + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn monomorphized_tag_with_polymorphic_arg() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + a = A + wrap = Wrapped a + + useWrap1 : [Wrapped [A], Other] -> U8 + useWrap1 = + \w -> when w is + Wrapped A -> 2 + Other -> 3 + + useWrap2 : [Wrapped [A, B]] -> U8 + useWrap2 = + \w -> when w is + Wrapped A -> 5 + Wrapped B -> 7 + + useWrap1 wrap * useWrap2 wrap + "# + ), + 10, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn monomorphized_tag_with_polymorphic_arg_and_monomorphic_arg() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + mono : U8 + mono = 15 + poly = A + wrap = Wrapped poly mono + + useWrap1 : [Wrapped [A] U8, Other] -> U8 + useWrap1 = + \w -> when w is + Wrapped A n -> n + Other -> 0 + + useWrap2 : [Wrapped [A, B] U8] -> U8 + useWrap2 = + \w -> when w is + Wrapped A n -> n + Wrapped B _ -> 0 + + useWrap1 wrap * useWrap2 wrap + "# + ), + 225, + u8 + ) +} diff --git a/compiler/test_mono/generated/alias_variable.txt b/compiler/test_mono/generated/alias_variable.txt index 095d77af3a..657376d73a 100644 --- a/compiler/test_mono/generated/alias_variable.txt +++ b/compiler/test_mono/generated/alias_variable.txt @@ -1,4 +1,3 @@ procedure Test.0 (): - let Test.1 : Builtin(Int(I64)) = 5i64; let Test.3 : Builtin(Int(I64)) = 3i64; ret Test.3; diff --git a/compiler/test_mono/generated/alias_variable_and_return_it.txt b/compiler/test_mono/generated/alias_variable_and_return_it.txt index ff5f614740..9e5824a185 100644 --- a/compiler/test_mono/generated/alias_variable_and_return_it.txt +++ b/compiler/test_mono/generated/alias_variable_and_return_it.txt @@ -1,3 +1,3 @@ procedure Test.0 (): - let Test.1 : Builtin(Int(I64)) = 5i64; - ret Test.1; + let Test.2 : Builtin(Int(I64)) = 5i64; + ret Test.2; diff --git a/compiler/test_mono/generated/closure_in_list.txt b/compiler/test_mono/generated/closure_in_list.txt index 9fa9aaeae9..b4de3287e0 100644 --- a/compiler/test_mono/generated/closure_in_list.txt +++ b/compiler/test_mono/generated/closure_in_list.txt @@ -3,13 +3,13 @@ procedure List.7 (#Attr.2): ret Test.7; procedure Test.1 (Test.5): - let Test.2 : Builtin(Int(I64)) = 41i64; let Test.11 : LambdaSet([( Test.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; let Test.10 : Builtin(List(LambdaSet([( Test.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }))) = Array [Test.11]; ret Test.10; procedure Test.3 (Test.9, #Attr.12): let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 #Attr.12; + let Test.2 : Builtin(Int(I64)) = 41i64; ret Test.2; procedure Test.0 (): diff --git a/compiler/test_mono/generated/empty_list_of_function_type.txt b/compiler/test_mono/generated/empty_list_of_function_type.txt index e68e0bf85d..e24846c6b4 100644 --- a/compiler/test_mono/generated/empty_list_of_function_type.txt +++ b/compiler/test_mono/generated/empty_list_of_function_type.txt @@ -15,7 +15,6 @@ procedure Test.2 (Test.6): ret Test.24; procedure Test.0 (): - let Test.1 : Builtin(List(LambdaSet([]LambdaSet { set: Ok(()), representation: Struct([]) }))) = Array []; joinpoint Test.22 Test.3: let Test.14 : Builtin(Int(U64)) = 0i64; let Test.7 : Union(NonRecursive([[Struct([])], [LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) })]])) = CallByName List.3 Test.3 Test.14; @@ -35,9 +34,9 @@ procedure Test.0 (): in let Test.25 : Builtin(Bool) = false; if Test.25 then + let Test.1 : Builtin(List(LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }))) = Array []; jump Test.22 Test.1; else - dec Test.1; let Test.23 : LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = Struct {}; let Test.21 : Builtin(List(LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }))) = Array [Test.23]; jump Test.22 Test.21; diff --git a/compiler/test_mono/generated/ir_int_add.txt b/compiler/test_mono/generated/ir_int_add.txt index 798fd708d7..2d49bd57c1 100644 --- a/compiler/test_mono/generated/ir_int_add.txt +++ b/compiler/test_mono/generated/ir_int_add.txt @@ -1,19 +1,19 @@ procedure List.7 (#Attr.2): - let Test.6 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; - ret Test.6; + let Test.7 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + ret Test.7; procedure Num.24 (#Attr.2, #Attr.3): let Test.5 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.5; procedure Test.0 (): - let Test.1 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64]; - let Test.9 : Builtin(Int(U64)) = 5i64; - let Test.10 : Builtin(Int(U64)) = 4i64; - let Test.7 : Builtin(Int(U64)) = CallByName Num.24 Test.9 Test.10; - let Test.8 : Builtin(Int(U64)) = 3i64; - let Test.3 : Builtin(Int(U64)) = CallByName Num.24 Test.7 Test.8; - let Test.4 : Builtin(Int(U64)) = CallByName List.7 Test.1; - dec Test.1; + let Test.10 : Builtin(Int(U64)) = 5i64; + let Test.11 : Builtin(Int(U64)) = 4i64; + let Test.8 : Builtin(Int(U64)) = CallByName Num.24 Test.10 Test.11; + let Test.9 : Builtin(Int(U64)) = 3i64; + let Test.3 : Builtin(Int(U64)) = CallByName Num.24 Test.8 Test.9; + let Test.6 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64]; + let Test.4 : Builtin(Int(U64)) = CallByName List.7 Test.6; + dec Test.6; let Test.2 : Builtin(Int(U64)) = CallByName Num.24 Test.3 Test.4; ret Test.2; diff --git a/compiler/test_mono/generated/ir_two_defs.txt b/compiler/test_mono/generated/ir_two_defs.txt index 1811869476..66b9bd2728 100644 --- a/compiler/test_mono/generated/ir_two_defs.txt +++ b/compiler/test_mono/generated/ir_two_defs.txt @@ -1,9 +1,9 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.4 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.4; + let Test.6 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + ret Test.6; procedure Test.0 (): - let Test.1 : Builtin(Int(I64)) = 3i64; - let Test.2 : Builtin(Int(I64)) = 4i64; - let Test.3 : Builtin(Int(I64)) = CallByName Num.24 Test.1 Test.2; + let Test.4 : Builtin(Int(I64)) = 3i64; + let Test.5 : Builtin(Int(I64)) = 4i64; + let Test.3 : Builtin(Int(I64)) = CallByName Num.24 Test.4 Test.5; ret Test.3; diff --git a/compiler/test_mono/generated/let_x_in_x.txt b/compiler/test_mono/generated/let_x_in_x.txt index 39b16fd536..1ae2b5dd47 100644 --- a/compiler/test_mono/generated/let_x_in_x.txt +++ b/compiler/test_mono/generated/let_x_in_x.txt @@ -1,5 +1,3 @@ procedure Test.0 (): - let Test.1 : Builtin(Int(I64)) = 5i64; - let Test.4 : Builtin(Int(I64)) = 17i64; let Test.2 : Builtin(Int(I64)) = 1337i64; ret Test.2; diff --git a/compiler/test_mono/generated/let_x_in_x_indirect.txt b/compiler/test_mono/generated/let_x_in_x_indirect.txt index 761529956b..8897207353 100644 --- a/compiler/test_mono/generated/let_x_in_x_indirect.txt +++ b/compiler/test_mono/generated/let_x_in_x_indirect.txt @@ -1,8 +1,6 @@ procedure Test.0 (): - let Test.1 : Builtin(Int(I64)) = 5i64; - let Test.4 : Builtin(Int(I64)) = 17i64; - let Test.5 : Builtin(Int(I64)) = 1i64; let Test.2 : Builtin(Int(I64)) = 1337i64; - let Test.7 : Struct([Builtin(Int(I64)), Builtin(Int(I64))]) = Struct {Test.2, Test.4}; + let Test.3 : Builtin(Int(I64)) = 17i64; + let Test.7 : Struct([Builtin(Int(I64)), Builtin(Int(I64))]) = Struct {Test.2, Test.3}; let Test.6 : Builtin(Int(I64)) = StructAtIndex 0 Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/list_len.txt b/compiler/test_mono/generated/list_len.txt index 41e4167e0f..c210581d86 100644 --- a/compiler/test_mono/generated/list_len.txt +++ b/compiler/test_mono/generated/list_len.txt @@ -1,6 +1,6 @@ procedure List.7 (#Attr.2): - let Test.7 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; - ret Test.7; + let Test.10 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + ret Test.10; procedure List.7 (#Attr.2): let Test.8 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; @@ -11,11 +11,11 @@ procedure Num.24 (#Attr.2, #Attr.3): ret Test.6; procedure Test.0 (): - let Test.1 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; - let Test.2 : Builtin(List(Builtin(Float(F64)))) = Array [1f64]; - let Test.4 : Builtin(Int(U64)) = CallByName List.7 Test.1; - dec Test.1; - let Test.5 : Builtin(Int(U64)) = CallByName List.7 Test.2; - dec Test.2; + let Test.9 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; + let Test.4 : Builtin(Int(U64)) = CallByName List.7 Test.9; + dec Test.9; + let Test.7 : Builtin(List(Builtin(Float(F64)))) = Array [1f64]; + let Test.5 : Builtin(Int(U64)) = CallByName List.7 Test.7; + dec Test.7; let Test.3 : Builtin(Int(U64)) = CallByName Num.24 Test.4 Test.5; ret Test.3; diff --git a/compiler/test_mono/generated/monomorphized_applied_tag.txt b/compiler/test_mono/generated/monomorphized_applied_tag.txt new file mode 100644 index 0000000000..90dd27c931 --- /dev/null +++ b/compiler/test_mono/generated/monomorphized_applied_tag.txt @@ -0,0 +1,20 @@ +procedure Test.2 (Test.4): + let Test.11 : Builtin(Int(U8)) = 0i64; + let Test.12 : Builtin(Int(U8)) = GetTagId Test.4; + let Test.13 : Builtin(Bool) = lowlevel Eq Test.11 Test.12; + if Test.13 then + let Test.5 : Builtin(Str) = UnionAtIndex (Id 0) (Index 0) Test.4; + inc Test.5; + dec Test.4; + ret Test.5; + else + let Test.6 : Builtin(Str) = UnionAtIndex (Id 1) (Index 0) Test.4; + inc Test.6; + dec Test.4; + ret Test.6; + +procedure Test.0 (): + let Test.14 : Builtin(Str) = "A"; + let Test.8 : Union(NonRecursive([[Builtin(Str)], [Builtin(Str)]])) = A Test.14; + let Test.7 : Builtin(Str) = CallByName Test.2 Test.8; + ret Test.7; diff --git a/compiler/test_mono/generated/monomorphized_floats.txt b/compiler/test_mono/generated/monomorphized_floats.txt new file mode 100644 index 0000000000..90eef8acc7 --- /dev/null +++ b/compiler/test_mono/generated/monomorphized_floats.txt @@ -0,0 +1,9 @@ +procedure Test.2 (Test.3, Test.4): + let Test.8 : Builtin(Int(U64)) = 18i64; + ret Test.8; + +procedure Test.0 (): + let Test.6 : Builtin(Float(F32)) = 100f64; + let Test.7 : Builtin(Float(F64)) = 100f64; + let Test.5 : Builtin(Int(U64)) = CallByName Test.2 Test.6 Test.7; + ret Test.5; diff --git a/compiler/test_mono/generated/monomorphized_ints.txt b/compiler/test_mono/generated/monomorphized_ints.txt new file mode 100644 index 0000000000..5ffdd51f13 --- /dev/null +++ b/compiler/test_mono/generated/monomorphized_ints.txt @@ -0,0 +1,9 @@ +procedure Test.2 (Test.3, Test.4): + let Test.8 : Builtin(Int(U64)) = 18i64; + ret Test.8; + +procedure Test.0 (): + let Test.6 : Builtin(Int(U8)) = 100i64; + let Test.7 : Builtin(Int(U32)) = 100i64; + let Test.5 : Builtin(Int(U64)) = CallByName Test.2 Test.6 Test.7; + ret Test.5; diff --git a/compiler/test_mono/generated/monomorphized_ints_aliased.txt b/compiler/test_mono/generated/monomorphized_ints_aliased.txt new file mode 100644 index 0000000000..3b9735dfa5 --- /dev/null +++ b/compiler/test_mono/generated/monomorphized_ints_aliased.txt @@ -0,0 +1,23 @@ +procedure Num.24 (#Attr.2, #Attr.3): + let Test.12 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3; + ret Test.12; + +procedure Test.4 (Test.7, Test.8): + let Test.17 : Builtin(Int(U64)) = 1i64; + ret Test.17; + +procedure Test.4 (Test.7, Test.8): + let Test.18 : Builtin(Int(U64)) = 1i64; + ret Test.18; + +procedure Test.0 (): + let Test.4 : LambdaSet([( Test.4, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = Struct {}; + let Test.4 : LambdaSet([( Test.4, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = Struct {}; + let Test.15 : Builtin(Int(U8)) = 100i64; + let Test.16 : Builtin(Int(U32)) = 100i64; + let Test.10 : Builtin(Int(U64)) = CallByName Test.4 Test.15 Test.16; + let Test.13 : Builtin(Int(U32)) = 100i64; + let Test.14 : Builtin(Int(U8)) = 100i64; + let Test.11 : Builtin(Int(U64)) = CallByName Test.4 Test.13 Test.14; + let Test.9 : Builtin(Int(U64)) = CallByName Num.24 Test.10 Test.11; + ret Test.9; diff --git a/compiler/test_mono/generated/monomorphized_list.txt b/compiler/test_mono/generated/monomorphized_list.txt new file mode 100644 index 0000000000..d38a0682ff --- /dev/null +++ b/compiler/test_mono/generated/monomorphized_list.txt @@ -0,0 +1,11 @@ +procedure Test.2 (Test.3, Test.4): + let Test.8 : Builtin(Int(U64)) = 18i64; + ret Test.8; + +procedure Test.0 (): + let Test.6 : Builtin(List(Builtin(Int(U8)))) = Array [1i64, 2i64, 3i64]; + let Test.7 : Builtin(List(Builtin(Int(U16)))) = Array [1i64, 2i64, 3i64]; + let Test.5 : Builtin(Int(U64)) = CallByName Test.2 Test.6 Test.7; + dec Test.7; + dec Test.6; + ret Test.5; diff --git a/compiler/test_mono/generated/monomorphized_tag.txt b/compiler/test_mono/generated/monomorphized_tag.txt new file mode 100644 index 0000000000..a1cb92209d --- /dev/null +++ b/compiler/test_mono/generated/monomorphized_tag.txt @@ -0,0 +1,9 @@ +procedure Test.2 (Test.4, Test.5): + let Test.9 : Builtin(Int(U8)) = 18i64; + ret Test.9; + +procedure Test.0 (): + let Test.7 : Builtin(Bool) = false; + let Test.8 : Builtin(Int(U8)) = 0u8; + let Test.6 : Builtin(Int(U8)) = CallByName Test.2 Test.7 Test.8; + ret Test.6; diff --git a/compiler/test_mono/generated/monomorphized_tag_with_aliased_args.txt b/compiler/test_mono/generated/monomorphized_tag_with_aliased_args.txt new file mode 100644 index 0000000000..02a88fb375 --- /dev/null +++ b/compiler/test_mono/generated/monomorphized_tag_with_aliased_args.txt @@ -0,0 +1,10 @@ +procedure Test.4 (Test.8): + let Test.11 : Builtin(Int(U64)) = 1i64; + ret Test.11; + +procedure Test.0 (): + let Test.13 : Builtin(Bool) = false; + let Test.12 : Builtin(Bool) = false; + let Test.10 : Struct([Builtin(Bool), Builtin(Bool)]) = Struct {Test.12, Test.13}; + let Test.9 : Builtin(Int(U64)) = CallByName Test.4 Test.10; + ret Test.9; diff --git a/compiler/test_mono/generated/nested_closure.txt b/compiler/test_mono/generated/nested_closure.txt index f8413a0265..b7a572df71 100644 --- a/compiler/test_mono/generated/nested_closure.txt +++ b/compiler/test_mono/generated/nested_closure.txt @@ -1,10 +1,10 @@ procedure Test.1 (Test.5): - let Test.2 : Builtin(Int(I64)) = 42i64; let Test.3 : LambdaSet([( Test.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; ret Test.3; procedure Test.3 (Test.9, #Attr.12): let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 #Attr.12; + let Test.2 : Builtin(Int(I64)) = 42i64; ret Test.2; procedure Test.0 (): diff --git a/compiler/test_mono/generated/record_optional_field_function_use_default.txt b/compiler/test_mono/generated/record_optional_field_function_use_default.txt index 327b3e2f02..8b3ec11a1d 100644 --- a/compiler/test_mono/generated/record_optional_field_function_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_function_use_default.txt @@ -1,13 +1,13 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.8; + let Test.9 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + ret Test.9; procedure Test.1 (Test.4): - let Test.2 : Builtin(Int(I64)) = 10i64; - let Test.7 : Builtin(Int(I64)) = CallByName Num.24 Test.2 Test.4; + let Test.8 : Builtin(Int(I64)) = 10i64; + let Test.7 : Builtin(Int(I64)) = CallByName Num.24 Test.8 Test.4; ret Test.7; procedure Test.0 (): - let Test.9 : Builtin(Int(I64)) = 9i64; - let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.9; + let Test.10 : Builtin(Int(I64)) = 9i64; + let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.10; ret Test.5; diff --git a/compiler/test_mono/src/tests.rs b/compiler/test_mono/src/tests.rs index 75f3c19201..8926a4942a 100644 --- a/compiler/test_mono/src/tests.rs +++ b/compiler/test_mono/src/tests.rs @@ -1111,6 +1111,131 @@ fn empty_list_of_function_type() { ) } +#[mono_test] +fn monomorphized_ints() { + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + x = 100 + + f : U8, U32 -> Nat + f = \_, _ -> 18 + + f x x + "# + ) +} + +#[mono_test] +fn monomorphized_floats() { + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + x = 100.0 + + f : F32, F64 -> Nat + f = \_, _ -> 18 + + f x x + "# + ) +} + +#[mono_test] +#[ignore = "TODO"] +fn monomorphized_ints_aliased() { + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + y = 100 + w1 = y + w2 = y + + f = \_, _ -> 1 + + f1 : U8, U32 -> Nat + f1 = f + + f2 : U32, U8 -> Nat + f2 = f + + f1 w1 w2 + f2 w1 w2 + "# + ) +} + +#[mono_test] +fn monomorphized_tag() { + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + b = False + f : Bool, [True, False, Idk] -> U8 + f = \_, _ -> 18 + f b b + "# + ) +} + +#[mono_test] +fn monomorphized_tag_with_aliased_args() { + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + b = False + c = False + a = A b c + f : [A Bool Bool] -> Nat + f = \_ -> 1 + f a + "# + ) +} + +#[mono_test] +fn monomorphized_list() { + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + l = [1, 2, 3] + + f : List U8, List U16 -> Nat + f = \_, _ -> 18 + + f l l + "# + ) +} + +#[mono_test] +fn monomorphized_applied_tag() { + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + main = + a = A "A" + f = \x -> + when x is + A y -> y + B y -> y + f a + "# + ) +} + // #[ignore] // #[mono_test] // fn static_str_closure() { From ae104c3791e2e3426a83e61f3eac918a46af9a0f Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Wed, 19 Jan 2022 23:16:48 -0500 Subject: [PATCH 264/541] Elide unnecessary lifetimes --- compiler/mono/src/ir.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index e6597c0114..277395ba1b 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -234,7 +234,7 @@ enum PartialExprLink { pub struct PartialExprs(BumpMap); impl PartialExprs { - pub fn new_in<'a>(arena: &'a Bump) -> Self { + pub fn new_in(arena: &Bump) -> Self { Self(BumpMap::new_in(arena)) } From 649695f6e3f10e568896e961d7796268047900d8 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Tue, 18 Jan 2022 00:45:29 -0500 Subject: [PATCH 265/541] Fix wasm refcount tests --- compiler/test_gen/src/gen_refcount.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs index c62e27f19c..8fe30ca01b 100644 --- a/compiler/test_gen/src/gen_refcount.rs +++ b/compiler/test_gen/src/gen_refcount.rs @@ -59,7 +59,10 @@ fn list_int_inc() { ), RocList>, &[ - 3, // list + // TODO be smarter about coalescing polymorphic list values + 1, // list0 + 1, // list1 + 1, // list2 1 // result ] ); @@ -77,7 +80,10 @@ fn list_int_dealloc() { ), usize, &[ - 0, // list + // TODO be smarter about coalescing polymorphic list values + 0, // list0 + 0, // list1 + 0, // list2 0 // result ] ); From 8fd139ba6758cec48be533ce33bbb42e65877419 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 20 Jan 2022 00:23:03 -0500 Subject: [PATCH 266/541] Handle unspecialized symbols captured in closures --- compiler/mono/src/ir.rs | 101 +++++++++++++++++++----- compiler/test_gen/src/gen_primitives.rs | 25 ++++++ 2 files changed, 106 insertions(+), 20 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 277395ba1b..8966a16dc8 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -266,6 +266,11 @@ impl PartialExprs { } } } + + pub fn remove(&mut self, symbol: Symbol) { + debug_assert!(self.contains(symbol)); + self.0.remove(&symbol); + } } #[derive(Clone, Copy, Debug, PartialEq)] @@ -3861,9 +3866,16 @@ pub fn with_hole<'a>( ); match raw_layout { - RawFunctionLayout::Function(_, lambda_set, _) => { - construct_closure_data(env, lambda_set, name, &[], assigned, hole) - } + RawFunctionLayout::Function(_, lambda_set, _) => construct_closure_data( + env, + procs, + layout_cache, + lambda_set, + name, + &[], + assigned, + hole, + ), RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!(), } } @@ -4057,10 +4069,18 @@ pub fn with_hole<'a>( // define the closure data let symbols = - Vec::from_iter_in(captured_symbols.iter().map(|x| x.0), env.arena) - .into_bump_slice(); + Vec::from_iter_in(captured_symbols.iter(), env.arena).into_bump_slice(); - construct_closure_data(env, lambda_set, name, symbols, assigned, hole) + construct_closure_data( + env, + procs, + layout_cache, + lambda_set, + name, + symbols, + assigned, + hole, + ) } } } @@ -4512,17 +4532,20 @@ pub fn with_hole<'a>( } } +#[allow(clippy::too_many_arguments)] fn construct_closure_data<'a>( env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, lambda_set: LambdaSet<'a>, name: Symbol, - symbols: &'a [Symbol], + symbols: &'a [&(Symbol, Variable)], assigned: Symbol, hole: &'a Stmt<'a>, ) -> Stmt<'a> { let lambda_set_layout = Layout::LambdaSet(lambda_set); - match lambda_set.layout_for_member(name) { + let mut result = match lambda_set.layout_for_member(name) { ClosureRepresentation::Union { tag_id, alphabetic_order_fields: field_layouts, @@ -4531,8 +4554,10 @@ fn construct_closure_data<'a>( } => { // captured variables are in symbol-alphabetic order, but now we want // them ordered by their alignment requirements - let mut combined = - Vec::from_iter_in(symbols.iter().zip(field_layouts.iter()), env.arena); + let mut combined = Vec::from_iter_in( + symbols.iter().map(|&&(s, _)| s).zip(field_layouts.iter()), + env.arena, + ); let ptr_bytes = env.ptr_bytes; @@ -4544,7 +4569,7 @@ fn construct_closure_data<'a>( }); let symbols = - Vec::from_iter_in(combined.iter().map(|(a, _)| **a), env.arena).into_bump_slice(); + Vec::from_iter_in(combined.iter().map(|(a, _)| *a), env.arena).into_bump_slice(); let expr = Expr::Tag { tag_id, @@ -4560,8 +4585,10 @@ fn construct_closure_data<'a>( // captured variables are in symbol-alphabetic order, but now we want // them ordered by their alignment requirements - let mut combined = - Vec::from_iter_in(symbols.iter().zip(field_layouts.iter()), env.arena); + let mut combined = Vec::from_iter_in( + symbols.iter().map(|&(s, _)| s).zip(field_layouts.iter()), + env.arena, + ); let ptr_bytes = env.ptr_bytes; @@ -4605,7 +4632,23 @@ fn construct_closure_data<'a>( Stmt::Let(assigned, expr, lambda_set_layout, hole) } _ => unreachable!(), + }; + + // Some of the captured symbols may be references to polymorphic expressions, + // which have not been specialized yet. We need to perform those + // specializations now so that there are real symbols to capture. + // + // TODO: this is not quite right. What we should actually be doing is removing references to + // polymorphic expressions from the captured symbols, and allowing the specializations of those + // symbols to be inlined when specializing the closure body elsewhere. + for &&(symbol, var) in symbols { + if procs.partial_exprs.contains(symbol) { + result = + reuse_function_symbol(env, procs, layout_cache, Some(var), symbol, result, symbol); + } } + + result } #[allow(clippy::too_many_arguments)] @@ -4908,9 +4951,16 @@ fn tag_union_to_function<'a>( ); match raw_layout { - RawFunctionLayout::Function(_, lambda_set, _) => { - construct_closure_data(env, lambda_set, proc_symbol, &[], assigned, hole) - } + RawFunctionLayout::Function(_, lambda_set, _) => construct_closure_data( + env, + procs, + layout_cache, + lambda_set, + proc_symbol, + &[], + assigned, + hole, + ), RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!(), } } @@ -5388,7 +5438,12 @@ pub fn from_can<'a>( .partial_exprs .insert(*symbol, def.loc_expr.value, def.expr_var); - return from_can(env, variable, cont.value, procs, layout_cache); + let result = from_can(env, variable, cont.value, procs, layout_cache); + + // We won't see this symbol again. + procs.partial_exprs.remove(*symbol); + + return result; } _ => { let rest = from_can(env, variable, cont.value, procs, layout_cache); @@ -6655,7 +6710,7 @@ fn reuse_function_symbol<'a>( let symbols = match captured { CapturedSymbols::Captured(captured_symbols) => { - Vec::from_iter_in(captured_symbols.iter().map(|x| x.0), env.arena) + Vec::from_iter_in(captured_symbols.iter(), env.arena) .into_bump_slice() } CapturedSymbols::None => unreachable!(), @@ -6663,6 +6718,8 @@ fn reuse_function_symbol<'a>( construct_closure_data( env, + procs, + layout_cache, lambda_set, original, symbols, @@ -6699,6 +6756,8 @@ fn reuse_function_symbol<'a>( // unification may still cause it to have an extra argument construct_closure_data( env, + procs, + layout_cache, lambda_set, original, &[], @@ -7382,8 +7441,8 @@ fn call_specialized_proc<'a>( .map(|pp| &pp.captured_symbols) { Some(&CapturedSymbols::Captured(captured_symbols)) => { - let symbols = Vec::from_iter_in(captured_symbols.iter().map(|x| x.0), env.arena) - .into_bump_slice(); + let symbols = + Vec::from_iter_in(captured_symbols.iter(), env.arena).into_bump_slice(); let closure_data_symbol = env.unique_symbol(); @@ -7408,6 +7467,8 @@ fn call_specialized_proc<'a>( let result = construct_closure_data( env, + procs, + layout_cache, lambda_set, proc_name, symbols, diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index 206821b6f9..3c4a6a26cb 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -3182,3 +3182,28 @@ fn recursively_build_effect() { RocStr ); } + +#[test] +#[ignore = "TODO; currently generates bad code because `a` isn't specialized inside the closure."] +#[cfg(any(feature = "gen-llvm"))] +fn polymophic_expression_captured_inside_closure() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + asU8 : U8 -> U8 + asU8 = \_ -> 30 + + main = + a = 15 + f = \{} -> + asU8 a + + f {} + "# + ), + 30, + u8 + ); +} From e7e2fa063f0ed4a44d2a1f08f3faade60f256d8f Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 20 Jan 2022 01:23:31 -0500 Subject: [PATCH 267/541] Specialize polymorphic values in record updates --- compiler/mono/src/ir.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 8966a16dc8..e2784ef451 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -4008,13 +4008,36 @@ pub fn with_hole<'a>( ); } CopyExisting(index) => { + let record_needs_specialization = + procs.partial_exprs.contains(structure); + let specialized_structure_sym = if record_needs_specialization { + // We need to specialize the record now; create a new one for it. + // TODO: reuse this symbol for all updates + env.unique_symbol() + } else { + // The record is already good. + structure + }; + let access_expr = Expr::StructAtIndex { - structure, + structure: specialized_structure_sym, index, field_layouts, }; stmt = Stmt::Let(*symbol, access_expr, *field_layout, arena.alloc(stmt)); + + if record_needs_specialization { + stmt = reuse_function_symbol( + env, + procs, + layout_cache, + Some(record_var), + specialized_structure_sym, + stmt, + structure, + ); + } } } } From f2ae02213a91b74b5f4fda6bfb7c7ac7a3b34c9e Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Thu, 20 Jan 2022 08:29:30 -0600 Subject: [PATCH 268/541] remove deprecated symbols and reorder remainder --- compiler/module/src/symbol.rs | 200 +++++++++++++++++----------------- 1 file changed, 99 insertions(+), 101 deletions(-) diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 50056ea268..7d3472d224 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -903,107 +903,105 @@ define_builtins! { 15 NUM_F32: "F32" imported // the Num.F32 type alias 16 NUM_FLOATINGPOINT: "FloatingPoint" imported // Float : Num FloatingPoint 17 NUM_AT_FLOATINGPOINT: "@FloatingPoint" // the Float.@FloatingPoint private tag - 18 NUM_MAX_INT: "" // removed (replaced functionally by NUM_MAX_I128) - 19 NUM_MIN_INT: "" // removed (replaced functionally by NUM_MIN_I128) - 20 NUM_MAX_FLOAT: "maxFloat" - 21 NUM_MIN_FLOAT: "minFloat" - 22 NUM_ABS: "abs" - 23 NUM_NEG: "neg" - 24 NUM_ADD: "add" - 25 NUM_SUB: "sub" - 26 NUM_MUL: "mul" - 27 NUM_LT: "isLt" - 28 NUM_LTE: "isLte" - 29 NUM_GT: "isGt" - 30 NUM_GTE: "isGte" - 31 NUM_TO_FLOAT: "toFloat" - 32 NUM_SIN: "sin" - 33 NUM_COS: "cos" - 34 NUM_TAN: "tan" - 35 NUM_IS_ZERO: "isZero" - 36 NUM_IS_EVEN: "isEven" - 37 NUM_IS_ODD: "isOdd" - 38 NUM_IS_POSITIVE: "isPositive" - 39 NUM_IS_NEGATIVE: "isNegative" - 40 NUM_REM: "rem" - 41 NUM_DIV_FLOAT: "div" - 42 NUM_DIV_INT: "divFloor" - 43 NUM_MOD_INT: "modInt" - 44 NUM_MOD_FLOAT: "modFloat" - 45 NUM_SQRT: "sqrt" - 46 NUM_LOG: "log" - 47 NUM_ROUND: "round" - 48 NUM_COMPARE: "compare" - 49 NUM_POW: "pow" - 50 NUM_CEILING: "ceiling" - 51 NUM_POW_INT: "powInt" - 52 NUM_FLOOR: "floor" - 53 NUM_ADD_WRAP: "addWrap" - 54 NUM_ADD_CHECKED: "addChecked" - 55 NUM_ADD_SATURATED: "addSaturated" - 56 NUM_ATAN: "atan" - 57 NUM_ACOS: "acos" - 58 NUM_ASIN: "asin" - 59 NUM_AT_SIGNED128: "@Signed128" - 60 NUM_SIGNED128: "Signed128" imported - 61 NUM_AT_SIGNED64: "@Signed64" - 62 NUM_SIGNED64: "Signed64" imported - 63 NUM_AT_SIGNED32: "@Signed32" - 64 NUM_SIGNED32: "Signed32" imported - 65 NUM_AT_SIGNED16: "@Signed16" - 66 NUM_SIGNED16: "Signed16" imported - 67 NUM_AT_SIGNED8: "@Signed8" - 68 NUM_SIGNED8: "Signed8" imported - 69 NUM_AT_UNSIGNED128: "@Unsigned128" - 70 NUM_UNSIGNED128: "Unsigned128" imported - 71 NUM_AT_UNSIGNED64: "@Unsigned64" - 72 NUM_UNSIGNED64: "Unsigned64" imported - 73 NUM_AT_UNSIGNED32: "@Unsigned32" - 74 NUM_UNSIGNED32: "Unsigned32" imported - 75 NUM_AT_UNSIGNED16: "@Unsigned16" - 76 NUM_UNSIGNED16: "Unsigned16" imported - 77 NUM_AT_UNSIGNED8: "@Unsigned8" - 78 NUM_UNSIGNED8: "Unsigned8" imported - 79 NUM_AT_BINARY64: "@Binary64" - 80 NUM_BINARY64: "Binary64" imported - 81 NUM_AT_BINARY32: "@Binary32" - 82 NUM_BINARY32: "Binary32" imported - 83 NUM_BITWISE_AND: "bitwiseAnd" - 84 NUM_BITWISE_XOR: "bitwiseXor" - 85 NUM_BITWISE_OR: "bitwiseOr" - 86 NUM_SHIFT_LEFT: "shiftLeftBy" - 87 NUM_SHIFT_RIGHT: "shiftRightBy" - 88 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy" - 89 NUM_SUB_WRAP: "subWrap" - 90 NUM_SUB_CHECKED: "subChecked" - 91 NUM_SUB_SATURATED: "subSaturated" - 92 NUM_MUL_WRAP: "mulWrap" - 93 NUM_MUL_CHECKED: "mulChecked" - 94 NUM_INT: "Int" imported - 95 NUM_FLOAT: "Float" imported - 96 NUM_AT_NATURAL: "@Natural" - 97 NUM_NATURAL: "Natural" imported - 98 NUM_NAT: "Nat" imported - 99 NUM_INT_CAST: "intCast" - 100 NUM_MAX_I128: "maxI128" - 101 NUM_IS_MULTIPLE_OF: "isMultipleOf" - 102 NUM_AT_DECIMAL: "@Decimal" - 103 NUM_DECIMAL: "Decimal" imported - 104 NUM_DEC: "Dec" imported // the Num.Dectype alias - 105 NUM_BYTES_TO_U16: "bytesToU16" - 106 NUM_BYTES_TO_U32: "bytesToU32" - 107 NUM_CAST_TO_NAT: "#castToNat" - 108 NUM_DIV_CEIL: "divCeil" - 109 NUM_TO_STR: "toStr" - 110 NUM_MIN_I128: "minI128" - 111 NUM_MIN_I32: "minI32" - 112 NUM_MAX_I32: "maxI32" - 113 NUM_MIN_U32: "minU32" - 114 NUM_MAX_U32: "maxU32" - 115 NUM_MIN_I64: "minI64" - 116 NUM_MAX_I64: "maxI64" - 117 NUM_MIN_U64: "minU64" - 118 NUM_MAX_U64: "maxU64" + 18 NUM_MAX_FLOAT: "maxFloat" + 19 NUM_MIN_FLOAT: "minFloat" + 20 NUM_ABS: "abs" + 21 NUM_NEG: "neg" + 22 NUM_ADD: "add" + 23 NUM_SUB: "sub" + 24 NUM_MUL: "mul" + 25 NUM_LT: "isLt" + 26 NUM_LTE: "isLte" + 27 NUM_GT: "isGt" + 28 NUM_GTE: "isGte" + 29 NUM_TO_FLOAT: "toFloat" + 30 NUM_SIN: "sin" + 31 NUM_COS: "cos" + 32 NUM_TAN: "tan" + 33 NUM_IS_ZERO: "isZero" + 34 NUM_IS_EVEN: "isEven" + 35 NUM_IS_ODD: "isOdd" + 36 NUM_IS_POSITIVE: "isPositive" + 37 NUM_IS_NEGATIVE: "isNegative" + 38 NUM_REM: "rem" + 39 NUM_DIV_FLOAT: "div" + 40 NUM_DIV_INT: "divFloor" + 41 NUM_MOD_INT: "modInt" + 42 NUM_MOD_FLOAT: "modFloat" + 43 NUM_SQRT: "sqrt" + 44 NUM_LOG: "log" + 45 NUM_ROUND: "round" + 46 NUM_COMPARE: "compare" + 47 NUM_POW: "pow" + 48 NUM_CEILING: "ceiling" + 49 NUM_POW_INT: "powInt" + 50 NUM_FLOOR: "floor" + 51 NUM_ADD_WRAP: "addWrap" + 52 NUM_ADD_CHECKED: "addChecked" + 53 NUM_ADD_SATURATED: "addSaturated" + 54 NUM_ATAN: "atan" + 55 NUM_ACOS: "acos" + 56 NUM_ASIN: "asin" + 57 NUM_AT_SIGNED128: "@Signed128" + 58 NUM_SIGNED128: "Signed128" imported + 59 NUM_AT_SIGNED64: "@Signed64" + 60 NUM_SIGNED64: "Signed64" imported + 61 NUM_AT_SIGNED32: "@Signed32" + 62 NUM_SIGNED32: "Signed32" imported + 63 NUM_AT_SIGNED16: "@Signed16" + 64 NUM_SIGNED16: "Signed16" imported + 65 NUM_AT_SIGNED8: "@Signed8" + 66 NUM_SIGNED8: "Signed8" imported + 67 NUM_AT_UNSIGNED128: "@Unsigned128" + 68 NUM_UNSIGNED128: "Unsigned128" imported + 69 NUM_AT_UNSIGNED64: "@Unsigned64" + 70 NUM_UNSIGNED64: "Unsigned64" imported + 71 NUM_AT_UNSIGNED32: "@Unsigned32" + 72 NUM_UNSIGNED32: "Unsigned32" imported + 73 NUM_AT_UNSIGNED16: "@Unsigned16" + 74 NUM_UNSIGNED16: "Unsigned16" imported + 75 NUM_AT_UNSIGNED8: "@Unsigned8" + 76 NUM_UNSIGNED8: "Unsigned8" imported + 77 NUM_AT_BINARY64: "@Binary64" + 78 NUM_BINARY64: "Binary64" imported + 79 NUM_AT_BINARY32: "@Binary32" + 80 NUM_BINARY32: "Binary32" imported + 81 NUM_BITWISE_AND: "bitwiseAnd" + 82 NUM_BITWISE_XOR: "bitwiseXor" + 83 NUM_BITWISE_OR: "bitwiseOr" + 84 NUM_SHIFT_LEFT: "shiftLeftBy" + 85 NUM_SHIFT_RIGHT: "shiftRightBy" + 86 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy" + 87 NUM_SUB_WRAP: "subWrap" + 88 NUM_SUB_CHECKED: "subChecked" + 89 NUM_SUB_SATURATED: "subSaturated" + 90 NUM_MUL_WRAP: "mulWrap" + 91 NUM_MUL_CHECKED: "mulChecked" + 92 NUM_INT: "Int" imported + 93 NUM_FLOAT: "Float" imported + 94 NUM_AT_NATURAL: "@Natural" + 95 NUM_NATURAL: "Natural" imported + 96 NUM_NAT: "Nat" imported + 97 NUM_INT_CAST: "intCast" + 98 NUM_MAX_I128: "maxI128" + 99 NUM_IS_MULTIPLE_OF: "isMultipleOf" + 100 NUM_AT_DECIMAL: "@Decimal" + 101 NUM_DECIMAL: "Decimal" imported + 102 NUM_DEC: "Dec" imported // the Num.Dectype alias + 103 NUM_BYTES_TO_U16: "bytesToU16" + 104 NUM_BYTES_TO_U32: "bytesToU32" + 105 NUM_CAST_TO_NAT: "#castToNat" + 106 NUM_DIV_CEIL: "divCeil" + 107 NUM_TO_STR: "toStr" + 108 NUM_MIN_I128: "minI128" + 109 NUM_MIN_I32: "minI32" + 110 NUM_MAX_I32: "maxI32" + 111 NUM_MIN_U32: "minU32" + 112 NUM_MAX_U32: "maxU32" + 113 NUM_MIN_I64: "minI64" + 114 NUM_MAX_I64: "maxI64" + 115 NUM_MIN_U64: "minU64" + 116 NUM_MAX_U64: "maxU64" } 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias From 25db1189afab22399e5f01558030a293d37e1222 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 20 Jan 2022 10:23:13 -0500 Subject: [PATCH 269/541] Monomorphize expressions in refcount test --- compiler/test_gen/src/gen_refcount.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs index 8fe30ca01b..e16cad765b 100644 --- a/compiler/test_gen/src/gen_refcount.rs +++ b/compiler/test_gen/src/gen_refcount.rs @@ -136,6 +136,7 @@ fn struct_inc() { indoc!( r#" s = Str.concat "A long enough string " "to be heap-allocated" + r1 : { a: I64, b: Str, c: Str } r1 = { a: 123, b: s, c: s } { y: r1, z: r1 } "# @@ -152,6 +153,7 @@ fn struct_dealloc() { indoc!( r#" s = Str.concat "A long enough string " "to be heap-allocated" + r1 : { a: I64, b: Str, c: Str } r1 = { a: 123, b: s, c: s } r2 = { x: 456, y: r1, z: r1 } r2.x From 53be0e267430a78ba5a7d32cb7a175e90952ad92 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 20 Jan 2022 17:56:43 -0500 Subject: [PATCH 270/541] Reduce visibility of partial exprs --- compiler/mono/src/ir.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index e2784ef451..62d49cca05 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -231,26 +231,26 @@ enum PartialExprLink { /// Polymorphic *functions* are stored in `PartialProc`s, since functions are /// non longer first-class once we finish lowering to the IR. #[derive(Clone, Debug)] -pub struct PartialExprs(BumpMap); +struct PartialExprs(BumpMap); impl PartialExprs { - pub fn new_in(arena: &Bump) -> Self { + fn new_in(arena: &Bump) -> Self { Self(BumpMap::new_in(arena)) } - pub fn insert(&mut self, symbol: Symbol, expr: roc_can::expr::Expr, expr_var: Variable) { + fn insert(&mut self, symbol: Symbol, expr: roc_can::expr::Expr, expr_var: Variable) { self.0.insert(symbol, PartialExprLink::Expr(expr, expr_var)); } - pub fn insert_alias(&mut self, symbol: Symbol, aliases: Symbol) { + fn insert_alias(&mut self, symbol: Symbol, aliases: Symbol) { self.0.insert(symbol, PartialExprLink::Aliases(aliases)); } - pub fn contains(&self, symbol: Symbol) -> bool { + fn contains(&self, symbol: Symbol) -> bool { self.0.contains_key(&symbol) } - pub fn get(&mut self, mut symbol: Symbol) -> Option<(&roc_can::expr::Expr, Variable)> { + fn get(&mut self, mut symbol: Symbol) -> Option<(&roc_can::expr::Expr, Variable)> { // In practice the alias chain is very short loop { match self.0.get(&symbol) { @@ -267,7 +267,7 @@ impl PartialExprs { } } - pub fn remove(&mut self, symbol: Symbol) { + fn remove(&mut self, symbol: Symbol) { debug_assert!(self.contains(symbol)); self.0.remove(&symbol); } @@ -734,7 +734,7 @@ impl<'a> Specialized<'a> { #[derive(Clone, Debug)] pub struct Procs<'a> { pub partial_procs: PartialProcs<'a>, - pub partial_exprs: PartialExprs, + partial_exprs: PartialExprs, pub imported_module_thunks: &'a [Symbol], pub module_thunks: &'a [Symbol], pending_specializations: PendingSpecializations<'a>, From b281ea8c2ce147860bb38a0dbeec293decaa9a04 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 20 Jan 2022 18:09:04 -0500 Subject: [PATCH 271/541] Make `var_contains_content` a loop --- compiler/types/src/subs.rs | 122 +++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 65 deletions(-) diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 6f4a67cacb..08bd430f36 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -3483,77 +3483,69 @@ fn var_contains_content_help

( where P: Fn(&Content) -> bool + Copy, { - if seen_recursion_vars.contains(&var) { - return false; - } + let mut stack = vec![var]; - let content = subs.get_content_without_compacting(var); - - if predicate(content) { - return true; - } - - macro_rules! check_var { - ($v:expr) => { - var_contains_content_help(subs, $v, predicate, seen_recursion_vars) - }; - } - - macro_rules! check_var_slice { + macro_rules! push_var_slice { ($slice:expr) => { - subs.get_subs_slice($slice) - .into_iter() - .any(|v| check_var!(*v)) + stack.extend(subs.get_subs_slice($slice)) }; } - use Content::*; - use FlatType::*; - match content { - FlexVar(_) => false, - RigidVar(_) => false, - RecursionVar { - structure, - opt_name: _, - } => { - seen_recursion_vars.insert(var); - var_contains_content_help(subs, *structure, predicate, seen_recursion_vars) + while let Some(var) = stack.pop() { + if seen_recursion_vars.contains(&var) { + continue; } - Structure(flat_type) => match flat_type { - Apply(_, vars) => check_var_slice!(*vars), - Func(args, clos, ret) => { - check_var_slice!(*args) || check_var!(*clos) || check_var!(*ret) - } - Record(fields, var) => check_var_slice!(fields.variables()) || check_var!(*var), - TagUnion(tags, ext_var) => { - for i in tags.variables() { - if check_var_slice!(subs[i]) { - return true; - } - } - if check_var!(*ext_var) { - return true; - } - false - } - FunctionOrTagUnion(_, _, var) => check_var!(*var), - RecursiveTagUnion(rec_var, tags, ext_var) => { - seen_recursion_vars.insert(*rec_var); - for i in tags.variables() { - if check_var_slice!(subs[i]) { - return true; - } - } - if check_var!(*ext_var) { - return true; - } - false - } - Erroneous(_) | EmptyRecord | EmptyTagUnion => false, - }, - Alias(_, arguments, real_type_var) => { - check_var_slice!(arguments.variables()) || check_var!(*real_type_var) + + let content = subs.get_content_without_compacting(var); + + if predicate(content) { + return true; + } + + use Content::*; + use FlatType::*; + match content { + FlexVar(_) | RigidVar(_) => {} + RecursionVar { + structure, + opt_name: _, + } => { + seen_recursion_vars.insert(var); + stack.push(*structure); + } + Structure(flat_type) => match flat_type { + Apply(_, vars) => push_var_slice!(*vars), + Func(args, clos, ret) => { + push_var_slice!(*args); + stack.push(*clos); + stack.push(*ret); + } + Record(fields, var) => { + push_var_slice!(fields.variables()); + stack.push(*var); + } + TagUnion(tags, ext_var) => { + for i in tags.variables() { + push_var_slice!(subs[i]); + } + stack.push(*ext_var); + } + FunctionOrTagUnion(_, _, var) => stack.push(*var), + RecursiveTagUnion(rec_var, tags, ext_var) => { + seen_recursion_vars.insert(*rec_var); + for i in tags.variables() { + push_var_slice!(subs[i]); + } + stack.push(*ext_var); + } + Erroneous(_) | EmptyRecord | EmptyTagUnion => {} + }, + Alias(_, arguments, real_type_var) => { + push_var_slice!(arguments.variables()); + stack.push(*real_type_var); + } + Error => {} } - Error => false, } + false } From 06d506f63b4dba7c5aacba321d9e5c7f83e5defa Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 21 Jan 2022 20:01:45 +0100 Subject: [PATCH 272/541] use Task.loop in Deriv --- examples/benchmarks/Deriv.roc | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/examples/benchmarks/Deriv.roc b/examples/benchmarks/Deriv.roc index 5e5a81f8b2..2cad781765 100644 --- a/examples/benchmarks/Deriv.roc +++ b/examples/benchmarks/Deriv.roc @@ -21,6 +21,22 @@ main = |> Task.map (\_ -> {}) +nest : (I64, Expr -> IO Expr), I64, Expr -> IO Expr +nest = \f, n, e -> Task.loop { s: n, f, m: n, x: e } nestHelp + +State : { s : I64, f : I64, Expr -> IO Expr, m : I64, x : Expr } + +nestHelp : State -> IO [ Step State, Done Expr ] +nestHelp = \{ s, f, m, x } -> + when m is + 0 -> + Task.succeed (Done x) + + _ -> + w <- Task.after (f (s - m) x) + + Task.succeed (Step { s, f, m: (m - 1), x: w }) + Expr : [ Val I64, Var Str, Add Expr Expr, Mul Expr Expr, Pow Expr Expr, Ln Expr ] divmod : I64, I64 -> Result { div : I64, mod : I64 } [ DivByZero ]* @@ -180,18 +196,6 @@ count = \expr -> Ln f -> count f -nest : (I64, Expr -> IO Expr), I64, Expr -> IO Expr -nest = \f, n, e -> nestHelp n f n e - -nestHelp : I64, (I64, Expr -> IO Expr), I64, Expr -> IO Expr -nestHelp = \s, f, m, x -> - when m is - 0 -> - Task.succeed x - - _ -> - f (s - m) x |> Task.after \w -> nestHelp s f (m - 1) w - deriv : I64, Expr -> IO Expr deriv = \i, f -> fprime = d "x" f From 1328e4515dc487ce7dc27309e1f264b2039dea5a Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 21 Jan 2022 20:57:03 +0100 Subject: [PATCH 273/541] use Task.loop in False --- examples/false-interpreter/False.roc | 42 +++++++++++--------- examples/false-interpreter/platform/Task.roc | 20 +++++++++- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/examples/false-interpreter/False.roc b/examples/false-interpreter/False.roc index 57fc3c14a9..d7e0ac0c4f 100644 --- a/examples/false-interpreter/False.roc +++ b/examples/false-interpreter/False.roc @@ -89,6 +89,10 @@ isWhitespace = \char -> == 0x9# tab interpretCtx : Context -> Task Context InterpreterErrors interpretCtx = \ctx -> + Task.loop ctx interpretCtxLoop + +interpretCtxLoop : Context -> Task [ Step Context, Done Context ] InterpreterErrors +interpretCtxLoop = \ctx -> when ctx.state is Executing if Context.inWhileScope ctx -> # Deal with the current while loop potentially looping. @@ -104,11 +108,11 @@ interpretCtx = \ctx -> if n == 0 then newScope = { scope & whileInfo: None } - interpretCtx { popCtx & scopes: List.set ctx.scopes last newScope } + Task.succeed (Step { popCtx & scopes: List.set ctx.scopes last newScope }) else newScope = { scope & whileInfo: Some { state: InBody, body, cond } } - interpretCtx { popCtx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: body, index: 0, whileInfo: None } } + Task.succeed (Step { popCtx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: body, index: 0, whileInfo: None } }) Err e -> Task.fail e @@ -117,7 +121,7 @@ interpretCtx = \ctx -> # Just rand the body. Run the condition again. newScope = { scope & whileInfo: Some { state: InCond, body, cond } } - interpretCtx { ctx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: cond, index: 0, whileInfo: None } } + Task.succeed (Step { ctx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: cond, index: 0, whileInfo: None } }) None -> Task.fail NoScope @@ -131,7 +135,7 @@ interpretCtx = \ctx -> when result is Ok (T val newCtx) -> execCtx <- Task.await (stepExecCtx newCtx val) - interpretCtx execCtx + Task.succeed (Step execCtx) Err NoScope -> Task.fail NoScope @@ -143,9 +147,9 @@ interpretCtx = \ctx -> # If no scopes left, all execution complete. if List.isEmpty dropCtx.scopes then - Task.succeed dropCtx + Task.succeed (Done dropCtx) else - interpretCtx dropCtx + Task.succeed (Step dropCtx) InComment -> result <- Task.attempt (Context.getChar ctx) @@ -153,9 +157,9 @@ interpretCtx = \ctx -> Ok (T val newCtx) -> if val == 0x7D then # `}` end of comment - interpretCtx { newCtx & state: Executing } + Task.succeed (Step { newCtx & state: Executing }) else - interpretCtx { newCtx & state: InComment } + Task.succeed (Step { newCtx & state: InComment }) Err NoScope -> Task.fail NoScope @@ -174,13 +178,13 @@ interpretCtx = \ctx -> # so this is make i64 mul by 10 then convert back to i32. nextAccum = (10 * Num.intCast accum) + Num.intCast (val - 0x30) - interpretCtx { newCtx & state: InNumber (Num.intCast nextAccum) } + Task.succeed (Step { newCtx & state: InNumber (Num.intCast nextAccum) }) else # outside of number now, this needs to be executed. pushCtx = Context.pushStack newCtx (Number accum) execCtx <- Task.await (stepExecCtx { pushCtx & state: Executing } val) - interpretCtx execCtx + Task.succeed (Step execCtx) Err NoScope -> Task.fail NoScope @@ -197,12 +201,12 @@ interpretCtx = \ctx -> when Str.fromUtf8 bytes is Ok str -> { } <- Task.await (Stdout.raw str) - interpretCtx { newCtx & state: Executing } + Task.succeed (Step { newCtx & state: Executing }) Err _ -> Task.fail BadUtf8 else - interpretCtx { newCtx & state: InString (List.append bytes val) } + Task.succeed (Step { newCtx & state: InString (List.append bytes val) }) Err NoScope -> Task.fail NoScope @@ -216,17 +220,17 @@ interpretCtx = \ctx -> Ok (T val newCtx) -> if val == 0x5B then # start of a nested lambda `[` - interpretCtx { newCtx & state: InLambda (depth + 1) (List.append bytes val) } + Task.succeed (Step { newCtx & state: InLambda (depth + 1) (List.append bytes val) }) else if val == 0x5D then # `]` end of current lambda if depth == 0 then # end of all lambdas - interpretCtx (Context.pushStack { newCtx & state: Executing } (Lambda bytes)) + Task.succeed (Step (Context.pushStack { newCtx & state: Executing } (Lambda bytes))) else # end of nested lambda - interpretCtx { newCtx & state: InLambda (depth - 1) (List.append bytes val) } + Task.succeed (Step { newCtx & state: InLambda (depth - 1) (List.append bytes val) }) else - interpretCtx { newCtx & state: InLambda depth (List.append bytes val) } + Task.succeed (Step { newCtx & state: InLambda depth (List.append bytes val) }) Err NoScope -> Task.fail NoScope @@ -252,14 +256,14 @@ interpretCtx = \ctx -> when result2 is Ok a -> - interpretCtx a + Task.succeed (Step a) Err e -> Task.fail e Ok (T 0x9F newCtx) -> # This is supposed to flush io buffers. We don't buffer, so it does nothing - interpretCtx newCtx + Task.succeed (Step newCtx) Ok (T x _) -> data = Num.toStr (Num.intCast x) @@ -276,7 +280,7 @@ interpretCtx = \ctx -> result <- Task.attempt (Context.getChar { ctx & state: Executing }) when result is Ok (T x newCtx) -> - interpretCtx (Context.pushStack newCtx (Number (Num.intCast x))) + Task.succeed (Step (Context.pushStack newCtx (Number (Num.intCast x)))) Err NoScope -> Task.fail NoScope diff --git a/examples/false-interpreter/platform/Task.roc b/examples/false-interpreter/platform/Task.roc index 520eba4976..0ad7c223b2 100644 --- a/examples/false-interpreter/platform/Task.roc +++ b/examples/false-interpreter/platform/Task.roc @@ -1,9 +1,27 @@ interface Task - exposes [ Task, succeed, fail, await, map, onFail, attempt, fromResult ] + exposes [ Task, succeed, fail, await, map, onFail, attempt, fromResult, loop ] imports [ fx.Effect ] Task ok err : Effect.Effect (Result ok err) +loop : state, (state -> Task [ Step state, Done done ] err) -> Task done err +loop = \state, step -> + looper = \current -> + step current + |> Effect.map + \res -> + when res is + Ok (Step newState) -> + Step newState + + Ok (Done result) -> + Done (Ok result) + + Err e -> + Done (Err e) + + Effect.loop state looper + succeed : val -> Task val * succeed = \val -> Effect.always (Ok val) From 05c01a81f56dc76308fcde872fb7c75bd255ff20 Mon Sep 17 00:00:00 2001 From: Eric Newbury Date: Fri, 21 Jan 2022 15:34:24 -0500 Subject: [PATCH 274/541] adding List.sortAsc builtin --- compiler/builtins/src/std.rs | 7 +++++++ compiler/can/src/builtins.rs | 34 ++++++++++++++++++++++++++++++- compiler/module/src/symbol.rs | 1 + compiler/test_gen/src/gen_list.rs | 11 ++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 53bd661fe4..d28ab24cb0 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -1302,6 +1302,13 @@ pub fn types() -> MutMap { Box::new(list_type(flex(TVAR1))), ); + // sortAsc : List (Num a) -> List (Num a) + add_top_level_function_type!( + Symbol::LIST_SORT_ASC, + vec![list_type(num_type(flex(TVAR1)))], + Box::new(list_type(num_type(flex(TVAR1)))) + ); + // find : List elem, (elem -> Bool) -> Result elem [ NotFound ]* { let not_found = SolvedType::TagUnion( diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 0f47ed431a..9e2f8a13e7 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -51,7 +51,7 @@ macro_rules! macro_magic { /// even those that are relied on transitively! pub fn builtin_dependencies(symbol: Symbol) -> &'static [Symbol] { match symbol { - // Symbol::LIST_SORT_ASC => &[Symbol::LIST_SORT_WITH, Symbol::NUM_COMPARE], + Symbol::LIST_SORT_ASC => &[Symbol::LIST_SORT_WITH, Symbol::NUM_COMPARE], Symbol::LIST_PRODUCT => &[Symbol::LIST_WALK, Symbol::NUM_MUL], Symbol::LIST_SUM => &[Symbol::LIST_WALK, Symbol::NUM_ADD], Symbol::LIST_JOIN_MAP => &[Symbol::LIST_WALK, Symbol::LIST_CONCAT], @@ -141,6 +141,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option LIST_WALK_BACKWARDS => list_walk_backwards, LIST_WALK_UNTIL => list_walk_until, LIST_SORT_WITH => list_sort_with, + LIST_SORT_ASC => list_sort_asc, LIST_ANY => list_any, LIST_ALL => list_all, LIST_FIND => list_find, @@ -3418,6 +3419,37 @@ fn list_sort_with(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_2(symbol, LowLevel::ListSortWith, var_store) } +/// List.sortAsc : List (Num a) -> List (Num a) +fn list_sort_asc(symbol: Symbol, var_store: &mut VarStore) -> Def { + let list_var = var_store.fresh(); + let closure_var = var_store.fresh(); + let ret_var = list_var; + + let function = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(Symbol::LIST_SORT_WITH)), + var_store.fresh(), + ret_var, + ); + + let body = Expr::Call( + Box::new(function), + vec![ + (list_var, Loc::at_zero(Var(Symbol::ARG_1))), + (closure_var, Loc::at_zero(Var(Symbol::NUM_COMPARE))), + ], + CalledVia::Space, + ); + + defn( + symbol, + vec![(list_var, Symbol::ARG_1)], + var_store, + body, + ret_var, + ) +} + /// List.any: List elem, (elem -> Bool) -> Bool fn list_any(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_2(symbol, LowLevel::ListAny, var_store) diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 2110083ec4..2f436f5092 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1110,6 +1110,7 @@ define_builtins! { 52 LIST_ALL: "all" 53 LIST_DROP_IF: "dropIf" 54 LIST_DROP_IF_PREDICATE: "#dropIfPred" + 55 LIST_SORT_ASC: "sortAsc" } 5 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index b9db2a0c0b..25985526d2 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -2421,6 +2421,17 @@ fn list_sort_with() { ); } +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn list_sort_asc() { + assert_evals_to!("List.sortAsc []", RocList::from_slice(&[]), RocList); + assert_evals_to!( + "List.sortAsc [ 4,3,2,1 ]", + RocList::from_slice(&[1, 2, 3, 4]), + RocList + ); +} + #[test] #[cfg(any(feature = "gen-llvm"))] fn list_any() { From 7e7104d9ba9fdc38e596ace4b6c52a66bb855bb8 Mon Sep 17 00:00:00 2001 From: Eric Newbury Date: Fri, 21 Jan 2022 16:46:47 -0500 Subject: [PATCH 275/541] WIP --- compiler/builtins/src/std.rs | 7 ++++ compiler/can/src/builtins.rs | 60 +++++++++++++++++++++++++++++++++++ compiler/module/src/symbol.rs | 2 ++ 3 files changed, 69 insertions(+) diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 53bd661fe4..c59ddd0866 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -1302,6 +1302,13 @@ pub fn types() -> MutMap { Box::new(list_type(flex(TVAR1))), ); + // sortDesc : List (Num a) -> List (Num a) + add_top_level_function_type!( + Symbol::LIST_SORT_DESC, + vec![list_type(num_type(flex(TVAR1)))], + Box::new(list_type(num_type(flex(TVAR1)))) + ); + // find : List elem, (elem -> Bool) -> Result elem [ NotFound ]* { let not_found = SolvedType::TagUnion( diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 0f47ed431a..d61e629291 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -52,6 +52,11 @@ macro_rules! macro_magic { pub fn builtin_dependencies(symbol: Symbol) -> &'static [Symbol] { match symbol { // Symbol::LIST_SORT_ASC => &[Symbol::LIST_SORT_WITH, Symbol::NUM_COMPARE], + Symbol::LIST_SORT_DESC => &[ + Symbol::LIST_SORT_WITH, + Symbol::LIST_SORT_DESC_COMPARE, + Symbol::NUM_COMPARE, + ], Symbol::LIST_PRODUCT => &[Symbol::LIST_WALK, Symbol::NUM_MUL], Symbol::LIST_SUM => &[Symbol::LIST_WALK, Symbol::NUM_ADD], Symbol::LIST_JOIN_MAP => &[Symbol::LIST_WALK, Symbol::LIST_CONCAT], @@ -141,6 +146,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option LIST_WALK_BACKWARDS => list_walk_backwards, LIST_WALK_UNTIL => list_walk_until, LIST_SORT_WITH => list_sort_with, + LIST_SORT_DESC => list_sort_desc, LIST_ANY => list_any, LIST_ALL => list_all, LIST_FIND => list_find, @@ -3418,6 +3424,60 @@ fn list_sort_with(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_2(symbol, LowLevel::ListSortWith, var_store) } +/// List.sortDesc : List (Num a) -> List (Num a) +fn list_sort_desc(symbol: Symbol, var_store: &mut VarStore) -> Def { + let list_var = var_store.fresh(); + let num_var = var_store.fresh(); + let closure_var = var_store.fresh(); + let compare_ret_var = var_store.fresh(); + let ret_var = list_var; + + let closure = Closure(ClosureData { + function_type: closure_var, + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: compare_ret_var, + name: Symbol::LIST_SORT_DESC_COMPARE, + recursive: Recursive::NotRecursive, + captured_symbols: vec![(Symbol::ARG_2, num_var)], + arguments: vec![ + (num_var, no_region(Pattern::Identifier(Symbol::ARG_2))), + (num_var, no_region(Pattern::Identifier(Symbol::ARG_3))), + ], + loc_body: { + Box::new(no_region(RunLowLevel { + op: LowLevel::NumCompare, + args: vec![(num_var, Var(Symbol::ARG_3)), (num_var, Var(Symbol::ARG_2))], + ret_var: compare_ret_var, + })) + }, + }); + + let function = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(Symbol::LIST_SORT_WITH)), + var_store.fresh(), + ret_var, + ); + + let body = Expr::Call( + Box::new(function), + vec![ + (list_var, Loc::at_zero(Var(Symbol::ARG_1))), + (closure_var, Loc::at_zero(closure)), + ], + CalledVia::Space, + ); + + defn( + symbol, + vec![(list_var, Symbol::ARG_1)], + var_store, + body, + ret_var, + ) +} + /// List.any: List elem, (elem -> Bool) -> Bool fn list_any(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_2(symbol, LowLevel::ListAny, var_store) diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 2110083ec4..190bd12e64 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1110,6 +1110,8 @@ define_builtins! { 52 LIST_ALL: "all" 53 LIST_DROP_IF: "dropIf" 54 LIST_DROP_IF_PREDICATE: "#dropIfPred" + 55 LIST_SORT_DESC: "sortDesc" + 56 LIST_SORT_DESC_COMPARE: "#sortDescCompare" } 5 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias From 7919cd9feed35e76c84c96e88b1a7679dfc876c2 Mon Sep 17 00:00:00 2001 From: Eric Newbury Date: Fri, 21 Jan 2022 17:08:43 -0500 Subject: [PATCH 276/541] add test --- compiler/test_gen/src/gen_list.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 25985526d2..585013cc54 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -2432,6 +2432,17 @@ fn list_sort_asc() { ); } +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn list_sort_desc() { + assert_evals_to!("List.sortDesc []", RocList::from_slice(&[]), RocList); + assert_evals_to!( + "List.sortDesc [ 1,2,3,4 ]", + RocList::from_slice(&[4, 3, 2, 1]), + RocList + ); +} + #[test] #[cfg(any(feature = "gen-llvm"))] fn list_any() { From 2c139100ca2093e291d5872f96726d224990568c Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 21 Jan 2022 23:30:04 +0100 Subject: [PATCH 277/541] fix big error --- compiler/can/src/builtins.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 7bd1071d35..e538c816cd 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -52,11 +52,7 @@ macro_rules! macro_magic { pub fn builtin_dependencies(symbol: Symbol) -> &'static [Symbol] { match symbol { Symbol::LIST_SORT_ASC => &[Symbol::LIST_SORT_WITH, Symbol::NUM_COMPARE], - Symbol::LIST_SORT_DESC => &[ - Symbol::LIST_SORT_WITH, - Symbol::LIST_SORT_DESC_COMPARE, - Symbol::NUM_COMPARE, - ], + Symbol::LIST_SORT_DESC => &[Symbol::LIST_SORT_WITH], Symbol::LIST_PRODUCT => &[Symbol::LIST_WALK, Symbol::NUM_MUL], Symbol::LIST_SUM => &[Symbol::LIST_WALK, Symbol::NUM_ADD], Symbol::LIST_JOIN_MAP => &[Symbol::LIST_WALK, Symbol::LIST_CONCAT], @@ -3471,7 +3467,7 @@ fn list_sort_desc(symbol: Symbol, var_store: &mut VarStore) -> Def { return_type: compare_ret_var, name: Symbol::LIST_SORT_DESC_COMPARE, recursive: Recursive::NotRecursive, - captured_symbols: vec![(Symbol::ARG_2, num_var)], + captured_symbols: vec![], arguments: vec![ (num_var, no_region(Pattern::Identifier(Symbol::ARG_2))), (num_var, no_region(Pattern::Identifier(Symbol::ARG_3))), From 38f0a3717fa552aa82c3ed8a76e2bae02cf2c059 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Fri, 21 Jan 2022 23:21:38 -0700 Subject: [PATCH 278/541] Extract repeated min/max logic --- compiler/can/src/builtins.rs | 268 ++++++----------------------------- 1 file changed, 40 insertions(+), 228 deletions(-) diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index e538c816cd..b2e2c5164a 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1238,272 +1238,54 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def { /// Num.minI32: I32 fn num_min_i32(symbol: Symbol, var_store: &mut VarStore) -> Def { - let int_var = var_store.fresh(); - let int_precision_var = var_store.fresh(); - let body = int::(int_var, int_precision_var, i32::MIN); - - let std = roc_builtins::std::types(); - let solved = std.get(&symbol).unwrap(); - let mut free_vars = roc_types::solved_types::FreeVars::default(); - let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); - - let annotation = crate::def::Annotation { - signature, - introduced_variables: Default::default(), - region: Region::zero(), - aliases: Default::default(), - }; - - Def { - annotation: Some(annotation), - expr_var: int_var, - loc_expr: Loc::at_zero(body), - loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), - pattern_vars: SendMap::default(), - } + int_min_or_max::(symbol, var_store, i32::MIN); } /// Num.maxI32: I32 fn num_max_i32(symbol: Symbol, var_store: &mut VarStore) -> Def { - let int_var = var_store.fresh(); - let int_precision_var = var_store.fresh(); - let body = int::(int_var, int_precision_var, i32::MAX); - - let std = roc_builtins::std::types(); - let solved = std.get(&symbol).unwrap(); - let mut free_vars = roc_types::solved_types::FreeVars::default(); - let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); - - let annotation = crate::def::Annotation { - signature, - introduced_variables: Default::default(), - region: Region::zero(), - aliases: Default::default(), - }; - - Def { - annotation: Some(annotation), - expr_var: int_var, - loc_expr: Loc::at_zero(body), - loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), - pattern_vars: SendMap::default(), - } + int_min_or_max::(symbol, var_store, i32::MAX); } /// Num.minU32: U32 fn num_min_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { - let int_var = var_store.fresh(); - let int_precision_var = var_store.fresh(); - let body = int::(int_var, int_precision_var, u32::MIN); - - let std = roc_builtins::std::types(); - let solved = std.get(&symbol).unwrap(); - let mut free_vars = roc_types::solved_types::FreeVars::default(); - let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); - - let annotation = crate::def::Annotation { - signature, - introduced_variables: Default::default(), - region: Region::zero(), - aliases: Default::default(), - }; - - Def { - annotation: Some(annotation), - expr_var: int_var, - loc_expr: Loc::at_zero(body), - loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), - pattern_vars: SendMap::default(), - } + int_min_or_max::(symbol, var_store, u32::MIN); } /// Num.maxU32: U32 fn num_max_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { - let int_var = var_store.fresh(); - let int_precision_var = var_store.fresh(); - let body = int::(int_var, int_precision_var, u32::MAX); - - let std = roc_builtins::std::types(); - let solved = std.get(&symbol).unwrap(); - let mut free_vars = roc_types::solved_types::FreeVars::default(); - let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); - - let annotation = crate::def::Annotation { - signature, - introduced_variables: Default::default(), - region: Region::zero(), - aliases: Default::default(), - }; - - Def { - annotation: Some(annotation), - expr_var: int_var, - loc_expr: Loc::at_zero(body), - loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), - pattern_vars: SendMap::default(), - } + int_min_or_max::(symbol, var_store, u32::MAX); } /// Num.minI64: I64 fn num_min_i64(symbol: Symbol, var_store: &mut VarStore) -> Def { - let int_var = var_store.fresh(); - let int_precision_var = var_store.fresh(); - let body = int::(int_var, int_precision_var, i64::MIN); - - let std = roc_builtins::std::types(); - let solved = std.get(&symbol).unwrap(); - let mut free_vars = roc_types::solved_types::FreeVars::default(); - let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); - - let annotation = crate::def::Annotation { - signature, - introduced_variables: Default::default(), - region: Region::zero(), - aliases: Default::default(), - }; - - Def { - annotation: Some(annotation), - expr_var: int_var, - loc_expr: Loc::at_zero(body), - loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), - pattern_vars: SendMap::default(), - } + int_min_or_max::(symbol, var_store, i64::MIN); } /// Num.maxI64: I64 fn num_max_i64(symbol: Symbol, var_store: &mut VarStore) -> Def { - let int_var = var_store.fresh(); - let int_precision_var = var_store.fresh(); - let body = int::(int_var, int_precision_var, i64::MAX); - - let std = roc_builtins::std::types(); - let solved = std.get(&symbol).unwrap(); - let mut free_vars = roc_types::solved_types::FreeVars::default(); - let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); - - let annotation = crate::def::Annotation { - signature, - introduced_variables: Default::default(), - region: Region::zero(), - aliases: Default::default(), - }; - - Def { - annotation: Some(annotation), - expr_var: int_var, - loc_expr: Loc::at_zero(body), - loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), - pattern_vars: SendMap::default(), - } + int_min_or_max::(symbol, var_store, i64::MAX); } /// Num.minU64: U64 fn num_min_u64(symbol: Symbol, var_store: &mut VarStore) -> Def { - let int_var = var_store.fresh(); - let int_precision_var = var_store.fresh(); - let body = int::(int_var, int_precision_var, u64::MIN); - - let std = roc_builtins::std::types(); - let solved = std.get(&symbol).unwrap(); - let mut free_vars = roc_types::solved_types::FreeVars::default(); - let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); - - let annotation = crate::def::Annotation { - signature, - introduced_variables: Default::default(), - region: Region::zero(), - aliases: Default::default(), - }; - - Def { - annotation: Some(annotation), - expr_var: int_var, - loc_expr: Loc::at_zero(body), - loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), - pattern_vars: SendMap::default(), - } + int_min_or_max::(symbol, var_store, u64::MIN); } /// Num.maxU64: U64 fn num_max_u64(symbol: Symbol, var_store: &mut VarStore) -> Def { - let int_var = var_store.fresh(); - let int_precision_var = var_store.fresh(); - let body = int::(int_var, int_precision_var, u64::MAX); - - let std = roc_builtins::std::types(); - let solved = std.get(&symbol).unwrap(); - let mut free_vars = roc_types::solved_types::FreeVars::default(); - let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); - - let annotation = crate::def::Annotation { - signature, - introduced_variables: Default::default(), - region: Region::zero(), - aliases: Default::default(), - }; - - Def { - annotation: Some(annotation), - expr_var: int_var, - loc_expr: Loc::at_zero(body), - loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), - pattern_vars: SendMap::default(), - } + int_min_or_max::(symbol, var_store, iu64::MAX); } /// Num.minI128: I128 fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { - let int_var = var_store.fresh(); - let int_precision_var = var_store.fresh(); - let body = int::(int_var, int_precision_var, i128::MIN); - - let std = roc_builtins::std::types(); - let solved = std.get(&symbol).unwrap(); - let mut free_vars = roc_types::solved_types::FreeVars::default(); - let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); - - let annotation = crate::def::Annotation { - signature, - introduced_variables: Default::default(), - region: Region::zero(), - aliases: Default::default(), - }; - - Def { - annotation: Some(annotation), - expr_var: int_var, - loc_expr: Loc::at_zero(body), - loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), + int_min_or_max::(symbol, var_store, i128::MIN); pattern_vars: SendMap::default(), } } /// Num.maxI128: I128 fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { - let int_var = var_store.fresh(); - let int_precision_var = var_store.fresh(); - let body = int::(int_var, int_precision_var, i128::MAX); - - let std = roc_builtins::std::types(); - let solved = std.get(&symbol).unwrap(); - let mut free_vars = roc_types::solved_types::FreeVars::default(); - let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); - - let annotation = crate::def::Annotation { - signature, - introduced_variables: Default::default(), - region: Region::zero(), - aliases: Default::default(), - }; - - Def { - annotation: Some(annotation), - expr_var: int_var, - loc_expr: Loc::at_zero(body), - loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), - pattern_vars: SendMap::default(), - } + int_min_or_max::(symbol, var_store, i128::MIN); } /// List.isEmpty : List * -> Bool @@ -5126,6 +4908,36 @@ fn defn_help( }) } +#[inline(always)] +fn int_min_or_max(symbol: Symbol, var_store: &mut VarStore, i: I128) -> Def +where + I128: Into, +{ + let int_var = var_store.fresh(); + let int_precision_var = var_store.fresh(); + let body = int::(int_var, int_precision_var, i); + + let std = roc_builtins::std::types(); + let solved = std.get(&symbol).unwrap(); + let mut free_vars = roc_types::solved_types::FreeVars::default(); + let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); + + let annotation = crate::def::Annotation { + signature, + introduced_variables: Default::default(), + region: Region::zero(), + aliases: Default::default(), + }; + + Def { + annotation: Some(annotation), + expr_var: int_var, + loc_expr: Loc::at_zero(body), + loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), + pattern_vars: SendMap::default(), + } +} + #[inline(always)] fn int(num_var: Variable, precision_var: Variable, i: I128) -> Expr where From 9a8a4c6ed7faf42d2d8c7c8c5bbefb0775127a72 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Fri, 21 Jan 2022 23:23:40 -0700 Subject: [PATCH 279/541] Add `Num.(min/max)(I/U)(8/16)` builtins --- compiler/builtins/docs/Num.roc | 74 ++++++++++++++++++++ compiler/builtins/src/std.rs | 24 +++++++ compiler/can/src/builtins.rs | 70 +++++++++++++++---- compiler/module/src/symbol.rs | 8 +++ compiler/test_gen/src/gen_num.rs | 112 +++++++++++++++++++++++++++++++ 5 files changed, 276 insertions(+), 12 deletions(-) diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index 792d688030..70b7eab5ad 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -62,12 +62,20 @@ interface Num isZero, log, maxFloat, + maxI8, + maxU8, + maxI16, + maxU16, maxI32, maxU32, maxI64, maxU64, maxI128, minFloat, + minI8, + minU8, + minI16, + minU16, minI32, minU32, minI64, @@ -791,6 +799,72 @@ minNat : Nat ## 32-bit system, this will be equal to #Num.maxU32. maxNat : Nat +## The lowest number that can be stored in an #I8 without underflowing its +## available memory and crashing. +## +## For reference, this number is `-128`. +## +## Note that the positive version of this number is larger than #Int.maxI8, +## which means if you call #Num.abs on #Int.minI8, it will overflow and crash! +minI8 : I8 + +## The highest number that can be stored in an #I8 without overflowing its +## available memory and crashing. +## +## For reference, this number is `127`. +## +## Note that this is smaller than the positive version of #Int.minI8, +## which means if you call #Num.abs on #Int.minI8, it will overflow and crash! +maxI8 : I8 + +## The lowest number that can be stored in a #U8 without underflowing its +## available memory and crashing. +## +## For reference, this number is zero, because #U8 is +## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), +## and zero is the lowest unsigned number. +## Unsigned numbers cannot be negative. +minU8 : U8 + +## The highest number that can be stored in a #U8 without overflowing its +## available memory and crashing. +## +## For reference, this number is `255`. +maxU8 : U8 + +## The lowest number that can be stored in an #I16 without underflowing its +## available memory and crashing. +## +## For reference, this number is `-32_768`. +## +## Note that the positive version of this number is larger than #Int.maxI16, +## which means if you call #Num.abs on #Int.minI16, it will overflow and crash! +minI16 : I16 + +## The highest number that can be stored in an #I16 without overflowing its +## available memory and crashing. +## +## For reference, this number is `32_767`. +## +## Note that this is smaller than the positive version of #Int.minI16, +## which means if you call #Num.abs on #Int.minI16, it will overflow and crash! +maxI16 : I16 + +## The lowest number that can be stored in a #U16 without underflowing its +## available memory and crashing. +## +## For reference, this number is zero, because #U16 is +## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), +## and zero is the lowest unsigned number. +## Unsigned numbers cannot be negative. +minU16 : U16 + +## The highest number that can be stored in a #U16 without overflowing its +## available memory and crashing. +## +## For reference, this number is `65_535`. +maxU16 : U16 + ## The lowest number that can be stored in an #I32 without underflowing its ## available memory and crashing. ## diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index d16b31ee2c..15276e9e8b 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -391,6 +391,30 @@ pub fn types() -> MutMap { Box::new(bool_type()), ); + // minI8 : I8 + add_type!(Symbol::NUM_MIN_I8, i8_type()); + + // maxI8 : I8 + add_type!(Symbol::NUM_MAX_I8, i8_type()); + + // minU8 : U8 + add_type!(Symbol::NUM_MIN_U8, u8_type()); + + // maxU8 : U8 + add_type!(Symbol::NUM_MAX_U8, u8_type()); + + // minI16 : I16 + add_type!(Symbol::NUM_MIN_I16, i16_type()); + + // maxI16 : I16 + add_type!(Symbol::NUM_MAX_I16, i16_type()); + + // minU16 : U16 + add_type!(Symbol::NUM_MIN_U16, u16_type()); + + // maxU16 : U16 + add_type!(Symbol::NUM_MAX_U16, u16_type()); + // minI32 : I32 add_type!(Symbol::NUM_MIN_I32, i32_type()); diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index b2e2c5164a..cd90b4d698 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -223,6 +223,14 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_SHIFT_RIGHT => num_shift_right_by, NUM_SHIFT_RIGHT_ZERO_FILL => num_shift_right_zf_by, NUM_INT_CAST=> num_int_cast, + NUM_MIN_I8=> num_min_i8, + NUM_MAX_I8=> num_max_i8, + NUM_MIN_U8=> num_min_u8, + NUM_MAX_U8=> num_max_u8, + NUM_MIN_I16=> num_min_i16, + NUM_MAX_I16=> num_max_i16, + NUM_MIN_U16=> num_min_u16, + NUM_MAX_U16=> num_max_u16, NUM_MIN_I32=> num_min_i32, NUM_MAX_I32=> num_max_i32, NUM_MIN_U32=> num_min_u32, @@ -1236,56 +1244,94 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_1(symbol, LowLevel::NumIntCast, var_store) } +/// Num.minI8: I8 +fn num_min_i8(symbol: Symbol, var_store: &mut VarStore) -> Def { + int_min_or_max::(symbol, var_store, i8::MIN) +} + +/// Num.maxI8: I8 +fn num_max_i8(symbol: Symbol, var_store: &mut VarStore) -> Def { + int_min_or_max::(symbol, var_store, i8::MAX) +} + +/// Num.minU8: U8 +fn num_min_u8(symbol: Symbol, var_store: &mut VarStore) -> Def { + int_min_or_max::(symbol, var_store, u8::MIN) +} + +/// Num.maxU8: U8 +fn num_max_u8(symbol: Symbol, var_store: &mut VarStore) -> Def { + int_min_or_max::(symbol, var_store, u8::MAX) +} + +/// Num.minI16: I16 +fn num_min_i16(symbol: Symbol, var_store: &mut VarStore) -> Def { + int_min_or_max::(symbol, var_store, i16::MIN) +} + +/// Num.maxI16: I16 +fn num_max_i16(symbol: Symbol, var_store: &mut VarStore) -> Def { + int_min_or_max::(symbol, var_store, i16::MAX) +} + +/// Num.minU16: U16 +fn num_min_u16(symbol: Symbol, var_store: &mut VarStore) -> Def { + int_min_or_max::(symbol, var_store, u16::MIN) +} + +/// Num.maxU16: U16 +fn num_max_u16(symbol: Symbol, var_store: &mut VarStore) -> Def { + int_min_or_max::(symbol, var_store, u16::MAX) +} + /// Num.minI32: I32 fn num_min_i32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i32::MIN); + int_min_or_max::(symbol, var_store, i32::MIN) } /// Num.maxI32: I32 fn num_max_i32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i32::MAX); + int_min_or_max::(symbol, var_store, i32::MAX) } /// Num.minU32: U32 fn num_min_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u32::MIN); + int_min_or_max::(symbol, var_store, u32::MIN) } /// Num.maxU32: U32 fn num_max_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u32::MAX); + int_min_or_max::(symbol, var_store, u32::MAX) } /// Num.minI64: I64 fn num_min_i64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i64::MIN); + int_min_or_max::(symbol, var_store, i64::MIN) } /// Num.maxI64: I64 fn num_max_i64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i64::MAX); + int_min_or_max::(symbol, var_store, i64::MAX) } /// Num.minU64: U64 fn num_min_u64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u64::MIN); + int_min_or_max::(symbol, var_store, u64::MIN) } /// Num.maxU64: U64 fn num_max_u64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, iu64::MAX); + int_min_or_max::(symbol, var_store, u64::MAX) } /// Num.minI128: I128 fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i128::MIN); - pattern_vars: SendMap::default(), - } + int_min_or_max::(symbol, var_store, i128::MIN) } /// Num.maxI128: I128 fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i128::MIN); + int_min_or_max::(symbol, var_store, i128::MAX) } /// List.isEmpty : List * -> Bool diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index b17a5006cd..4ebf2b5df1 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1002,6 +1002,14 @@ define_builtins! { 114 NUM_MAX_I64: "maxI64" 115 NUM_MIN_U64: "minU64" 116 NUM_MAX_U64: "maxU64" + 117 NUM_MIN_I8: "minI8" + 118 NUM_MAX_I8: "maxI8" + 119 NUM_MIN_U8: "minU8" + 120 NUM_MAX_U8: "maxU8" + 121 NUM_MIN_I16: "minI16" + 122 NUM_MAX_I16: "maxI16" + 123 NUM_MIN_U16: "minU16" + 124 NUM_MAX_U16: "maxU16" } 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index 4e989632b5..fd7009328e 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -1941,6 +1941,118 @@ fn max_u32() { ); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_i16() { + assert_evals_to!( + indoc!( + r#" + Num.minI16 + "# + ), + i16::MIN, + i16 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn max_i16() { + assert_evals_to!( + indoc!( + r#" + Num.maxI16 + "# + ), + i16::MAX, + i16 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_u16() { + assert_evals_to!( + indoc!( + r#" + Num.minU16 + "# + ), + u16::MIN, + u16 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn max_u16() { + assert_evals_to!( + indoc!( + r#" + Num.maxU16 + "# + ), + u16::MAX, + u16 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_i8() { + assert_evals_to!( + indoc!( + r#" + Num.minI8 + "# + ), + i8::MIN, + i8 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn max_i8() { + assert_evals_to!( + indoc!( + r#" + Num.maxI8 + "# + ), + i8::MAX, + i8 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_u8() { + assert_evals_to!( + indoc!( + r#" + Num.minU8 + "# + ), + u8::MIN, + u8 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn max_u8() { + assert_evals_to!( + indoc!( + r#" + Num.maxU8 + "# + ), + u8::MAX, + u8 + ); +} + #[test] #[cfg(any(feature = "gen-llvm"))] fn is_multiple_of() { From 14ef9be3d27196d7f522ff5febfc5ec0c25b5c7b Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Fri, 21 Jan 2022 23:32:33 -0700 Subject: [PATCH 280/541] Sort `Num.(min/max)*` builtin symbol defs --- compiler/module/src/symbol.rs | 54 +++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 4ebf2b5df1..6d9c7461cd 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -983,33 +983,33 @@ define_builtins! { 95 NUM_NATURAL: "Natural" imported 96 NUM_NAT: "Nat" imported 97 NUM_INT_CAST: "intCast" - 98 NUM_MAX_I128: "maxI128" - 99 NUM_IS_MULTIPLE_OF: "isMultipleOf" - 100 NUM_AT_DECIMAL: "@Decimal" - 101 NUM_DECIMAL: "Decimal" imported - 102 NUM_DEC: "Dec" imported // the Num.Dectype alias - 103 NUM_BYTES_TO_U16: "bytesToU16" - 104 NUM_BYTES_TO_U32: "bytesToU32" - 105 NUM_CAST_TO_NAT: "#castToNat" - 106 NUM_DIV_CEIL: "divCeil" - 107 NUM_TO_STR: "toStr" - 108 NUM_MIN_I128: "minI128" - 109 NUM_MIN_I32: "minI32" - 110 NUM_MAX_I32: "maxI32" - 111 NUM_MIN_U32: "minU32" - 112 NUM_MAX_U32: "maxU32" - 113 NUM_MIN_I64: "minI64" - 114 NUM_MAX_I64: "maxI64" - 115 NUM_MIN_U64: "minU64" - 116 NUM_MAX_U64: "maxU64" - 117 NUM_MIN_I8: "minI8" - 118 NUM_MAX_I8: "maxI8" - 119 NUM_MIN_U8: "minU8" - 120 NUM_MAX_U8: "maxU8" - 121 NUM_MIN_I16: "minI16" - 122 NUM_MAX_I16: "maxI16" - 123 NUM_MIN_U16: "minU16" - 124 NUM_MAX_U16: "maxU16" + 98 NUM_IS_MULTIPLE_OF: "isMultipleOf" + 99 NUM_AT_DECIMAL: "@Decimal" + 100 NUM_DECIMAL: "Decimal" imported + 101 NUM_DEC: "Dec" imported // the Num.Dectype alias + 102 NUM_BYTES_TO_U16: "bytesToU16" + 103 NUM_BYTES_TO_U32: "bytesToU32" + 104 NUM_CAST_TO_NAT: "#castToNat" + 105 NUM_DIV_CEIL: "divCeil" + 106 NUM_TO_STR: "toStr" + 107 NUM_MIN_I8: "minI8" + 108 NUM_MAX_I8: "maxI8" + 109 NUM_MIN_U8: "minU8" + 110 NUM_MAX_U8: "maxU8" + 111 NUM_MIN_I16: "minI16" + 112 NUM_MAX_I16: "maxI16" + 113 NUM_MIN_U16: "minU16" + 114 NUM_MAX_U16: "maxU16" + 115 NUM_MIN_I32: "minI32" + 116 NUM_MAX_I32: "maxI32" + 117 NUM_MIN_U32: "minU32" + 118 NUM_MAX_U32: "maxU32" + 119 NUM_MIN_I64: "minI64" + 120 NUM_MAX_I64: "maxI64" + 121 NUM_MIN_U64: "minU64" + 122 NUM_MAX_U64: "maxU64" + 123 NUM_MIN_I128: "minI128" + 124 NUM_MAX_I128: "maxI128" } 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias From 48a3e871e8d75fce1a98967e65c2209cbf1978ac Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sat, 22 Jan 2022 14:26:32 -0500 Subject: [PATCH 281/541] Report self-recursive aliases at their declaration site, not in usages Closes #2380 --- compiler/can/src/def.rs | 67 +++----------------------- compiler/types/src/types.rs | 3 ++ reporting/src/error/type.rs | 8 ++-- reporting/tests/test_reporting.rs | 79 +++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 65 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 1ed85980e1..05584133e2 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -311,15 +311,19 @@ pub fn canonicalize_defs<'a>( vec![], &mut can_ann.typ, var_store, + // Don't report any errors yet. We'll take care of self and mutual + // recursion errors after the sorted introductions are complete. &mut false, ); } - scope.add_alias(symbol, ann.region, can_vars.clone(), can_ann.typ.clone()); + scope.add_alias(symbol, name.region, can_vars.clone(), can_ann.typ.clone()); let alias = scope.lookup_alias(symbol).expect("alias is added to scope"); aliases.insert(symbol, alias.clone()); } + // Now that we know the alias dependency graph, we can try to insert recursion variables + // where aliases are recursive tag unions, or detect illegal recursions. correct_mutual_recursive_type_alias(env, &mut aliases, var_store); // Now that we have the scope completely assembled, and shadowing resolved, @@ -962,66 +966,7 @@ fn canonicalize_pending_def<'a>( } } - Alias { - name, ann, vars, .. - } => { - let symbol = name.value; - let can_ann = canonicalize_annotation(env, scope, &ann.value, ann.region, var_store); - - // Record all the annotation's references in output.references.lookups - - for symbol in can_ann.references { - output.references.lookups.insert(symbol); - output.references.referenced_aliases.insert(symbol); - } - - let mut can_vars: Vec> = Vec::with_capacity(vars.len()); - - for loc_lowercase in vars { - if let Some(var) = can_ann - .introduced_variables - .var_by_name(&loc_lowercase.value) - { - // This is a valid lowercase rigid var for the alias. - can_vars.push(Loc { - value: (loc_lowercase.value.clone(), *var), - region: loc_lowercase.region, - }); - } else { - env.problems.push(Problem::PhantomTypeArgument { - alias: symbol, - variable_region: loc_lowercase.region, - variable_name: loc_lowercase.value.clone(), - }); - } - } - - scope.add_alias(symbol, name.region, can_vars.clone(), can_ann.typ.clone()); - - if can_ann.typ.contains_symbol(symbol) { - // the alias is recursive. If it's a tag union, we attempt to fix this - if let Type::TagUnion(tags, ext) = can_ann.typ { - // re-canonicalize the alias with the alias already in scope - let rec_var = var_store.fresh(); - let mut rec_type_union = Type::RecursiveTagUnion(rec_var, tags, ext); - rec_type_union.substitute_alias(symbol, &Type::Variable(rec_var)); - - scope.add_alias(symbol, name.region, can_vars, rec_type_union); - } else { - env.problems - .push(Problem::CyclicAlias(symbol, name.region, vec![])); - return output; - } - } - - let alias = scope.lookup_alias(symbol).expect("alias was not added"); - aliases.insert(symbol, alias.clone()); - - output - .introduced_variables - .union(&can_ann.introduced_variables); - } - + Alias { .. } => unreachable!("Aliases are handled in a separate pass"), InvalidAlias => { // invalid aliases (shadowed, incorrect patterns) get ignored } diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index c7c093dd18..9087ac80d6 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -886,6 +886,9 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet) { accum.insert(*symbol); args.iter().for_each(|arg| symbols_help(arg, accum)); } + Erroneous(Problem::CyclicAlias(alias, _, _)) => { + accum.insert(*alias); + } EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {} } } diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index ec477d78d7..3bcde68b07 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -100,10 +100,10 @@ pub fn type_problem<'b>( report(title, doc, filename) } - CyclicAlias(symbol, region, others) => { - let (doc, title) = cyclic_alias(alloc, lines, symbol, region, others); - - report(title, doc, filename) + CyclicAlias(..) => { + // We'll also report cyclic aliases as a canonicalization problem, no need to + // re-report them. + None } SolvedTypeError => None, // Don't re-report cascading errors - see https://github.com/rtfeldman/roc/pull/1711 diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 3a004094ec..d69da25ae1 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -7074,4 +7074,83 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn issue_2380_annotations_only() { + report_problem_as( + indoc!( + r#" + F : F + a : F + a + "# + ), + indoc!( + r#" + ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + + The `F` alias is self-recursive in an invalid way: + + 1│ F : F + ^ + + Recursion in aliases is only allowed if recursion happens behind a + tag. + "# + ), + ) + } + + #[test] + fn issue_2380_typed_body() { + report_problem_as( + indoc!( + r#" + F : F + a : F + a = 1 + a + "# + ), + indoc!( + r#" + ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + + The `F` alias is self-recursive in an invalid way: + + 1│ F : F + ^ + + Recursion in aliases is only allowed if recursion happens behind a + tag. + "# + ), + ) + } + + #[test] + fn issue_2380_alias_with_vars() { + report_problem_as( + indoc!( + r#" + F a b : F a b + a : F Str Str + a + "# + ), + indoc!( + r#" + ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + + The `F` alias is self-recursive in an invalid way: + + 1│ F a b : F a b + ^ + + Recursion in aliases is only allowed if recursion happens behind a + tag. + "# + ), + ) + } } From 390a50c53798b8a9a9b0aefe6ba9481ae9be39b4 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 23 Jan 2022 00:20:28 +0100 Subject: [PATCH 282/541] use mainForHost generic in effect platform --- examples/effect/thing/platform-dir/host.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/effect/thing/platform-dir/host.zig b/examples/effect/thing/platform-dir/host.zig index 35d30c99ab..94f6fbe87d 100644 --- a/examples/effect/thing/platform-dir/host.zig +++ b/examples/effect/thing/platform-dir/host.zig @@ -23,7 +23,7 @@ comptime { const mem = std.mem; const Allocator = mem.Allocator; -extern fn roc__mainForHost_1_exposed([*]u8) void; +extern fn roc__mainForHost_1_exposed_generic([*]u8) void; extern fn roc__mainForHost_size() i64; extern fn roc__mainForHost_1_Fx_caller(*const u8, [*]u8, [*]u8) void; extern fn roc__mainForHost_1_Fx_size() i64; @@ -82,7 +82,7 @@ pub export fn main() u8 { var ts1: std.os.timespec = undefined; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; - roc__mainForHost_1_exposed(output); + roc__mainForHost_1_exposed_generic(output); call_the_closure(output); From 7baec2b201defe3229fff10ceb9c41a33e33878f Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 23 Jan 2022 00:21:18 +0100 Subject: [PATCH 283/541] make tag allocas at the function entry point block --- compiler/gen_llvm/src/llvm/build.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 1c59ac6cc2..6f5e9cac8a 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -1474,10 +1474,17 @@ fn build_wrapped_tag<'a, 'ctx, 'env>( pub fn tag_alloca<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, - type_: BasicTypeEnum<'ctx>, + basic_type: BasicTypeEnum<'ctx>, name: &str, ) -> PointerValue<'ctx> { - let result_alloca = env.builder.build_alloca(type_, name); + // let result_alloca = env.builder.build_alloca(basic_type, name); + let parent = env + .builder + .get_insert_block() + .unwrap() + .get_parent() + .unwrap(); + let result_alloca = create_entry_block_alloca(env, parent, basic_type, name); // Initialize all memory of the alloca. This _should_ not be required, but currently // LLVM can access uninitialized memory after applying some optimizations. Hopefully @@ -1496,7 +1503,7 @@ pub fn tag_alloca<'a, 'ctx, 'env>( // After inlining, those checks are combined. That means that even if the tag is Err, // a check is done on the "string" to see if it is big or small, which will touch the // uninitialized memory. - let all_zeros = type_.const_zero(); + let all_zeros = basic_type.const_zero(); env.builder.build_store(result_alloca, all_zeros); result_alloca From 38b03282fda09039f378e829019c4cd81da10720 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 23 Jan 2022 00:40:14 +0100 Subject: [PATCH 284/541] working version of joinpoints with phi nodes --- compiler/gen_llvm/src/llvm/build.rs | 57 +++++++++++++++++------------ 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 6f5e9cac8a..9fc792c607 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -45,7 +45,7 @@ use inkwell::types::{ use inkwell::values::BasicValueEnum::{self, *}; use inkwell::values::{ BasicMetadataValueEnum, BasicValue, CallSiteValue, CallableValue, FloatValue, FunctionValue, - InstructionOpcode, InstructionValue, IntValue, PointerValue, StructValue, + InstructionOpcode, InstructionValue, IntValue, PhiValue, PointerValue, StructValue, }; use inkwell::OptimizationLevel; use inkwell::{AddressSpace, IntPredicate}; @@ -129,7 +129,7 @@ impl<'ctx> Iterator for FunctionIterator<'ctx> { pub struct Scope<'a, 'ctx> { symbols: ImMap, BasicValueEnum<'ctx>)>, pub top_level_thunks: ImMap, FunctionValue<'ctx>)>, - join_points: ImMap, &'a [PointerValue<'ctx>])>, + join_points: ImMap, &'a [PhiValue<'ctx>])>, } impl<'a, 'ctx> Scope<'a, 'ctx> { @@ -2655,21 +2655,30 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( let builder = env.builder; let context = env.context; - let mut joinpoint_args = Vec::with_capacity_in(parameters.len(), env.arena); - - for param in parameters.iter() { - let btype = basic_type_from_layout(env, ¶m.layout); - joinpoint_args.push(create_entry_block_alloca( - env, - parent, - btype, - "joinpointarg", - )); - } - // create new block let cont_block = context.append_basic_block(parent, "joinpointcont"); + let mut joinpoint_args = Vec::with_capacity_in(parameters.len(), env.arena); + { + let current = builder.get_insert_block().unwrap(); + builder.position_at_end(cont_block); + + for param in parameters.iter() { + let basic_type = basic_type_from_layout(env, ¶m.layout); + + let phi_type = if param.layout.is_passed_by_reference() { + basic_type.ptr_type(AddressSpace::Generic).into() + } else { + basic_type + }; + + let phi_node = env.builder.build_phi(phi_type, "joinpointarg"); + joinpoint_args.push(phi_node); + } + + builder.position_at_end(current); + } + // store this join point let joinpoint_args = joinpoint_args.into_bump_slice(); scope.join_points.insert(*id, (cont_block, joinpoint_args)); @@ -2689,12 +2698,9 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( // put the cont block at the back builder.position_at_end(cont_block); - for (ptr, param) in joinpoint_args.iter().zip(parameters.iter()) { - let value = if param.layout.is_passed_by_reference() { - (*ptr).into() - } else { - env.builder.build_load(*ptr, "load_jp_argument") - }; + // bind the values + for (phi_value, param) in joinpoint_args.iter().zip(parameters.iter()) { + let value = phi_value.as_basic_value(); scope.insert(param.symbol, (param.layout, value)); } @@ -2715,15 +2721,18 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( result } + Jump(join_point, arguments) => { let builder = env.builder; let context = env.context; - let (cont_block, argument_pointers) = scope.join_points.get(join_point).unwrap(); + let (cont_block, argument_phi_values) = scope.join_points.get(join_point).unwrap(); - for (pointer, argument) in argument_pointers.iter().zip(arguments.iter()) { - let (value, layout) = load_symbol_and_layout(scope, argument); + let current_block = builder.get_insert_block().unwrap(); - store_roc_value(env, *layout, *pointer, value); + for (phi_value, argument) in argument_phi_values.iter().zip(arguments.iter()) { + let (value, _) = load_symbol_and_layout(scope, argument); + + phi_value.add_incoming(&[(&value, current_block)]); } builder.build_unconditional_branch(*cont_block); From 638c56442cf3c2e975602020ee094ad32dc8245f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 22 Jan 2022 22:41:41 -0500 Subject: [PATCH 285/541] Delete commented-out line --- compiler/gen_llvm/src/llvm/build.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 9fc792c607..e30b8aa084 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -1477,7 +1477,6 @@ pub fn tag_alloca<'a, 'ctx, 'env>( basic_type: BasicTypeEnum<'ctx>, name: &str, ) -> PointerValue<'ctx> { - // let result_alloca = env.builder.build_alloca(basic_type, name); let parent = env .builder .get_insert_block() From fbf3ba77e955bcd98bc5e95185ca0381cd1bf7a7 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 23 Jan 2022 15:44:42 +0100 Subject: [PATCH 286/541] fix debug impl --- compiler/mono/src/layout.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index d347933464..f26b8cc3d4 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -524,14 +524,23 @@ impl std::fmt::Debug for SetElement<'_> { impl std::fmt::Debug for LambdaSet<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let entries = self.set.iter().map(|x| SetElement { - symbol: x.0, - layout: x.1, - }); - let set = f.debug_list().entries(entries).finish(); + struct Helper<'a> { + set: &'a [(Symbol, &'a [Layout<'a>])], + } + + impl std::fmt::Debug for Helper<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let entries = self.set.iter().map(|x| SetElement { + symbol: x.0, + layout: x.1, + }); + + f.debug_list().entries(entries).finish() + } + } f.debug_struct("LambdaSet") - .field("set", &set) + .field("set", &Helper { set: self.set }) .field("representation", &self.representation) .finish() } From 8698ea3c72f0a6b9501fe96881f16b481322bc5f Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 23 Jan 2022 15:46:53 +0100 Subject: [PATCH 287/541] update mono tests --- compiler/test_mono/generated/closure_in_list.txt | 7 ++++--- .../generated/empty_list_of_function_type.txt | 16 ++++++++-------- compiler/test_mono/generated/factorial.txt | 8 ++++---- compiler/test_mono/generated/ir_int_add.txt | 8 ++++---- compiler/test_mono/generated/ir_plus.txt | 4 ++-- compiler/test_mono/generated/ir_round.txt | 4 ++-- compiler/test_mono/generated/ir_two_defs.txt | 4 ++-- compiler/test_mono/generated/ir_when_idiv.txt | 4 ++-- compiler/test_mono/generated/ir_when_just.txt | 4 ++-- .../generated/linked_list_length_twice.txt | 6 +++--- .../generated/list_cannot_update_inplace.txt | 4 ++-- compiler/test_mono/generated/list_len.txt | 4 ++-- .../generated/monomorphized_ints_aliased.txt | 8 ++++---- compiler/test_mono/generated/nested_closure.txt | 5 +++-- .../test_mono/generated/nested_pattern_match.txt | 4 ++-- compiler/test_mono/generated/optional_when.txt | 8 ++++---- compiler/test_mono/generated/quicksort_help.txt | 12 ++++++------ ...rd_optional_field_function_no_use_default.txt | 4 ++-- ...ecord_optional_field_function_use_default.txt | 4 ++-- .../record_optional_field_let_no_use_default.txt | 4 ++-- .../record_optional_field_let_use_default.txt | 4 ++-- .../generated/somehow_drops_definitions.txt | 12 ++++++------ .../test_mono/generated/specialize_closures.txt | 12 ++++++------ .../test_mono/generated/specialize_lowlevel.txt | 12 ++++++------ .../test_mono/generated/when_nested_maybe.txt | 4 ++-- compiler/test_mono/generated/when_on_record.txt | 4 ++-- .../test_mono/generated/when_on_two_values.txt | 4 ++-- 27 files changed, 88 insertions(+), 86 deletions(-) diff --git a/compiler/test_mono/generated/closure_in_list.txt b/compiler/test_mono/generated/closure_in_list.txt index b4de3287e0..7ac145a0d4 100644 --- a/compiler/test_mono/generated/closure_in_list.txt +++ b/compiler/test_mono/generated/closure_in_list.txt @@ -3,8 +3,9 @@ procedure List.7 (#Attr.2): ret Test.7; procedure Test.1 (Test.5): - let Test.11 : LambdaSet([( Test.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; - let Test.10 : Builtin(List(LambdaSet([( Test.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }))) = Array [Test.11]; + let Test.2 : Builtin(Int(I64)) = 41i64; + let Test.11 : LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; + let Test.10 : Builtin(List(LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }))) = Array [Test.11]; ret Test.10; procedure Test.3 (Test.9, #Attr.12): @@ -14,7 +15,7 @@ procedure Test.3 (Test.9, #Attr.12): procedure Test.0 (): let Test.8 : Struct([]) = Struct {}; - let Test.4 : Builtin(List(LambdaSet([( Test.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }))) = CallByName Test.1 Test.8; + let Test.4 : Builtin(List(LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }))) = CallByName Test.1 Test.8; let Test.6 : Builtin(Int(U64)) = CallByName List.7 Test.4; dec Test.4; ret Test.6; diff --git a/compiler/test_mono/generated/empty_list_of_function_type.txt b/compiler/test_mono/generated/empty_list_of_function_type.txt index e24846c6b4..44c1c42994 100644 --- a/compiler/test_mono/generated/empty_list_of_function_type.txt +++ b/compiler/test_mono/generated/empty_list_of_function_type.txt @@ -2,12 +2,12 @@ procedure List.3 (#Attr.2, #Attr.3): let Test.20 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; let Test.17 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.20; if Test.17 then - let Test.19 : LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.18 : Union(NonRecursive([[Struct([])], [LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) })]])) = Ok Test.19; + let Test.19 : LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) }) = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.18 : Union(NonRecursive([[Struct([])], [LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) })]])) = Ok Test.19; ret Test.18; else let Test.16 : Struct([]) = Struct {}; - let Test.15 : Union(NonRecursive([[Struct([])], [LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) })]])) = Err Test.16; + let Test.15 : Union(NonRecursive([[Struct([])], [LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) })]])) = Err Test.16; ret Test.15; procedure Test.2 (Test.6): @@ -17,13 +17,13 @@ procedure Test.2 (Test.6): procedure Test.0 (): joinpoint Test.22 Test.3: let Test.14 : Builtin(Int(U64)) = 0i64; - let Test.7 : Union(NonRecursive([[Struct([])], [LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) })]])) = CallByName List.3 Test.3 Test.14; + let Test.7 : Union(NonRecursive([[Struct([])], [LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) })]])) = CallByName List.3 Test.3 Test.14; dec Test.3; let Test.11 : Builtin(Int(U8)) = 1i64; let Test.12 : Builtin(Int(U8)) = GetTagId Test.7; let Test.13 : Builtin(Bool) = lowlevel Eq Test.11 Test.12; if Test.13 then - let Test.5 : LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = UnionAtIndex (Id 1) (Index 0) Test.7; + let Test.5 : LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) }) = UnionAtIndex (Id 1) (Index 0) Test.7; let Test.9 : Builtin(Str) = "foo"; let Test.8 : Builtin(Str) = CallByName Test.2 Test.9; dec Test.9; @@ -34,9 +34,9 @@ procedure Test.0 (): in let Test.25 : Builtin(Bool) = false; if Test.25 then - let Test.1 : Builtin(List(LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }))) = Array []; + let Test.1 : Builtin(List(LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) }))) = Array []; jump Test.22 Test.1; else - let Test.23 : LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = Struct {}; - let Test.21 : Builtin(List(LambdaSet([( Test.2, [])]LambdaSet { set: Ok(()), representation: Struct([]) }))) = Array [Test.23]; + let Test.23 : LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) }) = Struct {}; + let Test.21 : Builtin(List(LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) }))) = Array [Test.23]; jump Test.22 Test.21; diff --git a/compiler/test_mono/generated/factorial.txt b/compiler/test_mono/generated/factorial.txt index 3d5b2a9508..d0c12615da 100644 --- a/compiler/test_mono/generated/factorial.txt +++ b/compiler/test_mono/generated/factorial.txt @@ -1,8 +1,8 @@ -procedure Num.25 (#Attr.2, #Attr.3): +procedure Num.23 (#Attr.2, #Attr.3): let Test.14 : Builtin(Int(I64)) = lowlevel NumSub #Attr.2 #Attr.3; ret Test.14; -procedure Num.26 (#Attr.2, #Attr.3): +procedure Num.24 (#Attr.2, #Attr.3): let Test.12 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; ret Test.12; @@ -14,8 +14,8 @@ procedure Test.1 (Test.17, Test.18): ret Test.3; else let Test.13 : Builtin(Int(I64)) = 1i64; - let Test.10 : Builtin(Int(I64)) = CallByName Num.25 Test.2 Test.13; - let Test.11 : Builtin(Int(I64)) = CallByName Num.26 Test.2 Test.3; + let Test.10 : Builtin(Int(I64)) = CallByName Num.23 Test.2 Test.13; + let Test.11 : Builtin(Int(I64)) = CallByName Num.24 Test.2 Test.3; jump Test.7 Test.10 Test.11; in jump Test.7 Test.17 Test.18; diff --git a/compiler/test_mono/generated/ir_int_add.txt b/compiler/test_mono/generated/ir_int_add.txt index 2d49bd57c1..e4ef7ac607 100644 --- a/compiler/test_mono/generated/ir_int_add.txt +++ b/compiler/test_mono/generated/ir_int_add.txt @@ -2,18 +2,18 @@ procedure List.7 (#Attr.2): let Test.7 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; ret Test.7; -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.5 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.5; procedure Test.0 (): let Test.10 : Builtin(Int(U64)) = 5i64; let Test.11 : Builtin(Int(U64)) = 4i64; - let Test.8 : Builtin(Int(U64)) = CallByName Num.24 Test.10 Test.11; + let Test.8 : Builtin(Int(U64)) = CallByName Num.22 Test.10 Test.11; let Test.9 : Builtin(Int(U64)) = 3i64; - let Test.3 : Builtin(Int(U64)) = CallByName Num.24 Test.8 Test.9; + let Test.3 : Builtin(Int(U64)) = CallByName Num.22 Test.8 Test.9; let Test.6 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64]; let Test.4 : Builtin(Int(U64)) = CallByName List.7 Test.6; dec Test.6; - let Test.2 : Builtin(Int(U64)) = CallByName Num.24 Test.3 Test.4; + let Test.2 : Builtin(Int(U64)) = CallByName Num.22 Test.3 Test.4; ret Test.2; diff --git a/compiler/test_mono/generated/ir_plus.txt b/compiler/test_mono/generated/ir_plus.txt index c4143d76e4..b0c0e1a060 100644 --- a/compiler/test_mono/generated/ir_plus.txt +++ b/compiler/test_mono/generated/ir_plus.txt @@ -1,9 +1,9 @@ -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.4 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.4; procedure Test.0 (): let Test.2 : Builtin(Int(I64)) = 1i64; let Test.3 : Builtin(Int(I64)) = 2i64; - let Test.1 : Builtin(Int(I64)) = CallByName Num.24 Test.2 Test.3; + let Test.1 : Builtin(Int(I64)) = CallByName Num.22 Test.2 Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/ir_round.txt b/compiler/test_mono/generated/ir_round.txt index 4fa406093e..7f16bbcd71 100644 --- a/compiler/test_mono/generated/ir_round.txt +++ b/compiler/test_mono/generated/ir_round.txt @@ -1,8 +1,8 @@ -procedure Num.47 (#Attr.2): +procedure Num.45 (#Attr.2): let Test.3 : Builtin(Int(I64)) = lowlevel NumRound #Attr.2; ret Test.3; procedure Test.0 (): let Test.2 : Builtin(Float(F64)) = 3.6f64; - let Test.1 : Builtin(Int(I64)) = CallByName Num.47 Test.2; + let Test.1 : Builtin(Int(I64)) = CallByName Num.45 Test.2; ret Test.1; diff --git a/compiler/test_mono/generated/ir_two_defs.txt b/compiler/test_mono/generated/ir_two_defs.txt index 66b9bd2728..b39fc79319 100644 --- a/compiler/test_mono/generated/ir_two_defs.txt +++ b/compiler/test_mono/generated/ir_two_defs.txt @@ -1,9 +1,9 @@ -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.6 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.6; procedure Test.0 (): let Test.4 : Builtin(Int(I64)) = 3i64; let Test.5 : Builtin(Int(I64)) = 4i64; - let Test.3 : Builtin(Int(I64)) = CallByName Num.24 Test.4 Test.5; + let Test.3 : Builtin(Int(I64)) = CallByName Num.22 Test.4 Test.5; ret Test.3; diff --git a/compiler/test_mono/generated/ir_when_idiv.txt b/compiler/test_mono/generated/ir_when_idiv.txt index 12893c9523..6b60e0547c 100644 --- a/compiler/test_mono/generated/ir_when_idiv.txt +++ b/compiler/test_mono/generated/ir_when_idiv.txt @@ -1,4 +1,4 @@ -procedure Num.42 (#Attr.2, #Attr.3): +procedure Num.40 (#Attr.2, #Attr.3): let Test.15 : Builtin(Int(I64)) = 0i64; let Test.12 : Builtin(Bool) = lowlevel NotEq #Attr.3 Test.15; if Test.12 then @@ -13,7 +13,7 @@ procedure Num.42 (#Attr.2, #Attr.3): procedure Test.0 (): let Test.8 : Builtin(Int(I64)) = 1000i64; let Test.9 : Builtin(Int(I64)) = 10i64; - let Test.2 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName Num.42 Test.8 Test.9; + let Test.2 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName Num.40 Test.8 Test.9; let Test.5 : Builtin(Int(U8)) = 1i64; let Test.6 : Builtin(Int(U8)) = GetTagId Test.2; let Test.7 : Builtin(Bool) = lowlevel Eq Test.5 Test.6; diff --git a/compiler/test_mono/generated/ir_when_just.txt b/compiler/test_mono/generated/ir_when_just.txt index 5019316f49..a2ab04d249 100644 --- a/compiler/test_mono/generated/ir_when_just.txt +++ b/compiler/test_mono/generated/ir_when_just.txt @@ -1,4 +1,4 @@ -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.6 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.6; @@ -11,7 +11,7 @@ procedure Test.0 (): if Test.10 then let Test.3 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.1; let Test.5 : Builtin(Int(I64)) = 1i64; - let Test.4 : Builtin(Int(I64)) = CallByName Num.24 Test.3 Test.5; + let Test.4 : Builtin(Int(I64)) = CallByName Num.22 Test.3 Test.5; ret Test.4; else let Test.7 : Builtin(Int(I64)) = 1i64; diff --git a/compiler/test_mono/generated/linked_list_length_twice.txt b/compiler/test_mono/generated/linked_list_length_twice.txt index 793926eec5..c791ad1cd4 100644 --- a/compiler/test_mono/generated/linked_list_length_twice.txt +++ b/compiler/test_mono/generated/linked_list_length_twice.txt @@ -1,4 +1,4 @@ -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.10 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.10; @@ -13,7 +13,7 @@ procedure Test.3 (Test.5): let Test.6 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Builtin(Int(I64)), RecursivePointer] }) = UnionAtIndex (Id 0) (Index 1) Test.5; let Test.14 : Builtin(Int(I64)) = 1i64; let Test.15 : Builtin(Int(I64)) = CallByName Test.3 Test.6; - let Test.13 : Builtin(Int(I64)) = CallByName Num.24 Test.14 Test.15; + let Test.13 : Builtin(Int(I64)) = CallByName Num.22 Test.14 Test.15; ret Test.13; procedure Test.0 (): @@ -21,5 +21,5 @@ procedure Test.0 (): let Test.8 : Builtin(Int(I64)) = CallByName Test.3 Test.2; let Test.9 : Builtin(Int(I64)) = CallByName Test.3 Test.2; dec Test.2; - let Test.7 : Builtin(Int(I64)) = CallByName Num.24 Test.8 Test.9; + let Test.7 : Builtin(Int(I64)) = CallByName Num.22 Test.8 Test.9; ret Test.7; diff --git a/compiler/test_mono/generated/list_cannot_update_inplace.txt b/compiler/test_mono/generated/list_cannot_update_inplace.txt index 113f7b74cb..c2087e2d74 100644 --- a/compiler/test_mono/generated/list_cannot_update_inplace.txt +++ b/compiler/test_mono/generated/list_cannot_update_inplace.txt @@ -11,7 +11,7 @@ procedure List.7 (#Attr.2): let Test.9 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; ret Test.9; -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.7 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.7; @@ -33,5 +33,5 @@ procedure Test.0 (): let Test.8 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1; let Test.6 : Builtin(Int(U64)) = CallByName List.7 Test.8; dec Test.8; - let Test.4 : Builtin(Int(U64)) = CallByName Num.24 Test.5 Test.6; + let Test.4 : Builtin(Int(U64)) = CallByName Num.22 Test.5 Test.6; ret Test.4; diff --git a/compiler/test_mono/generated/list_len.txt b/compiler/test_mono/generated/list_len.txt index c210581d86..80b6093747 100644 --- a/compiler/test_mono/generated/list_len.txt +++ b/compiler/test_mono/generated/list_len.txt @@ -6,7 +6,7 @@ procedure List.7 (#Attr.2): let Test.8 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; ret Test.8; -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.6 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.6; @@ -17,5 +17,5 @@ procedure Test.0 (): let Test.7 : Builtin(List(Builtin(Float(F64)))) = Array [1f64]; let Test.5 : Builtin(Int(U64)) = CallByName List.7 Test.7; dec Test.7; - let Test.3 : Builtin(Int(U64)) = CallByName Num.24 Test.4 Test.5; + let Test.3 : Builtin(Int(U64)) = CallByName Num.22 Test.4 Test.5; ret Test.3; diff --git a/compiler/test_mono/generated/monomorphized_ints_aliased.txt b/compiler/test_mono/generated/monomorphized_ints_aliased.txt index 3b9735dfa5..50bd650f9d 100644 --- a/compiler/test_mono/generated/monomorphized_ints_aliased.txt +++ b/compiler/test_mono/generated/monomorphized_ints_aliased.txt @@ -1,4 +1,4 @@ -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.12 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.12; @@ -11,13 +11,13 @@ procedure Test.4 (Test.7, Test.8): ret Test.18; procedure Test.0 (): - let Test.4 : LambdaSet([( Test.4, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = Struct {}; - let Test.4 : LambdaSet([( Test.4, [])]LambdaSet { set: Ok(()), representation: Struct([]) }) = Struct {}; + let Test.4 : LambdaSet(LambdaSet { set: [( Test.4, [])], representation: Struct([]) }) = Struct {}; + let Test.4 : LambdaSet(LambdaSet { set: [( Test.4, [])], representation: Struct([]) }) = Struct {}; let Test.15 : Builtin(Int(U8)) = 100i64; let Test.16 : Builtin(Int(U32)) = 100i64; let Test.10 : Builtin(Int(U64)) = CallByName Test.4 Test.15 Test.16; let Test.13 : Builtin(Int(U32)) = 100i64; let Test.14 : Builtin(Int(U8)) = 100i64; let Test.11 : Builtin(Int(U64)) = CallByName Test.4 Test.13 Test.14; - let Test.9 : Builtin(Int(U64)) = CallByName Num.24 Test.10 Test.11; + let Test.9 : Builtin(Int(U64)) = CallByName Num.22 Test.10 Test.11; ret Test.9; diff --git a/compiler/test_mono/generated/nested_closure.txt b/compiler/test_mono/generated/nested_closure.txt index b7a572df71..af44f33389 100644 --- a/compiler/test_mono/generated/nested_closure.txt +++ b/compiler/test_mono/generated/nested_closure.txt @@ -1,5 +1,6 @@ procedure Test.1 (Test.5): - let Test.3 : LambdaSet([( Test.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; + let Test.2 : Builtin(Int(I64)) = 42i64; + let Test.3 : LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; ret Test.3; procedure Test.3 (Test.9, #Attr.12): @@ -9,7 +10,7 @@ procedure Test.3 (Test.9, #Attr.12): procedure Test.0 (): let Test.8 : Struct([]) = Struct {}; - let Test.4 : LambdaSet([( Test.3, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Struct([Builtin(Int(I64))]) }) = CallByName Test.1 Test.8; + let Test.4 : LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = CallByName Test.1 Test.8; let Test.7 : Struct([]) = Struct {}; let Test.6 : Builtin(Int(I64)) = CallByName Test.3 Test.7 Test.4; ret Test.6; diff --git a/compiler/test_mono/generated/nested_pattern_match.txt b/compiler/test_mono/generated/nested_pattern_match.txt index 157843e919..facbc46055 100644 --- a/compiler/test_mono/generated/nested_pattern_match.txt +++ b/compiler/test_mono/generated/nested_pattern_match.txt @@ -1,4 +1,4 @@ -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; @@ -22,7 +22,7 @@ procedure Test.0 (): let Test.10 : Union(NonRecursive([[Builtin(Int(I64))], []])) = UnionAtIndex (Id 0) (Index 0) Test.2; let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.10; let Test.7 : Builtin(Int(I64)) = 1i64; - let Test.6 : Builtin(Int(I64)) = CallByName Num.24 Test.5 Test.7; + let Test.6 : Builtin(Int(I64)) = CallByName Num.22 Test.5 Test.7; ret Test.6; else jump Test.16; diff --git a/compiler/test_mono/generated/optional_when.txt b/compiler/test_mono/generated/optional_when.txt index bca94afc2d..9c98133311 100644 --- a/compiler/test_mono/generated/optional_when.txt +++ b/compiler/test_mono/generated/optional_when.txt @@ -1,4 +1,4 @@ -procedure Num.26 (#Attr.2, #Attr.3): +procedure Num.24 (#Attr.2, #Attr.3): let Test.17 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; ret Test.17; @@ -36,7 +36,7 @@ procedure Test.0 (): let Test.26 : Builtin(Bool) = false; let Test.19 : Struct([Builtin(Int(I64)), Builtin(Bool)]) = Struct {Test.25, Test.26}; let Test.2 : Builtin(Int(I64)) = CallByName Test.1 Test.19; - let Test.18 : Builtin(Int(I64)) = CallByName Num.26 Test.2 Test.3; - let Test.16 : Builtin(Int(I64)) = CallByName Num.26 Test.18 Test.4; - let Test.15 : Builtin(Int(I64)) = CallByName Num.26 Test.16 Test.5; + let Test.18 : Builtin(Int(I64)) = CallByName Num.24 Test.2 Test.3; + let Test.16 : Builtin(Int(I64)) = CallByName Num.24 Test.18 Test.4; + let Test.15 : Builtin(Int(I64)) = CallByName Num.24 Test.16 Test.5; ret Test.15; diff --git a/compiler/test_mono/generated/quicksort_help.txt b/compiler/test_mono/generated/quicksort_help.txt index 062a9e39ce..911b38e71c 100644 --- a/compiler/test_mono/generated/quicksort_help.txt +++ b/compiler/test_mono/generated/quicksort_help.txt @@ -1,18 +1,18 @@ -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.19 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.19; -procedure Num.25 (#Attr.2, #Attr.3): +procedure Num.23 (#Attr.2, #Attr.3): let Test.22 : Builtin(Int(I64)) = lowlevel NumSub #Attr.2 #Attr.3; ret Test.22; -procedure Num.27 (#Attr.2, #Attr.3): +procedure Num.25 (#Attr.2, #Attr.3): let Test.26 : Builtin(Bool) = lowlevel NumLt #Attr.2 #Attr.3; ret Test.26; procedure Test.1 (Test.27, Test.28, Test.29): joinpoint Test.12 Test.2 Test.3 Test.4: - let Test.14 : Builtin(Bool) = CallByName Num.27 Test.3 Test.4; + let Test.14 : Builtin(Bool) = CallByName Num.25 Test.3 Test.4; if Test.14 then dec Test.2; let Test.25 : Builtin(List(Union(NonRecursive([])))) = Array []; @@ -23,10 +23,10 @@ procedure Test.1 (Test.27, Test.28, Test.29): inc Test.6; dec Test.23; let Test.21 : Builtin(Int(I64)) = 1i64; - let Test.20 : Builtin(Int(I64)) = CallByName Num.25 Test.5 Test.21; + let Test.20 : Builtin(Int(I64)) = CallByName Num.23 Test.5 Test.21; let Test.16 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.6 Test.3 Test.20; let Test.18 : Builtin(Int(I64)) = 1i64; - let Test.17 : Builtin(Int(I64)) = CallByName Num.24 Test.5 Test.18; + let Test.17 : Builtin(Int(I64)) = CallByName Num.22 Test.5 Test.18; jump Test.12 Test.16 Test.17 Test.4; else ret Test.2; diff --git a/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt b/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt index e648f4a266..3c98d6876b 100644 --- a/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt @@ -1,11 +1,11 @@ -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.1 (Test.4): let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 Test.4; let Test.3 : Builtin(Int(I64)) = StructAtIndex 1 Test.4; - let Test.7 : Builtin(Int(I64)) = CallByName Num.24 Test.2 Test.3; + let Test.7 : Builtin(Int(I64)) = CallByName Num.22 Test.2 Test.3; ret Test.7; procedure Test.0 (): diff --git a/compiler/test_mono/generated/record_optional_field_function_use_default.txt b/compiler/test_mono/generated/record_optional_field_function_use_default.txt index 8b3ec11a1d..aee1aa3793 100644 --- a/compiler/test_mono/generated/record_optional_field_function_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_function_use_default.txt @@ -1,10 +1,10 @@ -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.9 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.9; procedure Test.1 (Test.4): let Test.8 : Builtin(Int(I64)) = 10i64; - let Test.7 : Builtin(Int(I64)) = CallByName Num.24 Test.8 Test.4; + let Test.7 : Builtin(Int(I64)) = CallByName Num.22 Test.8 Test.4; ret Test.7; procedure Test.0 (): diff --git a/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt b/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt index cb651b5ae9..255130f13b 100644 --- a/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt @@ -1,11 +1,11 @@ -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.1 (Test.2): let Test.3 : Builtin(Int(I64)) = StructAtIndex 0 Test.2; let Test.4 : Builtin(Int(I64)) = StructAtIndex 1 Test.2; - let Test.7 : Builtin(Int(I64)) = CallByName Num.24 Test.3 Test.4; + let Test.7 : Builtin(Int(I64)) = CallByName Num.22 Test.3 Test.4; ret Test.7; procedure Test.0 (): diff --git a/compiler/test_mono/generated/record_optional_field_let_use_default.txt b/compiler/test_mono/generated/record_optional_field_let_use_default.txt index 1e42d1eb24..978c5b026d 100644 --- a/compiler/test_mono/generated/record_optional_field_let_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_let_use_default.txt @@ -1,10 +1,10 @@ -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.1 (Test.2): let Test.3 : Builtin(Int(I64)) = 10i64; - let Test.7 : Builtin(Int(I64)) = CallByName Num.24 Test.3 Test.2; + let Test.7 : Builtin(Int(I64)) = CallByName Num.22 Test.3 Test.2; ret Test.7; procedure Test.0 (): diff --git a/compiler/test_mono/generated/somehow_drops_definitions.txt b/compiler/test_mono/generated/somehow_drops_definitions.txt index 0c92ef232e..1dd4ed9c40 100644 --- a/compiler/test_mono/generated/somehow_drops_definitions.txt +++ b/compiler/test_mono/generated/somehow_drops_definitions.txt @@ -1,8 +1,8 @@ -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.27 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.27; -procedure Num.26 (#Attr.2, #Attr.3): +procedure Num.24 (#Attr.2, #Attr.3): let Test.22 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; ret Test.22; @@ -16,12 +16,12 @@ procedure Test.2 (): procedure Test.3 (Test.6): let Test.26 : Builtin(Int(I64)) = CallByName Test.1; - let Test.25 : Builtin(Int(I64)) = CallByName Num.24 Test.6 Test.26; + let Test.25 : Builtin(Int(I64)) = CallByName Num.22 Test.6 Test.26; ret Test.25; procedure Test.4 (Test.7): let Test.21 : Builtin(Int(I64)) = CallByName Test.2; - let Test.20 : Builtin(Int(I64)) = CallByName Num.26 Test.7 Test.21; + let Test.20 : Builtin(Int(I64)) = CallByName Num.24 Test.7 Test.21; ret Test.20; procedure Test.5 (Test.8, Test.9): @@ -46,8 +46,8 @@ procedure Test.0 (): in let Test.24 : Builtin(Bool) = true; if Test.24 then - let Test.3 : LambdaSet([( Test.3, []), ( Test.4, [])]LambdaSet { set: Ok(()), representation: Builtin(Bool) }) = false; + let Test.3 : LambdaSet(LambdaSet { set: [( Test.3, []), ( Test.4, [])], representation: Builtin(Bool) }) = false; jump Test.19 Test.3; else - let Test.4 : LambdaSet([( Test.3, []), ( Test.4, [])]LambdaSet { set: Ok(()), representation: Builtin(Bool) }) = true; + let Test.4 : LambdaSet(LambdaSet { set: [( Test.3, []), ( Test.4, [])], representation: Builtin(Bool) }) = true; jump Test.19 Test.4; diff --git a/compiler/test_mono/generated/specialize_closures.txt b/compiler/test_mono/generated/specialize_closures.txt index 73d49133ce..772676526b 100644 --- a/compiler/test_mono/generated/specialize_closures.txt +++ b/compiler/test_mono/generated/specialize_closures.txt @@ -1,8 +1,8 @@ -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.28 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.28; -procedure Num.26 (#Attr.2, #Attr.3): +procedure Num.24 (#Attr.2, #Attr.3): let Test.25 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; ret Test.25; @@ -23,14 +23,14 @@ procedure Test.1 (Test.2, Test.3): procedure Test.7 (Test.10, #Attr.12): let Test.4 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) #Attr.12; - let Test.27 : Builtin(Int(I64)) = CallByName Num.24 Test.10 Test.4; + let Test.27 : Builtin(Int(I64)) = CallByName Num.22 Test.10 Test.4; ret Test.27; procedure Test.8 (Test.11, #Attr.12): let Test.6 : Builtin(Bool) = UnionAtIndex (Id 1) (Index 1) #Attr.12; let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) #Attr.12; if Test.6 then - let Test.24 : Builtin(Int(I64)) = CallByName Num.26 Test.11 Test.5; + let Test.24 : Builtin(Int(I64)) = CallByName Num.24 Test.11 Test.5; ret Test.24; else ret Test.11; @@ -46,8 +46,8 @@ procedure Test.0 (): in let Test.26 : Builtin(Bool) = true; if Test.26 then - let Test.7 : LambdaSet([( Test.7, [Builtin(Int(I64))]), ( Test.8, [Builtin(Int(I64)), Builtin(Bool)])]LambdaSet { set: Ok(()), representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Bool)]])) }) = ClosureTag(Test.7) Test.4; + let Test.7 : LambdaSet(LambdaSet { set: [( Test.7, [Builtin(Int(I64))]), ( Test.8, [Builtin(Int(I64)), Builtin(Bool)])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Bool)]])) }) = ClosureTag(Test.7) Test.4; jump Test.22 Test.7; else - let Test.8 : LambdaSet([( Test.7, [Builtin(Int(I64))]), ( Test.8, [Builtin(Int(I64)), Builtin(Bool)])]LambdaSet { set: Ok(()), representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Bool)]])) }) = ClosureTag(Test.8) Test.5 Test.6; + let Test.8 : LambdaSet(LambdaSet { set: [( Test.7, [Builtin(Int(I64))]), ( Test.8, [Builtin(Int(I64)), Builtin(Bool)])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Bool)]])) }) = ClosureTag(Test.8) Test.5 Test.6; jump Test.22 Test.8; diff --git a/compiler/test_mono/generated/specialize_lowlevel.txt b/compiler/test_mono/generated/specialize_lowlevel.txt index 3271b1fd41..4dba44f752 100644 --- a/compiler/test_mono/generated/specialize_lowlevel.txt +++ b/compiler/test_mono/generated/specialize_lowlevel.txt @@ -1,19 +1,19 @@ -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.24 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.24; -procedure Num.26 (#Attr.2, #Attr.3): +procedure Num.24 (#Attr.2, #Attr.3): let Test.21 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; ret Test.21; procedure Test.6 (Test.8, #Attr.12): let Test.4 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) #Attr.12; - let Test.23 : Builtin(Int(I64)) = CallByName Num.24 Test.8 Test.4; + let Test.23 : Builtin(Int(I64)) = CallByName Num.22 Test.8 Test.4; ret Test.23; procedure Test.7 (Test.9, #Attr.12): let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) #Attr.12; - let Test.20 : Builtin(Int(I64)) = CallByName Num.26 Test.9 Test.5; + let Test.20 : Builtin(Int(I64)) = CallByName Num.24 Test.9 Test.5; ret Test.20; procedure Test.0 (): @@ -37,8 +37,8 @@ procedure Test.0 (): in let Test.22 : Builtin(Bool) = true; if Test.22 then - let Test.6 : LambdaSet([( Test.6, [Builtin(Int(I64))]), ( Test.7, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) }) = ClosureTag(Test.6) Test.4; + let Test.6 : LambdaSet(LambdaSet { set: [( Test.6, [Builtin(Int(I64))]), ( Test.7, [Builtin(Int(I64))])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) }) = ClosureTag(Test.6) Test.4; jump Test.19 Test.6; else - let Test.7 : LambdaSet([( Test.6, [Builtin(Int(I64))]), ( Test.7, [Builtin(Int(I64))])]LambdaSet { set: Ok(()), representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) }) = ClosureTag(Test.7) Test.5; + let Test.7 : LambdaSet(LambdaSet { set: [( Test.6, [Builtin(Int(I64))]), ( Test.7, [Builtin(Int(I64))])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) }) = ClosureTag(Test.7) Test.5; jump Test.19 Test.7; diff --git a/compiler/test_mono/generated/when_nested_maybe.txt b/compiler/test_mono/generated/when_nested_maybe.txt index 157843e919..facbc46055 100644 --- a/compiler/test_mono/generated/when_nested_maybe.txt +++ b/compiler/test_mono/generated/when_nested_maybe.txt @@ -1,4 +1,4 @@ -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; @@ -22,7 +22,7 @@ procedure Test.0 (): let Test.10 : Union(NonRecursive([[Builtin(Int(I64))], []])) = UnionAtIndex (Id 0) (Index 0) Test.2; let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.10; let Test.7 : Builtin(Int(I64)) = 1i64; - let Test.6 : Builtin(Int(I64)) = CallByName Num.24 Test.5 Test.7; + let Test.6 : Builtin(Int(I64)) = CallByName Num.22 Test.5 Test.7; ret Test.6; else jump Test.16; diff --git a/compiler/test_mono/generated/when_on_record.txt b/compiler/test_mono/generated/when_on_record.txt index 7b96273077..b606928426 100644 --- a/compiler/test_mono/generated/when_on_record.txt +++ b/compiler/test_mono/generated/when_on_record.txt @@ -1,9 +1,9 @@ -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.5 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.5; procedure Test.0 (): let Test.6 : Builtin(Int(I64)) = 2i64; let Test.4 : Builtin(Int(I64)) = 3i64; - let Test.3 : Builtin(Int(I64)) = CallByName Num.24 Test.6 Test.4; + let Test.3 : Builtin(Int(I64)) = CallByName Num.22 Test.6 Test.4; ret Test.3; diff --git a/compiler/test_mono/generated/when_on_two_values.txt b/compiler/test_mono/generated/when_on_two_values.txt index 34a0d9dd5f..4af8530b49 100644 --- a/compiler/test_mono/generated/when_on_two_values.txt +++ b/compiler/test_mono/generated/when_on_two_values.txt @@ -1,4 +1,4 @@ -procedure Num.24 (#Attr.2, #Attr.3): +procedure Num.22 (#Attr.2, #Attr.3): let Test.7 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.7; @@ -9,7 +9,7 @@ procedure Test.0 (): joinpoint Test.12: let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 Test.4; let Test.3 : Builtin(Int(I64)) = StructAtIndex 1 Test.4; - let Test.6 : Builtin(Int(I64)) = CallByName Num.24 Test.2 Test.3; + let Test.6 : Builtin(Int(I64)) = CallByName Num.22 Test.2 Test.3; ret Test.6; in let Test.10 : Builtin(Int(I64)) = StructAtIndex 1 Test.4; From 620e3f2913f5eafe4b6f1e61948e9f41e818448b Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 23 Jan 2022 10:11:17 -0500 Subject: [PATCH 288/541] parse tests for provided/required types in module headers --- .../tests/snapshots/pass/provides_type.header.roc | 4 ++++ .../tests/snapshots/pass/requires_type.header.roc | 10 ++++++++++ compiler/parse/tests/test_parse.rs | 2 ++ 3 files changed, 16 insertions(+) create mode 100644 compiler/parse/tests/snapshots/pass/provides_type.header.roc create mode 100644 compiler/parse/tests/snapshots/pass/requires_type.header.roc diff --git a/compiler/parse/tests/snapshots/pass/provides_type.header.roc b/compiler/parse/tests/snapshots/pass/provides_type.header.roc new file mode 100644 index 0000000000..124c3c132c --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/provides_type.header.roc @@ -0,0 +1,4 @@ +app "test" + packages { pf: "./platform" } + imports [ foo.Bar.Baz ] + provides [ quicksort ] { Flags : {}, Model, } to pf diff --git a/compiler/parse/tests/snapshots/pass/requires_type.header.roc b/compiler/parse/tests/snapshots/pass/requires_type.header.roc new file mode 100644 index 0000000000..343674511a --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/requires_type.header.roc @@ -0,0 +1,10 @@ +platform "test/types" + requires { Flags, Model, } { main : App Flags Model } + exposes [] + packages {} + imports [] + provides [ mainForHost ] + effects fx.Effect {} + +mainForHost : App Flags Model +mainForHost = main diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 6276c73ead..28d1825a8f 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -198,6 +198,7 @@ mod test_parse { pass/positive_float.expr, pass/positive_int.expr, pass/private_qualified_tag.expr, + pass/provides_type.header, pass/qualified_field.expr, pass/qualified_global_tag.expr, pass/qualified_var.expr, @@ -205,6 +206,7 @@ mod test_parse { pass/record_func_type_decl.expr, pass/record_update.expr, pass/record_with_if.expr, + pass/requires_type.header, pass/single_arg_closure.expr, pass/single_underscore_closure.expr, pass/space_only_after_minus.expr, From c1c0ffb25f3217a14e5a64d060d15528c2567ed4 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 23 Jan 2022 11:09:29 -0500 Subject: [PATCH 289/541] Use UppercaseIdent over PlatformRigids --- cli/src/format.rs | 5 +- compiler/fmt/src/annotation.rs | 17 ++++ compiler/fmt/src/module.rs | 14 +--- compiler/parse/src/header.rs | 10 +-- compiler/parse/src/ident.rs | 32 ++++++++ compiler/parse/src/module.rs | 26 +++---- ...nonempty_platform_header.header.result-ast | 19 +++-- .../pass/nonempty_platform_header.header.roc | 2 +- .../pass/requires_type.header.result-ast | 77 +++++++++++++++++++ compiler/parse/tests/test_parse.rs | 6 ++ 10 files changed, 157 insertions(+), 51 deletions(-) create mode 100644 compiler/parse/tests/snapshots/pass/requires_type.header.result-ast diff --git a/cli/src/format.rs b/cli/src/format.rs index 13f3edc71f..ea2f1afea0 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -12,10 +12,11 @@ use roc_parse::ast::{ }; use roc_parse::header::{ AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, - PackageName, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent, + PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, }; use roc_parse::{ ast::{Def, Module}, + ident::UppercaseIdent, module::{self, module_defs}, parser::{Parser, SyntaxError}, state::State, @@ -285,7 +286,7 @@ impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> { } } -impl<'a> RemoveSpaces<'a> for PlatformRigid<'a> { +impl<'a> RemoveSpaces<'a> for UppercaseIdent<'a> { fn remove_spaces(&self, _arena: &'a Bump) -> Self { *self } diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs index 638f2946e6..b36b4d56b6 100644 --- a/compiler/fmt/src/annotation.rs +++ b/compiler/fmt/src/annotation.rs @@ -4,6 +4,7 @@ use crate::{ Buf, }; use roc_parse::ast::{AliasHeader, AssignedField, Expr, Tag, TypeAnnotation}; +use roc_parse::ident::UppercaseIdent; use roc_region::all::Loc; /// Does an AST node need parens around it? @@ -107,6 +108,22 @@ where } } +impl<'a> Formattable for UppercaseIdent<'a> { + fn is_multiline(&self) -> bool { + false + } + + fn format_with_options<'buf>( + &self, + buf: &mut Buf<'buf>, + _parens: Parens, + _newlines: Newlines, + _indent: u16, + ) { + buf.push_str((*self).into()) + } +} + impl<'a> Formattable for TypeAnnotation<'a> { fn is_multiline(&self) -> bool { use roc_parse::ast::TypeAnnotation::*; diff --git a/compiler/fmt/src/module.rs b/compiler/fmt/src/module.rs index 42a5c303be..808cc6be9b 100644 --- a/compiler/fmt/src/module.rs +++ b/compiler/fmt/src/module.rs @@ -6,7 +6,7 @@ use crate::Buf; use roc_parse::ast::{Collection, Module, Spaced}; use roc_parse::header::{ AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, - PackageName, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent, + PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, }; use roc_region::all::Loc; @@ -206,18 +206,6 @@ impl<'a, T: Formattable> Formattable for Spaced<'a, T> { } } -impl<'a> Formattable for PlatformRigid<'a> { - fn is_multiline(&self) -> bool { - false - } - - fn format<'buf>(&self, buf: &mut Buf<'buf>, _indent: u16) { - buf.push_str(self.rigid); - buf.push_str("=>"); - buf.push_str(self.alias); - } -} - fn fmt_imports<'a, 'buf>( buf: &mut Buf<'buf>, loc_entries: Collection<'a, Loc>>>, diff --git a/compiler/parse/src/header.rs b/compiler/parse/src/header.rs index 59aeda3751..0e45d42f39 100644 --- a/compiler/parse/src/header.rs +++ b/compiler/parse/src/header.rs @@ -1,6 +1,6 @@ use crate::ast::{Collection, CommentOrNewline, Spaced, StrLiteral, TypeAnnotation}; use crate::blankspace::space0_e; -use crate::ident::lowercase_ident; +use crate::ident::{lowercase_ident, UppercaseIdent}; use crate::parser::Progress::*; use crate::parser::{specialize, word1, EPackageEntry, EPackageName, Parser}; use crate::state::State; @@ -126,15 +126,9 @@ pub struct PackageHeader<'a> { pub after_imports: &'a [CommentOrNewline<'a>], } -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct PlatformRigid<'a> { - pub rigid: &'a str, - pub alias: &'a str, -} - #[derive(Clone, Debug, PartialEq)] pub struct PlatformRequires<'a> { - pub rigids: Collection<'a, Loc>>>, + pub rigids: Collection<'a, Loc>>>, pub signature: Loc>>, } diff --git a/compiler/parse/src/ident.rs b/compiler/parse/src/ident.rs index 773d86b4f5..73044aa6a9 100644 --- a/compiler/parse/src/ident.rs +++ b/compiler/parse/src/ident.rs @@ -5,6 +5,23 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; use roc_region::all::Position; +/// A global tag, for example. Must start with an uppercase letter +/// and then contain only letters and numbers afterwards - no dots allowed! +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct UppercaseIdent<'a>(&'a str); + +impl<'a> From<&'a str> for UppercaseIdent<'a> { + fn from(string: &'a str) -> Self { + UppercaseIdent(string) + } +} + +impl<'a> From> for &'a str { + fn from(ident: UppercaseIdent<'a>) -> Self { + ident.0 + } +} + /// The parser accepts all of these in any position where any one of them could /// appear. This way, canonicalization can give more helpful error messages like /// "you can't redefine this tag!" if you wrote `Foo = ...` or @@ -91,6 +108,21 @@ pub fn tag_name<'a>() -> impl Parser<'a, &'a str, ()> { } } +/// This could be: +/// +/// * A module name +/// * A type name +/// * A global tag +pub fn uppercase<'a>() -> impl Parser<'a, UppercaseIdent<'a>, ()> { + move |_, state: State<'a>| match chomp_uppercase_part(state.bytes()) { + Err(progress) => Err((progress, (), state)), + Ok(ident) => { + let width = ident.len(); + Ok((MadeProgress, ident.into(), state.advance(width))) + } + } +} + /// This could be: /// /// * A module name diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index 7b5f3206fa..d313ffa11d 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -2,13 +2,13 @@ use crate::ast::{Collection, CommentOrNewline, Def, Module, Spaced}; use crate::blankspace::{space0_around_ee, space0_before_e, space0_e}; use crate::header::{ package_entry, package_name, AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, - ModuleName, PackageEntry, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent, + ModuleName, PackageEntry, PlatformHeader, PlatformRequires, To, TypedIdent, }; -use crate::ident::{lowercase_ident, unqualified_ident, uppercase_ident}; +use crate::ident::{self, lowercase_ident, unqualified_ident, uppercase_ident, UppercaseIdent}; use crate::parser::Progress::{self, *}; use crate::parser::{ - backtrackable, specialize, specialize_region, word1, word2, EEffects, EExposes, EHeader, - EImports, EPackages, EProvides, ERequires, ETypedIdent, Parser, SourceError, SyntaxError, + backtrackable, specialize, specialize_region, word1, EEffects, EExposes, EHeader, EImports, + EPackages, EProvides, ERequires, ETypedIdent, Parser, SourceError, SyntaxError, }; use crate::state::State; use crate::string_literal; @@ -439,10 +439,13 @@ fn platform_requires<'a>() -> impl Parser<'a, PlatformRequires<'a>, ERequires<'a #[inline(always)] fn requires_rigids<'a>( min_indent: u32, -) -> impl Parser<'a, Collection<'a, Loc>>>, ERequires<'a>> { +) -> impl Parser<'a, Collection<'a, Loc>>>, ERequires<'a>> { collection_trailing_sep_e!( word1(b'{', ERequires::ListStart), - specialize(|_, pos| ERequires::Rigid(pos), loc!(requires_rigid())), + specialize( + |_, pos| ERequires::Rigid(pos), + loc!(map!(ident::uppercase(), Spaced::Item)) + ), word1(b',', ERequires::ListEnd), word1(b'}', ERequires::ListEnd), min_indent, @@ -453,17 +456,6 @@ fn requires_rigids<'a>( ) } -#[inline(always)] -fn requires_rigid<'a>() -> impl Parser<'a, Spaced<'a, PlatformRigid<'a>>, ()> { - map!( - and!( - lowercase_ident(), - skip_first!(word2(b'=', b'>', |_| ()), uppercase_ident()) - ), - |(rigid, alias)| Spaced::Item(PlatformRigid { rigid, alias }) - ) -} - #[inline(always)] fn requires_typed_ident<'a>() -> impl Parser<'a, Loc>>, ERequires<'a>> { skip_first!( diff --git a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast index a36b138082..5dddd9d923 100644 --- a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast @@ -5,15 +5,14 @@ Platform { ), requires: PlatformRequires { rigids: [ - @36-48 PlatformRigid { - rigid: "model", - alias: "Model", - }, + @36-41 UppercaseIdent( + "Model", + ), ], - signature: @52-61 TypedIdent { - ident: @52-56 "main", + signature: @45-54 TypedIdent { + ident: @45-49 "main", spaces_before_colon: [], - ann: @59-61 Record { + ann: @52-54 Record { fields: [], ext: None, }, @@ -21,17 +20,17 @@ Platform { }, exposes: [], packages: [ - @94-106 PackageEntry { + @87-99 PackageEntry { shorthand: "foo", spaces_after_shorthand: [], - package_name: @99-106 PackageName( + package_name: @92-99 PackageName( "./foo", ), }, ], imports: [], provides: [ - @139-150 ExposedName( + @132-143 ExposedName( "mainForHost", ), ], diff --git a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc index c38245cdc6..97673c692a 100644 --- a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc +++ b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc @@ -1,5 +1,5 @@ platform "foo/barbaz" - requires {model=>Model} { main : {} } + requires {Model} { main : {} } exposes [] packages { foo: "./foo" } imports [] diff --git a/compiler/parse/tests/snapshots/pass/requires_type.header.result-ast b/compiler/parse/tests/snapshots/pass/requires_type.header.result-ast new file mode 100644 index 0000000000..2195dcac19 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/requires_type.header.result-ast @@ -0,0 +1,77 @@ +Platform { + header: PlatformHeader { + name: @9-21 PackageName( + "test/types", + ), + requires: PlatformRequires { + rigids: [ + @37-42 UppercaseIdent( + "Flags", + ), + @44-49 UppercaseIdent( + "Model", + ), + ], + signature: @55-77 TypedIdent { + ident: @55-59 "main", + spaces_before_colon: [], + ann: @62-77 Apply( + "", + "App", + [ + @66-71 Apply( + "", + "Flags", + [], + ), + @72-77 Apply( + "", + "Model", + [], + ), + ], + ), + }, + }, + exposes: [], + packages: [], + imports: [], + provides: [ + @141-152 ExposedName( + "mainForHost", + ), + ], + effects: Effects { + spaces_before_effects_keyword: [ + Newline, + ], + spaces_after_effects_keyword: [], + spaces_after_type_name: [], + effect_shortname: "fx", + effect_type_name: "Effect", + entries: [], + }, + before_header: [], + after_platform_keyword: [], + before_requires: [ + Newline, + ], + after_requires: [], + before_exposes: [ + Newline, + ], + after_exposes: [], + before_packages: [ + Newline, + ], + after_packages: [], + before_imports: [ + Newline, + ], + after_imports: [], + before_provides: [ + Newline, + ], + after_provides: [], + }, +} diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 28d1825a8f..d575d677a1 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -266,8 +266,14 @@ mod test_parse { let result = func(&input); let actual_result = if should_pass { + eprintln!("The source code for this test did not successfully parse!\n"); + result.unwrap() } else { + eprintln!( + "The source code for this test successfully parsed, but it was not expected to!\n" + ); + result.unwrap_err() }; From 01942fd98e09c2a72b6bada72138514cf302842b Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 23 Jan 2022 11:52:59 -0500 Subject: [PATCH 290/541] Parse app header provided types --- compiler/parse/src/header.rs | 1 + compiler/parse/src/module.rs | 71 +++++++++++++++---- .../pass/empty_app_header.header.result-ast | 1 + .../pass/full_app_header.header.result-ast | 1 + ...p_header_trailing_commas.header.result-ast | 1 + .../pass/minimal_app_header.header.result-ast | 1 + 6 files changed, 61 insertions(+), 15 deletions(-) diff --git a/compiler/parse/src/header.rs b/compiler/parse/src/header.rs index 0e45d42f39..5f30370bae 100644 --- a/compiler/parse/src/header.rs +++ b/compiler/parse/src/header.rs @@ -93,6 +93,7 @@ pub struct AppHeader<'a> { pub packages: Collection<'a, Loc>>>, pub imports: Collection<'a, Loc>>>, pub provides: Collection<'a, Loc>>>, + pub provides_types: Option>>>>, pub to: Loc>, // Potential comments and newlines - these will typically all be empty. diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index d313ffa11d..3fbec0382a 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -7,8 +7,8 @@ use crate::header::{ use crate::ident::{self, lowercase_ident, unqualified_ident, uppercase_ident, UppercaseIdent}; use crate::parser::Progress::{self, *}; use crate::parser::{ - backtrackable, specialize, specialize_region, word1, EEffects, EExposes, EHeader, EImports, - EPackages, EProvides, ERequires, ETypedIdent, Parser, SourceError, SyntaxError, + backtrackable, optional, specialize, specialize_region, word1, EEffects, EExposes, EHeader, + EImports, EPackages, EProvides, ERequires, ETypedIdent, Parser, SourceError, SyntaxError, }; use crate::state::State; use crate::string_literal; @@ -227,6 +227,7 @@ fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> { packages, imports, provides: provides.entries, + provides_types: provides.types, to: provides.to, before_header: &[] as &[_], after_app_keyword, @@ -265,7 +266,7 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> { let (_, ((before_imports, after_imports), imports), state) = specialize(EHeader::Imports, imports()).parse(arena, state)?; - let (_, ((before_provides, after_provides), provides), state) = + let (_, ((before_provides, after_provides), (provides, _provides_type)), state) = specialize(EHeader::Provides, provides_without_to()).parse(arena, state)?; let (_, effects, state) = specialize(EHeader::Effects, effects()).parse(arena, state)?; @@ -299,6 +300,7 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> { #[derive(Debug)] struct ProvidesTo<'a> { entries: Collection<'a, Loc>>>, + types: Option>>>>, to: Loc>, before_provides_keyword: &'a [CommentOrNewline<'a>], @@ -337,11 +339,12 @@ fn provides_to<'a>() -> impl Parser<'a, ProvidesTo<'a>, EProvides<'a>> { ) ), |( - ((before_provides_keyword, after_provides_keyword), entries), + ((before_provides_keyword, after_provides_keyword), (entries, provides_types)), ((before_to_keyword, after_to_keyword), to), )| { ProvidesTo { entries, + types: provides_types, to, before_provides_keyword, after_provides_keyword, @@ -357,7 +360,10 @@ fn provides_without_to<'a>() -> impl Parser< 'a, ( (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), - Collection<'a, Loc>>>, + ( + Collection<'a, Loc>>>, + Option>>>>, + ), ), EProvides<'a>, > { @@ -371,20 +377,55 @@ fn provides_without_to<'a>() -> impl Parser< EProvides::IndentProvides, EProvides::IndentListStart ), - collection_trailing_sep_e!( - word1(b'[', EProvides::ListStart), - exposes_entry(EProvides::Identifier), - word1(b',', EProvides::ListEnd), - word1(b']', EProvides::ListEnd), - min_indent, - EProvides::Open, - EProvides::Space, - EProvides::IndentListEnd, - Spaced::SpaceBefore + and!( + collection_trailing_sep_e!( + word1(b'[', EProvides::ListStart), + exposes_entry(EProvides::Identifier), + word1(b',', EProvides::ListEnd), + word1(b']', EProvides::ListEnd), + min_indent, + EProvides::Open, + EProvides::Space, + EProvides::IndentListEnd, + Spaced::SpaceBefore + ), + // Optionally + optional(provides_types()) ) ) } +#[inline(always)] +fn provides_types<'a>( +) -> impl Parser<'a, Collection<'a, Loc>>>, EProvides<'a>> { + let min_indent = 1; + collection_trailing_sep_e!( + word1(b'{', EProvides::ListStart), + provides_type_entry(EProvides::Identifier), + word1(b',', EProvides::ListEnd), + word1(b'}', EProvides::ListEnd), + min_indent, + EProvides::Open, + EProvides::Space, + EProvides::IndentListEnd, + Spaced::SpaceBefore + ) +} + +fn provides_type_entry<'a, F, E>( + to_expectation: F, +) -> impl Parser<'a, Loc>>, E> +where + F: Fn(Position) -> E, + F: Copy, + E: 'a, +{ + loc!(map!( + specialize(|_, pos| to_expectation(pos), ident::uppercase()), + Spaced::Item + )) +} + fn exposes_entry<'a, F, E>( to_expectation: F, ) -> impl Parser<'a, Loc>>, E> diff --git a/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast b/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast index 7b14020ab4..d0ba5ec7b0 100644 --- a/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast @@ -6,6 +6,7 @@ App { packages: [], imports: [], provides: [], + provides_types: None, to: @53-57 ExistingPackage( "blah", ), diff --git a/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast b/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast index 5576408d8b..76545b43e9 100644 --- a/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast @@ -26,6 +26,7 @@ App { "quicksort", ), ], + provides_types: None, to: @108-110 ExistingPackage( "pf", ), diff --git a/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast b/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast index 236aed2d63..8840febceb 100644 --- a/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast @@ -51,6 +51,7 @@ App { "quicksort", ), ], + provides_types: None, to: @175-177 ExistingPackage( "pf", ), diff --git a/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast b/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast index b3d4004d77..225de00938 100644 --- a/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast @@ -6,6 +6,7 @@ App { packages: [], imports: [], provides: [], + provides_types: None, to: @30-38 NewPackage( PackageName( "./blah", From f6126cda695c76cc61e5631dac8e06f5d8ab871d Mon Sep 17 00:00:00 2001 From: hafiz <20735482+ayazhafiz@users.noreply.github.com> Date: Sun, 23 Jan 2022 11:56:13 -0500 Subject: [PATCH 291/541] Revert "Use `Task.loop` in examples" --- examples/benchmarks/Deriv.roc | 28 ++++++------- examples/false-interpreter/False.roc | 42 +++++++++----------- examples/false-interpreter/platform/Task.roc | 20 +--------- 3 files changed, 32 insertions(+), 58 deletions(-) diff --git a/examples/benchmarks/Deriv.roc b/examples/benchmarks/Deriv.roc index 2cad781765..5e5a81f8b2 100644 --- a/examples/benchmarks/Deriv.roc +++ b/examples/benchmarks/Deriv.roc @@ -21,22 +21,6 @@ main = |> Task.map (\_ -> {}) -nest : (I64, Expr -> IO Expr), I64, Expr -> IO Expr -nest = \f, n, e -> Task.loop { s: n, f, m: n, x: e } nestHelp - -State : { s : I64, f : I64, Expr -> IO Expr, m : I64, x : Expr } - -nestHelp : State -> IO [ Step State, Done Expr ] -nestHelp = \{ s, f, m, x } -> - when m is - 0 -> - Task.succeed (Done x) - - _ -> - w <- Task.after (f (s - m) x) - - Task.succeed (Step { s, f, m: (m - 1), x: w }) - Expr : [ Val I64, Var Str, Add Expr Expr, Mul Expr Expr, Pow Expr Expr, Ln Expr ] divmod : I64, I64 -> Result { div : I64, mod : I64 } [ DivByZero ]* @@ -196,6 +180,18 @@ count = \expr -> Ln f -> count f +nest : (I64, Expr -> IO Expr), I64, Expr -> IO Expr +nest = \f, n, e -> nestHelp n f n e + +nestHelp : I64, (I64, Expr -> IO Expr), I64, Expr -> IO Expr +nestHelp = \s, f, m, x -> + when m is + 0 -> + Task.succeed x + + _ -> + f (s - m) x |> Task.after \w -> nestHelp s f (m - 1) w + deriv : I64, Expr -> IO Expr deriv = \i, f -> fprime = d "x" f diff --git a/examples/false-interpreter/False.roc b/examples/false-interpreter/False.roc index d7e0ac0c4f..57fc3c14a9 100644 --- a/examples/false-interpreter/False.roc +++ b/examples/false-interpreter/False.roc @@ -89,10 +89,6 @@ isWhitespace = \char -> == 0x9# tab interpretCtx : Context -> Task Context InterpreterErrors interpretCtx = \ctx -> - Task.loop ctx interpretCtxLoop - -interpretCtxLoop : Context -> Task [ Step Context, Done Context ] InterpreterErrors -interpretCtxLoop = \ctx -> when ctx.state is Executing if Context.inWhileScope ctx -> # Deal with the current while loop potentially looping. @@ -108,11 +104,11 @@ interpretCtxLoop = \ctx -> if n == 0 then newScope = { scope & whileInfo: None } - Task.succeed (Step { popCtx & scopes: List.set ctx.scopes last newScope }) + interpretCtx { popCtx & scopes: List.set ctx.scopes last newScope } else newScope = { scope & whileInfo: Some { state: InBody, body, cond } } - Task.succeed (Step { popCtx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: body, index: 0, whileInfo: None } }) + interpretCtx { popCtx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: body, index: 0, whileInfo: None } } Err e -> Task.fail e @@ -121,7 +117,7 @@ interpretCtxLoop = \ctx -> # Just rand the body. Run the condition again. newScope = { scope & whileInfo: Some { state: InCond, body, cond } } - Task.succeed (Step { ctx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: cond, index: 0, whileInfo: None } }) + interpretCtx { ctx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: cond, index: 0, whileInfo: None } } None -> Task.fail NoScope @@ -135,7 +131,7 @@ interpretCtxLoop = \ctx -> when result is Ok (T val newCtx) -> execCtx <- Task.await (stepExecCtx newCtx val) - Task.succeed (Step execCtx) + interpretCtx execCtx Err NoScope -> Task.fail NoScope @@ -147,9 +143,9 @@ interpretCtxLoop = \ctx -> # If no scopes left, all execution complete. if List.isEmpty dropCtx.scopes then - Task.succeed (Done dropCtx) + Task.succeed dropCtx else - Task.succeed (Step dropCtx) + interpretCtx dropCtx InComment -> result <- Task.attempt (Context.getChar ctx) @@ -157,9 +153,9 @@ interpretCtxLoop = \ctx -> Ok (T val newCtx) -> if val == 0x7D then # `}` end of comment - Task.succeed (Step { newCtx & state: Executing }) + interpretCtx { newCtx & state: Executing } else - Task.succeed (Step { newCtx & state: InComment }) + interpretCtx { newCtx & state: InComment } Err NoScope -> Task.fail NoScope @@ -178,13 +174,13 @@ interpretCtxLoop = \ctx -> # so this is make i64 mul by 10 then convert back to i32. nextAccum = (10 * Num.intCast accum) + Num.intCast (val - 0x30) - Task.succeed (Step { newCtx & state: InNumber (Num.intCast nextAccum) }) + interpretCtx { newCtx & state: InNumber (Num.intCast nextAccum) } else # outside of number now, this needs to be executed. pushCtx = Context.pushStack newCtx (Number accum) execCtx <- Task.await (stepExecCtx { pushCtx & state: Executing } val) - Task.succeed (Step execCtx) + interpretCtx execCtx Err NoScope -> Task.fail NoScope @@ -201,12 +197,12 @@ interpretCtxLoop = \ctx -> when Str.fromUtf8 bytes is Ok str -> { } <- Task.await (Stdout.raw str) - Task.succeed (Step { newCtx & state: Executing }) + interpretCtx { newCtx & state: Executing } Err _ -> Task.fail BadUtf8 else - Task.succeed (Step { newCtx & state: InString (List.append bytes val) }) + interpretCtx { newCtx & state: InString (List.append bytes val) } Err NoScope -> Task.fail NoScope @@ -220,17 +216,17 @@ interpretCtxLoop = \ctx -> Ok (T val newCtx) -> if val == 0x5B then # start of a nested lambda `[` - Task.succeed (Step { newCtx & state: InLambda (depth + 1) (List.append bytes val) }) + interpretCtx { newCtx & state: InLambda (depth + 1) (List.append bytes val) } else if val == 0x5D then # `]` end of current lambda if depth == 0 then # end of all lambdas - Task.succeed (Step (Context.pushStack { newCtx & state: Executing } (Lambda bytes))) + interpretCtx (Context.pushStack { newCtx & state: Executing } (Lambda bytes)) else # end of nested lambda - Task.succeed (Step { newCtx & state: InLambda (depth - 1) (List.append bytes val) }) + interpretCtx { newCtx & state: InLambda (depth - 1) (List.append bytes val) } else - Task.succeed (Step { newCtx & state: InLambda depth (List.append bytes val) }) + interpretCtx { newCtx & state: InLambda depth (List.append bytes val) } Err NoScope -> Task.fail NoScope @@ -256,14 +252,14 @@ interpretCtxLoop = \ctx -> when result2 is Ok a -> - Task.succeed (Step a) + interpretCtx a Err e -> Task.fail e Ok (T 0x9F newCtx) -> # This is supposed to flush io buffers. We don't buffer, so it does nothing - Task.succeed (Step newCtx) + interpretCtx newCtx Ok (T x _) -> data = Num.toStr (Num.intCast x) @@ -280,7 +276,7 @@ interpretCtxLoop = \ctx -> result <- Task.attempt (Context.getChar { ctx & state: Executing }) when result is Ok (T x newCtx) -> - Task.succeed (Step (Context.pushStack newCtx (Number (Num.intCast x)))) + interpretCtx (Context.pushStack newCtx (Number (Num.intCast x))) Err NoScope -> Task.fail NoScope diff --git a/examples/false-interpreter/platform/Task.roc b/examples/false-interpreter/platform/Task.roc index 0ad7c223b2..520eba4976 100644 --- a/examples/false-interpreter/platform/Task.roc +++ b/examples/false-interpreter/platform/Task.roc @@ -1,27 +1,9 @@ interface Task - exposes [ Task, succeed, fail, await, map, onFail, attempt, fromResult, loop ] + exposes [ Task, succeed, fail, await, map, onFail, attempt, fromResult ] imports [ fx.Effect ] Task ok err : Effect.Effect (Result ok err) -loop : state, (state -> Task [ Step state, Done done ] err) -> Task done err -loop = \state, step -> - looper = \current -> - step current - |> Effect.map - \res -> - when res is - Ok (Step newState) -> - Step newState - - Ok (Done result) -> - Done (Ok result) - - Err e -> - Done (Err e) - - Effect.loop state looper - succeed : val -> Task val * succeed = \val -> Effect.always (Ok val) From 1ab621dd5435810b2f1caa4fe1c888db6f0d5d15 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 23 Jan 2022 12:02:34 -0500 Subject: [PATCH 292/541] Support optional spaces in app header types --- compiler/parse/src/module.rs | 34 +++++++++++++------ .../snapshots/pass/provides_type.header.roc | 2 +- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index 3fbec0382a..0852be36ee 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -399,16 +399,30 @@ fn provides_without_to<'a>() -> impl Parser< fn provides_types<'a>( ) -> impl Parser<'a, Collection<'a, Loc>>>, EProvides<'a>> { let min_indent = 1; - collection_trailing_sep_e!( - word1(b'{', EProvides::ListStart), - provides_type_entry(EProvides::Identifier), - word1(b',', EProvides::ListEnd), - word1(b'}', EProvides::ListEnd), - min_indent, - EProvides::Open, - EProvides::Space, - EProvides::IndentListEnd, - Spaced::SpaceBefore + + skip_first!( + // We only support spaces here, not newlines, because this is not intended + // to be the design forever. Someday it will hopefully work like Elm, + // where platform authors can provide functions like Browser.sandbox which + // present an API based on ordinary-looking type variables. + zero_or_more!(word1( + b' ', + // HACK: If this errors, EProvides::Provides is not an accurate reflection + // of what went wrong. However, this is both skipped and zero_or_more, + // so this error should never be visible to anyone in practice! + EProvides::Provides + )), + collection_trailing_sep_e!( + word1(b'{', EProvides::ListStart), + provides_type_entry(EProvides::Identifier), + word1(b',', EProvides::ListEnd), + word1(b'}', EProvides::ListEnd), + min_indent, + EProvides::Open, + EProvides::Space, + EProvides::IndentListEnd, + Spaced::SpaceBefore + ) ) } diff --git a/compiler/parse/tests/snapshots/pass/provides_type.header.roc b/compiler/parse/tests/snapshots/pass/provides_type.header.roc index 124c3c132c..c49764ac4a 100644 --- a/compiler/parse/tests/snapshots/pass/provides_type.header.roc +++ b/compiler/parse/tests/snapshots/pass/provides_type.header.roc @@ -1,4 +1,4 @@ app "test" packages { pf: "./platform" } imports [ foo.Bar.Baz ] - provides [ quicksort ] { Flags : {}, Model, } to pf + provides [ quicksort ] { Flags, Model, } to pf From 4fadc775e8f5d4a194c244f0fdeca2f738e8c342 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 23 Jan 2022 12:20:35 -0500 Subject: [PATCH 293/541] Add parse tests snapshot --- .../pass/provides_type.header.result-ast | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 compiler/parse/tests/snapshots/pass/provides_type.header.result-ast diff --git a/compiler/parse/tests/snapshots/pass/provides_type.header.result-ast b/compiler/parse/tests/snapshots/pass/provides_type.header.result-ast new file mode 100644 index 0000000000..1991b3bd8f --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/provides_type.header.result-ast @@ -0,0 +1,59 @@ +App { + header: AppHeader { + name: @4-10 PlainLine( + "test", + ), + packages: [ + @26-42 PackageEntry { + shorthand: "pf", + spaces_after_shorthand: [], + package_name: @30-42 PackageName( + "./platform", + ), + }, + ], + imports: [ + @59-70 Package( + "foo", + ModuleName( + "Bar.Baz", + ), + [], + ), + ], + provides: [ + @88-97 ExposedName( + "quicksort", + ), + ], + provides_types: Some( + [ + @102-107 UppercaseIdent( + "Flags", + ), + @109-114 UppercaseIdent( + "Model", + ), + ], + ), + to: @121-123 ExistingPackage( + "pf", + ), + before_header: [], + after_app_keyword: [], + before_packages: [ + Newline, + ], + after_packages: [], + before_imports: [ + Newline, + ], + after_imports: [], + before_provides: [ + Newline, + ], + after_provides: [], + before_to: [], + after_to: [], + }, +} From b2f2fcd6a8199ede1989d7768e33b3a40568c009 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 23 Jan 2022 11:12:29 -0500 Subject: [PATCH 294/541] Collect tags from extension variables during monomorphization Fixes #2365 --- compiler/mono/src/ir.rs | 49 ++++++++--------- compiler/mono/src/layout.rs | 86 +++++++++++++----------------- compiler/test_gen/src/gen_tags.rs | 82 ++++++++++++++++++++++++++++ compiler/types/src/pretty_print.rs | 4 +- compiler/types/src/subs.rs | 58 ++++++++++++++++---- 5 files changed, 194 insertions(+), 85 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 9fd2034d0b..d1e2402cca 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -5118,41 +5118,42 @@ fn register_capturing_closure<'a>( let is_self_recursive = !matches!(recursive, roc_can::expr::Recursive::NotRecursive); - // does this function capture any local values? - let function_layout = layout_cache.raw_from_var(env.arena, function_type, env.subs); - - let captured_symbols = match function_layout { - Ok(RawFunctionLayout::Function(_, lambda_set, _)) => { - if let Layout::Struct(&[]) = lambda_set.runtime_representation() { - CapturedSymbols::None - } else { - let mut temp = Vec::from_iter_in(captured_symbols, env.arena); - temp.sort(); - CapturedSymbols::Captured(temp.into_bump_slice()) + let captured_symbols = match *env.subs.get_content_without_compacting(function_type) { + Content::Structure(FlatType::Func(_, closure_var, _)) => { + match LambdaSet::from_var(env.arena, env.subs, closure_var, env.ptr_bytes) { + Ok(lambda_set) => { + if let Layout::Struct(&[]) = lambda_set.runtime_representation() { + CapturedSymbols::None + } else { + let mut temp = Vec::from_iter_in(captured_symbols, env.arena); + temp.sort(); + CapturedSymbols::Captured(temp.into_bump_slice()) + } + } + Err(_) => { + // just allow this. see https://github.com/rtfeldman/roc/issues/1585 + if captured_symbols.is_empty() { + CapturedSymbols::None + } else { + let mut temp = Vec::from_iter_in(captured_symbols, env.arena); + temp.sort(); + CapturedSymbols::Captured(temp.into_bump_slice()) + } + } } } - Ok(RawFunctionLayout::ZeroArgumentThunk(_)) => { - // top-level thunks cannot capture any variables + _ => { + // This is a value (zero-argument thunk); it cannot capture any variables. debug_assert!( captured_symbols.is_empty(), "{:?} with layout {:?} {:?} {:?}", &captured_symbols, - function_layout, + layout_cache.raw_from_var(env.arena, function_type, env.subs), env.subs, (function_type, closure_type, closure_ext_var), ); CapturedSymbols::None } - Err(_) => { - // just allow this. see https://github.com/rtfeldman/roc/issues/1585 - if captured_symbols.is_empty() { - CapturedSymbols::None - } else { - let mut temp = Vec::from_iter_in(captured_symbols, env.arena); - temp.sort(); - CapturedSymbols::Captured(temp.into_bump_slice()) - } - } }; let partial_proc = PartialProc::from_named_function( diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index d347933464..3e6078e038 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -7,7 +7,7 @@ use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{Interns, Symbol}; use roc_problem::can::RuntimeError; use roc_types::subs::{ - Content, FlatType, RecordFields, Subs, UnionTags, Variable, VariableSubsSlice, + Content, FlatType, RecordFields, Subs, UnionTags, UnsortedUnionTags, Variable, }; use roc_types::types::{gather_fields_unsorted_iter, RecordField}; use std::collections::hash_map::Entry; @@ -1571,18 +1571,26 @@ fn layout_from_flat_type<'a>( } } TagUnion(tags, ext_var) => { + let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var); + debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); - Ok(layout_from_tag_union(arena, tags, subs, env.ptr_bytes)) + Ok(layout_from_tag_union(arena, &tags, subs, env.ptr_bytes)) } FunctionOrTagUnion(tag_name, _, ext_var) => { - debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); + debug_assert!( + ext_var_is_empty_tag_union(subs, ext_var), + "If ext_var wasn't empty, this wouldn't be a FunctionOrTagUnion!" + ); - let tags = UnionTags::from_tag_name_index(tag_name); + let union_tags = UnionTags::from_tag_name_index(tag_name); + let (tags, _) = union_tags.unsorted_tags_and_ext(subs, ext_var); - Ok(layout_from_tag_union(arena, tags, subs, env.ptr_bytes)) + Ok(layout_from_tag_union(arena, &tags, subs, env.ptr_bytes)) } RecursiveTagUnion(rec_var, tags, ext_var) => { + let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var); + debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); // some observations @@ -1594,9 +1602,9 @@ fn layout_from_flat_type<'a>( // That means none of the optimizations for enums or single tag tag unions apply let rec_var = subs.get_root_key_without_compacting(rec_var); - let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); + let mut tag_layouts = Vec::with_capacity_in(tags.tags.len(), arena); - let tags_vec = cheap_sort_tags(arena, tags, subs); + let tags_vec = cheap_sort_tags(&tags); let mut nullable = None; @@ -1610,7 +1618,7 @@ fn layout_from_flat_type<'a>( } env.insert_seen(rec_var); - for (index, (_name, variables)) in tags_vec.into_iter().enumerate() { + for (index, &(_name, variables)) in tags_vec.iter().enumerate() { if matches!(nullable, Some(i) if i == index as TagIdIntType) { // don't add the nullable case continue; @@ -1618,8 +1626,7 @@ fn layout_from_flat_type<'a>( let mut tag_layout = Vec::with_capacity_in(variables.len() + 1, arena); - for var_index in variables { - let var = subs[var_index]; + for &var in variables { // TODO does this cause problems with mutually recursive unions? if rec_var == subs.get_root_key_without_compacting(var) { tag_layout.push(Layout::RecursivePointer); @@ -1902,13 +1909,14 @@ fn is_recursive_tag_union(layout: &Layout) -> bool { fn union_sorted_tags_help_new<'a>( arena: &'a Bump, - mut tags_vec: Vec<(&'_ TagName, VariableSubsSlice)>, + tags_list: &[(&'_ TagName, &[Variable])], opt_rec_var: Option, subs: &Subs, ptr_bytes: u32, ) -> UnionVariant<'a> { // sort up front; make sure the ordering stays intact! - tags_vec.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); + let mut tags_list = Vec::from_iter_in(tags_list.iter(), arena); + tags_list.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); let mut env = Env { arena, @@ -1917,27 +1925,26 @@ fn union_sorted_tags_help_new<'a>( ptr_bytes, }; - match tags_vec.len() { + match tags_list.len() { 0 => { // trying to instantiate a type with no values UnionVariant::Never } 1 => { - let (tag_name, arguments) = tags_vec.remove(0); + let &(tag_name, arguments) = tags_list.remove(0); let tag_name = tag_name.clone(); // just one tag in the union (but with arguments) can be a struct - let mut layouts = Vec::with_capacity_in(tags_vec.len(), arena); + let mut layouts = Vec::with_capacity_in(tags_list.len(), arena); // special-case NUM_AT_NUM: if its argument is a FlexVar, make it Int match tag_name { TagName::Private(Symbol::NUM_AT_NUM) => { - let var = subs[arguments.into_iter().next().unwrap()]; + let var = arguments[0]; layouts.push(unwrap_num_tag(subs, var, ptr_bytes).expect("invalid num layout")); } _ => { - for var_index in arguments { - let var = subs[var_index]; + for &var in arguments { match Layout::from_var(&mut env, var) { Ok(layout) => { layouts.push(layout); @@ -1980,7 +1987,7 @@ fn union_sorted_tags_help_new<'a>( } num_tags => { // default path - let mut answer = Vec::with_capacity_in(tags_vec.len(), arena); + let mut answer = Vec::with_capacity_in(tags_list.len(), arena); let mut has_any_arguments = false; let mut nullable: Option<(TagIdIntType, TagName)> = None; @@ -1988,7 +1995,7 @@ fn union_sorted_tags_help_new<'a>( // only recursive tag unions can be nullable let is_recursive = opt_rec_var.is_some(); if is_recursive && GENERATE_NULLABLE { - for (index, (name, variables)) in tags_vec.iter().enumerate() { + for (index, (name, variables)) in tags_list.iter().enumerate() { if variables.is_empty() { nullable = Some((index as TagIdIntType, (*name).clone())); break; @@ -1996,7 +2003,7 @@ fn union_sorted_tags_help_new<'a>( } } - for (index, (tag_name, arguments)) in tags_vec.into_iter().enumerate() { + for (index, &(tag_name, arguments)) in tags_list.into_iter().enumerate() { // reserve space for the tag discriminant if matches!(nullable, Some((i, _)) if i as usize == index) { debug_assert!(arguments.is_empty()); @@ -2005,8 +2012,7 @@ fn union_sorted_tags_help_new<'a>( let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, arena); - for var_index in arguments { - let var = subs[var_index]; + for &var in arguments { match Layout::from_var(&mut env, var) { Ok(layout) => { has_any_arguments = true; @@ -2317,38 +2323,19 @@ pub fn union_sorted_tags_help<'a>( } } -fn cheap_sort_tags<'a, 'b>( - arena: &'a Bump, - tags: UnionTags, - subs: &'b Subs, -) -> Vec<'a, (&'b TagName, VariableSubsSlice)> { - let mut tags_vec = Vec::with_capacity_in(tags.len(), arena); - - for (tag_index, index) in tags.iter_all() { - let tag = &subs[tag_index]; - let slice = subs[index]; - - tags_vec.push((tag, slice)); - } - - tags_vec +fn cheap_sort_tags<'a>(tags: &'a UnsortedUnionTags) -> &'a [(&'a TagName, &'a [Variable])] { + &tags.tags } fn layout_from_newtype<'a>( arena: &'a Bump, - tags: UnionTags, + tags: &UnsortedUnionTags, subs: &Subs, ptr_bytes: u32, ) -> Layout<'a> { debug_assert!(tags.is_newtype_wrapper(subs)); - let slice_index = tags.variables().into_iter().next().unwrap(); - let slice = subs[slice_index]; - let var_index = slice.into_iter().next().unwrap(); - let var = subs[var_index]; - - let tag_name_index = tags.tag_names().into_iter().next().unwrap(); - let tag_name = &subs[tag_name_index]; + let (tag_name, var) = tags.get_newtype(subs); if tag_name == &TagName::Private(Symbol::NUM_AT_NUM) { unwrap_num_tag(subs, var, ptr_bytes).expect("invalid Num argument") @@ -2379,7 +2366,7 @@ fn layout_from_newtype<'a>( fn layout_from_tag_union<'a>( arena: &'a Bump, - tags: UnionTags, + tags: &UnsortedUnionTags, subs: &Subs, ptr_bytes: u32, ) -> Layout<'a> { @@ -2389,14 +2376,13 @@ fn layout_from_tag_union<'a>( return layout_from_newtype(arena, tags, subs, ptr_bytes); } - let tags_vec = cheap_sort_tags(arena, tags, subs); + let tags_vec = cheap_sort_tags(tags); match tags_vec.get(0) { Some((tag_name, arguments)) if *tag_name == &TagName::Private(Symbol::NUM_AT_NUM) => { debug_assert_eq!(arguments.len(), 1); - let var_index = arguments.into_iter().next().unwrap(); - let var = subs[var_index]; + let &var = arguments.iter().next().unwrap(); unwrap_num_tag(subs, var, ptr_bytes).expect("invalid Num argument") } diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index b8062dbde2..27ce93b468 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -1367,3 +1367,85 @@ fn monomorphized_tag_with_polymorphic_arg_and_monomorphic_arg() { u8 ) } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn issue_2365_monomorphize_tag_with_non_empty_ext_var() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + Single a : [A, B, C]a + Compound a : Single [D, E, F]a + + single : {} -> Single * + single = \{} -> C + + compound : {} -> Compound * + compound = \{} -> single {} + + main = compound {} + "# + ), + 2, // C + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn issue_2365_monomorphize_tag_with_non_empty_ext_var_wrapped() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + Single a : [A, B, C]a + Compound a : Single [D, E, F]a + + single : {} -> Result Str (Single *) + single = \{} -> Err C + + compound : {} -> Result Str (Compound *) + compound = \{} -> + when single {} is + Ok s -> Ok s + Err e -> Err e + + main = compound {} + "# + ), + 2, // C + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn issue_2365_monomorphize_tag_with_non_empty_ext_var_wrapped_nested() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + Single a : [A, B, C]a + Compound a : Single [D, E, F]a + + main = + single : {} -> Result Str (Single *) + single = \{} -> Err C + + compound : {} -> Result Str (Compound *) + compound = \{} -> + when single {} is + Ok s -> Ok s + Err e -> Err e + + compound {} + "# + ), + 2, // C + u8 + ) +} diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index 6a48f7088f..a861242bd5 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -406,8 +406,8 @@ fn write_sorted_tags2<'a>( ext_var: Variable, ) -> ExtContent<'a> { // Sort the fields so they always end up in the same order. - let (it, new_ext_var) = tags.unsorted_iterator_and_ext(subs, ext_var); - let mut sorted_fields: Vec<_> = it.collect(); + let (tags, new_ext_var) = tags.unsorted_tags_and_ext(subs, ext_var); + let mut sorted_fields = tags.tags; let interns = &env.interns; let home = env.home; diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 08bd430f36..f00039f18d 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -830,7 +830,10 @@ fn integer_type( ) { // define the type Signed64 (which is an alias for [ @Signed64 ]) { - let tags = UnionTags::insert_into_subs(subs, [(TagName::Private(num_at_signed64), [])]); + let tags = UnionTags::insert_into_subs::( + subs, + [(TagName::Private(num_at_signed64), [])], + ); subs.set_content(at_signed64, { Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) @@ -1082,7 +1085,7 @@ impl Subs { Content::Structure(FlatType::EmptyTagUnion), ); - let bool_union_tags = UnionTags::insert_into_subs( + let bool_union_tags = UnionTags::insert_into_subs::( &mut subs, [ (TagName::Global("False".into()), []), @@ -1724,10 +1727,11 @@ impl UnionTags { pub fn compare(x: &(TagName, T), y: &(TagName, T)) -> std::cmp::Ordering { first(x, y) } - pub fn insert_into_subs(subs: &mut Subs, input: I) -> Self + pub fn insert_into_subs(subs: &mut Subs, input: I) -> Self where + V: Into, I: IntoIterator, - I2: IntoIterator, + I2: IntoIterator, { let tag_names_start = subs.tag_names.len() as u32; let variables_start = subs.variable_slices.len() as u32; @@ -1740,7 +1744,8 @@ impl UnionTags { let mut length = 0; for (k, v) in it { - let variables = VariableSubsSlice::insert_into_subs(subs, v.into_iter()); + let variables = + VariableSubsSlice::insert_into_subs(subs, v.into_iter().map(|v| v.into())); subs.tag_names.push(k); subs.variable_slices.push(variables); @@ -1813,16 +1818,17 @@ impl UnionTags { it.map(f) } - pub fn unsorted_iterator_and_ext<'a>( + #[inline(always)] + pub fn unsorted_tags_and_ext<'a>( &'a self, subs: &'a Subs, ext: Variable, - ) -> (impl Iterator + 'a, Variable) { + ) -> (UnsortedUnionTags<'a>, Variable) { let (it, ext) = crate::types::gather_tags_unsorted_iter(subs, *self, ext); - let f = move |(label, slice): (_, SubsSlice)| (label, subs.get_subs_slice(slice)); + let it = it.map(f); - (it.map(f), ext) + (UnsortedUnionTags { tags: it.collect() }, ext) } #[inline(always)] @@ -1877,6 +1883,25 @@ impl UnionTags { } } +#[derive(Debug)] +pub struct UnsortedUnionTags<'a> { + pub tags: Vec<(&'a TagName, &'a [Variable])>, +} + +impl<'a> UnsortedUnionTags<'a> { + pub fn is_newtype_wrapper(&self, _subs: &Subs) -> bool { + if self.tags.len() != 1 { + return false; + } + self.tags[0].1.len() == 1 + } + + pub fn get_newtype(&self, _subs: &Subs) -> (&TagName, Variable) { + let (tag_name, vars) = self.tags[0]; + (tag_name, vars[0]) + } +} + pub type SortedTagsIterator<'a> = Box + 'a>; pub type SortedTagsSlicesIterator<'a> = Box + 'a>; @@ -2025,6 +2050,21 @@ impl RecordFields { it } + #[inline(always)] + pub fn unsorted_iterator_and_ext<'a>( + &'a self, + subs: &'a Subs, + ext: Variable, + ) -> ( + impl Iterator)> + 'a, + Variable, + ) { + let (it, ext) = crate::types::gather_fields_unsorted_iter(subs, *self, ext) + .expect("Something weird ended up in a record type"); + + (it, ext) + } + /// Get a sorted iterator over the fields of this record type /// /// Implementation: When the record has an `ext` variable that is the empty record, then From 3692b38447ca504ba70f81b6d52fbd11acd2f329 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 23 Jan 2022 12:32:45 -0500 Subject: [PATCH 295/541] Disable wasm tests for now --- compiler/test_gen/src/gen_tags.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index 27ce93b468..bbbc9fa490 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -1369,7 +1369,7 @@ fn monomorphized_tag_with_polymorphic_arg_and_monomorphic_arg() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm"))] fn issue_2365_monomorphize_tag_with_non_empty_ext_var() { assert_evals_to!( indoc!( @@ -1394,7 +1394,7 @@ fn issue_2365_monomorphize_tag_with_non_empty_ext_var() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm"))] fn issue_2365_monomorphize_tag_with_non_empty_ext_var_wrapped() { assert_evals_to!( indoc!( @@ -1422,7 +1422,7 @@ fn issue_2365_monomorphize_tag_with_non_empty_ext_var_wrapped() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm"))] fn issue_2365_monomorphize_tag_with_non_empty_ext_var_wrapped_nested() { assert_evals_to!( indoc!( From 6545968c34b3e03abab5f72a0907e76c11c2d18e Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 23 Jan 2022 18:33:50 +0100 Subject: [PATCH 296/541] import UserApp's provided types into the Package-Config module --- cli/src/format.rs | 1 + compiler/load/src/file.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/cli/src/format.rs b/cli/src/format.rs index ea2f1afea0..c628c931dd 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -177,6 +177,7 @@ impl<'a> RemoveSpaces<'a> for Module<'a> { packages: header.packages.remove_spaces(arena), imports: header.imports.remove_spaces(arena), provides: header.provides.remove_spaces(arena), + provides_types: header.provides_types.map(|ts| ts.remove_spaces(arena)), to: header.to.remove_spaces(arena), before_header: &[], after_app_keyword: &[], diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index bc2cd1c4df..60f3fddfa7 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -26,6 +26,7 @@ use roc_mono::layout::{Layout, LayoutCache, LayoutProblem}; use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation}; use roc_parse::header::PackageName; use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent}; +use roc_parse::ident::UppercaseIdent; use roc_parse::module::module_defs; use roc_parse::parser::{FileError, Parser, SyntaxError}; use roc_region::all::{LineInfo, Loc, Region}; @@ -2920,6 +2921,7 @@ struct PlatformHeaderInfo<'a> { packages: &'a [Loc>], provides: &'a [Loc>], requires: &'a [Loc>], + requires_types: &'a [Loc>], imports: &'a [Loc>], } @@ -2940,6 +2942,7 @@ fn send_header_two<'a>( packages, provides, requires, + requires_types, imports, } = info; @@ -3044,6 +3047,18 @@ fn send_header_two<'a>( scope.insert(ident, (symbol, entry.ident.region)); } + + for entry in requires_types { + let string: &str = entry.value.into(); + let ident: Ident = string.into(); + let ident_id = ident_ids.get_or_insert(&ident); + let symbol = Symbol::new(app_module_id, ident_id); + + // Since this value is exposed, add it to our module's default scope. + debug_assert!(!scope.contains_key(&ident.clone())); + + scope.insert(ident, (symbol, entry.region)); + } } let ident_ids = ident_ids_by_module.get_mut(&home).unwrap(); @@ -3289,6 +3304,7 @@ fn fabricate_pkg_config_module<'a>( header.requires.signature.region, header.requires.signature.extract_spaces().item, )]), + requires_types: unspace(arena, header.requires.rigids.items), imports: unspace(arena, header.imports.items), }; From 632d809f2a0ce541c5a2f054f08db58b9f9bbddc Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sat, 22 Jan 2022 20:27:56 -0500 Subject: [PATCH 297/541] Don't end repl inputs on lines that have incomplete function bodies --- cli/src/repl.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cli/src/repl.rs b/cli/src/repl.rs index 61c10a03a5..0ca12fba55 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -1,7 +1,7 @@ use const_format::concatcp; #[cfg(feature = "llvm")] use gen::{gen_and_eval, ReplOutput}; -use roc_parse::parser::{EExpr, SyntaxError}; +use roc_parse::parser::{EExpr, ELambda, SyntaxError}; use rustyline::highlight::{Highlighter, PromptInfo}; use rustyline::validate::{self, ValidationContext, ValidationResult, Validator}; use rustyline_derive::{Completer, Helper, Hinter}; @@ -97,7 +97,8 @@ impl Validator for InputValidator { match roc_parse::expr::parse_loc_expr(0, &arena, state) { // Special case some syntax errors to allow for multi-line inputs Err((_, EExpr::DefMissingFinalExpr(_), _)) - | Err((_, EExpr::DefMissingFinalExpr2(_, _), _)) => { + | Err((_, EExpr::DefMissingFinalExpr2(_, _), _)) + | Err((_, EExpr::Lambda(ELambda::Body(_, _), _), _)) => { Ok(ValidationResult::Incomplete) } _ => Ok(ValidationResult::Valid(None)), From 0eede1cd86186c7d25957954b74949a48ce7a144 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sat, 22 Jan 2022 20:29:17 -0500 Subject: [PATCH 298/541] Generate unique symbols for shadowing identifiers This code has a shadowing error: ``` b = False f = \b -> b f b ``` but prior to this commit, the compiler would hit an internal error during monomorphization and not even get to report the error. The reason was that when we entered the closure `\b -> b`, we would try to introduce the identifier `b` to the scope, see that it shadows an existing identifier, and not insert the identifier. But this meant that when checking the body of `\b -> b`, we would think that we captured the value `b` in the outer scope, but that's incorrect! The present patch fixes the issue by generating new symbols for shadowing identifiers, so deeper scopes pick up the correct reference. This also means in the future we may be able to compile and execute code with shadows, even though it will still be an error. Closes #2343 --- cli/tests/repl_eval.rs | 34 +++++++++++++++++++++++++++++++ compiler/can/src/annotation.rs | 2 +- compiler/can/src/def.rs | 8 +++----- compiler/can/src/module.rs | 2 +- compiler/can/src/pattern.rs | 24 +++++++++++----------- compiler/can/src/scope.rs | 14 +++++++++---- compiler/constrain/src/pattern.rs | 7 +++---- compiler/mono/src/ir.rs | 6 +++--- 8 files changed, 67 insertions(+), 30 deletions(-) diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index 3d36609272..5069c29f98 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -925,4 +925,38 @@ mod repl_eval { ), ); } + + #[test] + fn issue_2343_complete_mono_with_shadowed_vars() { + expect_failure( + indoc!( + r#" + b = False + f = \b -> + when b is + True -> 5 + False -> 15 + f b + "# + ), + indoc!( + r#" + ── DUPLICATE NAME ────────────────────────────────────────────────────────────── + + The b name is first defined here: + + 4│ b = False + ^ + + But then it's defined a second time here: + + 5│ f = \b -> + ^ + + Since these variables have the same name, it's easy to use the wrong + one on accident. Give one of them a new name. + "# + ), + ); + } } diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 2867a23f03..a78ed60cb9 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -390,7 +390,7 @@ fn can_annotation_help( ) { Ok(symbol) => symbol, - Err((original_region, shadow)) => { + Err((original_region, shadow, _new_symbol)) => { let problem = Problem::Shadowed(original_region, shadow.clone()); env.problem(roc_problem::can::Problem::ShadowingInAnnotation { diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 05584133e2..43e7af2e95 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -809,7 +809,7 @@ fn pattern_to_vars_by_symbol( ) { use Pattern::*; match pattern { - Identifier(symbol) => { + Identifier(symbol) | Shadowed(_, _, symbol) => { vars_by_symbol.insert(*symbol, expr_var); } @@ -832,8 +832,6 @@ fn pattern_to_vars_by_symbol( | Underscore | MalformedPattern(_, _) | UnsupportedPattern(_) => {} - - Shadowed(_, _) => {} } } @@ -884,7 +882,7 @@ fn canonicalize_pending_def<'a>( Pattern::Identifier(symbol) => RuntimeError::NoImplementationNamed { def_symbol: *symbol, }, - Pattern::Shadowed(region, loc_ident) => RuntimeError::Shadowing { + Pattern::Shadowed(region, loc_ident, _new_symbol) => RuntimeError::Shadowing { original_region: *region, shadow: loc_ident.clone(), }, @@ -1502,7 +1500,7 @@ fn to_pending_def<'a>( )) } - Err((original_region, loc_shadowed_symbol)) => { + Err((original_region, loc_shadowed_symbol, _new_symbol)) => { env.problem(Problem::ShadowingInAnnotation { original_region, shadow: loc_shadowed_symbol, diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 1f57cc8734..7a292807bb 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -398,7 +398,7 @@ fn fix_values_captured_in_closure_pattern( | FloatLiteral(_, _, _) | StrLiteral(_) | Underscore - | Shadowed(_, _) + | Shadowed(..) | MalformedPattern(_, _) | UnsupportedPattern(_) => (), } diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 66132dbafb..6c9392e919 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -32,7 +32,7 @@ pub enum Pattern { Underscore, // Runtime Exceptions - Shadowed(Region, Loc), + Shadowed(Region, Loc, Symbol), // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! UnsupportedPattern(Region), // parse error patterns @@ -65,7 +65,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec) { use Pattern::*; match pattern { - Identifier(symbol) => { + Identifier(symbol) | Shadowed(_, _, symbol) => { symbols.push(*symbol); } @@ -92,8 +92,6 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec) { | Underscore | MalformedPattern(_, _) | UnsupportedPattern(_) => {} - - Shadowed(_, _) => {} } } @@ -121,13 +119,14 @@ pub fn canonicalize_pattern<'a>( Pattern::Identifier(symbol) } - Err((original_region, shadow)) => { + Err((original_region, shadow, new_symbol)) => { env.problem(Problem::RuntimeError(RuntimeError::Shadowing { original_region, shadow: shadow.clone(), })); + output.references.bound_symbols.insert(new_symbol); - Pattern::Shadowed(original_region, shadow) + Pattern::Shadowed(original_region, shadow, new_symbol) } }, GlobalTag(name) => { @@ -268,7 +267,7 @@ pub fn canonicalize_pattern<'a>( }, }); } - Err((original_region, shadow)) => { + Err((original_region, shadow, new_symbol)) => { env.problem(Problem::RuntimeError(RuntimeError::Shadowing { original_region, shadow: shadow.clone(), @@ -278,7 +277,8 @@ pub fn canonicalize_pattern<'a>( // are, we're definitely shadowed and will // get a runtime exception as soon as we // encounter the first bad pattern. - opt_erroneous = Some(Pattern::Shadowed(original_region, shadow)); + opt_erroneous = + Some(Pattern::Shadowed(original_region, shadow, new_symbol)); } }; } @@ -339,7 +339,7 @@ pub fn canonicalize_pattern<'a>( }, }); } - Err((original_region, shadow)) => { + Err((original_region, shadow, new_symbol)) => { env.problem(Problem::RuntimeError(RuntimeError::Shadowing { original_region, shadow: shadow.clone(), @@ -349,7 +349,8 @@ pub fn canonicalize_pattern<'a>( // are, we're definitely shadowed and will // get a runtime exception as soon as we // encounter the first bad pattern. - opt_erroneous = Some(Pattern::Shadowed(original_region, shadow)); + opt_erroneous = + Some(Pattern::Shadowed(original_region, shadow, new_symbol)); } }; } @@ -452,7 +453,7 @@ fn add_bindings_from_patterns( use Pattern::*; match pattern { - Identifier(symbol) => { + Identifier(symbol) | Shadowed(_, _, symbol) => { answer.push((*symbol, *region)); } AppliedTag { @@ -477,7 +478,6 @@ fn add_bindings_from_patterns( | FloatLiteral(_, _, _) | StrLiteral(_) | Underscore - | Shadowed(_, _) | MalformedPattern(_, _) | UnsupportedPattern(_) => (), } diff --git a/compiler/can/src/scope.rs b/compiler/can/src/scope.rs index eeab87438d..de7b233b08 100644 --- a/compiler/can/src/scope.rs +++ b/compiler/can/src/scope.rs @@ -110,15 +110,21 @@ impl Scope { exposed_ident_ids: &IdentIds, all_ident_ids: &mut IdentIds, region: Region, - ) -> Result)> { + ) -> Result, Symbol)> { match self.idents.get(&ident) { - Some((_, original_region)) => { + Some(&(_, original_region)) => { let shadow = Loc { - value: ident, + value: ident.clone(), region, }; - Err((*original_region, shadow)) + let ident_id = all_ident_ids.add(ident.clone()); + let symbol = Symbol::new(self.home, ident_id); + + self.symbols.insert(symbol, region); + self.idents.insert(ident, (symbol, region)); + + Err((original_region, shadow, symbol)) } None => { // If this IdentId was already added previously diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 3844ad3f08..2a1ce6fa46 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -48,12 +48,11 @@ fn headers_from_annotation_help( headers: &mut SendMap>, ) -> bool { match pattern { - Identifier(symbol) => { + Identifier(symbol) | Shadowed(_, _, symbol) => { headers.insert(*symbol, annotation.clone()); true } Underscore - | Shadowed(_, _) | MalformedPattern(_, _) | UnsupportedPattern(_) | NumLiteral(_, _, _) @@ -159,11 +158,11 @@ pub fn constrain_pattern( PresenceConstraint::IsOpen, )); } - Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed(_, _) => { + Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) => { // Neither the _ pattern nor erroneous ones add any constraints. } - Identifier(symbol) => { + Identifier(symbol) | Shadowed(_, _, symbol) => { if destruct_position { state.constraints.push(Constraint::Present( expected.get_type_ref().clone(), diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 9fd2034d0b..b6c13f7f75 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -1980,12 +1980,12 @@ fn pattern_to_when<'a>( // for underscore we generate a dummy Symbol (env.unique_symbol(), body) } - Shadowed(region, loc_ident) => { + Shadowed(region, loc_ident, new_symbol) => { let error = roc_problem::can::RuntimeError::Shadowing { original_region: *region, shadow: loc_ident.clone(), }; - (env.unique_symbol(), Loc::at_zero(RuntimeError(error))) + (*new_symbol, Loc::at_zero(RuntimeError(error))) } UnsupportedPattern(region) => { @@ -7652,7 +7652,7 @@ fn from_can_pattern_help<'a>( } } StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())), - Shadowed(region, ident) => Err(RuntimeError::Shadowing { + Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing { original_region: *region, shadow: ident.clone(), }), From 095204ec7afb95118bad484089ad4fea8411d29f Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 23 Jan 2022 01:06:53 -0500 Subject: [PATCH 299/541] Fix can tests --- compiler/can/tests/test_can.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index 26ec355746..196be508c0 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -368,9 +368,11 @@ mod test_can { let arena = Bump::new(); let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src); - assert_eq!(problems.len(), 1); + assert_eq!(problems.len(), 2); assert!(problems.iter().all(|problem| match problem { Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true, + // Due to one of the shadows + Problem::UnusedDef(..) => true, _ => false, })); } @@ -389,9 +391,11 @@ mod test_can { let arena = Bump::new(); let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src); - assert_eq!(problems.len(), 1); + assert_eq!(problems.len(), 2); assert!(problems.iter().all(|problem| match problem { Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true, + // Due to one of the shadows + Problem::UnusedDef(..) => true, _ => false, })); } @@ -410,10 +414,12 @@ mod test_can { let arena = Bump::new(); let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src); - assert_eq!(problems.len(), 1); + assert_eq!(problems.len(), 2); println!("{:#?}", problems); assert!(problems.iter().all(|problem| match problem { Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true, + // Due to one of the shadows + Problem::UnusedDef(..) => true, _ => false, })); } From c6d47dbca56edb0d8f6080b971ad104abc970ffb Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 23 Jan 2022 02:12:17 -0500 Subject: [PATCH 300/541] Fix reporting tests --- reporting/tests/test_reporting.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index d69da25ae1..c9550b9ab0 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -424,6 +424,16 @@ mod test_reporting { `Booly` is not used anywhere in your code. + 3│ Booly : [ Yes, No, Maybe ] + ^^^^^^^^^^^^^^^^^^^^^^^^^^ + + If you didn't intend on using `Booly` then remove it so future readers + of your code don't wonder why it is there. + + ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + + `Booly` is not used anywhere in your code. + 1│ Booly : [ Yes, No ] ^^^^^^^^^^^^^^^^^^^ From 7b22b42a8fda4927fc6c9c7446f1228b90188d09 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Sun, 23 Jan 2022 00:02:25 -0700 Subject: [PATCH 301/541] Fix args order in some docs These changes reflect the builtins as they're currently implemented, but I wish that instead they worked as (previously) described. Should this PR be abandoned in favor of an actual argument swap? --- compiler/builtins/docs/List.roc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index 09700da429..b024cea057 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -205,7 +205,7 @@ empty : List * ## Returns a list with the given length, where every element is the given value. ## ## -repeat : elem, Nat -> List elem +repeat : Nat, elem -> List elem ## Returns a list of all the integers between one and another, ## including both of the given numbers. @@ -277,7 +277,7 @@ map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e ## This works like [List.map], except it also passes the index ## of the element to the conversion function. -mapWithIndex : List before, (before, Nat -> after) -> List after +mapWithIndex : List before, (Nat, before -> after) -> List after ## This works like [List.map], except at any time you can return `Err` to ## cancel the entire operation immediately, and return that #Err. From 29303c4ba4109e84e30cbc7709622271ac82c87a Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Sun, 23 Jan 2022 00:16:40 -0700 Subject: [PATCH 302/541] Add some missing exposures to docs --- compiler/builtins/docs/List.roc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index 09700da429..25ddbc6344 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -32,6 +32,8 @@ interface List set, single, sortWith, + split, + sublist, sum, swap, walk, From 71f359fbdc7dd576eef0a14c677d917ab95824e5 Mon Sep 17 00:00:00 2001 From: Mats Sigge Date: Sun, 23 Jan 2022 09:40:34 +0100 Subject: [PATCH 303/541] Move macros from roc_reporting to new roc_error_macros module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `internal_error!` and `user_error!´ macros can't be used everywhere when they live in `roc_reporting` due to circular dependencies. --- Cargo.lock | 13 +++++--- Cargo.toml | 1 + Earthfile | 2 +- ast/Cargo.toml | 2 +- ast/src/lang/core/str.rs | 2 +- cli/Cargo.toml | 1 + cli/src/format.rs | 2 +- compiler/gen_dev/Cargo.toml | 2 +- compiler/gen_dev/src/generic64/aarch64.rs | 2 +- compiler/gen_dev/src/generic64/mod.rs | 2 +- compiler/gen_dev/src/generic64/x86_64.rs | 2 +- compiler/gen_dev/src/lib.rs | 2 +- compiler/gen_dev/src/object_builder.rs | 2 +- compiler/gen_llvm/Cargo.toml | 2 +- compiler/gen_llvm/src/llvm/build.rs | 2 +- compiler/gen_wasm/Cargo.toml | 2 +- compiler/gen_wasm/src/backend.rs | 2 +- compiler/gen_wasm/src/low_level.rs | 2 +- compiler/gen_wasm/src/storage.rs | 2 +- .../gen_wasm/src/wasm_module/code_builder.rs | 2 +- compiler/gen_wasm/src/wasm_module/mod.rs | 2 +- compiler/gen_wasm/src/wasm_module/opcodes.rs | 2 +- compiler/gen_wasm/src/wasm_module/sections.rs | 2 +- .../gen_wasm/src/wasm_module/serialize.rs | 2 +- error_macros/Cargo.toml | 8 +++++ error_macros/src/lib.rs | 31 ++++++++++++++++++ reporting/src/lib.rs | 32 ------------------- 27 files changed, 71 insertions(+), 57 deletions(-) create mode 100644 error_macros/Cargo.toml create mode 100644 error_macros/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index fb77fdb6cc..f98de87446 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3248,12 +3248,12 @@ dependencies = [ "roc_builtins", "roc_can", "roc_collections", + "roc_error_macros", "roc_load", "roc_module", "roc_parse", "roc_problem", "roc_region", - "roc_reporting", "roc_types", "roc_unify", "snafu", @@ -3338,6 +3338,7 @@ dependencies = [ "roc_constrain", "roc_docs", "roc_editor", + "roc_error_macros", "roc_fmt", "roc_gen_llvm", "roc_linker", @@ -3470,6 +3471,10 @@ dependencies = [ "winit", ] +[[package]] +name = "roc_error_macros" +version = "0.1.0" + [[package]] name = "roc_fmt" version = "0.1.0" @@ -3495,12 +3500,12 @@ dependencies = [ "roc_builtins", "roc_can", "roc_collections", + "roc_error_macros", "roc_module", "roc_mono", "roc_parse", "roc_problem", "roc_region", - "roc_reporting", "roc_solve", "roc_std", "roc_types", @@ -3517,9 +3522,9 @@ dependencies = [ "morphic_lib", "roc_builtins", "roc_collections", + "roc_error_macros", "roc_module", "roc_mono", - "roc_reporting", "roc_std", "target-lexicon", ] @@ -3531,9 +3536,9 @@ dependencies = [ "bumpalo", "roc_builtins", "roc_collections", + "roc_error_macros", "roc_module", "roc_mono", - "roc_reporting", "roc_std", ] diff --git a/Cargo.toml b/Cargo.toml index 234708adcb..465e29e8a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "ast", "cli", "code_markup", + "error_macros", "reporting", "roc_std", "test_utils", diff --git a/Earthfile b/Earthfile index fdf87c0946..a1a1ce8c14 100644 --- a/Earthfile +++ b/Earthfile @@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt: copy-dirs: FROM +install-zig-llvm-valgrind-clippy-rustfmt - COPY --dir cli cli_utils compiler docs editor ast code_markup utils test_utils reporting roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./ + COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros utils test_utils reporting roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./ test-zig: FROM +install-zig-llvm-valgrind-clippy-rustfmt diff --git a/ast/Cargo.toml b/ast/Cargo.toml index 9c3498821c..66407ee82b 100644 --- a/ast/Cargo.toml +++ b/ast/Cargo.toml @@ -17,7 +17,7 @@ roc_problem = { path = "../compiler/problem" } roc_types = { path = "../compiler/types" } roc_unify = { path = "../compiler/unify"} roc_load = { path = "../compiler/load" } -roc_reporting = { path = "../reporting" } +roc_error_macros = { path = "../error_macros" } arrayvec = "0.7.2" bumpalo = { version = "3.8.0", features = ["collections"] } libc = "0.2.106" diff --git a/ast/src/lang/core/str.rs b/ast/src/lang/core/str.rs index 982e684629..dee65a3436 100644 --- a/ast/src/lang/core/str.rs +++ b/ast/src/lang/core/str.rs @@ -1,6 +1,6 @@ +use roc_error_macros::internal_error; use roc_module::{called_via::CalledVia, symbol::Symbol}; use roc_parse::ast::StrLiteral; -use roc_reporting::internal_error; use crate::{ ast_error::{ASTResult, UnexpectedASTNode}, diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 7b3d945425..bba74100cc 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -62,6 +62,7 @@ roc_gen_llvm = { path = "../compiler/gen_llvm", optional = true } roc_build = { path = "../compiler/build", default-features = false } roc_fmt = { path = "../compiler/fmt" } roc_reporting = { path = "../reporting" } +roc_error_macros = { path = "../error_macros" } roc_editor = { path = "../editor", optional = true } roc_linker = { path = "../linker" } clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] } diff --git a/cli/src/format.rs b/cli/src/format.rs index 13f3edc71f..ea289b4647 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use bumpalo::collections::Vec; use bumpalo::Bump; +use roc_error_macros::{internal_error, user_error}; use roc_fmt::def::fmt_def; use roc_fmt::module::fmt_module; use roc_fmt::Buf; @@ -21,7 +22,6 @@ use roc_parse::{ state::State, }; use roc_region::all::{Loc, Region}; -use roc_reporting::{internal_error, user_error}; pub fn format(files: std::vec::Vec) { for file in files { diff --git a/compiler/gen_dev/Cargo.toml b/compiler/gen_dev/Cargo.toml index 41fe784e7e..8efcaca241 100644 --- a/compiler/gen_dev/Cargo.toml +++ b/compiler/gen_dev/Cargo.toml @@ -16,7 +16,7 @@ roc_builtins = { path = "../builtins" } roc_unify = { path = "../unify" } roc_solve = { path = "../solve" } roc_mono = { path = "../mono" } -roc_reporting = { path = "../../reporting" } +roc_error_macros = { path = "../../error_macros" } bumpalo = { version = "3.8.0", features = ["collections"] } target-lexicon = "0.12.2" # TODO: Deal with the update of object to 0.27. diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 3c493882dd..591c799052 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -3,9 +3,9 @@ use crate::Relocation; use bumpalo::collections::Vec; use packed_struct::prelude::*; use roc_collections::all::MutMap; +use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::layout::Layout; -use roc_reporting::internal_error; #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] #[allow(dead_code)] diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 1a3a549be0..26f336ab40 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -2,11 +2,11 @@ use crate::{Backend, Env, Relocation}; use bumpalo::collections::Vec; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::{MutMap, MutSet}; +use roc_error_macros::internal_error; use roc_module::symbol::{Interns, Symbol}; use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::ir::{BranchInfo, JoinPointId, Literal, Param, ProcLayout, SelfRecursive, Stmt}; use roc_mono::layout::{Builtin, Layout}; -use roc_reporting::internal_error; use std::marker::PhantomData; pub mod aarch64; diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 7060f78af0..b6cba72e95 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -5,9 +5,9 @@ use crate::{ use bumpalo::collections::Vec; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::MutMap; +use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout}; -use roc_reporting::internal_error; // Not sure exactly how I want to represent registers. // If we want max speed, we would likely make them structs that impl the same trait to avoid ifs. diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 20d3d6e8b4..af382ee988 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -5,6 +5,7 @@ use bumpalo::{collections::Vec, Bump}; use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_collections::all::{MutMap, MutSet}; +use roc_error_macros::internal_error; use roc_module::ident::{ModuleName, TagName}; use roc_module::low_level::{LowLevel, LowLevelWrapperType}; use roc_module::symbol::{Interns, ModuleId, Symbol}; @@ -14,7 +15,6 @@ use roc_mono::ir::{ SelfRecursive, Stmt, }; use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds}; -use roc_reporting::internal_error; mod generic64; mod object_builder; diff --git a/compiler/gen_dev/src/object_builder.rs b/compiler/gen_dev/src/object_builder.rs index ac8fa1d922..d8675c880d 100644 --- a/compiler/gen_dev/src/object_builder.rs +++ b/compiler/gen_dev/src/object_builder.rs @@ -8,11 +8,11 @@ use object::{ SymbolFlags, SymbolKind, SymbolScope, }; use roc_collections::all::MutMap; +use roc_error_macros::internal_error; use roc_module::symbol; use roc_module::symbol::Interns; use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::layout::LayoutIds; -use roc_reporting::internal_error; use target_lexicon::{Architecture as TargetArch, BinaryFormat as TargetBF, Triple}; // This is used by some code below which is currently commented out. diff --git a/compiler/gen_llvm/Cargo.toml b/compiler/gen_llvm/Cargo.toml index 2c8fb91ab9..32a79c4767 100644 --- a/compiler/gen_llvm/Cargo.toml +++ b/compiler/gen_llvm/Cargo.toml @@ -10,7 +10,7 @@ edition = "2018" roc_collections = { path = "../collections" } roc_module = { path = "../module" } roc_builtins = { path = "../builtins" } -roc_reporting = { path = "../../reporting" } +roc_error_macros = { path = "../../error_macros" } roc_mono = { path = "../mono" } roc_std = { path = "../../roc_std" } morphic_lib = { path = "../../vendor/morphic_lib" } diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index e30b8aa084..d1a08a4bd8 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -55,6 +55,7 @@ use morphic_lib::{ use roc_builtins::bitcode::{self, FloatWidth, IntWidth, IntrinsicName}; use roc_builtins::{float_intrinsic, int_intrinsic}; use roc_collections::all::{ImMap, MutMap, MutSet}; +use roc_error_macros::internal_error; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_mono::ir::{ @@ -62,7 +63,6 @@ use roc_mono::ir::{ ModifyRc, OptLevel, ProcLayout, }; use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, TagIdIntType, UnionLayout}; -use roc_reporting::internal_error; use target_lexicon::{Architecture, OperatingSystem, Triple}; /// This is for Inkwell's FunctionValue::verify - we want to know the verification diff --git a/compiler/gen_wasm/Cargo.toml b/compiler/gen_wasm/Cargo.toml index c4e9386c69..90578803f7 100644 --- a/compiler/gen_wasm/Cargo.toml +++ b/compiler/gen_wasm/Cargo.toml @@ -12,4 +12,4 @@ roc_collections = { path = "../collections" } roc_module = { path = "../module" } roc_mono = { path = "../mono" } roc_std = { path = "../../roc_std" } -roc_reporting = { path = "../../reporting" } +roc_error_macros = { path = "../../error_macros" } diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 97533c074b..1200a80e12 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -12,8 +12,8 @@ use roc_mono::ir::{ ProcLayout, Stmt, }; +use roc_error_macros::internal_error; use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout}; -use roc_reporting::internal_error; use crate::layout::{CallConv, ReturnMethod, WasmLayout}; use crate::low_level::LowLevelCall; diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index 1ea652d999..eae0a0cfb1 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -1,9 +1,9 @@ use bumpalo::collections::Vec; use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; +use roc_error_macros::internal_error; use roc_module::low_level::{LowLevel, LowLevel::*}; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout, UnionLayout}; -use roc_reporting::internal_error; use crate::backend::WasmBackend; use crate::layout::CallConv; diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 9caa0ba21e..c60220d7ae 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -2,9 +2,9 @@ use bumpalo::collections::Vec; use bumpalo::Bump; use roc_collections::all::MutMap; +use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::layout::Layout; -use roc_reporting::internal_error; use crate::layout::{ CallConv, ReturnMethod, StackMemoryFormat, WasmLayout, ZigVersion, BUILTINS_ZIG_VERSION, diff --git a/compiler/gen_wasm/src/wasm_module/code_builder.rs b/compiler/gen_wasm/src/wasm_module/code_builder.rs index ca80469ba7..6ba1042cb1 100644 --- a/compiler/gen_wasm/src/wasm_module/code_builder.rs +++ b/compiler/gen_wasm/src/wasm_module/code_builder.rs @@ -1,7 +1,7 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; use core::panic; -use roc_reporting::internal_error; +use roc_error_macros::internal_error; use roc_module::symbol::Symbol; diff --git a/compiler/gen_wasm/src/wasm_module/mod.rs b/compiler/gen_wasm/src/wasm_module/mod.rs index 5f494967b8..2ac492e620 100644 --- a/compiler/gen_wasm/src/wasm_module/mod.rs +++ b/compiler/gen_wasm/src/wasm_module/mod.rs @@ -8,7 +8,7 @@ pub mod serialize; use bumpalo::Bump; pub use code_builder::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState}; pub use linking::SymInfo; -use roc_reporting::internal_error; +use roc_error_macros::internal_error; pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature}; use crate::wasm_module::serialize::SkipBytes; diff --git a/compiler/gen_wasm/src/wasm_module/opcodes.rs b/compiler/gen_wasm/src/wasm_module/opcodes.rs index cd6f8b59d9..04f779dfbf 100644 --- a/compiler/gen_wasm/src/wasm_module/opcodes.rs +++ b/compiler/gen_wasm/src/wasm_module/opcodes.rs @@ -1,4 +1,4 @@ -use roc_reporting::internal_error; +use roc_error_macros::internal_error; use super::serialize::{parse_u32_or_panic, SkipBytes}; diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 117d705a07..ea8ca5ec62 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -1,7 +1,7 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; use roc_collections::all::MutMap; -use roc_reporting::internal_error; +use roc_error_macros::internal_error; use super::dead_code::{ copy_preloads_shrinking_dead_fns, parse_preloads_call_graph, trace_call_graph, diff --git a/compiler/gen_wasm/src/wasm_module/serialize.rs b/compiler/gen_wasm/src/wasm_module/serialize.rs index 8c2204a04b..99561309c5 100644 --- a/compiler/gen_wasm/src/wasm_module/serialize.rs +++ b/compiler/gen_wasm/src/wasm_module/serialize.rs @@ -1,7 +1,7 @@ use std::{fmt::Debug, iter::FromIterator}; use bumpalo::collections::vec::Vec; -use roc_reporting::internal_error; +use roc_error_macros::internal_error; /// In the WebAssembly binary format, all integers are variable-length encoded (using LEB-128) /// A small value like 3 or 100 is encoded as 1 byte. The value 128 needs 2 bytes, etc. diff --git a/error_macros/Cargo.toml b/error_macros/Cargo.toml new file mode 100644 index 0000000000..d5c7276898 --- /dev/null +++ b/error_macros/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "roc_error_macros" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" + +[dependencies] diff --git a/error_macros/src/lib.rs b/error_macros/src/lib.rs new file mode 100644 index 0000000000..d9b2e0e9a3 --- /dev/null +++ b/error_macros/src/lib.rs @@ -0,0 +1,31 @@ +/// `internal_error!` should be used whenever a compiler invariant is broken. +/// It is a wrapper around panic that tells the user to file a bug. +/// This should only be used in cases where there would be a compiler bug and the user can't fix it. +/// If there is simply an unimplemented feature, please use `unimplemented!` +/// If there is a user error, please use roc_reporting to print a nice error message. +#[macro_export] +macro_rules! internal_error { + ($($arg:tt)*) => ({ + eprintln!("An internal compiler expectation was broken."); + eprintln!("This is definitely a compiler bug."); + // TODO: update this to the new bug template. + eprintln!("Please file an issue here: https://github.com/rtfeldman/roc/issues/new/choose"); + #[allow(clippy::panic)] { + panic!($($arg)*); + } + }) +} + +/// `user_error!` should only ever be used temporarily. +/// It is a way to document locations where we do not yet have nice error reporting. +/// All cases of `user_error!` should eventually be replaced with pretty error printing using roc_reporting. +#[macro_export] +macro_rules! user_error { + ($($arg:tt)*) => ({ + eprintln!("We ran into an issue while compiling your code."); + eprintln!("Sadly, we don't havs a pretty error message for this case yet."); + eprintln!("If you can't figure out the problem from the context below, please reach out at: https://roc.zulipchat.com/"); + eprintln!($($arg)*); + std::process::exit(1); + }) +} diff --git a/reporting/src/lib.rs b/reporting/src/lib.rs index 1f7520b931..ff2791a5f8 100644 --- a/reporting/src/lib.rs +++ b/reporting/src/lib.rs @@ -4,35 +4,3 @@ pub mod error; pub mod report; - -/// `internal_error!` should be used whenever a compiler invariant is broken. -/// It is a wrapper around panic that tells the user to file a bug. -/// This should only be used in cases where there would be a compiler bug and the user can't fix it. -/// If there is simply an unimplemented feature, please use `unimplemented!` -/// If there is a user error, please use roc_reporting to print a nice error message. -#[macro_export] -macro_rules! internal_error { - ($($arg:tt)*) => ({ - eprintln!("An internal compiler expectation was broken."); - eprintln!("This is definitely a compiler bug."); - // TODO: update this to the new bug template. - eprintln!("Please file an issue here: https://github.com/rtfeldman/roc/issues/new/choose"); - #[allow(clippy::panic)] { - panic!($($arg)*); - } - }) -} - -/// `user_error!` should only ever be used temporarily. -/// It is a way to document locations where we do not yet have nice error reporting. -/// All cases of `user_error!` should eventually be replaced with pretty error printing using roc_reporting. -#[macro_export] -macro_rules! user_error { - ($($arg:tt)*) => ({ - eprintln!("We ran into an issue while compiling your code."); - eprintln!("Sadly, we don't havs a pretty error message for this case yet."); - eprintln!("If you can't figure out the problem from the context below, please reach out at: https://roc.zulipchat.com/"); - eprintln!($($arg)*); - std::process::exit(1); - }) -} From 69bcec71ebdce2ff203bf2ca3ec89140896d7fa2 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 23 Jan 2022 18:44:50 +0100 Subject: [PATCH 304/541] mark provided types as exposed, so we don't get warnings they are unused --- compiler/load/src/file.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 60f3fddfa7..c7a7ccc61c 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -2543,6 +2543,20 @@ fn parse_header<'a>( let packages = unspace(arena, header.packages.items); + let mut exposes = bumpalo::collections::Vec::new_in(arena); + exposes.extend(unspace(arena, header.provides.items)); + + if let Some(provided_types) = header.provides_types { + for provided_type in unspace(arena, provided_types.items) { + let string: &str = provided_type.value.into(); + let exposed_name = ExposedName::new(string); + + exposes.push(Loc::at(provided_type.region, exposed_name)); + } + } + + let exposes = exposes.into_bump_slice(); + let info = HeaderInfo { loc_name: Loc { region: header.name.region, @@ -2552,7 +2566,7 @@ fn parse_header<'a>( is_root_module, opt_shorthand, packages, - exposes: unspace(arena, header.provides.items), + exposes, imports: unspace(arena, header.imports.items), to_platform: Some(header.to.value), }; From 616eccb9320884fafc0e695e13f76000b0935614 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 23 Jan 2022 21:05:55 +0100 Subject: [PATCH 305/541] make tui example that uses primitive TEA --- examples/tui/Main.roc | 15 ++ examples/tui/platform/Package-Config.roc | 10 + examples/tui/platform/Program.roc | 10 + examples/tui/platform/host.zig | 268 +++++++++++++++++++++++ 4 files changed, 303 insertions(+) create mode 100644 examples/tui/Main.roc create mode 100644 examples/tui/platform/Package-Config.roc create mode 100644 examples/tui/platform/Program.roc create mode 100644 examples/tui/platform/host.zig diff --git a/examples/tui/Main.roc b/examples/tui/Main.roc new file mode 100644 index 0000000000..a51a8d8df7 --- /dev/null +++ b/examples/tui/Main.roc @@ -0,0 +1,15 @@ +app "echo" + packages { pf: "platform" } + imports [ pf.Program.{ Program } ] + provides [ main ] { Model } to pf + + +Model : Str + +main : Program Model +main = + { + init: \{} -> "Hello World", + update: \model, new -> Str.concat model new, + view: \model -> Str.concat model "!", + } diff --git a/examples/tui/platform/Package-Config.roc b/examples/tui/platform/Package-Config.roc new file mode 100644 index 0000000000..fc4d169f2a --- /dev/null +++ b/examples/tui/platform/Package-Config.roc @@ -0,0 +1,10 @@ +platform "folkertdev/foo" + requires { Model } { main : Effect {} } + exposes [] + packages {} + imports [] + provides [ mainForHost ] + effects fx.Effect { } + +mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View } +mainForHost = main diff --git a/examples/tui/platform/Program.roc b/examples/tui/platform/Program.roc new file mode 100644 index 0000000000..1959b96b33 --- /dev/null +++ b/examples/tui/platform/Program.roc @@ -0,0 +1,10 @@ +interface Program + exposes [Program] + imports [] + +Program model : + { + init : {} -> model, + update : model, Str -> model, + view : model -> Str, + } diff --git a/examples/tui/platform/host.zig b/examples/tui/platform/host.zig new file mode 100644 index 0000000000..199b9c903c --- /dev/null +++ b/examples/tui/platform/host.zig @@ -0,0 +1,268 @@ +const std = @import("std"); +const str = @import("str"); +const RocStr = str.RocStr; +const testing = std.testing; +const expectEqual = testing.expectEqual; +const expect = testing.expect; +const maxInt = std.math.maxInt; + +comptime { + // This is a workaround for https://github.com/ziglang/zig/issues/8218 + // which is only necessary on macOS. + // + // Once that issue is fixed, we can undo the changes in + // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing + // -fcompiler-rt in link.rs instead of doing this. Note that this + // workaround is present in many host.zig files, so make sure to undo + // it everywhere! + if (std.builtin.os.tag == .macos) { + _ = @import("compiler_rt"); + } +} + +const mem = std.mem; +const Allocator = mem.Allocator; + +const Program = extern struct { init: RocStr, update: Unit, view: Unit }; + +extern fn roc__mainForHost_1_exposed() Program; +extern fn roc__mainForHost_size() i64; + +const ConstModel = [*]const u8; +const MutModel = [*]u8; + +extern fn roc__mainForHost_1_Init_caller([*]u8, [*]u8, MutModel) void; +extern fn roc__mainForHost_1_Init_size() i64; +extern fn roc__mainForHost_1_Init_result_size() i64; + +fn allocate_model(allocator: *Allocator) MutModel { + const size = roc__mainForHost_1_Init_result_size(); + const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable; + var output = @ptrCast([*]u8, raw_output); + + return output; +} + +fn init(allocator: *Allocator) ConstModel { + const closure: [*]u8 = undefined; + const output = allocate_model(allocator); + + roc__mainForHost_1_Init_caller(closure, closure, output); + + return output; +} + +extern fn roc__mainForHost_1_Update_caller(ConstModel, *const RocStr, [*]u8, MutModel) void; +extern fn roc__mainForHost_1_Update_size() i64; +extern fn roc__mainForHost_1_Update_result_size() i64; + +fn update(allocator: *Allocator, model: ConstModel, msg: RocStr) ConstModel { + const closure: [*]u8 = undefined; + const output = allocate_model(allocator); + + roc__mainForHost_1_Update_caller(model, &msg, closure, output); + + return output; +} + +extern fn roc__mainForHost_1_View_caller(ConstModel, [*]u8, *RocStr) void; +extern fn roc__mainForHost_1_View_size() i64; +extern fn roc__mainForHost_1_View_result_size() i64; + +fn view(input: ConstModel) RocStr { + const closure: [*]u8 = undefined; + var output: RocStr = undefined; + + roc__mainForHost_1_View_caller(input, closure, &output); + + return output; +} + +const Align = 2 * @alignOf(usize); +extern fn malloc(size: usize) callconv(.C) ?*align(Align) c_void; +extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*c_void; +extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void; +extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; +extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void; + +const DEBUG: bool = false; + +export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { + if (DEBUG) { + var ptr = malloc(size); + const stdout = std.io.getStdOut().writer(); + stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable; + return ptr; + } else { + return malloc(size); + } +} + +export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void { + if (DEBUG) { + const stdout = std.io.getStdOut().writer(); + stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable; + } + + return realloc(@alignCast(Align, @ptrCast([*]u8, c_ptr)), new_size); +} + +export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { + if (DEBUG) { + const stdout = std.io.getStdOut().writer(); + stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable; + } + + free(@alignCast(Align, @ptrCast([*]u8, c_ptr))); +} + +export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { + _ = tag_id; + + const stderr = std.io.getStdErr().writer(); + const msg = @ptrCast([*:0]const u8, c_ptr); + stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; + std.process.exit(0); +} + +export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void { + return memcpy(dst, src, size); +} + +export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void { + return memset(dst, value, size); +} + +const Unit = extern struct {}; + +pub export fn main() callconv(.C) u8 { + const allocator = std.heap.page_allocator; + + var ts1: std.os.timespec = undefined; + std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; + + const program = roc__mainForHost_1_exposed(); + + call_the_closure(program); + + var ts2: std.os.timespec = undefined; + std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; + + const delta = to_seconds(ts2) - to_seconds(ts1); + + const stderr = std.io.getStdErr().writer(); + stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable; + + return 0; +} + +fn to_seconds(tms: std.os.timespec) f64 { + return @intToFloat(f64, tms.tv_sec) + (@intToFloat(f64, tms.tv_nsec) / 1_000_000_000.0); +} + +fn call_the_closure(program: Program) void { + const allocator = std.heap.page_allocator; + const stdout = std.io.getStdOut().writer(); + const stdin = std.io.getStdIn().reader(); + + var buf: [1000]u8 = undefined; + + var model = init(allocator); + + while (true) { + const line = (stdin.readUntilDelimiterOrEof(buf[0..], '\n') catch unreachable) orelse return; + const to_append = RocStr.init(line.ptr, line.len); + + model = update(allocator, model, to_append); + + const viewed = view(model); + for (viewed.asSlice()) |char| { + stdout.print("{c}", .{char}) catch unreachable; + } + + stdout.print("\n", .{}) catch unreachable; + } + + // The closure returns result, nothing interesting to do with it + return; +} + +pub export fn roc_fx_putInt(int: i64) i64 { + const stdout = std.io.getStdOut().writer(); + + stdout.print("{d}", .{int}) catch unreachable; + + stdout.print("\n", .{}) catch unreachable; + + return 0; +} + +export fn roc_fx_putLine(rocPath: str.RocStr) callconv(.C) void { + const stdout = std.io.getStdOut().writer(); + + for (rocPath.asSlice()) |char| { + stdout.print("{c}", .{char}) catch unreachable; + } + + stdout.print("\n", .{}) catch unreachable; +} + +const GetInt = extern struct { + value: i64, + error_code: bool, + is_error: bool, +}; + +comptime { + if (@sizeOf(usize) == 8) { + @export(roc_fx_getInt_64bit, .{ .name = "roc_fx_getInt" }); + } else { + @export(roc_fx_getInt_32bit, .{ .name = "roc_fx_getInt" }); + } +} + +fn roc_fx_getInt_64bit() callconv(.C) GetInt { + if (roc_fx_getInt_help()) |value| { + const get_int = GetInt{ .is_error = false, .value = value, .error_code = false }; + return get_int; + } else |err| switch (err) { + error.InvalidCharacter => { + return GetInt{ .is_error = true, .value = 0, .error_code = false }; + }, + else => { + return GetInt{ .is_error = true, .value = 0, .error_code = true }; + }, + } + + return 0; +} + +fn roc_fx_getInt_32bit(output: *GetInt) callconv(.C) void { + if (roc_fx_getInt_help()) |value| { + const get_int = GetInt{ .is_error = false, .value = value, .error_code = false }; + output.* = get_int; + } else |err| switch (err) { + error.InvalidCharacter => { + output.* = GetInt{ .is_error = true, .value = 0, .error_code = false }; + }, + else => { + output.* = GetInt{ .is_error = true, .value = 0, .error_code = true }; + }, + } + + return; +} + +fn roc_fx_getInt_help() !i64 { + const stdin = std.io.getStdIn().reader(); + var buf: [40]u8 = undefined; + + const line: []u8 = (try stdin.readUntilDelimiterOrEof(&buf, '\n')) orelse ""; + + return std.fmt.parseInt(i64, line, 10); +} + +fn readLine() []u8 { + const stdin = std.io.getStdIn().reader(); + return (stdin.readUntilDelimiterOrEof(&line_buf, '\n') catch unreachable) orelse ""; +} From f76a75b00e5525762eef49b7446f9c51bac37792 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 23 Jan 2022 21:25:20 +0100 Subject: [PATCH 306/541] formatting fixes --- compiler/fmt/src/module.rs | 14 ++++++++++---- compiler/fmt/tests/test_fmt.rs | 2 +- examples/benchmarks/platform/Package-Config.roc | 2 +- examples/tui/Main.roc | 5 ++--- examples/tui/platform/Package-Config.roc | 4 ++-- examples/tui/platform/Program.roc | 6 +++--- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/compiler/fmt/src/module.rs b/compiler/fmt/src/module.rs index 808cc6be9b..1b059cc7ad 100644 --- a/compiler/fmt/src/module.rs +++ b/compiler/fmt/src/module.rs @@ -8,6 +8,7 @@ use roc_parse::header::{ AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, }; +use roc_parse::ident::UppercaseIdent; use roc_region::all::Loc; pub fn fmt_module<'a, 'buf>(buf: &mut Buf<'buf>, module: &'a Module<'a>) { @@ -76,7 +77,7 @@ pub fn fmt_app_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a AppHeader<'a>) buf.indent(indent); buf.push_str("provides"); fmt_default_spaces(buf, header.after_provides, indent); - fmt_provides(buf, header.provides, indent); + fmt_provides(buf, header.provides, header.provides_types, indent); fmt_default_spaces(buf, header.before_to, indent); buf.indent(indent); buf.push_str("to"); @@ -126,7 +127,7 @@ pub fn fmt_platform_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a PlatformHe buf.indent(indent); buf.push_str("provides"); fmt_default_spaces(buf, header.after_provides, indent); - fmt_provides(buf, header.provides, indent); + fmt_provides(buf, header.provides, None, indent); fmt_effects(buf, &header.effects, indent); } @@ -216,10 +217,15 @@ fn fmt_imports<'a, 'buf>( fn fmt_provides<'a, 'buf>( buf: &mut Buf<'buf>, - loc_entries: Collection<'a, Loc>>>, + loc_exposed_names: Collection<'a, Loc>>>, + loc_provided_types: Option>>>>, indent: u16, ) { - fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No) + fmt_collection(buf, indent, '[', ']', loc_exposed_names, Newlines::No); + if let Some(loc_provided_types) = loc_provided_types { + fmt_default_spaces(buf, &[], indent); + fmt_collection(buf, indent, '{', '}', loc_provided_types, Newlines::No); + } } fn fmt_to<'buf>(buf: &mut Buf<'buf>, to: To, indent: u16) { diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index 82609329a3..92e8dee122 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -2652,7 +2652,7 @@ mod test_fmt { fn single_line_platform() { module_formats_same( "platform \"folkertdev/foo\" \ - requires { model=>Model, msg=>Msg } { main : Effect {} } \ + requires { Model, Msg } { main : Effect {} } \ exposes [] \ packages {} \ imports [ Task.{ Task } ] \ diff --git a/examples/benchmarks/platform/Package-Config.roc b/examples/benchmarks/platform/Package-Config.roc index 214364498a..d6f3a4452e 100644 --- a/examples/benchmarks/platform/Package-Config.roc +++ b/examples/benchmarks/platform/Package-Config.roc @@ -1,5 +1,5 @@ platform "folkertdev/foo" - requires { model=>Model, msg=>Msg } { main : Effect {} } + requires { Model, Msg } { main : Effect {} } exposes [] packages {} imports [ Task.{ Task } ] diff --git a/examples/tui/Main.roc b/examples/tui/Main.roc index a51a8d8df7..fe3dccfc8a 100644 --- a/examples/tui/Main.roc +++ b/examples/tui/Main.roc @@ -3,13 +3,12 @@ app "echo" imports [ pf.Program.{ Program } ] provides [ main ] { Model } to pf - Model : Str main : Program Model main = - { - init: \{} -> "Hello World", + { + init: \{ } -> "Hello World", update: \model, new -> Str.concat model new, view: \model -> Str.concat model "!", } diff --git a/examples/tui/platform/Package-Config.roc b/examples/tui/platform/Package-Config.roc index fc4d169f2a..50ef94bc74 100644 --- a/examples/tui/platform/Package-Config.roc +++ b/examples/tui/platform/Package-Config.roc @@ -4,7 +4,7 @@ platform "folkertdev/foo" packages {} imports [] provides [ mainForHost ] - effects fx.Effect { } + effects fx.Effect {} -mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View } +mainForHost : { init : {} -> Model as Init, update : Model, Str -> Model as Update, view : Model -> Str as View } mainForHost = main diff --git a/examples/tui/platform/Program.roc b/examples/tui/platform/Program.roc index 1959b96b33..346661ba20 100644 --- a/examples/tui/platform/Program.roc +++ b/examples/tui/platform/Program.roc @@ -1,9 +1,9 @@ interface Program - exposes [Program] + exposes [ Program ] imports [] -Program model : - { +Program model : + { init : {} -> model, update : model, Str -> model, view : model -> Str, From cbdf3f037820b2d8b81795d545717e8c88733223 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 23 Jan 2022 21:41:54 +0100 Subject: [PATCH 307/541] tweaks --- cli/tests/cli_run.rs | 8 ++++++++ examples/tui/Main.roc | 2 +- examples/tui/platform/Package-Config.roc | 2 +- examples/tui/platform/host.zig | 5 +++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index be905da812..31788cd217 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -383,6 +383,14 @@ mod cli_run { expected_ending: "Hi, Giovanni Giorgio!\n", use_valgrind: true, }, + tui:"tui" => Example { + filename: "Main.roc", + executable_filename: "tui", + stdin: &["foo\n"], // NOTE: adding more lines leads to memory leaks + input_file: None, + expected_ending: "Hello Worldfoo!\n", + use_valgrind: true, + }, // custom_malloc:"custom-malloc" => Example { // filename: "Main.roc", // executable_filename: "custom-malloc-example", diff --git a/examples/tui/Main.roc b/examples/tui/Main.roc index fe3dccfc8a..12b1a8da01 100644 --- a/examples/tui/Main.roc +++ b/examples/tui/Main.roc @@ -1,4 +1,4 @@ -app "echo" +app "tui" packages { pf: "platform" } imports [ pf.Program.{ Program } ] provides [ main ] { Model } to pf diff --git a/examples/tui/platform/Package-Config.roc b/examples/tui/platform/Package-Config.roc index 50ef94bc74..edb97d61bd 100644 --- a/examples/tui/platform/Package-Config.roc +++ b/examples/tui/platform/Package-Config.roc @@ -6,5 +6,5 @@ platform "folkertdev/foo" provides [ mainForHost ] effects fx.Effect {} -mainForHost : { init : {} -> Model as Init, update : Model, Str -> Model as Update, view : Model -> Str as View } +mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View } mainForHost = main diff --git a/examples/tui/platform/host.zig b/examples/tui/platform/host.zig index 199b9c903c..2f8dcca83b 100644 --- a/examples/tui/platform/host.zig +++ b/examples/tui/platform/host.zig @@ -171,6 +171,11 @@ fn call_the_closure(program: Program) void { while (true) { const line = (stdin.readUntilDelimiterOrEof(buf[0..], '\n') catch unreachable) orelse return; + + if (line.len == 1 and line[0] == 'q') { + return; + } + const to_append = RocStr.init(line.ptr, line.len); model = update(allocator, model, to_append); From eb08c12099517ed761a3d2bc8268984596071bde Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 23 Jan 2022 21:45:06 +0100 Subject: [PATCH 308/541] format `as` aliases with parens --- compiler/fmt/src/annotation.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs index b36b4d56b6..08b7dc68a4 100644 --- a/compiler/fmt/src/annotation.rs +++ b/compiler/fmt/src/annotation.rs @@ -263,8 +263,9 @@ impl<'a> Formattable for TypeAnnotation<'a> { } As(lhs, _spaces, AliasHeader { name, vars }) => { - // TODO use spaces? - lhs.value.format(buf, indent); + // TODO use _spaces? + lhs.value + .format_with_options(buf, Parens::InFunctionType, Newlines::No, indent); buf.spaces(1); buf.push_str("as"); buf.spaces(1); From e7b506646b0ee41efef7a26d41f6edb65e167734 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 23 Jan 2022 19:00:32 -0500 Subject: [PATCH 309/541] Refinements from @folkertdev review --- compiler/mono/src/layout.rs | 11 +++-------- compiler/types/src/subs.rs | 15 +++++---------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 3e6078e038..ab45fd1455 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -1602,9 +1602,8 @@ fn layout_from_flat_type<'a>( // That means none of the optimizations for enums or single tag tag unions apply let rec_var = subs.get_root_key_without_compacting(rec_var); - let mut tag_layouts = Vec::with_capacity_in(tags.tags.len(), arena); - - let tags_vec = cheap_sort_tags(&tags); + let tags_vec = tags.tags; + let mut tag_layouts = Vec::with_capacity_in(tags_vec.len(), arena); let mut nullable = None; @@ -2323,10 +2322,6 @@ pub fn union_sorted_tags_help<'a>( } } -fn cheap_sort_tags<'a>(tags: &'a UnsortedUnionTags) -> &'a [(&'a TagName, &'a [Variable])] { - &tags.tags -} - fn layout_from_newtype<'a>( arena: &'a Bump, tags: &UnsortedUnionTags, @@ -2376,7 +2371,7 @@ fn layout_from_tag_union<'a>( return layout_from_newtype(arena, tags, subs, ptr_bytes); } - let tags_vec = cheap_sort_tags(tags); + let tags_vec = &tags.tags; match tags_vec.get(0) { Some((tag_name, arguments)) if *tag_name == &TagName::Private(Symbol::NUM_AT_NUM) => { diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index f00039f18d..2f27493b8e 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -830,10 +830,7 @@ fn integer_type( ) { // define the type Signed64 (which is an alias for [ @Signed64 ]) { - let tags = UnionTags::insert_into_subs::( - subs, - [(TagName::Private(num_at_signed64), [])], - ); + let tags = UnionTags::insert_into_subs(subs, [(TagName::Private(num_at_signed64), [])]); subs.set_content(at_signed64, { Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) @@ -1085,7 +1082,7 @@ impl Subs { Content::Structure(FlatType::EmptyTagUnion), ); - let bool_union_tags = UnionTags::insert_into_subs::( + let bool_union_tags = UnionTags::insert_into_subs( &mut subs, [ (TagName::Global("False".into()), []), @@ -1727,11 +1724,10 @@ impl UnionTags { pub fn compare(x: &(TagName, T), y: &(TagName, T)) -> std::cmp::Ordering { first(x, y) } - pub fn insert_into_subs(subs: &mut Subs, input: I) -> Self + pub fn insert_into_subs(subs: &mut Subs, input: I) -> Self where - V: Into, I: IntoIterator, - I2: IntoIterator, + I2: IntoIterator, { let tag_names_start = subs.tag_names.len() as u32; let variables_start = subs.variable_slices.len() as u32; @@ -1744,8 +1740,7 @@ impl UnionTags { let mut length = 0; for (k, v) in it { - let variables = - VariableSubsSlice::insert_into_subs(subs, v.into_iter().map(|v| v.into())); + let variables = VariableSubsSlice::insert_into_subs(subs, v.into_iter()); subs.tag_names.push(k); subs.variable_slices.push(variables); From c3f8c9da5ef2f9a575b1b7dbb22e5d1edbec969a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 23 Jan 2022 22:14:39 -0500 Subject: [PATCH 310/541] Add Mats Sigge to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 7d5a1e502f..117f57c396 100644 --- a/AUTHORS +++ b/AUTHORS @@ -61,3 +61,4 @@ Shahn Hogan Tankor Smash Matthias Devlamynck Jan Van Bruggen +Mats Sigge <> From 73bc50c952c5a77be4133a86108a31bc432f7333 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 25 Jan 2022 00:13:22 +0100 Subject: [PATCH 311/541] make the surgical linker aware of custom exported closure names --- cli/src/build.rs | 24 ++++++++-- cli/src/repl/gen.rs | 4 +- cli/tests/cli_run.rs | 2 +- compiler/build/src/link.rs | 89 ++++++++++++++++++----------------- compiler/build/src/program.rs | 5 +- compiler/load/src/file.rs | 37 ++++++++++----- linker/src/lib.rs | 42 +++++++++++------ 7 files changed, 124 insertions(+), 79 deletions(-) diff --git a/cli/src/build.rs b/cli/src/build.rs index 38da872e87..89c6d1d601 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -105,6 +105,21 @@ pub fn build_file<'a>( // To do this we will need to preprocess files just for their exported symbols. // Also, we should no longer need to do this once we have platforms on // a package repository, as we can then get precompiled hosts from there. + + let exposed_values = loaded + .exposed_to_host + .values + .keys() + .map(|x| x.as_str(&loaded.interns).to_string()) + .collect(); + + let exposed_closure_types = loaded + .exposed_to_host + .closure_types + .iter() + .map(|x| x.as_str(&loaded.interns).to_string()) + .collect(); + let rebuild_thread = spawn_rebuild_thread( opt_level, surgically_link, @@ -112,11 +127,8 @@ pub fn build_file<'a>( host_input_path.clone(), binary_path.clone(), target, - loaded - .exposed_to_host - .keys() - .map(|x| x.as_str(&loaded.interns).to_string()) - .collect(), + exposed_values, + exposed_closure_types, target_valgrind, ); @@ -291,6 +303,7 @@ fn spawn_rebuild_thread( binary_path: PathBuf, target: &Triple, exported_symbols: Vec, + exported_closure_types: Vec, target_valgrind: bool, ) -> std::thread::JoinHandle { let thread_local_target = target.clone(); @@ -305,6 +318,7 @@ fn spawn_rebuild_thread( &thread_local_target, host_input_path.as_path(), exported_symbols, + exported_closure_types, target_valgrind, ) .unwrap(); diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index 9b04e29ceb..5a52f74420 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -146,8 +146,8 @@ pub fn gen_and_eval<'a>( } } - debug_assert_eq!(exposed_to_host.len(), 1); - let (main_fn_symbol, main_fn_var) = exposed_to_host.iter().next().unwrap(); + debug_assert_eq!(exposed_to_host.values.len(), 1); + let (main_fn_symbol, main_fn_var) = exposed_to_host.values.iter().next().unwrap(); let main_fn_symbol = *main_fn_symbol; let main_fn_var = *main_fn_var; diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 31788cd217..bd7de64fed 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -87,7 +87,7 @@ mod cli_run { let compile_out = run_roc(&[&["build", file.to_str().unwrap()], &all_flags[..]].concat()); if !compile_out.stderr.is_empty() { - panic!("{}", compile_out.stderr); + panic!("roc build had stderr: {}", compile_out.stderr); } assert!(compile_out.status.success(), "bad status {:?}", compile_out); diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index d1cb5b617d..80f09f901b 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -753,49 +753,52 @@ fn link_linux( // NOTE: order of arguments to `ld` matters here! // The `-l` flags should go after the `.o` arguments - Ok(( - Command::new("ld") - // Don't allow LD_ env vars to affect this - .env_clear() - .env("PATH", &env_path) - // Keep NIX_ env vars - .envs( - env::vars() - .filter(|&(ref k, _)| k.starts_with("NIX_")) - .collect::>(), - ) - .args(&[ - "--gc-sections", - "--eh-frame-hdr", - "-A", - arch_str(target), - "-pie", - libcrt_path.join("crti.o").to_str().unwrap(), - libcrt_path.join("crtn.o").to_str().unwrap(), - ]) - .args(&base_args) - .args(&["-dynamic-linker", ld_linux]) - .args(input_paths) - // ld.lld requires this argument, and does not accept --arch - // .args(&["-L/usr/lib/x86_64-linux-gnu"]) - .args(&[ - // Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496365925 - // for discussion and further references - "-lc", - "-lm", - "-lpthread", - "-ldl", - "-lrt", - "-lutil", - "-lc_nonshared", - libgcc_path.to_str().unwrap(), - // Output - "-o", - output_path.as_path().to_str().unwrap(), // app (or app.so or app.dylib etc.) - ]) - .spawn()?, - output_path, - )) + + let mut command = Command::new("ld"); + + command + // Don't allow LD_ env vars to affect this + .env_clear() + .env("PATH", &env_path) + // Keep NIX_ env vars + .envs( + env::vars() + .filter(|&(ref k, _)| k.starts_with("NIX_")) + .collect::>(), + ) + .args(&[ + "--gc-sections", + "--eh-frame-hdr", + "-A", + arch_str(target), + "-pie", + libcrt_path.join("crti.o").to_str().unwrap(), + libcrt_path.join("crtn.o").to_str().unwrap(), + ]) + .args(&base_args) + .args(&["-dynamic-linker", ld_linux]) + .args(input_paths) + // ld.lld requires this argument, and does not accept --arch + // .args(&["-L/usr/lib/x86_64-linux-gnu"]) + .args(&[ + // Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496365925 + // for discussion and further references + "-lc", + "-lm", + "-lpthread", + "-ldl", + "-lrt", + "-lutil", + "-lc_nonshared", + libgcc_path.to_str().unwrap(), + // Output + "-o", + output_path.as_path().to_str().unwrap(), // app (or app.so or app.dylib etc.) + ]); + + let output = command.spawn()?; + + Ok((output, output_path)) } fn link_macos( diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index d8ee99e07c..c3ee7cc397 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -290,7 +290,7 @@ pub fn gen_from_mono_module_llvm( // in gen_tests, the compiler provides roc_panic // and sets up the setjump/longjump exception handling is_gen_test: false, - exposed_to_host: loaded.exposed_to_host.keys().copied().collect(), + exposed_to_host: loaded.exposed_to_host.values.keys().copied().collect(), }; roc_gen_llvm::llvm::build::build_procedures( @@ -495,6 +495,7 @@ fn gen_from_mono_module_dev_wasm32( let exposed_to_host = loaded .exposed_to_host + .values .keys() .copied() .collect::>(); @@ -544,7 +545,7 @@ fn gen_from_mono_module_dev_assembly( let env = roc_gen_dev::Env { arena, module_id, - exposed_to_host: exposed_to_host.keys().copied().collect(), + exposed_to_host: exposed_to_host.values.keys().copied().collect(), lazy_literals, generate_allocators, }; diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index c7a7ccc61c..ce3fb7ee11 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -737,11 +737,19 @@ pub struct MonomorphizedModule<'a> { pub mono_problems: MutMap>, pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, pub entry_point: EntryPoint<'a>, - pub exposed_to_host: MutMap, + pub exposed_to_host: ExposedToHost, pub sources: MutMap)>, pub timings: MutMap, } +#[derive(Clone, Debug, Default)] +pub struct ExposedToHost { + /// usually `mainForHost` + pub values: MutMap, + /// exposed closure types, typically `Fx` + pub closure_types: Vec, +} + impl<'a> MonomorphizedModule<'a> { pub fn total_problems(&self) -> usize { let mut total = 0; @@ -837,7 +845,7 @@ enum Msg<'a> { /// all modules are now monomorphized, we are done FinishedAllSpecialization { subs: Subs, - exposed_to_host: MutMap, + exposed_to_host: ExposedToHost, }, FailedToParse(FileError<'a, SyntaxError<'a>>), @@ -875,7 +883,7 @@ struct State<'a> { pub module_cache: ModuleCache<'a>, pub dependencies: Dependencies<'a>, pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, - pub exposed_to_host: MutMap, + pub exposed_to_host: ExposedToHost, /// This is the "final" list of IdentIds, after canonicalization and constraint gen /// have completed for a given module. @@ -1008,7 +1016,7 @@ enum BuildTask<'a> { module_id: ModuleId, ident_ids: IdentIds, decls: Vec, - exposed_to_host: MutMap, + exposed_to_host: ExposedToHost, }, MakeSpecializations { module_id: ModuleId, @@ -1475,7 +1483,7 @@ where module_cache: ModuleCache::default(), dependencies: Dependencies::default(), procedures: MutMap::default(), - exposed_to_host: MutMap::default(), + exposed_to_host: ExposedToHost::default(), exposed_types, arc_modules, arc_shorthands, @@ -1937,12 +1945,17 @@ fn update<'a>( }; if is_host_exposed { - state.exposed_to_host.extend( + state.exposed_to_host.values.extend( solved_module .exposed_vars_by_symbol .iter() .map(|(k, v)| (*k, *v)), ); + + state + .exposed_to_host + .closure_types + .extend(solved_module.aliases.keys().copied()); } if is_host_exposed && state.goal_phase == Phase::SolveTypes { @@ -2177,7 +2190,7 @@ fn update<'a>( fn finish_specialization( state: State, subs: Subs, - exposed_to_host: MutMap, + exposed_to_host: ExposedToHost, ) -> Result { let module_ids = Arc::try_unwrap(state.arc_modules) .unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids")) @@ -2235,8 +2248,8 @@ fn finish_specialization( let entry_point = { let symbol = match platform_data { None => { - debug_assert_eq!(exposed_to_host.len(), 1); - *exposed_to_host.iter().next().unwrap().0 + debug_assert_eq!(exposed_to_host.values.len(), 1); + *exposed_to_host.values.iter().next().unwrap().0 } Some(PlatformData { provides, .. }) => provides, }; @@ -3884,7 +3897,7 @@ fn build_pending_specializations<'a>( mut layout_cache: LayoutCache<'a>, ptr_bytes: u32, // TODO remove - exposed_to_host: MutMap, + exposed_to_host: ExposedToHost, ) -> Msg<'a> { let find_specializations_start = SystemTime::now(); @@ -3924,7 +3937,7 @@ fn build_pending_specializations<'a>( &mut module_thunks, &mut mono_env, def, - &exposed_to_host, + &exposed_to_host.values, false, ), DeclareRec(defs) => { @@ -3935,7 +3948,7 @@ fn build_pending_specializations<'a>( &mut module_thunks, &mut mono_env, def, - &exposed_to_host, + &exposed_to_host.values, true, ) } diff --git a/linker/src/lib.rs b/linker/src/lib.rs index 9bd11cecb6..b9d6ee8519 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -142,10 +142,11 @@ pub fn build_and_preprocess_host( target: &Triple, host_input_path: &Path, exposed_to_host: Vec, + exported_closure_types: Vec, target_valgrind: bool, ) -> io::Result<()> { let dummy_lib = host_input_path.with_file_name("libapp.so"); - generate_dynamic_lib(target, exposed_to_host, &dummy_lib)?; + generate_dynamic_lib(target, exposed_to_host, exported_closure_types, &dummy_lib)?; rebuild_host( opt_level, target, @@ -193,6 +194,7 @@ pub fn link_preprocessed_host( fn generate_dynamic_lib( _target: &Triple, exposed_to_host: Vec, + exported_closure_types: Vec, dummy_lib_path: &Path, ) -> io::Result<()> { let dummy_obj_file = Builder::new().prefix("roc_lib").suffix(".o").tempfile()?; @@ -203,25 +205,37 @@ fn generate_dynamic_lib( write::Object::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little); let text_section = out_object.section_id(write::StandardSection::Text); + + let mut add_symbol = |name: &String| { + out_object.add_symbol(write::Symbol { + name: name.as_bytes().to_vec(), + value: 0, + size: 0, + kind: SymbolKind::Text, + scope: SymbolScope::Dynamic, + weak: false, + section: write::SymbolSection::Section(text_section), + flags: SymbolFlags::None, + }); + }; + for sym in exposed_to_host { for name in &[ format!("roc__{}_1_exposed", sym), format!("roc__{}_1_exposed_generic", sym), - format!("roc__{}_1_Fx_caller", sym), - format!("roc__{}_1_Fx_size", sym), - format!("roc__{}_1_Fx_result_size", sym), format!("roc__{}_size", sym), ] { - out_object.add_symbol(write::Symbol { - name: name.as_bytes().to_vec(), - value: 0, - size: 0, - kind: SymbolKind::Text, - scope: SymbolScope::Dynamic, - weak: false, - section: write::SymbolSection::Section(text_section), - flags: SymbolFlags::None, - }); + add_symbol(name); + } + + for closure_type in &exported_closure_types { + for name in &[ + format!("roc__{}_1_{}_caller", sym, closure_type), + format!("roc__{}_1_{}_size", sym, closure_type), + format!("roc__{}_1_{}_result_size", sym, closure_type), + ] { + add_symbol(name) + } } } std::fs::write( From 062846b7eef430623a2c48b2e83514c5c30394eb Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 25 Jan 2022 09:23:42 +0100 Subject: [PATCH 312/541] fix test compilation --- compiler/test_mono/src/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/test_mono/src/tests.rs b/compiler/test_mono/src/tests.rs index 8926a4942a..4a398e6d38 100644 --- a/compiler/test_mono/src/tests.rs +++ b/compiler/test_mono/src/tests.rs @@ -136,9 +136,9 @@ fn compiles_to_ir(test_name: &str, src: &str) { assert_eq!(type_problems, Vec::new()); assert_eq!(mono_problems, Vec::new()); - debug_assert_eq!(exposed_to_host.len(), 1); + debug_assert_eq!(exposed_to_host.values.len(), 1); - let main_fn_symbol = exposed_to_host.keys().copied().next().unwrap(); + let main_fn_symbol = exposed_to_host.values.keys().copied().next().unwrap(); verify_procedures(test_name, procedures, main_fn_symbol); } From 8aae60ddda91d79e9a695c85425cf093e83edc09 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 25 Jan 2022 10:25:05 +0100 Subject: [PATCH 313/541] fix reporting tests --- reporting/src/error/parse.rs | 29 +++++++++++++++++++++++++++++ reporting/tests/test_reporting.rs | 6 +++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index 27129d659e..6922a8e9ee 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -3410,6 +3410,35 @@ fn to_requires_report<'a>( } } + ERequires::Open(pos) => { + let surroundings = Region::new(start, pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat(vec![ + alloc.reflow("I am expecting a list of type names like "), + alloc.keyword("{}"), + alloc.reflow(" or "), + alloc.keyword("{ Model }"), + alloc.reflow(" next. A full "), + alloc.keyword("requires"), + alloc.reflow(" definition looks like"), + ]), + alloc + .parser_suggestion("requires { Model, Msg } {main : Effect {}}") + .indent(4), + ]); + + Report { + filename, + doc, + title: "BAD REQUIRES".to_string(), + severity: Severity::RuntimeError, + } + } + _ => todo!("unhandled parse error {:?}", parse_problem), } } diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index c9550b9ab0..0cb8640770 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -6142,7 +6142,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── BAD REQUIRES RIGIDS ───────────────────────────────────────────────────────── + ── BAD REQUIRES ──────────────────────────────────────────────────────────────── I am partway through parsing a header, but I got stuck here: @@ -6150,10 +6150,10 @@ I need all branches in an `if` to have the same type! 2│ requires { main : Effect {} } ^ - I am expecting a list of rigids like `{}` or `{model=>Model}` next. A full + I am expecting a list of type names like `{}` or `{ Model }` next. A full `requires` definition looks like - requires {model=>Model, msg=>Msg} {main : Effect {}} + requires { Model, Msg } {main : Effect {}} "# ), ) From b72ad31a895ec5484fad90960371dcbfbe43b488 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 25 Jan 2022 12:05:13 +0100 Subject: [PATCH 314/541] fix more tests --- compiler/test_gen/src/helpers/dev.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/test_gen/src/helpers/dev.rs b/compiler/test_gen/src/helpers/dev.rs index 86d5bad36b..31a87d9c57 100644 --- a/compiler/test_gen/src/helpers/dev.rs +++ b/compiler/test_gen/src/helpers/dev.rs @@ -95,7 +95,7 @@ pub fn helper( // println!("=================================\n"); } - debug_assert_eq!(exposed_to_host.len(), 1); + debug_assert_eq!(exposed_to_host.values.len(), 1); let main_fn_symbol = loaded.entry_point.symbol; let main_fn_layout = loaded.entry_point.layout; @@ -179,7 +179,7 @@ pub fn helper( let env = roc_gen_dev::Env { arena, module_id, - exposed_to_host: exposed_to_host.keys().copied().collect(), + exposed_to_host: exposed_to_host.values.keys().copied().collect(), lazy_literals, generate_allocators: true, // Needed for testing, since we don't have a platform }; From 3137e14a85a7af644f47fafe63c303d8716f5a4e Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 25 Jan 2022 13:37:30 +0100 Subject: [PATCH 315/541] another test fix --- compiler/test_gen/src/helpers/wasm.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 21d6c6b36c..c7d6f5cfb2 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -117,14 +117,14 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( .. } = loaded; - debug_assert_eq!(exposed_to_host.len(), 1); + debug_assert_eq!(exposed_to_host.values.len(), 1); - let exposed_to_host = exposed_to_host.keys().copied().collect::>(); + let exposed_to_host = exposed_to_host.values.keys().copied().collect::>(); let env = roc_gen_wasm::Env { arena, module_id, - exposed_to_host, + exposed_to_host.values, }; let (mut module, called_preload_fns, main_fn_index) = From 9ee7198e455a8b77a4f675a45855ad0271794b72 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 25 Jan 2022 15:23:53 +0100 Subject: [PATCH 316/541] and another --- compiler/test_gen/src/helpers/wasm.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index c7d6f5cfb2..f68f6b5da8 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -119,12 +119,16 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( debug_assert_eq!(exposed_to_host.values.len(), 1); - let exposed_to_host = exposed_to_host.values.keys().copied().collect::>(); + let exposed_to_host = exposed_to_host + .values + .keys() + .copied() + .collect::>(); let env = roc_gen_wasm::Env { arena, module_id, - exposed_to_host.values, + exposed_to_host, }; let (mut module, called_preload_fns, main_fn_index) = From 2d23adb2a119482326faef72d1f70e4263814c67 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Jan 2022 13:55:06 +0100 Subject: [PATCH 317/541] add roc_target crate --- .gitignore | 2 +- Cargo.lock | 7 +++++++ Cargo.toml | 1 + compiler/target/Cargo.toml | 9 +++++++++ compiler/target/src/lib.rs | 15 +++++++++++++++ 5 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 compiler/target/Cargo.toml create mode 100644 compiler/target/src/lib.rs diff --git a/.gitignore b/.gitignore index 87635559c0..d5960bf0cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -target +target/ generated-docs zig-cache .direnv diff --git a/Cargo.lock b/Cargo.lock index f98de87446..7e01948455 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3717,6 +3717,13 @@ dependencies = [ "quickcheck_macros", ] +[[package]] +name = "roc_target" +version = "0.1.0" +dependencies = [ + "target-lexicon", +] + [[package]] name = "roc_test_utils" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 465e29e8a3..28a4f6193e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ members = [ "compiler/build", "compiler/arena_pool", "compiler/test_gen", + "compiler/target", "vendor/ena", "vendor/inkwell", "vendor/pathfinding", diff --git a/compiler/target/Cargo.toml b/compiler/target/Cargo.toml new file mode 100644 index 0000000000..28211dbdc9 --- /dev/null +++ b/compiler/target/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "roc_target" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" + +[dependencies] +target-lexicon = "0.12.2" diff --git a/compiler/target/src/lib.rs b/compiler/target/src/lib.rs new file mode 100644 index 0000000000..75e3f98111 --- /dev/null +++ b/compiler/target/src/lib.rs @@ -0,0 +1,15 @@ +#![warn(clippy::dbg_macro)] +// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. +#![allow(clippy::large_enum_variant)] + +pub struct TargetInfo { + architecture: Architecture, +} + +pub enum Architecture { + X86_64, + X86_32, + Aarch64, + Arm, + Wasm32, +} From 456404ccfecb943b710318e2d61b548d20485766 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Jan 2022 14:01:53 +0100 Subject: [PATCH 318/541] add some helpers --- compiler/target/src/lib.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/compiler/target/src/lib.rs b/compiler/target/src/lib.rs index 75e3f98111..933cc2f6c6 100644 --- a/compiler/target/src/lib.rs +++ b/compiler/target/src/lib.rs @@ -13,3 +13,27 @@ pub enum Architecture { Arm, Wasm32, } + +impl Architecture { + pub const fn ptr_width(&self) -> u32 { + use Architecture::*; + + match self { + X86_64 | Aarch64 | Arm => 8, + X86_32 | Wasm32 => 4, + } + } +} + +impl From for Architecture { + fn from(target: target_lexicon::Architecture) -> Self { + match target { + target_lexicon::Architecture::X86_64 => Architecture::X86_64, + target_lexicon::Architecture::X86_32(_) => Architecture::X86_32, + target_lexicon::Architecture::Aarch64(_) => Architecture::Aarch64, + target_lexicon::Architecture::Arm(_) => Architecture::Arm, + target_lexicon::Architecture::Wasm32 => Architecture::Wasm32, + _ => unreachable!("unsupported architecture"), + } + } +} From 7e9081233249196758b53ec53b14a3424a31747b Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Jan 2022 14:28:26 +0100 Subject: [PATCH 319/541] ptr_bytes -> target info, step 1 --- Cargo.lock | 3 + compiler/builtins/Cargo.toml | 1 + compiler/gen_llvm/Cargo.toml | 1 + compiler/gen_llvm/src/llvm/build.rs | 109 +++++----- compiler/mono/Cargo.toml | 1 + compiler/mono/src/layout.rs | 302 ++++++++++++++-------------- compiler/target/src/lib.rs | 27 ++- 7 files changed, 240 insertions(+), 204 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e01948455..4bf5787f06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3297,6 +3297,7 @@ dependencies = [ "roc_collections", "roc_module", "roc_region", + "roc_target", "roc_types", ] @@ -3526,6 +3527,7 @@ dependencies = [ "roc_module", "roc_mono", "roc_std", + "roc_target", "target-lexicon", ] @@ -3621,6 +3623,7 @@ dependencies = [ "roc_region", "roc_solve", "roc_std", + "roc_target", "roc_types", "roc_unify", "static_assertions", diff --git a/compiler/builtins/Cargo.toml b/compiler/builtins/Cargo.toml index 9321839c93..4b73584449 100644 --- a/compiler/builtins/Cargo.toml +++ b/compiler/builtins/Cargo.toml @@ -10,3 +10,4 @@ roc_collections = { path = "../collections" } roc_region = { path = "../region" } roc_module = { path = "../module" } roc_types = { path = "../types" } +roc_target = { path = "../target" } diff --git a/compiler/gen_llvm/Cargo.toml b/compiler/gen_llvm/Cargo.toml index 32a79c4767..591f4ff92c 100644 --- a/compiler/gen_llvm/Cargo.toml +++ b/compiler/gen_llvm/Cargo.toml @@ -12,6 +12,7 @@ roc_module = { path = "../module" } roc_builtins = { path = "../builtins" } roc_error_macros = { path = "../../error_macros" } roc_mono = { path = "../mono" } +roc_target = { path = "../target" } roc_std = { path = "../../roc_std" } morphic_lib = { path = "../../vendor/morphic_lib" } bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index d1a08a4bd8..94ffe74b89 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -63,6 +63,7 @@ use roc_mono::ir::{ ModifyRc, OptLevel, ProcLayout, }; use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, TagIdIntType, UnionLayout}; +use roc_target::TargetInfo; use target_lexicon::{Architecture, OperatingSystem, Triple}; /// This is for Inkwell's FunctionValue::verify - we want to know the verification @@ -166,7 +167,7 @@ pub struct Env<'a, 'ctx, 'env> { pub compile_unit: &'env DICompileUnit<'ctx>, pub module: &'ctx Module<'ctx>, pub interns: Interns, - pub ptr_bytes: u32, + pub target_info: TargetInfo, pub is_gen_test: bool, pub exposed_to_host: MutSet, } @@ -195,14 +196,14 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { pub fn ptr_int(&self) -> IntType<'ctx> { let ctx = self.context; - match self.ptr_bytes { + match self.target_info { 1 => ctx.i8_type(), 2 => ctx.i16_type(), 4 => ctx.i32_type(), 8 => ctx.i64_type(), _ => panic!( "Invalid target: Roc does't support compiling to {}-bit systems.", - self.ptr_bytes * 8 + self.target_info * 8 ), } } @@ -212,11 +213,11 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { /// on 64-bit systems, this is i128 /// on 32-bit systems, this is i64 pub fn str_list_c_abi(&self) -> IntType<'ctx> { - crate::llvm::convert::str_list_int(self.context, self.ptr_bytes) + crate::llvm::convert::str_list_int(self.context, self.target_info) } pub fn small_str_bytes(&self) -> u32 { - self.ptr_bytes * 2 + self.target_info * 2 } pub fn build_intrinsic_call( @@ -269,7 +270,7 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { } pub fn alignment_intvalue(&self, element_layout: &Layout<'a>) -> BasicValueEnum<'ctx> { - let alignment = element_layout.alignment_bytes(self.ptr_bytes); + let alignment = element_layout.alignment_bytes(self.target_info); let alignment_iv = self.alignment_const(alignment); alignment_iv.into() @@ -317,7 +318,7 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { ) -> CallSiteValue<'ctx> { let false_val = self.context.bool_type().const_int(0, false); - let intrinsic_name = match self.ptr_bytes { + let intrinsic_name = match self.target_info { 8 => LLVM_MEMSET_I64, 4 => LLVM_MEMSET_I32, other => { @@ -1438,7 +1439,7 @@ fn build_wrapped_tag<'a, 'ctx, 'env>( let raw_data_ptr = allocate_tag(env, parent, reuse_allocation, union_layout, tags); let struct_type = env.context.struct_type(&field_types, false); - if union_layout.stores_tag_id_as_data(env.ptr_bytes) { + if union_layout.stores_tag_id_as_data(env.target_info) { let tag_id_ptr = builder .build_struct_gep(raw_data_ptr, TAG_ID_INDEX, "tag_id_index") .unwrap(); @@ -1524,7 +1525,7 @@ pub fn build_tag<'a, 'ctx, 'env>( UnionLayout::NonRecursive(tags) => { debug_assert!(union_size > 1); - let internal_type = block_of_memory_slices(env.context, tags, env.ptr_bytes); + let internal_type = block_of_memory_slices(env.context, tags, env.target_info); let tag_id_type = basic_type_from_layout(env, &tag_id_layout).into_int_type(); let wrapper_type = env @@ -1711,7 +1712,7 @@ pub fn build_tag<'a, 'ctx, 'env>( other_fields, } => { let tag_struct_type = - block_of_memory_slices(env.context, &[other_fields], env.ptr_bytes); + block_of_memory_slices(env.context, &[other_fields], env.target_info); if tag_id == *nullable_id as _ { let output_type = tag_struct_type.ptr_type(AddressSpace::Generic); @@ -1788,7 +1789,7 @@ fn tag_pointer_set_tag_id<'a, 'ctx, 'env>( pointer: PointerValue<'ctx>, ) -> PointerValue<'ctx> { // we only have 3 bits, so can encode only 0..7 (or on 32-bit targets, 2 bits to encode 0..3) - debug_assert!((tag_id as u32) < env.ptr_bytes); + debug_assert!((tag_id as u32) < env.target_info); let ptr_int = env.ptr_int(); @@ -1813,7 +1814,7 @@ pub fn tag_pointer_read_tag_id<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, pointer: PointerValue<'ctx>, ) -> IntValue<'ctx> { - let (_, mask) = tag_pointer_tag_id_bits_and_mask(env.ptr_bytes); + let (_, mask) = tag_pointer_tag_id_bits_and_mask(env.target_info); let ptr_int = env.ptr_int(); let as_int = env.builder.build_ptr_to_int(pointer, ptr_int, "to_int"); @@ -1831,7 +1832,7 @@ pub fn tag_pointer_clear_tag_id<'a, 'ctx, 'env>( ) -> PointerValue<'ctx> { let ptr_int = env.ptr_int(); - let (tag_id_bits_mask, _) = tag_pointer_tag_id_bits_and_mask(env.ptr_bytes); + let (tag_id_bits_mask, _) = tag_pointer_tag_id_bits_and_mask(env.target_info); let as_int = env.builder.build_ptr_to_int(pointer, ptr_int, "to_int"); @@ -1918,7 +1919,7 @@ pub fn get_tag_id<'a, 'ctx, 'env>( UnionLayout::Recursive(_) => { let argument_ptr = argument.into_pointer_value(); - if union_layout.stores_tag_id_as_data(env.ptr_bytes) { + if union_layout.stores_tag_id_as_data(env.target_info) { get_tag_id_wrapped(env, argument_ptr) } else { tag_pointer_read_tag_id(env, argument_ptr) @@ -1949,7 +1950,7 @@ pub fn get_tag_id<'a, 'ctx, 'env>( { env.builder.position_at_end(else_block); - let tag_id = if union_layout.stores_tag_id_as_data(env.ptr_bytes) { + let tag_id = if union_layout.stores_tag_id_as_data(env.target_info) { get_tag_id_wrapped(env, argument_ptr) } else { tag_pointer_read_tag_id(env, argument_ptr) @@ -2057,12 +2058,12 @@ fn lookup_at_index_ptr2<'a, 'ctx, 'env>( let result = if field_layout.is_passed_by_reference() { let field_type = basic_type_from_layout(env, &field_layout); - let align_bytes = field_layout.alignment_bytes(env.ptr_bytes); + let align_bytes = field_layout.alignment_bytes(env.target_info); let alloca = tag_alloca(env, field_type, "copied_tag"); if align_bytes > 0 { let size = env .ptr_int() - .const_int(field_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(field_layout.stack_size(env.target_info) as u64, false); env.builder .build_memcpy(alloca, align_bytes, elem_ptr, align_bytes, size) @@ -2095,8 +2096,8 @@ pub fn reserve_with_refcount<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>, ) -> PointerValue<'ctx> { - let stack_size = layout.stack_size(env.ptr_bytes); - let alignment_bytes = layout.alignment_bytes(env.ptr_bytes); + let stack_size = layout.stack_size(env.target_info); + let alignment_bytes = layout.alignment_bytes(env.target_info); let basic_type = basic_type_from_layout(env, layout); @@ -2108,9 +2109,9 @@ fn reserve_with_refcount_union_as_block_of_memory<'a, 'ctx, 'env>( union_layout: UnionLayout<'a>, fields: &[&[Layout<'a>]], ) -> PointerValue<'ctx> { - let ptr_bytes = env.ptr_bytes; + let ptr_bytes = env.target_info; - let block_type = block_of_memory_slices(env.context, fields, env.ptr_bytes); + let block_type = block_of_memory_slices(env.context, fields, env.target_info); let basic_type = if union_layout.stores_tag_id_as_data(ptr_bytes) { let tag_id_type = basic_type_from_layout(env, &union_layout.tag_id_layout()); @@ -2124,17 +2125,17 @@ fn reserve_with_refcount_union_as_block_of_memory<'a, 'ctx, 'env>( let mut stack_size = fields .iter() - .map(|tag| tag.iter().map(|l| l.stack_size(env.ptr_bytes)).sum()) + .map(|tag| tag.iter().map(|l| l.stack_size(env.target_info)).sum()) .max() .unwrap_or_default(); if union_layout.stores_tag_id_as_data(ptr_bytes) { - stack_size += union_layout.tag_id_layout().stack_size(env.ptr_bytes); + stack_size += union_layout.tag_id_layout().stack_size(env.target_info); } let alignment_bytes = fields .iter() - .map(|tag| tag.iter().map(|l| l.alignment_bytes(env.ptr_bytes))) + .map(|tag| tag.iter().map(|l| l.alignment_bytes(env.target_info))) .flatten() .max() .unwrap_or(0); @@ -2154,7 +2155,7 @@ fn reserve_with_refcount_help<'a, 'ctx, 'env>( let value_bytes_intvalue = len_type.const_int(stack_size as u64, false); - let rc1 = crate::llvm::refcounting::refcount_1(ctx, env.ptr_bytes); + let rc1 = crate::llvm::refcounting::refcount_1(ctx, env.target_info); allocate_with_refcount_help(env, basic_type, alignment_bytes, value_bytes_intvalue, rc1) } @@ -2183,7 +2184,7 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>( let len_type = env.ptr_int(); - let extra_bytes = alignment_bytes.max(env.ptr_bytes); + let extra_bytes = alignment_bytes.max(env.target_info); let ptr = { // number of bytes we will allocated @@ -2208,8 +2209,8 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>( .into_pointer_value(); let index = match extra_bytes { - n if n == env.ptr_bytes => 1, - n if n == 2 * env.ptr_bytes => 2, + n if n == env.target_info => 1, + n if n == 2 * env.target_info => 2, _ => unreachable!("invalid extra_bytes, {}", extra_bytes), }; @@ -2228,11 +2229,11 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>( }; let refcount_ptr = match extra_bytes { - n if n == env.ptr_bytes => { + n if n == env.target_info => { // the allocated pointer is the same as the refcounted pointer unsafe { PointerToRefcount::from_ptr(env, ptr) } } - n if n == 2 * env.ptr_bytes => { + n if n == 2 * env.target_info => { // the refcount is stored just before the start of the actual data // but in this case (because of alignment) not at the start of the allocated buffer PointerToRefcount::from_ptr_to_data(env, data_ptr) @@ -2283,14 +2284,14 @@ fn list_literal<'a, 'ctx, 'env>( // if element_type.is_int_type() { if false { let element_type = element_type.into_int_type(); - let element_width = element_layout.stack_size(env.ptr_bytes); + let element_width = element_layout.stack_size(env.target_info); let size = list_length * element_width as usize; let alignment = element_layout - .alignment_bytes(env.ptr_bytes) - .max(env.ptr_bytes); + .alignment_bytes(env.target_info) + .max(env.target_info); let mut is_all_constant = true; - let zero_elements = (env.ptr_bytes as f64 / element_width as f64).ceil() as usize; + let zero_elements = (env.target_info as f64 / element_width as f64).ceil() as usize; // runtime-evaluated elements let mut runtime_evaluated_elements = Vec::with_capacity_in(list_length, env.arena); @@ -2471,12 +2472,12 @@ pub fn store_roc_value<'a, 'ctx, 'env>( value: BasicValueEnum<'ctx>, ) { if layout.is_passed_by_reference() { - let align_bytes = layout.alignment_bytes(env.ptr_bytes); + let align_bytes = layout.alignment_bytes(env.target_info); if align_bytes > 0 { let size = env .ptr_int() - .const_int(layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(layout.stack_size(env.target_info) as u64, false); env.builder .build_memcpy( @@ -2572,7 +2573,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( let destination = out_parameter.into_pointer_value(); if layout.is_passed_by_reference() { - let align_bytes = layout.alignment_bytes(env.ptr_bytes); + let align_bytes = layout.alignment_bytes(env.target_info); if align_bytes > 0 { let value_ptr = value.into_pointer_value(); @@ -2585,7 +2586,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( } else { let size = env .ptr_int() - .const_int(layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(layout.stack_size(env.target_info) as u64, false); env.builder .build_memcpy( @@ -2776,21 +2777,21 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( match layout { Layout::Builtin(Builtin::List(element_layout)) => { debug_assert!(value.is_struct_value()); - let alignment = element_layout.alignment_bytes(env.ptr_bytes); + let alignment = element_layout.alignment_bytes(env.target_info); build_list::decref(env, value.into_struct_value(), alignment); } Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => { debug_assert!(value.is_struct_value()); let alignment = key_layout - .alignment_bytes(env.ptr_bytes) - .max(value_layout.alignment_bytes(env.ptr_bytes)); + .alignment_bytes(env.target_info) + .max(value_layout.alignment_bytes(env.target_info)); build_dict::decref(env, value.into_struct_value(), alignment); } Layout::Builtin(Builtin::Set(key_layout)) => { debug_assert!(value.is_struct_value()); - let alignment = key_layout.alignment_bytes(env.ptr_bytes); + let alignment = key_layout.alignment_bytes(env.target_info); build_dict::decref(env, value.into_struct_value(), alignment); } @@ -3408,7 +3409,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>( builder.position_at_end(entry); - let wrapped_layout = roc_result_layout(env.arena, return_layout, env.ptr_bytes); + let wrapped_layout = roc_result_layout(env.arena, return_layout, env.target_info); call_roc_function(env, roc_function, &wrapped_layout, arguments_for_call) } else { call_roc_function(env, roc_function, &return_layout, arguments_for_call) @@ -3726,7 +3727,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( } pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> { - let type_ = env.context.i8_type().array_type(5 * env.ptr_bytes); + let type_ = env.context.i8_type().array_type(5 * env.target_info); let global = match env.module.get_global("roc_sjlj_buffer") { Some(global) => global, @@ -4329,12 +4330,12 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( let call_result = call_roc_function(env, evaluator, return_layout, &evaluator_arguments); if return_layout.is_passed_by_reference() { - let align_bytes = return_layout.alignment_bytes(env.ptr_bytes); + let align_bytes = return_layout.alignment_bytes(env.target_info); if align_bytes > 0 { let size = env .ptr_int() - .const_int(return_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(return_layout.stack_size(env.target_info) as u64, false); env.builder .build_memcpy( @@ -5020,7 +5021,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( Layout::Builtin(Builtin::List(element_layout)), Layout::Builtin(Builtin::List(result_layout)), ) => { - let argument_layouts = &[Layout::usize(env.ptr_bytes), **element_layout]; + let argument_layouts = &[Layout::usize(env.target_info), **element_layout]; let roc_function_call = roc_function_call( env, @@ -6075,7 +6076,7 @@ fn run_low_level<'a, 'ctx, 'env>( { bd.position_at_end(throw_block); - match env.ptr_bytes { + match env.target_info { 8 => { let fn_ptr_type = context .void_type() @@ -6194,8 +6195,8 @@ enum CCReturn { /// According to the C ABI, how should we return a value with the given layout? fn to_cc_return<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> CCReturn { - let return_size = layout.stack_size(env.ptr_bytes); - let pass_result_by_pointer = return_size > 2 * env.ptr_bytes; + let return_size = layout.stack_size(env.target_info); + let pass_result_by_pointer = return_size > 2 * env.target_info; if return_size == 0 { CCReturn::Void @@ -7129,7 +7130,7 @@ fn define_global_str_literal_ptr<'a, 'ctx, 'env>( let ptr = unsafe { env.builder.build_in_bounds_gep( ptr, - &[env.ptr_int().const_int(env.ptr_bytes as u64, false)], + &[env.ptr_int().const_int(env.target_info as u64, false)], "get_rc_ptr", ) }; @@ -7159,11 +7160,11 @@ fn define_global_str_literal<'a, 'ctx, 'env>( Some(current) => current, None => { - let size = message.bytes().len() + env.ptr_bytes as usize; + let size = message.bytes().len() + env.target_info as usize; let mut bytes = Vec::with_capacity_in(size, env.arena); // insert NULL bytes for the refcount - for _ in 0..env.ptr_bytes { + for _ in 0..env.target_info { bytes.push(env.context.i8_type().const_zero()); } @@ -7182,7 +7183,7 @@ fn define_global_str_literal<'a, 'ctx, 'env>( // strings are NULL-terminated, which means we can't store the refcount (which is 8 // NULL bytes) global.set_constant(true); - global.set_alignment(env.ptr_bytes); + global.set_alignment(env.target_info); global.set_unnamed_addr(true); global.set_linkage(inkwell::module::Linkage::Private); diff --git a/compiler/mono/Cargo.toml b/compiler/mono/Cargo.toml index 6cd3129ed3..88b33a8d82 100644 --- a/compiler/mono/Cargo.toml +++ b/compiler/mono/Cargo.toml @@ -16,6 +16,7 @@ roc_solve = { path = "../solve" } roc_std = { path = "../../roc_std" } roc_problem = { path = "../problem" } roc_builtins = { path = "../builtins" } +roc_target = { path = "../target" } ven_pretty = { path = "../../vendor/pretty" } morphic_lib = { path = "../../vendor/morphic_lib" } bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 3d19699399..c56a10cb4d 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -6,6 +6,7 @@ use roc_collections::all::{default_hasher, MutMap}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{Interns, Symbol}; use roc_problem::can::RuntimeError; +use roc_target::TargetInfo; use roc_types::subs::{ Content, FlatType, RecordFields, Subs, UnionTags, UnsortedUnionTags, Variable, }; @@ -123,7 +124,7 @@ impl<'a> RawFunctionLayout<'a> { // Nat Alias(Symbol::NUM_NAT, args, _) => { debug_assert!(args.is_empty()); - Ok(Self::ZeroArgumentThunk(Layout::usize(env.ptr_bytes))) + Ok(Self::ZeroArgumentThunk(Layout::usize(env.target_info))) } Alias(symbol, _, _) if symbol.is_builtin() => Ok(Self::ZeroArgumentThunk( @@ -158,7 +159,7 @@ impl<'a> RawFunctionLayout<'a> { let ret = arena.alloc(ret); let lambda_set = - LambdaSet::from_var(env.arena, env.subs, closure_var, env.ptr_bytes)?; + LambdaSet::from_var(env.arena, env.subs, closure_var, env.target_info)?; Ok(Self::Function(fn_args, lambda_set, ret)) } @@ -360,29 +361,29 @@ impl<'a> UnionLayout<'a> { Layout::Builtin(self.tag_id_builtin()) } - fn stores_tag_id_in_pointer_bits(tags: &[&[Layout<'a>]], ptr_bytes: u32) -> bool { - tags.len() < ptr_bytes as usize + fn stores_tag_id_in_pointer_bits(tags: &[&[Layout<'a>]], target_info: TargetInfo) -> bool { + tags.len() < target_info.ptr_width() as usize } // i.e. it is not implicit and not stored in the pointer bits - pub fn stores_tag_id_as_data(&self, ptr_bytes: u32) -> bool { + pub fn stores_tag_id_as_data(&self, target_info: TargetInfo) -> bool { match self { UnionLayout::NonRecursive(_) => true, UnionLayout::Recursive(tags) | UnionLayout::NullableWrapped { other_tags: tags, .. - } => !Self::stores_tag_id_in_pointer_bits(tags, ptr_bytes), + } => !Self::stores_tag_id_in_pointer_bits(tags, target_info), UnionLayout::NonNullableUnwrapped(_) | UnionLayout::NullableUnwrapped { .. } => false, } } - pub fn stores_tag_id_in_pointer(&self, ptr_bytes: u32) -> bool { + pub fn stores_tag_id_in_pointer(&self, target_info: TargetInfo) -> bool { match self { UnionLayout::NonRecursive(_) => false, UnionLayout::Recursive(tags) | UnionLayout::NullableWrapped { other_tags: tags, .. - } => Self::stores_tag_id_in_pointer_bits(tags, ptr_bytes), + } => Self::stores_tag_id_in_pointer_bits(tags, target_info), UnionLayout::NonNullableUnwrapped(_) | UnionLayout::NullableUnwrapped { .. } => false, } } @@ -406,76 +407,73 @@ impl<'a> UnionLayout<'a> { } } - fn tags_alignment_bytes(tags: &[&[Layout]], pointer_size: u32) -> u32 { + fn tags_alignment_bytes(tags: &[&[Layout]], target_info: TargetInfo) -> u32 { tags.iter() - .map(|fields| Layout::Struct(fields).alignment_bytes(pointer_size)) + .map(|fields| Layout::Struct(fields).alignment_bytes(target_info)) .max() .unwrap_or(0) } - pub fn allocation_alignment_bytes(&self, pointer_size: u32) -> u32 { + pub fn allocation_alignment_bytes(&self, target_info: TargetInfo) -> u32 { let allocation = match self { UnionLayout::NonRecursive(_) => unreachable!("not heap-allocated"), - UnionLayout::Recursive(tags) => Self::tags_alignment_bytes(tags, pointer_size), + UnionLayout::Recursive(tags) => Self::tags_alignment_bytes(tags, target_info), UnionLayout::NonNullableUnwrapped(fields) => { - Layout::Struct(fields).alignment_bytes(pointer_size) + Layout::Struct(fields).alignment_bytes(target_info) } UnionLayout::NullableWrapped { other_tags, .. } => { - Self::tags_alignment_bytes(other_tags, pointer_size) + Self::tags_alignment_bytes(other_tags, target_info) } UnionLayout::NullableUnwrapped { other_fields, .. } => { - Layout::Struct(other_fields).alignment_bytes(pointer_size) + Layout::Struct(other_fields).alignment_bytes(target_info) } }; // because we store a refcount, the alignment must be at least the size of a pointer - allocation.max(pointer_size) + allocation.max(target_info.ptr_width() as u32) } /// Size of the data in memory, whether it's stack or heap (for non-null tag ids) - pub fn data_size_and_alignment(&self, pointer_size: u32) -> (u32, u32) { - let id_data_layout = if self.stores_tag_id_as_data(pointer_size) { + pub fn data_size_and_alignment(&self, target_info: TargetInfo) -> (u32, u32) { + let id_data_layout = if self.stores_tag_id_as_data(target_info) { Some(self.tag_id_layout()) } else { None }; - self.data_size_and_alignment_help_match(id_data_layout, pointer_size) + self.data_size_and_alignment_help_match(id_data_layout, target_info) } /// Size of the data before the tag_id, if it exists. /// Returns None if the tag_id is not stored as data in the layout. - pub fn data_size_without_tag_id(&self, pointer_size: u32) -> Option { - if !self.stores_tag_id_as_data(pointer_size) { + pub fn data_size_without_tag_id(&self, target_info: TargetInfo) -> Option { + if !self.stores_tag_id_as_data(target_info) { return None; }; - Some( - self.data_size_and_alignment_help_match(None, pointer_size) - .0, - ) + Some(self.data_size_and_alignment_help_match(None, target_info).0) } fn data_size_and_alignment_help_match( &self, id_data_layout: Option, - pointer_size: u32, + target_info: TargetInfo, ) -> (u32, u32) { match self { Self::NonRecursive(tags) => { - Self::data_size_and_alignment_help(tags, id_data_layout, pointer_size) + Self::data_size_and_alignment_help(tags, id_data_layout, target_info) } Self::Recursive(tags) => { - Self::data_size_and_alignment_help(tags, id_data_layout, pointer_size) + Self::data_size_and_alignment_help(tags, id_data_layout, target_info) } Self::NonNullableUnwrapped(fields) => { - Self::data_size_and_alignment_help(&[fields], id_data_layout, pointer_size) + Self::data_size_and_alignment_help(&[fields], id_data_layout, target_info) } Self::NullableWrapped { other_tags, .. } => { - Self::data_size_and_alignment_help(other_tags, id_data_layout, pointer_size) + Self::data_size_and_alignment_help(other_tags, id_data_layout, target_info) } Self::NullableUnwrapped { other_fields, .. } => { - Self::data_size_and_alignment_help(&[other_fields], id_data_layout, pointer_size) + Self::data_size_and_alignment_help(&[other_fields], id_data_layout, target_info) } } } @@ -483,7 +481,7 @@ impl<'a> UnionLayout<'a> { fn data_size_and_alignment_help( variant_field_layouts: &[&[Layout]], id_data_layout: Option, - pointer_size: u32, + target_info: TargetInfo, ) -> (u32, u32) { let mut size = 0; let mut alignment_bytes = 0; @@ -497,7 +495,7 @@ impl<'a> UnionLayout<'a> { data = Layout::Struct(&fields_and_id); } - let (variant_size, variant_alignment) = data.stack_size_and_alignment(pointer_size); + let (variant_size, variant_alignment) = data.stack_size_and_alignment(target_info); alignment_bytes = alignment_bytes.max(variant_alignment); size = size.max(variant_size); } @@ -692,7 +690,7 @@ impl<'a> LambdaSet<'a> { arena: &'a Bump, subs: &Subs, closure_var: Variable, - ptr_bytes: u32, + target_info: TargetInfo, ) -> Result { let mut tags = std::vec::Vec::new(); match roc_types::pretty_print::chase_ext_tag_union(subs, closure_var, &mut tags) { @@ -706,7 +704,7 @@ impl<'a> LambdaSet<'a> { arena, subs, seen: Vec::new_in(arena), - ptr_bytes, + target_info, }; for (tag_name, variables) in tags.iter() { @@ -724,7 +722,7 @@ impl<'a> LambdaSet<'a> { } let representation = - arena.alloc(Self::make_representation(arena, subs, tags, ptr_bytes)); + arena.alloc(Self::make_representation(arena, subs, tags, target_info)); Ok(LambdaSet { set: set.into_bump_slice(), @@ -747,10 +745,10 @@ impl<'a> LambdaSet<'a> { arena: &'a Bump, subs: &Subs, tags: std::vec::Vec<(TagName, std::vec::Vec)>, - ptr_bytes: u32, + target_info: TargetInfo, ) -> Layout<'a> { // otherwise, this is a closure with a payload - let variant = union_sorted_tags_help(arena, tags, None, subs, ptr_bytes); + let variant = union_sorted_tags_help(arena, tags, None, subs, target_info); use UnionVariant::*; match variant { @@ -790,8 +788,8 @@ impl<'a> LambdaSet<'a> { } } - pub fn stack_size(&self, pointer_size: u32) -> u32 { - self.representation.stack_size(pointer_size) + pub fn stack_size(&self, target_info: TargetInfo) -> u32 { + self.representation.stack_size(target_info) } pub fn contains_refcounted(&self) -> bool { self.representation.contains_refcounted() @@ -800,8 +798,8 @@ impl<'a> LambdaSet<'a> { self.representation.safe_to_memcpy() } - pub fn alignment_bytes(&self, pointer_size: u32) -> u32 { - self.representation.alignment_bytes(pointer_size) + pub fn alignment_bytes(&self, target_info: TargetInfo) -> u32 { + self.representation.alignment_bytes(target_info) } } @@ -818,7 +816,7 @@ pub enum Builtin<'a> { } pub struct Env<'a, 'b> { - ptr_bytes: u32, + target_info: TargetInfo, arena: &'a Bump, seen: Vec<'a, Variable>, subs: &'b Subs, @@ -890,7 +888,7 @@ impl<'a> Layout<'a> { } Symbol::NUM_NAT | Symbol::NUM_NATURAL | Symbol::NUM_AT_NATURAL => { - return Ok(Layout::usize(env.ptr_bytes)) + return Ok(Layout::usize(env.target_info)) } _ => Self::from_var(env, actual_var), @@ -962,31 +960,31 @@ impl<'a> Layout<'a> { } } - pub fn stack_size(&self, pointer_size: u32) -> u32 { - let width = self.stack_size_without_alignment(pointer_size); - let alignment = self.alignment_bytes(pointer_size); + pub fn stack_size(&self, target_info: TargetInfo) -> u32 { + let width = self.stack_size_without_alignment(target_info); + let alignment = self.alignment_bytes(target_info); round_up_to_alignment(width, alignment) } - pub fn stack_size_and_alignment(&self, pointer_size: u32) -> (u32, u32) { - let width = self.stack_size_without_alignment(pointer_size); - let alignment = self.alignment_bytes(pointer_size); + pub fn stack_size_and_alignment(&self, target_info: TargetInfo) -> (u32, u32) { + let width = self.stack_size_without_alignment(target_info); + let alignment = self.alignment_bytes(target_info); let size = round_up_to_alignment(width, alignment); (size, alignment) } - fn stack_size_without_alignment(&self, pointer_size: u32) -> u32 { + fn stack_size_without_alignment(&self, target_info: TargetInfo) -> u32 { use Layout::*; match self { - Builtin(builtin) => builtin.stack_size(pointer_size), + Builtin(builtin) => builtin.stack_size(target_info), Struct(fields) => { let mut sum = 0; for field_layout in *fields { - sum += field_layout.stack_size(pointer_size); + sum += field_layout.stack_size(target_info); } sum @@ -995,26 +993,26 @@ impl<'a> Layout<'a> { use UnionLayout::*; match variant { - NonRecursive(_) => variant.data_size_and_alignment(pointer_size).0, + NonRecursive(_) => variant.data_size_and_alignment(target_info).0, Recursive(_) | NullableWrapped { .. } | NullableUnwrapped { .. } - | NonNullableUnwrapped(_) => pointer_size, + | NonNullableUnwrapped(_) => target_info.ptr_width() as u32, } } LambdaSet(lambda_set) => lambda_set .runtime_representation() - .stack_size_without_alignment(pointer_size), - RecursivePointer => pointer_size, + .stack_size_without_alignment(target_info), + RecursivePointer => target_info.ptr_width() as u32, } } - pub fn alignment_bytes(&self, pointer_size: u32) -> u32 { + pub fn alignment_bytes(&self, target_info: TargetInfo) -> u32 { match self { Layout::Struct(fields) => fields .iter() - .map(|x| x.alignment_bytes(pointer_size)) + .map(|x| x.alignment_bytes(target_info)) .max() .unwrap_or(0), @@ -1028,44 +1026,44 @@ impl<'a> Layout<'a> { .flat_map(|layouts| { layouts .iter() - .map(|layout| layout.alignment_bytes(pointer_size)) + .map(|layout| layout.alignment_bytes(target_info)) }) .max(); let tag_id_builtin = variant.tag_id_builtin(); match max_alignment { Some(align) => round_up_to_alignment( - align.max(tag_id_builtin.alignment_bytes(pointer_size)), - tag_id_builtin.alignment_bytes(pointer_size), + align.max(tag_id_builtin.alignment_bytes(target_info)), + tag_id_builtin.alignment_bytes(target_info), ), None => { // none of the tags had any payload, but the tag id still contains information - tag_id_builtin.alignment_bytes(pointer_size) + tag_id_builtin.alignment_bytes(target_info) } } } Recursive(_) | NullableWrapped { .. } | NullableUnwrapped { .. } - | NonNullableUnwrapped(_) => pointer_size, + | NonNullableUnwrapped(_) => target_info.ptr_width() as u32, } } Layout::LambdaSet(lambda_set) => lambda_set .runtime_representation() - .alignment_bytes(pointer_size), - Layout::Builtin(builtin) => builtin.alignment_bytes(pointer_size), - Layout::RecursivePointer => pointer_size, + .alignment_bytes(target_info), + Layout::Builtin(builtin) => builtin.alignment_bytes(target_info), + Layout::RecursivePointer => target_info.ptr_width() as u32, } } - pub fn allocation_alignment_bytes(&self, pointer_size: u32) -> u32 { + pub fn allocation_alignment_bytes(&self, target_info: TargetInfo) -> u32 { match self { - Layout::Builtin(builtin) => builtin.allocation_alignment_bytes(pointer_size), + Layout::Builtin(builtin) => builtin.allocation_alignment_bytes(target_info), Layout::Struct(_) => unreachable!("not heap-allocated"), - Layout::Union(union_layout) => union_layout.allocation_alignment_bytes(pointer_size), + Layout::Union(union_layout) => union_layout.allocation_alignment_bytes(target_info), Layout::LambdaSet(lambda_set) => lambda_set .runtime_representation() - .allocation_alignment_bytes(pointer_size), + .allocation_alignment_bytes(target_info), Layout::RecursivePointer => unreachable!("should be looked up to get an actual layout"), } } @@ -1149,7 +1147,7 @@ impl<'a> Layout<'a> { /// But if we're careful when to invalidate certain keys, we still get some benefit #[derive(Debug)] pub struct LayoutCache<'a> { - ptr_bytes: u32, + target_info: TargetInfo, _marker: std::marker::PhantomData<&'a u8>, } @@ -1161,9 +1159,9 @@ pub enum CachedLayout<'a> { } impl<'a> LayoutCache<'a> { - pub fn new(ptr_bytes: u32) -> Self { + pub fn new(target_info: TargetInfo) -> Self { Self { - ptr_bytes, + target_info, _marker: Default::default(), } } @@ -1181,7 +1179,7 @@ impl<'a> LayoutCache<'a> { arena, subs, seen: Vec::new_in(arena), - ptr_bytes: self.ptr_bytes, + target_info: self.target_info, }; Layout::from_var(&mut env, var) @@ -1200,7 +1198,7 @@ impl<'a> LayoutCache<'a> { arena, subs, seen: Vec::new_in(arena), - ptr_bytes: self.ptr_bytes, + target_info: self.target_info, }; RawFunctionLayout::from_var(&mut env, var) } @@ -1232,11 +1230,10 @@ impl<'a> Layout<'a> { Layout::Builtin(Builtin::Float(FloatWidth::F32)) } - pub fn usize(ptr_bytes: u32) -> Layout<'a> { - match ptr_bytes { - 4 => Self::u32(), - 8 => Self::u64(), - _ => panic!("width of usize {} not supported", ptr_bytes), + pub fn usize(target_info: TargetInfo) -> Layout<'a> { + match target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => Self::u32(), + roc_target::PtrWidth::Bytes8 => Self::u64(), } } @@ -1310,25 +1307,29 @@ impl<'a> Builtin<'a> { pub const WRAPPER_PTR: u32 = 0; pub const WRAPPER_LEN: u32 = 1; - pub fn stack_size(&self, pointer_size: u32) -> u32 { + pub fn stack_size(&self, target_info: TargetInfo) -> u32 { use Builtin::*; + let ptr_width = target_info.ptr_width() as u32; + match self { Int(int) => int.stack_size(), Float(float) => float.stack_size(), Bool => Builtin::I1_SIZE, Decimal => Builtin::DECIMAL_SIZE, - Str => Builtin::STR_WORDS * pointer_size, - Dict(_, _) => Builtin::DICT_WORDS * pointer_size, - Set(_) => Builtin::SET_WORDS * pointer_size, - List(_) => Builtin::LIST_WORDS * pointer_size, + Str => Builtin::STR_WORDS * ptr_width, + Dict(_, _) => Builtin::DICT_WORDS * ptr_width, + Set(_) => Builtin::SET_WORDS * ptr_width, + List(_) => Builtin::LIST_WORDS * ptr_width, } } - pub fn alignment_bytes(&self, pointer_size: u32) -> u32 { + pub fn alignment_bytes(&self, target_info: TargetInfo) -> u32 { use std::mem::align_of; use Builtin::*; + let ptr_width = target_info.ptr_width() as u32; + // for our data structures, what counts is the alignment of the `( ptr, len )` tuple, and // since both of those are one pointer size, the alignment of that structure is a pointer // size @@ -1336,16 +1337,16 @@ impl<'a> Builtin<'a> { Int(int_width) => int_width.alignment_bytes(), Float(float_width) => float_width.alignment_bytes(), Bool => align_of::() as u32, - Decimal => align_of::() as u32, - Dict(_, _) => pointer_size, - Set(_) => pointer_size, + Decimal => IntWidth::I128.alignment_bytes(), + Dict(_, _) => ptr_width, + Set(_) => ptr_width, // we often treat these as i128 (64-bit systems) // or i64 (32-bit systems). // // In webassembly, For that to be safe // they must be aligned to allow such access - List(_) => pointer_size, - Str => pointer_size, + List(_) => ptr_width, + Str => ptr_width, } } @@ -1425,21 +1426,23 @@ impl<'a> Builtin<'a> { } } - pub fn allocation_alignment_bytes(&self, pointer_size: u32) -> u32 { + pub fn allocation_alignment_bytes(&self, target_info: TargetInfo) -> u32 { + let ptr_width = target_info.ptr_width() as u32; + let allocation = match self { Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal => { unreachable!("not heap-allocated") } - Builtin::Str => pointer_size, + Builtin::Str => ptr_width, Builtin::Dict(k, v) => k - .alignment_bytes(pointer_size) - .max(v.alignment_bytes(pointer_size)) - .max(pointer_size), - Builtin::Set(k) => k.alignment_bytes(pointer_size).max(pointer_size), - Builtin::List(e) => e.alignment_bytes(pointer_size).max(pointer_size), + .alignment_bytes(target_info) + .max(v.alignment_bytes(target_info)) + .max(ptr_width), + Builtin::Set(k) => k.alignment_bytes(target_info).max(ptr_width), + Builtin::List(e) => e.alignment_bytes(target_info).max(ptr_width), }; - allocation.max(pointer_size) + allocation.max(ptr_width) } } @@ -1451,7 +1454,7 @@ fn layout_from_flat_type<'a>( let arena = env.arena; let subs = env.subs; - let ptr_bytes = env.ptr_bytes; + let target_info = env.target_info; match flat_type { Apply(symbol, args) => { @@ -1461,7 +1464,7 @@ fn layout_from_flat_type<'a>( // Ints Symbol::NUM_NAT => { debug_assert_eq!(args.len(), 0); - Ok(Layout::usize(env.ptr_bytes)) + Ok(Layout::usize(env.target_info)) } Symbol::NUM_I128 => { @@ -1527,7 +1530,7 @@ fn layout_from_flat_type<'a>( let var = args[0]; let content = subs.get_content_without_compacting(var); - layout_from_num_content(content) + layout_from_num_content(content, target_info) } Symbol::STR_STR => Ok(Layout::Builtin(Builtin::Str)), @@ -1543,7 +1546,8 @@ fn layout_from_flat_type<'a>( } } Func(_, closure_var, _) => { - let lambda_set = LambdaSet::from_var(env.arena, env.subs, closure_var, env.ptr_bytes)?; + let lambda_set = + LambdaSet::from_var(env.arena, env.subs, closure_var, env.target_info)?; Ok(Layout::LambdaSet(lambda_set)) } @@ -1563,8 +1567,8 @@ fn layout_from_flat_type<'a>( } pairs.sort_by(|(label1, layout1), (label2, layout2)| { - let size1 = layout1.alignment_bytes(ptr_bytes); - let size2 = layout2.alignment_bytes(ptr_bytes); + let size1 = layout1.alignment_bytes(target_info); + let size2 = layout2.alignment_bytes(target_info); size2.cmp(&size1).then(label1.cmp(label2)) }); @@ -1584,7 +1588,7 @@ fn layout_from_flat_type<'a>( debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); - Ok(layout_from_tag_union(arena, &tags, subs, env.ptr_bytes)) + Ok(layout_from_tag_union(arena, &tags, subs, env.target_info)) } FunctionOrTagUnion(tag_name, _, ext_var) => { debug_assert!( @@ -1595,7 +1599,7 @@ fn layout_from_flat_type<'a>( let union_tags = UnionTags::from_tag_name_index(tag_name); let (tags, _) = union_tags.unsorted_tags_and_ext(subs, ext_var); - Ok(layout_from_tag_union(arena, &tags, subs, env.ptr_bytes)) + Ok(layout_from_tag_union(arena, &tags, subs, env.target_info)) } RecursiveTagUnion(rec_var, tags, ext_var) => { let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var); @@ -1645,8 +1649,8 @@ fn layout_from_flat_type<'a>( } tag_layout.sort_by(|layout1, layout2| { - let size1 = layout1.alignment_bytes(ptr_bytes); - let size2 = layout2.alignment_bytes(ptr_bytes); + let size1 = layout1.alignment_bytes(target_info); + let size2 = layout2.alignment_bytes(target_info); size2.cmp(&size1) }); @@ -1691,13 +1695,13 @@ pub fn sort_record_fields<'a>( arena: &'a Bump, var: Variable, subs: &Subs, - ptr_bytes: u32, + target_info: TargetInfo, ) -> Result>, LayoutProblem> { let mut env = Env { arena, subs, seen: Vec::new_in(arena), - ptr_bytes, + target_info, }; let (it, _) = match gather_fields_unsorted_iter(subs, RecordFields::empty(), var) { @@ -1716,7 +1720,7 @@ fn sort_record_fields_help<'a>( env: &mut Env<'a, '_>, fields_map: impl Iterator)>, ) -> Result>, LayoutProblem> { - let ptr_bytes = env.ptr_bytes; + let target_info = env.target_info; // Sort the fields by label let mut sorted_fields = Vec::with_capacity_in(fields_map.size_hint().0, env.arena); @@ -1738,8 +1742,8 @@ fn sort_record_fields_help<'a>( |(label1, _, res_layout1), (label2, _, res_layout2)| match res_layout1 { Ok(layout1) | Err(layout1) => match res_layout2 { Ok(layout2) | Err(layout2) => { - let size1 = layout1.alignment_bytes(ptr_bytes); - let size2 = layout2.alignment_bytes(ptr_bytes); + let size1 = layout1.alignment_bytes(target_info); + let size2 = layout2.alignment_bytes(target_info); size2.cmp(&size1).then(label1.cmp(label2)) } @@ -1873,7 +1877,7 @@ pub fn union_sorted_tags<'a>( arena: &'a Bump, var: Variable, subs: &Subs, - ptr_bytes: u32, + target_info: TargetInfo, ) -> Result, LayoutProblem> { let var = if let Content::RecursionVar { structure, .. } = subs.get_content_without_compacting(var) { @@ -1886,7 +1890,7 @@ pub fn union_sorted_tags<'a>( let result = match roc_types::pretty_print::chase_ext_tag_union(subs, var, &mut tags_vec) { Ok(()) | Err((_, Content::FlexVar(_))) | Err((_, Content::RecursionVar { .. })) => { let opt_rec_var = get_recursion_var(subs, var); - union_sorted_tags_help(arena, tags_vec, opt_rec_var, subs, ptr_bytes) + union_sorted_tags_help(arena, tags_vec, opt_rec_var, subs, target_info) } Err((_, Content::Error)) => return Err(LayoutProblem::Erroneous), Err(other) => panic!("invalid content in tag union variable: {:?}", other), @@ -1920,7 +1924,7 @@ fn union_sorted_tags_help_new<'a>( tags_list: &[(&'_ TagName, &[Variable])], opt_rec_var: Option, subs: &Subs, - ptr_bytes: u32, + target_info: TargetInfo, ) -> UnionVariant<'a> { // sort up front; make sure the ordering stays intact! let mut tags_list = Vec::from_iter_in(tags_list.iter(), arena); @@ -1930,7 +1934,7 @@ fn union_sorted_tags_help_new<'a>( arena, subs, seen: Vec::new_in(arena), - ptr_bytes, + target_info, }; match tags_list.len() { @@ -1949,7 +1953,8 @@ fn union_sorted_tags_help_new<'a>( match tag_name { TagName::Private(Symbol::NUM_AT_NUM) => { let var = arguments[0]; - layouts.push(unwrap_num_tag(subs, var, ptr_bytes).expect("invalid num layout")); + layouts + .push(unwrap_num_tag(subs, var, target_info).expect("invalid num layout")); } _ => { for &var in arguments { @@ -1973,8 +1978,8 @@ fn union_sorted_tags_help_new<'a>( } layouts.sort_by(|layout1, layout2| { - let size1 = layout1.alignment_bytes(ptr_bytes); - let size2 = layout2.alignment_bytes(ptr_bytes); + let size1 = layout1.alignment_bytes(target_info); + let size2 = layout2.alignment_bytes(target_info); size2.cmp(&size1) }); @@ -2051,8 +2056,8 @@ fn union_sorted_tags_help_new<'a>( } arg_layouts.sort_by(|layout1, layout2| { - let size1 = layout1.alignment_bytes(ptr_bytes); - let size2 = layout2.alignment_bytes(ptr_bytes); + let size1 = layout1.alignment_bytes(target_info); + let size2 = layout2.alignment_bytes(target_info); size2.cmp(&size1) }); @@ -2123,7 +2128,7 @@ pub fn union_sorted_tags_help<'a>( mut tags_vec: std::vec::Vec<(TagName, std::vec::Vec)>, opt_rec_var: Option, subs: &Subs, - ptr_bytes: u32, + target_info: TargetInfo, ) -> UnionVariant<'a> { // sort up front; make sure the ordering stays intact! tags_vec.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); @@ -2132,7 +2137,7 @@ pub fn union_sorted_tags_help<'a>( arena, subs, seen: Vec::new_in(arena), - ptr_bytes, + target_info, }; match tags_vec.len() { @@ -2151,7 +2156,8 @@ pub fn union_sorted_tags_help<'a>( match tag_name { TagName::Private(Symbol::NUM_AT_NUM) => { layouts.push( - unwrap_num_tag(subs, arguments[0], ptr_bytes).expect("invalid num layout"), + unwrap_num_tag(subs, arguments[0], target_info) + .expect("invalid num layout"), ); } _ => { @@ -2181,8 +2187,8 @@ pub fn union_sorted_tags_help<'a>( } layouts.sort_by(|layout1, layout2| { - let size1 = layout1.alignment_bytes(ptr_bytes); - let size2 = layout2.alignment_bytes(ptr_bytes); + let size1 = layout1.alignment_bytes(target_info); + let size2 = layout2.alignment_bytes(target_info); size2.cmp(&size1) }); @@ -2264,8 +2270,8 @@ pub fn union_sorted_tags_help<'a>( } arg_layouts.sort_by(|layout1, layout2| { - let size1 = layout1.alignment_bytes(ptr_bytes); - let size2 = layout2.alignment_bytes(ptr_bytes); + let size1 = layout1.alignment_bytes(target_info); + let size2 = layout2.alignment_bytes(target_info); size2.cmp(&size1) }); @@ -2335,20 +2341,20 @@ fn layout_from_newtype<'a>( arena: &'a Bump, tags: &UnsortedUnionTags, subs: &Subs, - ptr_bytes: u32, + target_info: TargetInfo, ) -> Layout<'a> { debug_assert!(tags.is_newtype_wrapper(subs)); let (tag_name, var) = tags.get_newtype(subs); if tag_name == &TagName::Private(Symbol::NUM_AT_NUM) { - unwrap_num_tag(subs, var, ptr_bytes).expect("invalid Num argument") + unwrap_num_tag(subs, var, target_info).expect("invalid Num argument") } else { let mut env = Env { arena, subs, seen: Vec::new_in(arena), - ptr_bytes, + target_info, }; match Layout::from_var(&mut env, var) { @@ -2372,12 +2378,12 @@ fn layout_from_tag_union<'a>( arena: &'a Bump, tags: &UnsortedUnionTags, subs: &Subs, - ptr_bytes: u32, + target_info: TargetInfo, ) -> Layout<'a> { use UnionVariant::*; if tags.is_newtype_wrapper(subs) { - return layout_from_newtype(arena, tags, subs, ptr_bytes); + return layout_from_newtype(arena, tags, subs, target_info); } let tags_vec = &tags.tags; @@ -2388,11 +2394,12 @@ fn layout_from_tag_union<'a>( let &var = arguments.iter().next().unwrap(); - unwrap_num_tag(subs, var, ptr_bytes).expect("invalid Num argument") + unwrap_num_tag(subs, var, target_info).expect("invalid Num argument") } _ => { let opt_rec_var = None; - let variant = union_sorted_tags_help_new(arena, tags_vec, opt_rec_var, subs, ptr_bytes); + let variant = + union_sorted_tags_help_new(arena, tags_vec, opt_rec_var, subs, target_info); match variant { Never => Layout::VOID, @@ -2490,12 +2497,13 @@ pub fn ext_var_is_empty_tag_union(_: &Subs, _: Variable) -> bool { unreachable!(); } -fn layout_from_num_content<'a>(content: &Content) -> Result, LayoutProblem> { +fn layout_from_num_content<'a>( + content: &Content, + target_info: TargetInfo, +) -> Result, LayoutProblem> { use roc_types::subs::Content::*; use roc_types::subs::FlatType::*; - let ptr_bytes = 8; - match content { RecursionVar { .. } => panic!("recursion var in num"), FlexVar(_) | RigidVar(_) => { @@ -2507,7 +2515,7 @@ fn layout_from_num_content<'a>(content: &Content) -> Result, LayoutPr } Structure(Apply(symbol, args)) => match *symbol { // Ints - Symbol::NUM_NAT => Ok(Layout::usize(ptr_bytes)), + Symbol::NUM_NAT => Ok(Layout::usize(target_info)), Symbol::NUM_INTEGER => Ok(Layout::i64()), Symbol::NUM_I128 => Ok(Layout::i128()), @@ -2550,7 +2558,7 @@ fn layout_from_num_content<'a>(content: &Content) -> Result, LayoutPr fn unwrap_num_tag<'a>( subs: &Subs, var: Variable, - ptr_bytes: u32, + target_info: TargetInfo, ) -> Result, LayoutProblem> { match subs.get_content_without_compacting(var) { Content::Alias(Symbol::NUM_INTEGER, args, _) => { @@ -2575,7 +2583,7 @@ fn unwrap_num_tag<'a>( Symbol::NUM_UNSIGNED32 => Layout::u32(), Symbol::NUM_UNSIGNED16 => Layout::u16(), Symbol::NUM_UNSIGNED8 => Layout::u8(), - Symbol::NUM_NATURAL => Layout::usize(ptr_bytes), + Symbol::NUM_NATURAL => Layout::usize(target_info), _ => unreachable!("not a valid int variant: {:?} {:?}", symbol, args), }; @@ -2821,8 +2829,8 @@ mod test { let layout = Layout::Union(UnionLayout::NonRecursive(&tt)); - let ptr_width = 8; - assert_eq!(layout.stack_size(ptr_width), 1); - assert_eq!(layout.alignment_bytes(ptr_width), 1); + let target_info = TargetInfo::default_x86_64(); + assert_eq!(layout.stack_size(target_info), 1); + assert_eq!(layout.alignment_bytes(target_info), 1); } } diff --git a/compiler/target/src/lib.rs b/compiler/target/src/lib.rs index 933cc2f6c6..237ca81f29 100644 --- a/compiler/target/src/lib.rs +++ b/compiler/target/src/lib.rs @@ -2,10 +2,31 @@ // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. #![allow(clippy::large_enum_variant)] +#[derive(Debug, Clone, Copy)] pub struct TargetInfo { architecture: Architecture, } +impl TargetInfo { + pub const fn ptr_width(&self) -> PtrWidth { + self.architecture.ptr_width() + } + + pub const fn default_x86_64() -> Self { + TargetInfo { + architecture: Architecture::X86_64, + } + } +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +pub enum PtrWidth { + Bytes4 = 4, + Bytes8 = 8, +} + +#[derive(Debug, Clone, Copy)] pub enum Architecture { X86_64, X86_32, @@ -15,12 +36,12 @@ pub enum Architecture { } impl Architecture { - pub const fn ptr_width(&self) -> u32 { + pub const fn ptr_width(&self) -> PtrWidth { use Architecture::*; match self { - X86_64 | Aarch64 | Arm => 8, - X86_32 | Wasm32 => 4, + X86_64 | Aarch64 | Arm => PtrWidth::Bytes8, + X86_32 | Wasm32 => PtrWidth::Bytes4, } } } From 74932a4cab728eb7919e4eabec1c74d21d58da51 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Jan 2022 14:30:37 +0100 Subject: [PATCH 320/541] phase 2 --- cli/src/repl/gen.rs | 2 +- compiler/build/src/program.rs | 2 +- compiler/gen_llvm/src/llvm/build_dict.rs | 62 +++++++++++------------ compiler/gen_llvm/src/llvm/build_hash.rs | 10 ++-- compiler/gen_llvm/src/llvm/build_list.rs | 8 +-- compiler/gen_llvm/src/llvm/build_str.rs | 8 +-- compiler/gen_llvm/src/llvm/convert.rs | 22 ++++---- compiler/gen_llvm/src/llvm/externs.rs | 2 +- compiler/gen_llvm/src/llvm/refcounting.rs | 8 +-- compiler/load/src/file.rs | 4 +- compiler/mono/src/ir.rs | 52 +++++++++---------- compiler/test_gen/src/helpers/llvm.rs | 2 +- reporting/tests/test_reporting.rs | 2 +- 13 files changed, 93 insertions(+), 91 deletions(-) diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index 5a52f74420..1510a4c448 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -181,7 +181,7 @@ pub fn gen_and_eval<'a>( context: &context, interns, module, - ptr_bytes, + target_info: ptr_bytes, is_gen_test: true, // so roc_panic is generated // important! we don't want any procedures to get the C calling convention exposed_to_host: MutSet::default(), diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index c3ee7cc397..1ba657e615 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -286,7 +286,7 @@ pub fn gen_from_mono_module_llvm( context: &context, interns: loaded.interns, module, - ptr_bytes, + target_info: ptr_bytes, // in gen_tests, the compiler provides roc_panic // and sets up the setjump/longjump exception handling is_gen_test: false, diff --git a/compiler/gen_llvm/src/llvm/build_dict.rs b/compiler/gen_llvm/src/llvm/build_dict.rs index 4feb77aa2d..47f8595d70 100644 --- a/compiler/gen_llvm/src/llvm/build_dict.rs +++ b/compiler/gen_llvm/src/llvm/build_dict.rs @@ -105,15 +105,15 @@ pub fn dict_insert<'a, 'ctx, 'env>( let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env .ptr_int() - .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(value_layout.stack_size(env.target_info) as u64, false); let result_ptr = builder.build_alloca(zig_dict_type(env), "result_ptr"); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); @@ -162,15 +162,15 @@ pub fn dict_remove<'a, 'ctx, 'env>( let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env .ptr_int() - .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(value_layout.stack_size(env.target_info) as u64, false); let result_ptr = builder.build_alloca(zig_dict_type(env), "result_ptr"); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); @@ -218,13 +218,13 @@ pub fn dict_contains<'a, 'ctx, 'env>( let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env .ptr_int() - .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(value_layout.stack_size(env.target_info) as u64, false); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); @@ -264,13 +264,13 @@ pub fn dict_get<'a, 'ctx, 'env>( let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env .ptr_int() - .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(value_layout.stack_size(env.target_info) as u64, false); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); @@ -366,13 +366,13 @@ pub fn dict_elements_rc<'a, 'ctx, 'env>( ) { let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env .ptr_int() - .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(value_layout.stack_size(env.target_info) as u64, false); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let (key_fn, value_fn) = match rc_operation { @@ -412,13 +412,13 @@ pub fn dict_keys<'a, 'ctx, 'env>( let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env .ptr_int() - .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(value_layout.stack_size(env.target_info) as u64, false); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let inc_key_fn = build_inc_wrapper(env, layout_ids, key_layout); @@ -454,7 +454,7 @@ fn pass_dict_c_abi<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, dict: BasicValueEnum<'ctx>, ) -> BasicValueEnum<'ctx> { - match env.ptr_bytes { + match env.target_info { 4 => { let target_type = env.context.custom_width_int_type(96).into(); @@ -483,13 +483,13 @@ pub fn dict_union<'a, 'ctx, 'env>( let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env .ptr_int() - .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(value_layout.stack_size(env.target_info) as u64, false); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); @@ -576,13 +576,13 @@ fn dict_intersect_or_difference<'a, 'ctx, 'env>( let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env .ptr_int() - .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(value_layout.stack_size(env.target_info) as u64, false); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); @@ -631,7 +631,7 @@ pub fn dict_walk<'a, 'ctx, 'env>( let accum_ptr = builder.build_alloca(accum_bt, "accum_ptr"); env.builder.build_store(accum_ptr, accum); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let output_ptr = builder.build_alloca(accum_bt, "output_ptr"); @@ -671,13 +671,13 @@ pub fn dict_values<'a, 'ctx, 'env>( let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env .ptr_int() - .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(value_layout.stack_size(env.target_info) as u64, false); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let inc_value_fn = build_inc_wrapper(env, layout_ids, value_layout); @@ -729,14 +729,14 @@ pub fn set_from_list<'a, 'ctx, 'env>( let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env.ptr_int().const_zero(); let result_alloca = builder.build_alloca(zig_dict_type(env), "result_alloca"); let alignment = - Alignment::from_key_value_layout(key_layout, &Layout::Struct(&[]), env.ptr_bytes); + Alignment::from_key_value_layout(key_layout, &Layout::Struct(&[]), env.target_info); let alignment_iv = alignment.as_int_value(env.context); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); diff --git a/compiler/gen_llvm/src/llvm/build_hash.rs b/compiler/gen_llvm/src/llvm/build_hash.rs index 8036871a74..0db5348d1d 100644 --- a/compiler/gen_llvm/src/llvm/build_hash.rs +++ b/compiler/gen_llvm/src/llvm/build_hash.rs @@ -120,7 +120,7 @@ fn hash_builtin<'a, 'ctx, 'env>( builtin: &Builtin<'a>, when_recursive: WhenRecursive<'a>, ) -> IntValue<'ctx> { - let ptr_bytes = env.ptr_bytes; + let ptr_bytes = env.target_info; match builtin { Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal => { @@ -246,7 +246,7 @@ fn hash_struct<'a, 'ctx, 'env>( when_recursive: WhenRecursive<'a>, field_layouts: &[Layout<'a>], ) -> IntValue<'ctx> { - let ptr_bytes = env.ptr_bytes; + let ptr_bytes = env.target_info; let layout = Layout::Struct(field_layouts); @@ -423,7 +423,7 @@ fn hash_tag<'a, 'ctx, 'env>( env, seed, hash_bytes, - tag_id_layout.stack_size(env.ptr_bytes), + tag_id_layout.stack_size(env.target_info), ); // hash the tag data @@ -474,7 +474,7 @@ fn hash_tag<'a, 'ctx, 'env>( env, seed, hash_bytes, - tag_id_layout.stack_size(env.ptr_bytes), + tag_id_layout.stack_size(env.target_info), ); // hash the tag data @@ -574,7 +574,7 @@ fn hash_tag<'a, 'ctx, 'env>( env, seed, hash_bytes, - tag_id_layout.stack_size(env.ptr_bytes), + tag_id_layout.stack_size(env.target_info), ); // hash tag data diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index f2c5351d1b..169db029c1 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -87,7 +87,7 @@ pub fn layout_width<'a, 'ctx, 'env>( layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { env.ptr_int() - .const_int(layout.stack_size(env.ptr_bytes) as u64, false) + .const_int(layout.stack_size(env.target_info) as u64, false) .into() } @@ -1254,17 +1254,17 @@ pub fn allocate_list<'a, 'ctx, 'env>( let ctx = env.context; let len_type = env.ptr_int(); - let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64; + let elem_bytes = elem_layout.stack_size(env.target_info) as u64; let bytes_per_element = len_type.const_int(elem_bytes, false); let number_of_data_bytes = builder.build_int_mul(bytes_per_element, number_of_elements, "data_length"); // the refcount of a new list is initially 1 // we assume that the list is indeed used (dead variables are eliminated) - let rc1 = crate::llvm::refcounting::refcount_1(ctx, env.ptr_bytes); + let rc1 = crate::llvm::refcounting::refcount_1(ctx, env.target_info); let basic_type = basic_type_from_layout(env, elem_layout); - let alignment_bytes = elem_layout.alignment_bytes(env.ptr_bytes); + let alignment_bytes = elem_layout.alignment_bytes(env.target_info); allocate_with_refcount_help(env, basic_type, alignment_bytes, number_of_data_bytes, rc1) } diff --git a/compiler/gen_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index 3c92e39cdb..a7337cd07d 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -79,7 +79,7 @@ fn str_symbol_to_c_abi<'a, 'ctx, 'env>( ) -> IntValue<'ctx> { let string = load_symbol(scope, &symbol); - let target_type = match env.ptr_bytes { + let target_type = match env.target_info { 8 => env.context.i128_type().into(), 4 => env.context.i64_type().into(), _ => unreachable!(), @@ -96,7 +96,7 @@ pub fn str_to_c_abi<'a, 'ctx, 'env>( env.builder.build_store(cell, value); - let target_type = match env.ptr_bytes { + let target_type = match env.target_info { 8 => env.context.i128_type(), 4 => env.context.i64_type(), _ => unreachable!(), @@ -310,7 +310,7 @@ fn decode_from_utf8_result<'a, 'ctx, 'env>( let builder = env.builder; let ctx = env.context; - let fields = match env.ptr_bytes { + let fields = match env.target_info { 8 | 4 => [ env.ptr_int().into(), super::convert::zig_str_type(env).into(), @@ -322,7 +322,7 @@ fn decode_from_utf8_result<'a, 'ctx, 'env>( let record_type = env.context.struct_type(&fields, false); - match env.ptr_bytes { + match env.target_info { 8 | 4 => { let result_ptr_cast = env .builder diff --git a/compiler/gen_llvm/src/llvm/convert.rs b/compiler/gen_llvm/src/llvm/convert.rs index 1fc8179134..3bebd44fa1 100644 --- a/compiler/gen_llvm/src/llvm/convert.rs +++ b/compiler/gen_llvm/src/llvm/convert.rs @@ -36,7 +36,7 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>( match union_layout { NonRecursive(tags) => { - let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); + let data = block_of_memory_slices(env.context, tags, env.target_info); env.context.struct_type(&[data, tag_id_type], false).into() } @@ -44,9 +44,9 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>( | NullableWrapped { other_tags: tags, .. } => { - let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); + let data = block_of_memory_slices(env.context, tags, env.target_info); - if union_layout.stores_tag_id_as_data(env.ptr_bytes) { + if union_layout.stores_tag_id_as_data(env.target_info) { env.context .struct_type(&[data, tag_id_type], false) .ptr_type(AddressSpace::Generic) @@ -56,11 +56,12 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>( } } NullableUnwrapped { other_fields, .. } => { - let block = block_of_memory_slices(env.context, &[other_fields], env.ptr_bytes); + let block = + block_of_memory_slices(env.context, &[other_fields], env.target_info); block.ptr_type(AddressSpace::Generic).into() } NonNullableUnwrapped(fields) => { - let block = block_of_memory_slices(env.context, &[fields], env.ptr_bytes); + let block = block_of_memory_slices(env.context, &[fields], env.target_info); block.ptr_type(AddressSpace::Generic).into() } } @@ -95,7 +96,7 @@ pub fn basic_type_from_layout_1<'a, 'ctx, 'env>( match union_layout { NonRecursive(tags) => { - let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); + let data = block_of_memory_slices(env.context, tags, env.target_info); let struct_type = env.context.struct_type(&[data, tag_id_type], false); struct_type.ptr_type(AddressSpace::Generic).into() @@ -104,9 +105,9 @@ pub fn basic_type_from_layout_1<'a, 'ctx, 'env>( | NullableWrapped { other_tags: tags, .. } => { - let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); + let data = block_of_memory_slices(env.context, tags, env.target_info); - if union_layout.stores_tag_id_as_data(env.ptr_bytes) { + if union_layout.stores_tag_id_as_data(env.target_info) { env.context .struct_type(&[data, tag_id_type], false) .ptr_type(AddressSpace::Generic) @@ -116,11 +117,12 @@ pub fn basic_type_from_layout_1<'a, 'ctx, 'env>( } } NullableUnwrapped { other_fields, .. } => { - let block = block_of_memory_slices(env.context, &[other_fields], env.ptr_bytes); + let block = + block_of_memory_slices(env.context, &[other_fields], env.target_info); block.ptr_type(AddressSpace::Generic).into() } NonNullableUnwrapped(fields) => { - let block = block_of_memory_slices(env.context, &[fields], env.ptr_bytes); + let block = block_of_memory_slices(env.context, &[fields], env.target_info); block.ptr_type(AddressSpace::Generic).into() } } diff --git a/compiler/gen_llvm/src/llvm/externs.rs b/compiler/gen_llvm/src/llvm/externs.rs index 75bc506cc2..69b251cdcf 100644 --- a/compiler/gen_llvm/src/llvm/externs.rs +++ b/compiler/gen_llvm/src/llvm/externs.rs @@ -175,7 +175,7 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) { let buffer = crate::llvm::build::get_sjlj_buffer(env); // write our error message pointer - let index = env.ptr_int().const_int(3 * env.ptr_bytes as u64, false); + let index = env.ptr_int().const_int(3 * env.target_info as u64, false); let message_buffer_raw = unsafe { builder.build_gep(buffer, &[index], "raw_msg_buffer_ptr") }; let message_buffer = builder.build_bitcast( diff --git a/compiler/gen_llvm/src/llvm/refcounting.rs b/compiler/gen_llvm/src/llvm/refcounting.rs index b04acb06a9..229f3c4b86 100644 --- a/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/compiler/gen_llvm/src/llvm/refcounting.rs @@ -98,7 +98,7 @@ impl<'ctx> PointerToRefcount<'ctx> { pub fn is_1<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> { let current = self.get_refcount(env); - let one = refcount_1(env.context, env.ptr_bytes); + let one = refcount_1(env.context, env.target_info); env.builder .build_int_compare(IntPredicate::EQ, current, one, "is_one") @@ -163,8 +163,8 @@ impl<'ctx> PointerToRefcount<'ctx> { pub fn decrement<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) { let alignment = layout - .allocation_alignment_bytes(env.ptr_bytes) - .max(env.ptr_bytes); + .allocation_alignment_bytes(env.target_info) + .max(env.target_info); let context = env.context; let block = env.builder.get_insert_block().expect("to be in a function"); @@ -1192,7 +1192,7 @@ fn build_rec_union_help<'a, 'ctx, 'env>( debug_assert!(arg_val.is_pointer_value()); let current_tag_id = get_tag_id(env, fn_val, &union_layout, arg_val); - let value_ptr = if union_layout.stores_tag_id_in_pointer(env.ptr_bytes) { + let value_ptr = if union_layout.stores_tag_id_in_pointer(env.target_info) { tag_pointer_clear_tag_id(env, arg_val.into_pointer_value()) } else { arg_val.into_pointer_value() diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index ce3fb7ee11..f0b4753863 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -3824,7 +3824,7 @@ fn make_specializations<'a>( subs: &mut subs, home, ident_ids: &mut ident_ids, - ptr_bytes, + target_info: ptr_bytes, update_mode_ids: &mut update_mode_ids, // call_specialization_counter=0 is reserved call_specialization_counter: 1, @@ -3920,7 +3920,7 @@ fn build_pending_specializations<'a>( subs: &mut subs, home, ident_ids: &mut ident_ids, - ptr_bytes, + target_info: ptr_bytes, update_mode_ids: &mut update_mode_ids, // call_specialization_counter=0 is reserved call_specialization_counter: 1, diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 9b83b2b388..a2517b05f5 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -1071,7 +1071,7 @@ pub struct Env<'a, 'i> { pub problems: &'i mut std::vec::Vec, pub home: ModuleId, pub ident_ids: &'i mut IdentIds, - pub ptr_bytes: u32, + pub target_info: u32, pub update_mode_ids: &'i mut UpdateModeIds, pub call_specialization_counter: u32, } @@ -2471,7 +2471,7 @@ fn specialize_external<'a>( env.arena, ); - let ptr_bytes = env.ptr_bytes; + let ptr_bytes = env.target_info; combined.sort_by(|(_, layout1), (_, layout2)| { let size1 = layout1.alignment_bytes(ptr_bytes); @@ -2504,7 +2504,7 @@ fn specialize_external<'a>( env.arena, ); - let ptr_bytes = env.ptr_bytes; + let ptr_bytes = env.target_info; combined.sort_by(|(_, layout1), (_, layout2)| { let size1 = layout1.alignment_bytes(ptr_bytes); @@ -3009,14 +3009,14 @@ fn try_make_literal<'a>( match can_expr { Int(_, precision, _, int) => { - match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *precision, false) { + match num_argument_to_int_or_float(env.subs, env.target_info, *precision, false) { IntOrFloat::Int(_) => Some(Literal::Int(*int)), _ => unreachable!("unexpected float precision for integer"), } } Float(_, precision, float_str, float) => { - match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *precision, true) { + match num_argument_to_int_or_float(env.subs, env.target_info, *precision, true) { IntOrFloat::Float(_) => Some(Literal::Float(*float)), IntOrFloat::DecimalFloatType => { let dec = match RocDec::from_str(float_str) { @@ -3037,7 +3037,7 @@ fn try_make_literal<'a>( // Str(string) => Some(Literal::Str(env.arena.alloc(string))), Num(var, num_str, num) => { // first figure out what kind of number this is - match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *var, false) { + match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { IntOrFloat::Int(_) => Some(Literal::Int((*num).into())), IntOrFloat::Float(_) => Some(Literal::Float(*num as f64)), IntOrFloat::DecimalFloatType => { @@ -3072,7 +3072,7 @@ pub fn with_hole<'a>( match can_expr { Int(_, precision, _, int) => { - match num_argument_to_int_or_float(env.subs, env.ptr_bytes, precision, false) { + match num_argument_to_int_or_float(env.subs, env.target_info, precision, false) { IntOrFloat::Int(precision) => Stmt::Let( assigned, Expr::Literal(Literal::Int(int)), @@ -3084,7 +3084,7 @@ pub fn with_hole<'a>( } Float(_, precision, float_str, float) => { - match num_argument_to_int_or_float(env.subs, env.ptr_bytes, precision, true) { + match num_argument_to_int_or_float(env.subs, env.target_info, precision, true) { IntOrFloat::Float(precision) => Stmt::Let( assigned, Expr::Literal(Literal::Float(float)), @@ -3116,7 +3116,7 @@ pub fn with_hole<'a>( Num(var, num_str, num) => { // first figure out what kind of number this is - match num_argument_to_int_or_float(env.subs, env.ptr_bytes, var, false) { + match num_argument_to_int_or_float(env.subs, env.target_info, var, false) { IntOrFloat::Int(precision) => Stmt::Let( assigned, Expr::Literal(Literal::Int(num.into())), @@ -3393,7 +3393,7 @@ pub fn with_hole<'a>( env.arena, record_var, env.subs, - env.ptr_bytes, + env.target_info, ) { Ok(fields) => fields, Err(_) => return Stmt::RuntimeError("Can't create record with improper layout"), @@ -3754,7 +3754,7 @@ pub fn with_hole<'a>( env.arena, record_var, env.subs, - env.ptr_bytes, + env.target_info, ) { Ok(fields) => fields, Err(_) => return Stmt::RuntimeError("Can't access record with improper layout"), @@ -3911,7 +3911,7 @@ pub fn with_hole<'a>( env.arena, record_var, env.subs, - env.ptr_bytes, + env.target_info, ) { Ok(fields) => fields, Err(_) => return Stmt::RuntimeError("Can't update record with improper layout"), @@ -4586,7 +4586,7 @@ fn construct_closure_data<'a>( env.arena, ); - let ptr_bytes = env.ptr_bytes; + let ptr_bytes = env.target_info; combined.sort_by(|(_, layout1), (_, layout2)| { let size1 = layout1.alignment_bytes(ptr_bytes); @@ -4617,7 +4617,7 @@ fn construct_closure_data<'a>( env.arena, ); - let ptr_bytes = env.ptr_bytes; + let ptr_bytes = env.target_info; combined.sort_by(|(_, layout1), (_, layout2)| { let size1 = layout1.alignment_bytes(ptr_bytes); @@ -4692,7 +4692,7 @@ fn convert_tag_union<'a>( ) -> Stmt<'a> { use crate::layout::UnionVariant::*; let res_variant = - crate::layout::union_sorted_tags(env.arena, variant_var, env.subs, env.ptr_bytes); + crate::layout::union_sorted_tags(env.arena, variant_var, env.subs, env.target_info); let variant = match res_variant { Ok(cached) => cached, Err(LayoutProblem::UnresolvedTypeVar(_)) => { @@ -5035,7 +5035,7 @@ fn sorted_field_symbols<'a>( } }; - let alignment = layout.alignment_bytes(env.ptr_bytes); + let alignment = layout.alignment_bytes(env.target_info); let symbol = possible_reuse_symbol(env, procs, &arg.value); field_symbols_temp.push((alignment, symbol, ((var, arg), &*env.arena.alloc(symbol)))); @@ -5120,7 +5120,7 @@ fn register_capturing_closure<'a>( let captured_symbols = match *env.subs.get_content_without_compacting(function_type) { Content::Structure(FlatType::Func(_, closure_var, _)) => { - match LambdaSet::from_var(env.arena, env.subs, closure_var, env.ptr_bytes) { + match LambdaSet::from_var(env.arena, env.subs, closure_var, env.target_info) { Ok(lambda_set) => { if let Layout::Struct(&[]) = lambda_set.runtime_representation() { CapturedSymbols::None @@ -7621,7 +7621,7 @@ fn from_can_pattern_help<'a>( Underscore => Ok(Pattern::Underscore), Identifier(symbol) => Ok(Pattern::Identifier(*symbol)), IntLiteral(var, _, int) => { - match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *var, false) { + match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { IntOrFloat::Int(precision) => Ok(Pattern::IntLiteral(*int as i128, precision)), other => { panic!( @@ -7633,7 +7633,7 @@ fn from_can_pattern_help<'a>( } FloatLiteral(var, float_str, float) => { // TODO: Can I reuse num_argument_to_int_or_float here if I pass in true? - match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *var, true) { + match num_argument_to_int_or_float(env.subs, env.target_info, *var, true) { IntOrFloat::Int(_) => { panic!("Invalid precision for float pattern {:?}", var) } @@ -7663,7 +7663,7 @@ fn from_can_pattern_help<'a>( Err(RuntimeError::UnsupportedPattern(*region)) } NumLiteral(var, num_str, num) => { - match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *var, false) { + match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { IntOrFloat::Int(precision) => Ok(Pattern::IntLiteral(*num as i128, precision)), IntOrFloat::Float(precision) => Ok(Pattern::FloatLiteral(*num as u64, precision)), IntOrFloat::DecimalFloatType => { @@ -7686,7 +7686,7 @@ fn from_can_pattern_help<'a>( use crate::layout::UnionVariant::*; let res_variant = - crate::layout::union_sorted_tags(env.arena, *whole_var, env.subs, env.ptr_bytes) + crate::layout::union_sorted_tags(env.arena, *whole_var, env.subs, env.target_info) .map_err(Into::into); let variant = match res_variant { @@ -7768,12 +7768,12 @@ fn from_can_pattern_help<'a>( arguments.sort_by(|arg1, arg2| { let size1 = layout_cache .from_var(env.arena, arg1.0, env.subs) - .map(|x| x.alignment_bytes(env.ptr_bytes)) + .map(|x| x.alignment_bytes(env.target_info)) .unwrap_or(0); let size2 = layout_cache .from_var(env.arena, arg2.0, env.subs) - .map(|x| x.alignment_bytes(env.ptr_bytes)) + .map(|x| x.alignment_bytes(env.target_info)) .unwrap_or(0); size2.cmp(&size1) @@ -7806,8 +7806,8 @@ fn from_can_pattern_help<'a>( let layout2 = layout_cache.from_var(env.arena, arg2.0, env.subs).unwrap(); - let size1 = layout1.alignment_bytes(env.ptr_bytes); - let size2 = layout2.alignment_bytes(env.ptr_bytes); + let size1 = layout1.alignment_bytes(env.target_info); + let size2 = layout2.alignment_bytes(env.target_info); size2.cmp(&size1) }); @@ -8107,7 +8107,7 @@ fn from_can_pattern_help<'a>( } => { // sorted fields based on the type let sorted_fields = - crate::layout::sort_record_fields(env.arena, *whole_var, env.subs, env.ptr_bytes) + crate::layout::sort_record_fields(env.arena, *whole_var, env.subs, env.target_info) .map_err(RuntimeError::from)?; // sorted fields based on the destruct diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index e1bec306b1..6dc6c2b7f4 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -213,7 +213,7 @@ fn create_llvm_module<'a>( context, interns, module, - ptr_bytes, + target_info: ptr_bytes, is_gen_test, // important! we don't want any procedures to get the C calling convention exposed_to_host: MutSet::default(), diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 0cb8640770..d0c6946def 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -105,7 +105,7 @@ mod test_reporting { home, ident_ids: &mut ident_ids, update_mode_ids: &mut update_mode_ids, - ptr_bytes, + target_info: ptr_bytes, // call_specialization_counter=0 is reserved call_specialization_counter: 1, }; From 0ed259a80dc367a014d4ef0733e699f8108abefe Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Jan 2022 14:37:32 +0100 Subject: [PATCH 321/541] phase 3 --- compiler/mono/src/code_gen_help/equality.rs | 4 +++- compiler/mono/src/code_gen_help/mod.rs | 10 +++++----- compiler/mono/src/code_gen_help/refcount.rs | 20 +++++++++++--------- compiler/mono/src/ir.rs | 16 ++++++++-------- compiler/mono/src/layout.rs | 7 +++++++ 5 files changed, 34 insertions(+), 23 deletions(-) diff --git a/compiler/mono/src/code_gen_help/equality.rs b/compiler/mono/src/code_gen_help/equality.rs index db14a1e3b9..d58d095274 100644 --- a/compiler/mono/src/code_gen_help/equality.rs +++ b/compiler/mono/src/code_gen_help/equality.rs @@ -590,7 +590,9 @@ fn eq_list<'a>( // let size = literal int let size = root.create_symbol(ident_ids, "size"); - let size_expr = Expr::Literal(Literal::Int(elem_layout.stack_size(root.ptr_size) as i128)); + let size_expr = Expr::Literal(Literal::Int( + elem_layout.stack_size(root.target_info) as i128 + )); let size_stmt = |next| Stmt::Let(size, size_expr, layout_isize, next); // let list_size = len_1 * size diff --git a/compiler/mono/src/code_gen_help/mod.rs b/compiler/mono/src/code_gen_help/mod.rs index 61c4c32c9e..e74e4058ed 100644 --- a/compiler/mono/src/code_gen_help/mod.rs +++ b/compiler/mono/src/code_gen_help/mod.rs @@ -1,9 +1,9 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; -use roc_builtins::bitcode::IntWidth; use roc_module::ident::Ident; use roc_module::low_level::LowLevel; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; +use roc_target::TargetInfo; use crate::ir::{ Call, CallSpecId, CallType, Expr, HostExposedLayouts, JoinPointId, ModifyRc, Proc, ProcLayout, @@ -74,19 +74,19 @@ pub struct Context<'a> { pub struct CodeGenHelp<'a> { arena: &'a Bump, home: ModuleId, - ptr_size: u32, + target_info: TargetInfo, layout_isize: Layout<'a>, specializations: Vec<'a, Specialization<'a>>, debug_recursion_depth: usize, } impl<'a> CodeGenHelp<'a> { - pub fn new(arena: &'a Bump, intwidth_isize: IntWidth, home: ModuleId) -> Self { + pub fn new(arena: &'a Bump, target_info: TargetInfo, home: ModuleId) -> Self { CodeGenHelp { arena, home, - ptr_size: intwidth_isize.stack_size(), - layout_isize: Layout::Builtin(Builtin::Int(intwidth_isize)), + target_info, + layout_isize: Layout::usize(target_info), specializations: Vec::with_capacity_in(16, arena), debug_recursion_depth: 0, } diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index 2bc4aeff92..272e503d18 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -207,7 +207,7 @@ pub fn rc_ptr_from_data_ptr<'a>( // Mask for lower bits (for tag union id) let mask_sym = root.create_symbol(ident_ids, "mask"); - let mask_expr = Expr::Literal(Literal::Int(-(root.ptr_size as i128))); + let mask_expr = Expr::Literal(Literal::Int(-(root.target_info.ptr_width() as i128))); let mask_stmt = |next| Stmt::Let(mask_sym, mask_expr, root.layout_isize, next); let masked_sym = root.create_symbol(ident_ids, "masked"); @@ -222,7 +222,7 @@ pub fn rc_ptr_from_data_ptr<'a>( // Pointer size constant let ptr_size_sym = root.create_symbol(ident_ids, "ptr_size"); - let ptr_size_expr = Expr::Literal(Literal::Int(root.ptr_size as i128)); + let ptr_size_expr = Expr::Literal(Literal::Int(root.target_info.ptr_width() as i128)); let ptr_size_stmt = |next| Stmt::Let(ptr_size_sym, ptr_size_expr, root.layout_isize, next); // Refcount address @@ -382,7 +382,7 @@ fn refcount_str<'a>( // A pointer to the refcount value itself let rc_ptr = root.create_symbol(ident_ids, "rc_ptr"); - let alignment = root.ptr_size; + let alignment = root.target_info.ptr_width() as u32; let ret_unit_stmt = rc_return_stmt(root, ident_ids, ctx); let mod_rc_stmt = modify_refcount( @@ -487,7 +487,7 @@ fn refcount_list<'a>( // let rc_ptr = root.create_symbol(ident_ids, "rc_ptr"); - let alignment = layout.alignment_bytes(root.ptr_size); + let alignment = layout.alignment_bytes(root.target_info); let ret_stmt = rc_return_stmt(root, ident_ids, ctx); let modify_list = modify_refcount( @@ -584,7 +584,9 @@ fn refcount_list_elems<'a>( // let size = literal int let elem_size = root.create_symbol(ident_ids, "elem_size"); - let elem_size_expr = Expr::Literal(Literal::Int(elem_layout.stack_size(root.ptr_size) as i128)); + let elem_size_expr = Expr::Literal(Literal::Int( + elem_layout.stack_size(root.target_info) as i128 + )); let elem_size_stmt = |next| Stmt::Let(elem_size, elem_size_expr, layout_isize, next); // let list_size = len * size @@ -972,7 +974,7 @@ fn refcount_union_rec<'a>( let rc_structure_stmt = { let rc_ptr = root.create_symbol(ident_ids, "rc_ptr"); - let alignment = Layout::Union(union_layout).alignment_bytes(root.ptr_size); + let alignment = Layout::Union(union_layout).alignment_bytes(root.target_info); let ret_stmt = rc_return_stmt(root, ident_ids, ctx); let modify_structure_stmt = modify_refcount( root, @@ -988,7 +990,7 @@ fn refcount_union_rec<'a>( ident_ids, structure, rc_ptr, - union_layout.stores_tag_id_in_pointer(root.ptr_size), + union_layout.stores_tag_id_in_pointer(root.target_info), root.arena.alloc(modify_structure_stmt), ) }; @@ -1080,7 +1082,7 @@ fn refcount_union_tailrec<'a>( ) }; - let alignment = layout.alignment_bytes(root.ptr_size); + let alignment = layout.alignment_bytes(root.target_info); let modify_structure_stmt = modify_refcount( root, ident_ids, @@ -1095,7 +1097,7 @@ fn refcount_union_tailrec<'a>( ident_ids, current, rc_ptr, - union_layout.stores_tag_id_in_pointer(root.ptr_size), + union_layout.stores_tag_id_in_pointer(root.target_info), root.arena.alloc(modify_structure_stmt), ) }; diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index a2517b05f5..d048a0f4c2 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -16,6 +16,7 @@ use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_problem::can::RuntimeError; use roc_region::all::{Loc, Region}; use roc_std::RocDec; +use roc_target::TargetInfo; use roc_types::subs::{Content, FlatType, StorageSubs, Subs, Variable, VariableSubsSlice}; use std::collections::HashMap; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder}; @@ -1071,7 +1072,7 @@ pub struct Env<'a, 'i> { pub problems: &'i mut std::vec::Vec, pub home: ModuleId, pub ident_ids: &'i mut IdentIds, - pub target_info: u32, + pub target_info: TargetInfo, pub update_mode_ids: &'i mut UpdateModeIds, pub call_specialization_counter: u32, } @@ -8259,7 +8260,7 @@ pub enum IntOrFloat { /// Given the `a` in `Num a`, determines whether it's an int or a float pub fn num_argument_to_int_or_float( subs: &Subs, - ptr_bytes: u32, + target_info: TargetInfo, var: Variable, known_to_be_float: bool, ) -> IntOrFloat { @@ -8274,7 +8275,7 @@ pub fn num_argument_to_int_or_float( // Recurse on the second argument let var = subs[args.variables().into_iter().next().unwrap()]; - num_argument_to_int_or_float(subs, ptr_bytes, var, false) + num_argument_to_int_or_float(subs, target_info, var, false) } other @ Content::Alias(symbol, args, _) => { @@ -8292,16 +8293,15 @@ pub fn num_argument_to_int_or_float( // Recurse on the second argument let var = subs[args.variables().into_iter().next().unwrap()]; - num_argument_to_int_or_float(subs, ptr_bytes, var, true) + num_argument_to_int_or_float(subs, target_info, var, true) } Symbol::NUM_DECIMAL | Symbol::NUM_AT_DECIMAL => IntOrFloat::DecimalFloatType, Symbol::NUM_NAT | Symbol::NUM_NATURAL | Symbol::NUM_AT_NATURAL => { - let int_width = match ptr_bytes { - 4 => IntWidth::U32, - 8 => IntWidth::U64, - _ => panic!("unsupported word size"), + let int_width = match target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => IntWidth::U32, + roc_target::PtrWidth::Bytes8 => IntWidth::U64, }; IntOrFloat::Int(int_width) diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index c56a10cb4d..12d759f8b8 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -1237,6 +1237,13 @@ impl<'a> Layout<'a> { } } + pub fn isize(target_info: TargetInfo) -> Layout<'a> { + match target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => Self::i32(), + roc_target::PtrWidth::Bytes8 => Self::i64(), + } + } + pub fn bool() -> Layout<'a> { Layout::Builtin(Builtin::Bool) } From c663a35e161feb395a3fa3882742036b111e4f03 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Jan 2022 15:44:24 +0100 Subject: [PATCH 322/541] final phase --- Cargo.lock | 7 ++ ast/Cargo.toml | 1 + ast/src/module.rs | 3 +- cli/Cargo.toml | 1 + cli/src/build.rs | 9 +-- cli/src/repl/eval.rs | 73 +++++++++++--------- cli/src/repl/gen.rs | 10 +-- compiler/build/Cargo.toml | 1 + compiler/build/src/program.rs | 4 +- compiler/gen_dev/Cargo.toml | 1 + compiler/gen_dev/src/generic64/mod.rs | 17 ++--- compiler/gen_dev/src/generic64/x86_64.rs | 6 +- compiler/gen_llvm/src/llvm/build.rs | 82 +++++++++++------------ compiler/gen_llvm/src/llvm/build_dict.rs | 14 ++-- compiler/gen_llvm/src/llvm/build_str.rs | 25 +++---- compiler/gen_llvm/src/llvm/convert.rs | 25 +++---- compiler/gen_llvm/src/llvm/externs.rs | 4 +- compiler/gen_llvm/src/llvm/refcounting.rs | 17 ++--- compiler/gen_wasm/Cargo.toml | 1 + compiler/gen_wasm/src/backend.rs | 28 ++++---- compiler/gen_wasm/src/layout.rs | 6 +- compiler/gen_wasm/src/lib.rs | 15 ++++- compiler/load/Cargo.toml | 1 + compiler/load/src/file.rs | 37 +++++----- compiler/target/src/lib.rs | 14 ++++ docs/Cargo.toml | 1 + docs/src/lib.rs | 2 +- 27 files changed, 222 insertions(+), 183 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4bf5787f06..8ff94289d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3254,6 +3254,7 @@ dependencies = [ "roc_parse", "roc_problem", "roc_region", + "roc_target", "roc_types", "roc_unify", "snafu", @@ -3283,6 +3284,7 @@ dependencies = [ "roc_reporting", "roc_solve", "roc_std", + "roc_target", "roc_types", "roc_unify", "serde_json", @@ -3351,6 +3353,7 @@ dependencies = [ "roc_region", "roc_reporting", "roc_solve", + "roc_target", "roc_test_utils", "roc_types", "roc_unify", @@ -3417,6 +3420,7 @@ dependencies = [ "roc_module", "roc_parse", "roc_region", + "roc_target", "roc_types", "snafu", "tempfile", @@ -3509,6 +3513,7 @@ dependencies = [ "roc_region", "roc_solve", "roc_std", + "roc_target", "roc_types", "roc_unify", "target-lexicon", @@ -3542,6 +3547,7 @@ dependencies = [ "roc_module", "roc_mono", "roc_std", + "roc_target", ] [[package]] @@ -3589,6 +3595,7 @@ dependencies = [ "roc_region", "roc_reporting", "roc_solve", + "roc_target", "roc_types", "roc_unify", "tempfile", diff --git a/ast/Cargo.toml b/ast/Cargo.toml index 66407ee82b..f0edcc1fab 100644 --- a/ast/Cargo.toml +++ b/ast/Cargo.toml @@ -17,6 +17,7 @@ roc_problem = { path = "../compiler/problem" } roc_types = { path = "../compiler/types" } roc_unify = { path = "../compiler/unify"} roc_load = { path = "../compiler/load" } +roc_target = { path = "../compiler/target" } roc_error_macros = { path = "../error_macros" } arrayvec = "0.7.2" bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/ast/src/module.rs b/ast/src/module.rs index e744760c42..f13028e8d1 100644 --- a/ast/src/module.rs +++ b/ast/src/module.rs @@ -3,6 +3,7 @@ use std::path::Path; use bumpalo::Bump; use roc_collections::all::MutMap; use roc_load::file::LoadedModule; +use roc_target::TargetInfo; pub fn load_module(src_file: &Path) -> LoadedModule { let subs_by_module = MutMap::default(); @@ -19,7 +20,7 @@ pub fn load_module(src_file: &Path) -> LoadedModule { ) }), subs_by_module, - 8, + TargetInfo::default_x86_64(), roc_can::builtins::builtin_defs_map, ); diff --git a/cli/Cargo.toml b/cli/Cargo.toml index bba74100cc..f889f4b16b 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -61,6 +61,7 @@ roc_load = { path = "../compiler/load" } roc_gen_llvm = { path = "../compiler/gen_llvm", optional = true } roc_build = { path = "../compiler/build", default-features = false } roc_fmt = { path = "../compiler/fmt" } +roc_target = { path = "../compiler/target" } roc_reporting = { path = "../reporting" } roc_error_macros = { path = "../error_macros" } roc_editor = { path = "../editor", optional = true } diff --git a/cli/src/build.rs b/cli/src/build.rs index 89c6d1d601..4b0daee242 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -8,6 +8,7 @@ use roc_can::builtins::builtin_defs_map; use roc_collections::all::MutMap; use roc_load::file::LoadingProblem; use roc_mono::ir::OptLevel; +use roc_target::TargetInfo; use std::path::PathBuf; use std::time::{Duration, SystemTime}; use target_lexicon::Triple; @@ -58,7 +59,7 @@ pub fn build_file<'a>( target_valgrind: bool, ) -> Result> { let compilation_start = SystemTime::now(); - let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; + let target_info = TargetInfo::from(target); // Step 1: compile the app and generate the .o file let subs_by_module = MutMap::default(); @@ -72,7 +73,7 @@ pub fn build_file<'a>( stdlib, src_dir.as_path(), subs_by_module, - ptr_bytes, + target_info, builtin_defs_map, )?; @@ -356,7 +357,7 @@ pub fn check_file( // only used for generating errors. We don't do code generation, so hardcoding should be fine // we need monomorphization for when exhaustiveness checking - let ptr_bytes = 8; + let target_info = TargetInfo::default_x86_64(); // Step 1: compile the app and generate the .o file let subs_by_module = MutMap::default(); @@ -370,7 +371,7 @@ pub fn check_file( stdlib, src_dir.as_path(), subs_by_module, - ptr_bytes, + target_info, builtin_defs_map, )?; diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index bbd0e93a38..8b6ddd49b9 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -14,13 +14,14 @@ use roc_mono::layout::{ }; use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral}; use roc_region::all::{Loc, Region}; +use roc_target::TargetInfo; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; use std::cmp::{max_by_key, min_by_key}; struct Env<'a, 'env> { arena: &'a Bump, subs: &'env Subs, - ptr_bytes: u32, + target_info: TargetInfo, interns: &'env Interns, home: ModuleId, } @@ -47,12 +48,12 @@ pub unsafe fn jit_to_ast<'a>( interns: &'a Interns, home: ModuleId, subs: &'a Subs, - ptr_bytes: u32, + target_info: TargetInfo, ) -> Result, ToAstProblem> { let env = Env { arena, subs, - ptr_bytes, + target_info, interns, home, }; @@ -172,7 +173,7 @@ fn get_tags_vars_and_variant<'a>( let vars_of_tag: MutMap<_, _> = tags_vec.iter().cloned().collect(); let union_variant = - union_sorted_tags_help(env.arena, tags_vec, opt_rec_var, env.subs, env.ptr_bytes); + union_sorted_tags_help(env.arena, tags_vec, opt_rec_var, env.subs, env.target_info); (vars_of_tag, union_variant) } @@ -200,8 +201,12 @@ fn expr_of_tag<'a>( /// Gets the tag ID of a union variant, assuming that the tag ID is stored alongside (after) the /// tag data. The caller is expected to check that the tag ID is indeed stored this way. -fn tag_id_from_data(union_layout: UnionLayout, data_ptr: *const u8, ptr_bytes: u32) -> i64 { - let offset = union_layout.data_size_without_tag_id(ptr_bytes).unwrap(); +fn tag_id_from_data( + union_layout: UnionLayout, + data_ptr: *const u8, + target_info: TargetInfo, +) -> i64 { + let offset = union_layout.data_size_without_tag_id(target_info).unwrap(); unsafe { match union_layout.tag_id_builtin() { @@ -218,13 +223,12 @@ fn tag_id_from_data(union_layout: UnionLayout, data_ptr: *const u8, ptr_bytes: u } } -fn deref_ptr_of_ptr(ptr_of_ptr: *const u8, ptr_bytes: u32) -> *const u8 { +fn deref_ptr_of_ptr(ptr_of_ptr: *const u8, target_info: TargetInfo) -> *const u8 { unsafe { - match ptr_bytes { + match target_info.ptr_width() { // Our LLVM codegen represents pointers as i32/i64s. - 4 => *(ptr_of_ptr as *const i32) as *const u8, - 8 => *(ptr_of_ptr as *const i64) as *const u8, - _ => unreachable!(), + roc_target::PtrWidth::Bytes4 => *(ptr_of_ptr as *const i32) as *const u8, + roc_target::PtrWidth::Bytes8 => *(ptr_of_ptr as *const i64) as *const u8, } } } @@ -236,20 +240,20 @@ fn deref_ptr_of_ptr(ptr_of_ptr: *const u8, ptr_bytes: u32) -> *const u8 { fn tag_id_from_recursive_ptr( union_layout: UnionLayout, rec_ptr: *const u8, - ptr_bytes: u32, + target_info: TargetInfo, ) -> (i64, *const u8) { - let tag_in_ptr = union_layout.stores_tag_id_in_pointer(ptr_bytes); + let tag_in_ptr = union_layout.stores_tag_id_in_pointer(target_info); if tag_in_ptr { - let masked_ptr_to_data = deref_ptr_of_ptr(rec_ptr, ptr_bytes) as i64; - let (tag_id_bits, tag_id_mask) = tag_pointer_tag_id_bits_and_mask(ptr_bytes); + let masked_ptr_to_data = deref_ptr_of_ptr(rec_ptr, target_info) as i64; + let (tag_id_bits, tag_id_mask) = tag_pointer_tag_id_bits_and_mask(target_info); let tag_id = masked_ptr_to_data & (tag_id_mask as i64); // Clear the tag ID data from the pointer let ptr_to_data = ((masked_ptr_to_data >> tag_id_bits) << tag_id_bits) as *const u8; (tag_id as i64, ptr_to_data) } else { - let ptr_to_data = deref_ptr_of_ptr(rec_ptr, ptr_bytes); - let tag_id = tag_id_from_data(union_layout, ptr_to_data, ptr_bytes); + let ptr_to_data = deref_ptr_of_ptr(rec_ptr, target_info); + let tag_id = tag_id_from_data(union_layout, ptr_to_data, target_info); (tag_id, ptr_to_data) } } @@ -388,7 +392,7 @@ fn jit_to_ast_help<'a>( let fields = [Layout::u64(), *layout]; let layout = Layout::Struct(&fields); - let result_stack_size = layout.stack_size(env.ptr_bytes); + let result_stack_size = layout.stack_size(env.target_info); run_jit_function_dynamic_type!( lib, @@ -398,7 +402,7 @@ fn jit_to_ast_help<'a>( ) } Layout::Union(UnionLayout::NonRecursive(_)) => { - let size = layout.stack_size(env.ptr_bytes); + let size = layout.stack_size(env.target_info); Ok(run_jit_function_dynamic_type!( lib, main_fn_name, @@ -412,7 +416,7 @@ fn jit_to_ast_help<'a>( | Layout::Union(UnionLayout::NonNullableUnwrapped(_)) | Layout::Union(UnionLayout::NullableUnwrapped { .. }) | Layout::Union(UnionLayout::NullableWrapped { .. }) => { - let size = layout.stack_size(env.ptr_bytes); + let size = layout.stack_size(env.target_info); Ok(run_jit_function_dynamic_type!( lib, main_fn_name, @@ -506,7 +510,7 @@ fn ptr_to_ast<'a>( } (_, Layout::Builtin(Builtin::List(elem_layout))) => { // Turn the (ptr, len) wrapper struct into actual ptr and len values. - let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) }; + let len = unsafe { *(ptr.offset(env.target_info.ptr_width() as isize) as *const usize) }; let ptr = unsafe { *(ptr as *const *const u8) }; list_to_ast(env, ptr, len, elem_layout, content) @@ -573,7 +577,7 @@ fn ptr_to_ast<'a>( // Because this is a `NonRecursive`, the tag ID is definitely after the data. let tag_id = - tag_id_from_data(union_layout, ptr, env.ptr_bytes); + tag_id_from_data(union_layout, ptr, env.target_info); // use the tag ID as an index, to get its name and layout of any arguments let (tag_name, arg_layouts) = @@ -605,7 +609,7 @@ fn ptr_to_ast<'a>( _ => unreachable!("any other variant would have a different layout"), }; - let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(*union_layout, ptr, env.ptr_bytes); + let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(*union_layout, ptr, env.target_info); let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; expr_of_tag( @@ -633,7 +637,7 @@ fn ptr_to_ast<'a>( _ => unreachable!("any other variant would have a different layout"), }; - let ptr_to_data = deref_ptr_of_ptr(ptr, env.ptr_bytes); + let ptr_to_data = deref_ptr_of_ptr(ptr, env.target_info); expr_of_tag( env, @@ -663,7 +667,7 @@ fn ptr_to_ast<'a>( _ => unreachable!("any other variant would have a different layout"), }; - let ptr_to_data = deref_ptr_of_ptr(ptr, env.ptr_bytes); + let ptr_to_data = deref_ptr_of_ptr(ptr, env.target_info); if ptr_to_data.is_null() { tag_name_to_expr(env, &nullable_name) } else { @@ -694,11 +698,11 @@ fn ptr_to_ast<'a>( _ => unreachable!("any other variant would have a different layout"), }; - let ptr_to_data = deref_ptr_of_ptr(ptr, env.ptr_bytes); + let ptr_to_data = deref_ptr_of_ptr(ptr, env.target_info); if ptr_to_data.is_null() { tag_name_to_expr(env, &nullable_name) } else { - let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(*union_layout, ptr, env.ptr_bytes); + let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(*union_layout, ptr, env.target_info); let tag_id = if tag_id > nullable_id.into() { tag_id - 1 } else { tag_id }; @@ -749,7 +753,7 @@ fn list_to_ast<'a>( let arena = env.arena; let mut output = Vec::with_capacity_in(len, arena); - let elem_size = elem_layout.stack_size(env.ptr_bytes) as usize; + let elem_size = elem_layout.stack_size(env.target_info) as usize; for index in 0..len { let offset_bytes = index * elem_size; @@ -823,7 +827,7 @@ where output.push(&*arena.alloc(loc_expr)); // Advance the field pointer to the next field. - field_ptr = unsafe { field_ptr.offset(layout.stack_size(env.ptr_bytes) as isize) }; + field_ptr = unsafe { field_ptr.offset(layout.stack_size(env.target_info) as isize) }; } output @@ -908,7 +912,7 @@ fn struct_to_ast<'a>( // Advance the field pointer to the next field. field_ptr = - unsafe { field_ptr.offset(field_layout.stack_size(env.ptr_bytes) as isize) }; + unsafe { field_ptr.offset(field_layout.stack_size(env.target_info) as isize) }; } let output = output.into_bump_slice(); @@ -1083,8 +1087,13 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> .map(|(a, b)| (a.clone(), b.to_vec())) .collect(); - let union_variant = - union_sorted_tags_help(env.arena, tags_vec, None, env.subs, env.ptr_bytes); + let union_variant = union_sorted_tags_help( + env.arena, + tags_vec, + None, + env.subs, + env.target_info, + ); match union_variant { UnionVariant::ByteUnion(tagnames) => { diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index 1510a4c448..695fdd320b 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -13,6 +13,7 @@ use roc_load::file::LoadingProblem; use roc_mono::ir::OptLevel; use roc_parse::parser::SyntaxError; use roc_region::all::LineInfo; +use roc_target::TargetInfo; use roc_types::pretty_print::{content_to_string, name_all_type_vars}; use std::path::{Path, PathBuf}; use std::str::from_utf8_unchecked; @@ -43,7 +44,7 @@ pub fn gen_and_eval<'a>( let module_src = promote_expr_to_module(src_str); - let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; + let target_info = TargetInfo::from(&target); let exposed_types = MutMap::default(); let loaded = roc_load::file::load_and_monomorphize_from_str( @@ -53,7 +54,7 @@ pub fn gen_and_eval<'a>( &stdlib, src_dir, exposed_types, - ptr_bytes, + target_info, builtin_defs_map, ); @@ -133,7 +134,6 @@ pub fn gen_and_eval<'a>( } else { let context = Context::create(); let builder = context.create_builder(); - let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins( &target, &context, "", )); @@ -181,7 +181,7 @@ pub fn gen_and_eval<'a>( context: &context, interns, module, - target_info: ptr_bytes, + target_info, is_gen_test: true, // so roc_panic is generated // important! we don't want any procedures to get the C calling convention exposed_to_host: MutSet::default(), @@ -237,7 +237,7 @@ pub fn gen_and_eval<'a>( &env.interns, home, &subs, - ptr_bytes, + target_info, ) }; let mut expr = roc_fmt::Buf::new_in(&arena); diff --git a/compiler/build/Cargo.toml b/compiler/build/Cargo.toml index 80aaa3dd00..4ef876bbf6 100644 --- a/compiler/build/Cargo.toml +++ b/compiler/build/Cargo.toml @@ -19,6 +19,7 @@ roc_unify = { path = "../unify" } roc_solve = { path = "../solve" } roc_mono = { path = "../mono" } roc_load = { path = "../load" } +roc_target = { path = "../target" } roc_gen_llvm = { path = "../gen_llvm", optional = true } roc_gen_wasm = { path = "../gen_wasm", optional = true } roc_gen_dev = { path = "../gen_dev", default-features = false } diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 1ba657e615..24b2cb9f0e 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -235,7 +235,7 @@ pub fn gen_from_mono_module_llvm( let code_gen_start = SystemTime::now(); // Generate the binary - let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; + let target_info = roc_target::TargetInfo::from(target); let context = Context::create(); let module = arena.alloc(module_from_builtins(target, &context, "app")); @@ -286,7 +286,7 @@ pub fn gen_from_mono_module_llvm( context: &context, interns: loaded.interns, module, - target_info: ptr_bytes, + target_info, // in gen_tests, the compiler provides roc_panic // and sets up the setjump/longjump exception handling is_gen_test: false, diff --git a/compiler/gen_dev/Cargo.toml b/compiler/gen_dev/Cargo.toml index 8efcaca241..d454e3ffbb 100644 --- a/compiler/gen_dev/Cargo.toml +++ b/compiler/gen_dev/Cargo.toml @@ -16,6 +16,7 @@ roc_builtins = { path = "../builtins" } roc_unify = { path = "../unify" } roc_solve = { path = "../solve" } roc_mono = { path = "../mono" } +roc_target = { path = "../target" } roc_error_macros = { path = "../../error_macros" } bumpalo = { version = "3.8.0", features = ["collections"] } target-lexicon = "0.12.2" diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 26f336ab40..f30e98edc3 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -7,12 +7,13 @@ use roc_module::symbol::{Interns, Symbol}; use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::ir::{BranchInfo, JoinPointId, Literal, Param, ProcLayout, SelfRecursive, Stmt}; use roc_mono::layout::{Builtin, Layout}; +use roc_target::TargetInfo; use std::marker::PhantomData; pub mod aarch64; pub mod x86_64; -const PTR_SIZE: u32 = 8; +const TARGET_INFO: TargetInfo = TargetInfo::default_x86_64(); pub trait CallConv { const BASE_PTR_REG: GeneralReg; @@ -308,7 +309,7 @@ pub fn new_backend_64bit< phantom_cc: PhantomData, env, interns, - helper_proc_gen: CodeGenHelp::new(env.arena, IntWidth::I64, env.module_id), + helper_proc_gen: CodeGenHelp::new(env.arena, TARGET_INFO, env.module_id), helper_proc_symbols: bumpalo::vec![in env.arena], proc_name: None, is_self_recursive: None, @@ -974,7 +975,7 @@ impl< } fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) { - let struct_size = layout.stack_size(PTR_SIZE); + let struct_size = layout.stack_size(TARGET_INFO); if let Layout::Struct(field_layouts) = layout { if struct_size > 0 { @@ -991,7 +992,7 @@ impl< let mut current_offset = offset; for (field, field_layout) in fields.iter().zip(field_layouts.iter()) { self.copy_symbol_to_stack_offset(current_offset, field, field_layout); - let field_size = field_layout.stack_size(PTR_SIZE); + let field_size = field_layout.stack_size(TARGET_INFO); current_offset += field_size as i32; } } else { @@ -1029,14 +1030,14 @@ impl< if let Some(SymbolStorage::Base { offset, .. }) = self.symbol_storage_map.get(structure) { let mut data_offset = *offset; for i in 0..index { - let field_size = field_layouts[i as usize].stack_size(PTR_SIZE); + let field_size = field_layouts[i as usize].stack_size(TARGET_INFO); data_offset += field_size as i32; } self.symbol_storage_map.insert( *sym, SymbolStorage::Base { offset: data_offset, - size: field_layouts[index as usize].stack_size(PTR_SIZE), + size: field_layouts[index as usize].stack_size(TARGET_INFO), owned: false, }, ); @@ -1569,10 +1570,10 @@ impl< { debug_assert_eq!( *size, - layout.stack_size(PTR_SIZE), + layout.stack_size(TARGET_INFO), "expected struct to have same size as data being stored in it" ); - for i in 0..layout.stack_size(PTR_SIZE) as i32 { + for i in 0..layout.stack_size(TARGET_INFO) as i32 { ASM::mov_reg64_base32(&mut self.buf, tmp_reg, from_offset + i); ASM::mov_base32_reg64(&mut self.buf, to_offset + i, tmp_reg); } diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index b6cba72e95..359dd4670f 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -1,4 +1,4 @@ -use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage, PTR_SIZE}; +use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage, TARGET_INFO}; use crate::{ single_register_builtins, single_register_floats, single_register_integers, Relocation, }; @@ -451,7 +451,7 @@ impl CallConv for X86_64SystemV { fn returns_via_arg_pointer(ret_layout: &Layout) -> bool { // TODO: This may need to be more complex/extended to fully support the calling convention. // details here: https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf - ret_layout.stack_size(PTR_SIZE) > 16 + ret_layout.stack_size(TARGET_INFO) > 16 } } @@ -775,7 +775,7 @@ impl CallConv for X86_64WindowsFastcall { fn returns_via_arg_pointer(ret_layout: &Layout) -> bool { // TODO: This is not fully correct there are some exceptions for "vector" types. // details here: https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-160#return-values - ret_layout.stack_size(PTR_SIZE) > 8 + ret_layout.stack_size(TARGET_INFO) > 8 } } diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 94ffe74b89..a1e016f937 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -196,15 +196,9 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { pub fn ptr_int(&self) -> IntType<'ctx> { let ctx = self.context; - match self.target_info { - 1 => ctx.i8_type(), - 2 => ctx.i16_type(), - 4 => ctx.i32_type(), - 8 => ctx.i64_type(), - _ => panic!( - "Invalid target: Roc does't support compiling to {}-bit systems.", - self.target_info * 8 - ), + match self.target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => ctx.i32_type(), + roc_target::PtrWidth::Bytes8 => ctx.i64_type(), } } @@ -217,7 +211,7 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { } pub fn small_str_bytes(&self) -> u32 { - self.target_info * 2 + self.target_info.ptr_width() as u32 * 2 } pub fn build_intrinsic_call( @@ -318,12 +312,9 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { ) -> CallSiteValue<'ctx> { let false_val = self.context.bool_type().const_int(0, false); - let intrinsic_name = match self.target_info { - 8 => LLVM_MEMSET_I64, - 4 => LLVM_MEMSET_I32, - other => { - unreachable!("Unsupported number of ptr_bytes {:?}", other); - } + let intrinsic_name = match self.target_info.ptr_width() { + roc_target::PtrWidth::Bytes8 => LLVM_MEMSET_I64, + roc_target::PtrWidth::Bytes4 => LLVM_MEMSET_I32, }; self.build_intrinsic_call( @@ -1789,7 +1780,7 @@ fn tag_pointer_set_tag_id<'a, 'ctx, 'env>( pointer: PointerValue<'ctx>, ) -> PointerValue<'ctx> { // we only have 3 bits, so can encode only 0..7 (or on 32-bit targets, 2 bits to encode 0..3) - debug_assert!((tag_id as u32) < env.target_info); + debug_assert!((tag_id as u32) < env.target_info.ptr_width() as u32); let ptr_int = env.ptr_int(); @@ -1802,11 +1793,10 @@ fn tag_pointer_set_tag_id<'a, 'ctx, 'env>( .build_int_to_ptr(combined, pointer.get_type(), "to_ptr") } -pub fn tag_pointer_tag_id_bits_and_mask(ptr_bytes: u32) -> (u64, u64) { - match ptr_bytes { - 8 => (3, 0b0000_0111), - 4 => (2, 0b0000_0011), - _ => unreachable!(), +pub fn tag_pointer_tag_id_bits_and_mask(target_info: TargetInfo) -> (u64, u64) { + match target_info.ptr_width() { + roc_target::PtrWidth::Bytes8 => (3, 0b0000_0111), + roc_target::PtrWidth::Bytes4 => (2, 0b0000_0011), } } @@ -2183,8 +2173,9 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>( let builder = env.builder; let len_type = env.ptr_int(); + let ptr_width_u32 = env.target_info.ptr_width() as u32; - let extra_bytes = alignment_bytes.max(env.target_info); + let extra_bytes = alignment_bytes.max(ptr_width_u32); let ptr = { // number of bytes we will allocated @@ -2209,8 +2200,8 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>( .into_pointer_value(); let index = match extra_bytes { - n if n == env.target_info => 1, - n if n == 2 * env.target_info => 2, + n if n == ptr_width_u32 => 1, + n if n == 2 * ptr_width_u32 => 2, _ => unreachable!("invalid extra_bytes, {}", extra_bytes), }; @@ -2229,11 +2220,11 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>( }; let refcount_ptr = match extra_bytes { - n if n == env.target_info => { + n if n == ptr_width_u32 => { // the allocated pointer is the same as the refcounted pointer unsafe { PointerToRefcount::from_ptr(env, ptr) } } - n if n == 2 * env.target_info => { + n if n == 2 * ptr_width_u32 => { // the refcount is stored just before the start of the actual data // but in this case (because of alignment) not at the start of the allocated buffer PointerToRefcount::from_ptr_to_data(env, data_ptr) @@ -2288,10 +2279,11 @@ fn list_literal<'a, 'ctx, 'env>( let size = list_length * element_width as usize; let alignment = element_layout .alignment_bytes(env.target_info) - .max(env.target_info); + .max(env.target_info.ptr_width() as u32); let mut is_all_constant = true; - let zero_elements = (env.target_info as f64 / element_width as f64).ceil() as usize; + let zero_elements = + (env.target_info.ptr_width() as u8 as f64 / element_width as f64).ceil() as usize; // runtime-evaluated elements let mut runtime_evaluated_elements = Vec::with_capacity_in(list_length, env.arena); @@ -3727,7 +3719,10 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( } pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> { - let type_ = env.context.i8_type().array_type(5 * env.target_info); + let type_ = env + .context + .i8_type() + .array_type(5 * env.target_info.ptr_width() as u32); let global = match env.module.get_global("roc_sjlj_buffer") { Some(global) => global, @@ -3895,8 +3890,12 @@ fn make_exception_catcher<'a, 'ctx, 'env>( function_value } -fn roc_result_layout<'a>(arena: &'a Bump, return_layout: Layout<'a>, ptr_bytes: u32) -> Layout<'a> { - let elements = [Layout::u64(), Layout::usize(ptr_bytes), return_layout]; +fn roc_result_layout<'a>( + arena: &'a Bump, + return_layout: Layout<'a>, + target_info: TargetInfo, +) -> Layout<'a> { + let elements = [Layout::u64(), Layout::usize(target_info), return_layout]; Layout::Struct(arena.alloc(elements)) } @@ -6076,8 +6075,8 @@ fn run_low_level<'a, 'ctx, 'env>( { bd.position_at_end(throw_block); - match env.target_info { - 8 => { + match env.target_info.ptr_width() { + roc_target::PtrWidth::Bytes8 => { let fn_ptr_type = context .void_type() .fn_type(&[], false) @@ -6096,11 +6095,10 @@ fn run_low_level<'a, 'ctx, 'env>( bd.build_unconditional_branch(then_block); } - 4 => { + roc_target::PtrWidth::Bytes4 => { // temporary WASM implementation throw_exception(env, "An expectation failed!"); } - _ => unreachable!(), } } @@ -6196,7 +6194,7 @@ enum CCReturn { /// According to the C ABI, how should we return a value with the given layout? fn to_cc_return<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> CCReturn { let return_size = layout.stack_size(env.target_info); - let pass_result_by_pointer = return_size > 2 * env.target_info; + let pass_result_by_pointer = return_size > 2 * env.target_info.ptr_width() as u32; if return_size == 0 { CCReturn::Void @@ -7130,7 +7128,9 @@ fn define_global_str_literal_ptr<'a, 'ctx, 'env>( let ptr = unsafe { env.builder.build_in_bounds_gep( ptr, - &[env.ptr_int().const_int(env.target_info as u64, false)], + &[env + .ptr_int() + .const_int(env.target_info.ptr_width() as u64, false)], "get_rc_ptr", ) }; @@ -7160,11 +7160,11 @@ fn define_global_str_literal<'a, 'ctx, 'env>( Some(current) => current, None => { - let size = message.bytes().len() + env.target_info as usize; + let size = message.bytes().len() + env.target_info.ptr_width() as usize; let mut bytes = Vec::with_capacity_in(size, env.arena); // insert NULL bytes for the refcount - for _ in 0..env.target_info { + for _ in 0..env.target_info.ptr_width() as usize { bytes.push(env.context.i8_type().const_zero()); } @@ -7183,7 +7183,7 @@ fn define_global_str_literal<'a, 'ctx, 'env>( // strings are NULL-terminated, which means we can't store the refcount (which is 8 // NULL bytes) global.set_constant(true); - global.set_alignment(env.target_info); + global.set_alignment(env.target_info.ptr_width() as u32); global.set_unnamed_addr(true); global.set_linkage(inkwell::module::Linkage::Private); diff --git a/compiler/gen_llvm/src/llvm/build_dict.rs b/compiler/gen_llvm/src/llvm/build_dict.rs index 47f8595d70..37b81e9129 100644 --- a/compiler/gen_llvm/src/llvm/build_dict.rs +++ b/compiler/gen_llvm/src/llvm/build_dict.rs @@ -17,14 +17,15 @@ use inkwell::AddressSpace; use roc_builtins::bitcode; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout, LayoutIds}; +use roc_target::TargetInfo; #[repr(transparent)] struct Alignment(u8); impl Alignment { - fn from_key_value_layout(key: &Layout, value: &Layout, ptr_bytes: u32) -> Alignment { - let key_align = key.alignment_bytes(ptr_bytes); - let value_align = value.alignment_bytes(ptr_bytes); + fn from_key_value_layout(key: &Layout, value: &Layout, target_info: TargetInfo) -> Alignment { + let key_align = key.alignment_bytes(target_info); + let value_align = value.alignment_bytes(target_info); let mut bits = key_align.max(value_align) as u8; @@ -454,19 +455,18 @@ fn pass_dict_c_abi<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, dict: BasicValueEnum<'ctx>, ) -> BasicValueEnum<'ctx> { - match env.target_info { - 4 => { + match env.target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => { let target_type = env.context.custom_width_int_type(96).into(); complex_bitcast(env.builder, dict, target_type, "to_i96") } - 8 => { + roc_target::PtrWidth::Bytes8 => { let dict_ptr = env.builder.build_alloca(zig_dict_type(env), "dict_ptr"); env.builder.build_store(dict_ptr, dict); dict_ptr.into() } - _ => unreachable!(), } } diff --git a/compiler/gen_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index a7337cd07d..4ce155aac6 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -10,6 +10,7 @@ use morphic_lib::UpdateMode; use roc_builtins::bitcode::{self, IntWidth}; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout}; +use roc_target::PtrWidth; use super::build::load_symbol; @@ -79,10 +80,9 @@ fn str_symbol_to_c_abi<'a, 'ctx, 'env>( ) -> IntValue<'ctx> { let string = load_symbol(scope, &symbol); - let target_type = match env.target_info { - 8 => env.context.i128_type().into(), - 4 => env.context.i64_type().into(), - _ => unreachable!(), + let target_type = match env.target_info.ptr_width() { + PtrWidth::Bytes8 => env.context.i128_type().into(), + PtrWidth::Bytes4 => env.context.i64_type().into(), }; complex_bitcast(env.builder, string, target_type, "str_to_c_abi").into_int_value() @@ -96,10 +96,9 @@ pub fn str_to_c_abi<'a, 'ctx, 'env>( env.builder.build_store(cell, value); - let target_type = match env.target_info { - 8 => env.context.i128_type(), - 4 => env.context.i64_type(), - _ => unreachable!(), + let target_type = match env.target_info.ptr_width() { + PtrWidth::Bytes8 => env.context.i128_type(), + PtrWidth::Bytes4 => env.context.i64_type(), }; let target_type_ptr = env @@ -310,20 +309,19 @@ fn decode_from_utf8_result<'a, 'ctx, 'env>( let builder = env.builder; let ctx = env.context; - let fields = match env.target_info { - 8 | 4 => [ + let fields = match env.target_info.ptr_width() { + PtrWidth::Bytes4 | PtrWidth::Bytes8 => [ env.ptr_int().into(), super::convert::zig_str_type(env).into(), env.context.bool_type().into(), ctx.i8_type().into(), ], - _ => unreachable!(), }; let record_type = env.context.struct_type(&fields, false); - match env.target_info { - 8 | 4 => { + match env.target_info.ptr_width() { + PtrWidth::Bytes4 | PtrWidth::Bytes8 => { let result_ptr_cast = env .builder .build_bitcast( @@ -337,7 +335,6 @@ fn decode_from_utf8_result<'a, 'ctx, 'env>( .build_load(result_ptr_cast, "load_utf8_validate_bytes_result") .into_struct_value() } - _ => unreachable!(), } } diff --git a/compiler/gen_llvm/src/llvm/convert.rs b/compiler/gen_llvm/src/llvm/convert.rs index 3bebd44fa1..00d98ff5e3 100644 --- a/compiler/gen_llvm/src/llvm/convert.rs +++ b/compiler/gen_llvm/src/llvm/convert.rs @@ -4,6 +4,7 @@ use inkwell::types::{BasicType, BasicTypeEnum, FloatType, IntType, StructType}; use inkwell::AddressSpace; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_mono::layout::{Builtin, Layout, UnionLayout}; +use roc_target::TargetInfo; fn basic_type_from_record<'a, 'ctx, 'env>( env: &crate::llvm::build::Env<'a, 'ctx, 'env>, @@ -190,13 +191,13 @@ pub fn float_type_from_float_width<'a, 'ctx, 'env>( pub fn block_of_memory_slices<'ctx>( context: &'ctx Context, layouts: &[&[Layout<'_>]], - ptr_bytes: u32, + target_info: TargetInfo, ) -> BasicTypeEnum<'ctx> { let mut union_size = 0; for tag in layouts { let mut total = 0; for layout in tag.iter() { - total += layout.stack_size(ptr_bytes as u32); + total += layout.stack_size(target_info); } union_size = union_size.max(total); @@ -208,13 +209,13 @@ pub fn block_of_memory_slices<'ctx>( pub fn block_of_memory<'ctx>( context: &'ctx Context, layout: &Layout<'_>, - ptr_bytes: u32, + target_info: TargetInfo, ) -> BasicTypeEnum<'ctx> { // TODO make this dynamic - let mut union_size = layout.stack_size(ptr_bytes as u32); + let mut union_size = layout.stack_size(target_info); if let Layout::Union(UnionLayout::NonRecursive { .. }) = layout { - union_size -= ptr_bytes; + union_size -= target_info.ptr_width() as u32; } block_of_memory_help(context, union_size) @@ -253,16 +254,10 @@ fn block_of_memory_help(context: &Context, union_size: u32) -> BasicTypeEnum<'_> } /// The int type that the C ABI turns our RocList/RocStr into -pub fn str_list_int(ctx: &Context, ptr_bytes: u32) -> IntType<'_> { - match ptr_bytes { - 1 => ctx.i16_type(), - 2 => ctx.i32_type(), - 4 => ctx.i64_type(), - 8 => ctx.i128_type(), - _ => panic!( - "Invalid target: Roc does't support compiling to {}-bit systems.", - ptr_bytes * 8 - ), +pub fn str_list_int(ctx: &Context, target_info: TargetInfo) -> IntType<'_> { + match target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => ctx.i32_type(), + roc_target::PtrWidth::Bytes8 => ctx.i64_type(), } } diff --git a/compiler/gen_llvm/src/llvm/externs.rs b/compiler/gen_llvm/src/llvm/externs.rs index 69b251cdcf..91af05faf8 100644 --- a/compiler/gen_llvm/src/llvm/externs.rs +++ b/compiler/gen_llvm/src/llvm/externs.rs @@ -175,7 +175,9 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) { let buffer = crate::llvm::build::get_sjlj_buffer(env); // write our error message pointer - let index = env.ptr_int().const_int(3 * env.target_info as u64, false); + let index = env + .ptr_int() + .const_int(3 * env.target_info.ptr_width() as u64, false); let message_buffer_raw = unsafe { builder.build_gep(buffer, &[index], "raw_msg_buffer_ptr") }; let message_buffer = builder.build_bitcast( diff --git a/compiler/gen_llvm/src/llvm/refcounting.rs b/compiler/gen_llvm/src/llvm/refcounting.rs index 229f3c4b86..2962361c29 100644 --- a/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/compiler/gen_llvm/src/llvm/refcounting.rs @@ -18,21 +18,16 @@ use inkwell::{AddressSpace, IntPredicate}; use roc_module::symbol::Interns; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout}; +use roc_target::TargetInfo; /// "Infinite" reference count, for static values /// Ref counts are encoded as negative numbers where isize::MIN represents 1 pub const REFCOUNT_MAX: usize = 0_usize; -pub fn refcount_1(ctx: &Context, ptr_bytes: u32) -> IntValue<'_> { - match ptr_bytes { - 1 => ctx.i8_type().const_int(i8::MIN as u64, false), - 2 => ctx.i16_type().const_int(i16::MIN as u64, false), - 4 => ctx.i32_type().const_int(i32::MIN as u64, false), - 8 => ctx.i64_type().const_int(i64::MIN as u64, false), - _ => panic!( - "Invalid target: Roc does't support compiling to {}-bit systems.", - ptr_bytes * 8 - ), +pub fn refcount_1(ctx: &Context, target_info: TargetInfo) -> IntValue<'_> { + match target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => ctx.i32_type().const_int(i32::MIN as u64, false), + roc_target::PtrWidth::Bytes8 => ctx.i64_type().const_int(i64::MIN as u64, false), } } @@ -164,7 +159,7 @@ impl<'ctx> PointerToRefcount<'ctx> { pub fn decrement<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) { let alignment = layout .allocation_alignment_bytes(env.target_info) - .max(env.target_info); + .max(env.target_info.ptr_width() as u32); let context = env.context; let block = env.builder.get_insert_block().expect("to be in a function"); diff --git a/compiler/gen_wasm/Cargo.toml b/compiler/gen_wasm/Cargo.toml index 90578803f7..a40dd13703 100644 --- a/compiler/gen_wasm/Cargo.toml +++ b/compiler/gen_wasm/Cargo.toml @@ -11,5 +11,6 @@ roc_builtins = { path = "../builtins" } roc_collections = { path = "../collections" } roc_module = { path = "../module" } roc_mono = { path = "../mono" } +roc_target = { path = "../target" } roc_std = { path = "../../roc_std" } roc_error_macros = { path = "../../error_macros" } diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 1200a80e12..716758fc56 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -26,7 +26,7 @@ use crate::wasm_module::{ }; use crate::{ copy_memory, round_up_to_alignment, CopyMemoryConfig, Env, DEBUG_LOG_SETTINGS, MEMORY_NAME, - PTR_SIZE, PTR_TYPE, STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME, + PTR_SIZE, PTR_TYPE, STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME, TARGET_INFO, }; /// The memory address where the constants data will be loaded during module instantiation. @@ -943,7 +943,7 @@ impl<'a> WasmBackend<'a> { } }; for field in field_layouts.iter().take(index as usize) { - offset += field.stack_size(PTR_SIZE); + offset += field.stack_size(TARGET_INFO); } self.storage .copy_value_from_memory(&mut self.code_builder, sym, local_id, offset); @@ -1010,11 +1010,11 @@ impl<'a> WasmBackend<'a> { elems: &'a [ListLiteralElement<'a>], ) { if let StoredValue::StackMemory { location, .. } = storage { - let size = elem_layout.stack_size(PTR_SIZE) * (elems.len() as u32); + let size = elem_layout.stack_size(TARGET_INFO) * (elems.len() as u32); // Allocate heap space and store its address in a local variable let heap_local_id = self.storage.create_anonymous_local(PTR_TYPE); - let heap_alignment = elem_layout.alignment_bytes(PTR_SIZE); + let heap_alignment = elem_layout.alignment_bytes(TARGET_INFO); self.allocate_with_refcount(Some(size), heap_alignment, 1); self.code_builder.set_local(heap_local_id); @@ -1099,9 +1099,9 @@ impl<'a> WasmBackend<'a> { return; } - let stores_tag_id_as_data = union_layout.stores_tag_id_as_data(PTR_SIZE); - let stores_tag_id_in_pointer = union_layout.stores_tag_id_in_pointer(PTR_SIZE); - let (data_size, data_alignment) = union_layout.data_size_and_alignment(PTR_SIZE); + let stores_tag_id_as_data = union_layout.stores_tag_id_as_data(TARGET_INFO); + let stores_tag_id_in_pointer = union_layout.stores_tag_id_in_pointer(TARGET_INFO); + let (data_size, data_alignment) = union_layout.data_size_and_alignment(TARGET_INFO); // We're going to use the pointer many times, so put it in a local variable let stored_with_local = @@ -1138,7 +1138,7 @@ impl<'a> WasmBackend<'a> { if stores_tag_id_as_data { let id_offset = data_offset + data_size - data_alignment; - let id_align = union_layout.tag_id_builtin().alignment_bytes(PTR_SIZE); + let id_align = union_layout.tag_id_builtin().alignment_bytes(TARGET_INFO); let id_align = Align::from(id_align); self.code_builder.get_local(local_id); @@ -1218,11 +1218,11 @@ impl<'a> WasmBackend<'a> { } }; - if union_layout.stores_tag_id_as_data(PTR_SIZE) { - let (data_size, data_alignment) = union_layout.data_size_and_alignment(PTR_SIZE); + if union_layout.stores_tag_id_as_data(TARGET_INFO) { + let (data_size, data_alignment) = union_layout.data_size_and_alignment(TARGET_INFO); let id_offset = data_size - data_alignment; - let id_align = union_layout.tag_id_builtin().alignment_bytes(PTR_SIZE); + let id_align = union_layout.tag_id_builtin().alignment_bytes(TARGET_INFO); let id_align = Align::from(id_align); self.storage @@ -1237,7 +1237,7 @@ impl<'a> WasmBackend<'a> { Builtin::Int(IntWidth::U64) => self.code_builder.i64_load(id_align, id_offset), x => internal_error!("Unexpected layout for tag union id {:?}", x), } - } else if union_layout.stores_tag_id_in_pointer(PTR_SIZE) { + } else if union_layout.stores_tag_id_in_pointer(TARGET_INFO) { self.storage .load_symbols(&mut self.code_builder, &[structure]); self.code_builder.i32_const(3); @@ -1284,7 +1284,7 @@ impl<'a> WasmBackend<'a> { let field_offset: u32 = field_layouts .iter() .take(index as usize) - .map(|field_layout| field_layout.stack_size(PTR_SIZE)) + .map(|field_layout| field_layout.stack_size(TARGET_INFO)) .sum(); // Get pointer and offset to the tag's data @@ -1304,7 +1304,7 @@ impl<'a> WasmBackend<'a> { } }; - let stores_tag_id_in_pointer = union_layout.stores_tag_id_in_pointer(PTR_SIZE); + let stores_tag_id_in_pointer = union_layout.stores_tag_id_in_pointer(TARGET_INFO); let from_ptr = if stores_tag_id_in_pointer { let ptr = self.storage.create_anonymous_local(ValueType::I32); diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index 330de5261b..b7ad18b2ef 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -2,7 +2,7 @@ use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_mono::layout::{Layout, UnionLayout}; use crate::wasm_module::ValueType; -use crate::{PTR_SIZE, PTR_TYPE}; +use crate::{PTR_SIZE, PTR_TYPE, TARGET_INFO}; /// Manually keep up to date with the Zig version we are using for builtins pub const BUILTINS_ZIG_VERSION: ZigVersion = ZigVersion::Zig8; @@ -47,8 +47,8 @@ impl WasmLayout { use UnionLayout::*; use ValueType::*; - let size = layout.stack_size(PTR_SIZE); - let alignment_bytes = layout.alignment_bytes(PTR_SIZE); + let size = layout.stack_size(TARGET_INFO); + let alignment_bytes = layout.alignment_bytes(TARGET_INFO); match layout { Layout::Builtin(Int(int_width)) => { diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index b033c66583..9163e592e5 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -6,20 +6,29 @@ pub mod wasm_module; use bumpalo::{self, collections::Vec, Bump}; -use roc_builtins::bitcode::IntWidth; use roc_collections::all::{MutMap, MutSet}; use roc_module::low_level::LowLevelWrapperType; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::layout::LayoutIds; +use roc_target::TargetInfo; use crate::backend::WasmBackend; use crate::wasm_module::{ Align, CodeBuilder, Export, ExportType, LocalId, SymInfo, ValueType, WasmModule, }; -const PTR_SIZE: u32 = 4; +const TARGET_INFO: TargetInfo = TargetInfo::default_wasm32(); +const PTR_SIZE: u32 = { + let value = TARGET_INFO.ptr_width() as u32; + + // const assert that our pointer width is actually 4 + // the code relies on the pointer width being exactly 4 + assert!(value == 4); + + value +}; const PTR_TYPE: ValueType = ValueType::I32; pub const STACK_POINTER_GLOBAL_ID: u32 = 0; @@ -111,7 +120,7 @@ pub fn build_module_without_test_wrapper<'a>( proc_symbols, initial_module, fn_index_offset, - CodeGenHelp::new(env.arena, IntWidth::I32, env.module_id), + CodeGenHelp::new(env.arena, TargetInfo::default_wasm32(), env.module_id), ); if DEBUG_LOG_SETTINGS.user_procs_ir { diff --git a/compiler/load/Cargo.toml b/compiler/load/Cargo.toml index ac6505b016..27da9138af 100644 --- a/compiler/load/Cargo.toml +++ b/compiler/load/Cargo.toml @@ -18,6 +18,7 @@ roc_unify = { path = "../unify" } roc_parse = { path = "../parse" } roc_solve = { path = "../solve" } roc_mono = { path = "../mono" } +roc_target = { path = "../target" } roc_reporting = { path = "../../reporting" } morphic_lib = { path = "../../vendor/morphic_lib" } ven_pretty = { path = "../../vendor/pretty" } diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index f0b4753863..fed406405e 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -32,6 +32,7 @@ use roc_parse::parser::{FileError, Parser, SyntaxError}; use roc_region::all::{LineInfo, Loc, Region}; use roc_solve::module::SolvedModule; use roc_solve::solve; +use roc_target::TargetInfo; use roc_types::solved_types::Solved; use roc_types::subs::{Subs, VarStore, Variable}; use roc_types::types::{Alias, Type}; @@ -878,7 +879,7 @@ struct State<'a> { pub exposed_types: SubsByModule, pub output_path: Option<&'a str>, pub platform_path: PlatformPath<'a>, - pub ptr_bytes: u32, + pub target_info: TargetInfo, pub module_cache: ModuleCache<'a>, pub dependencies: Dependencies<'a>, @@ -1083,7 +1084,7 @@ pub fn load_and_typecheck<'a, F>( stdlib: &'a StdLib, src_dir: &Path, exposed_types: SubsByModule, - ptr_bytes: u32, + target_info: TargetInfo, look_up_builtin: F, ) -> Result> where @@ -1100,7 +1101,7 @@ where src_dir, exposed_types, Phase::SolveTypes, - ptr_bytes, + target_info, look_up_builtin, )? { Monomorphized(_) => unreachable!(""), @@ -1115,7 +1116,7 @@ pub fn load_and_monomorphize<'a, F>( stdlib: &'a StdLib, src_dir: &Path, exposed_types: SubsByModule, - ptr_bytes: u32, + target_info: TargetInfo, look_up_builtin: F, ) -> Result, LoadingProblem<'a>> where @@ -1132,7 +1133,7 @@ where src_dir, exposed_types, Phase::MakeSpecializations, - ptr_bytes, + target_info, look_up_builtin, )? { Monomorphized(module) => Ok(module), @@ -1148,7 +1149,7 @@ pub fn load_and_monomorphize_from_str<'a, F>( stdlib: &'a StdLib, src_dir: &Path, exposed_types: SubsByModule, - ptr_bytes: u32, + target_info: TargetInfo, look_up_builtin: F, ) -> Result, LoadingProblem<'a>> where @@ -1165,7 +1166,7 @@ where src_dir, exposed_types, Phase::MakeSpecializations, - ptr_bytes, + target_info, look_up_builtin, )? { Monomorphized(module) => Ok(module), @@ -1317,7 +1318,7 @@ fn load<'a, F>( src_dir: &Path, exposed_types: SubsByModule, goal_phase: Phase, - ptr_bytes: u32, + target_info: TargetInfo, look_up_builtins: F, ) -> Result, LoadingProblem<'a>> where @@ -1433,7 +1434,7 @@ where worker_arena, src_dir, msg_tx.clone(), - ptr_bytes, + target_info, look_up_builtins, ); @@ -1474,7 +1475,7 @@ where let mut state = State { root_id, - ptr_bytes, + target_info, platform_data: None, goal_phase, stdlib, @@ -1999,7 +2000,7 @@ fn update<'a>( let layout_cache = state .layout_caches .pop() - .unwrap_or_else(|| LayoutCache::new(state.ptr_bytes)); + .unwrap_or_else(|| LayoutCache::new(state.target_info)); let typechecked = TypeCheckedModule { module_id, @@ -3812,7 +3813,7 @@ fn make_specializations<'a>( mut layout_cache: LayoutCache<'a>, specializations_we_must_make: Vec, mut module_timing: ModuleTiming, - ptr_bytes: u32, + target_info: TargetInfo, ) -> Msg<'a> { let make_specializations_start = SystemTime::now(); let mut mono_problems = Vec::new(); @@ -3824,7 +3825,7 @@ fn make_specializations<'a>( subs: &mut subs, home, ident_ids: &mut ident_ids, - target_info: ptr_bytes, + target_info, update_mode_ids: &mut update_mode_ids, // call_specialization_counter=0 is reserved call_specialization_counter: 1, @@ -3895,7 +3896,7 @@ fn build_pending_specializations<'a>( decls: Vec, mut module_timing: ModuleTiming, mut layout_cache: LayoutCache<'a>, - ptr_bytes: u32, + target_info: TargetInfo, // TODO remove exposed_to_host: ExposedToHost, ) -> Msg<'a> { @@ -3920,7 +3921,7 @@ fn build_pending_specializations<'a>( subs: &mut subs, home, ident_ids: &mut ident_ids, - target_info: ptr_bytes, + target_info, update_mode_ids: &mut update_mode_ids, // call_specialization_counter=0 is reserved call_specialization_counter: 1, @@ -4128,7 +4129,7 @@ fn run_task<'a, F>( arena: &'a Bump, src_dir: &Path, msg_tx: MsgSender<'a>, - ptr_bytes: u32, + target_info: TargetInfo, look_up_builtins: F, ) -> Result<(), LoadingProblem<'a>> where @@ -4206,7 +4207,7 @@ where decls, module_timing, layout_cache, - ptr_bytes, + target_info, exposed_to_host, )), MakeSpecializations { @@ -4226,7 +4227,7 @@ where layout_cache, specializations_we_must_make, module_timing, - ptr_bytes, + target_info, )), }?; diff --git a/compiler/target/src/lib.rs b/compiler/target/src/lib.rs index 237ca81f29..e62cefb710 100644 --- a/compiler/target/src/lib.rs +++ b/compiler/target/src/lib.rs @@ -17,6 +17,20 @@ impl TargetInfo { architecture: Architecture::X86_64, } } + + pub const fn default_wasm32() -> Self { + TargetInfo { + architecture: Architecture::Wasm32, + } + } +} + +impl From<&target_lexicon::Triple> for TargetInfo { + fn from(triple: &target_lexicon::Triple) -> Self { + let architecture = Architecture::from(triple.architecture); + + Self { architecture } + } } #[repr(u8)] diff --git a/docs/Cargo.toml b/docs/Cargo.toml index 8a1e2637cb..77b1bf7972 100644 --- a/docs/Cargo.toml +++ b/docs/Cargo.toml @@ -18,6 +18,7 @@ roc_module = { path = "../compiler/module" } roc_region = { path = "../compiler/region" } roc_types = { path = "../compiler/types" } roc_parse = { path = "../compiler/parse" } +roc_target = { path = "../compiler/target" } roc_collections = { path = "../compiler/collections" } bumpalo = { version = "3.8.0", features = ["collections"] } snafu = { version = "0.6.10", features = ["backtraces"] } diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 35eaa93a13..8c791b72ee 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -428,7 +428,7 @@ pub fn load_modules_for_files(filenames: Vec, std_lib: StdLib) -> Vec() as u32, // This is just type-checking for docs, so "target" doesn't matter + roc_target::TargetInfo::default_x86_64(), // This is just type-checking for docs, so "target" doesn't matter builtin_defs_map, ) { Ok(loaded) => modules.push(loaded), From b9c318e9fb65dc3d7f57f956a0a50f217fa9c571 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Jan 2022 15:59:21 +0100 Subject: [PATCH 323/541] update the tests --- Cargo.lock | 4 ++++ compiler/load/tests/test_load.rs | 8 +++++--- compiler/solve/Cargo.toml | 1 + compiler/solve/tests/solve_expr.rs | 2 +- compiler/test_gen/Cargo.toml | 1 + compiler/test_gen/src/gen_tags.rs | 6 +++--- compiler/test_gen/src/helpers/dev.rs | 2 +- compiler/test_gen/src/helpers/llvm.rs | 8 ++++---- compiler/test_gen/src/helpers/wasm.rs | 3 +-- compiler/test_mono/Cargo.toml | 1 + compiler/test_mono/src/tests.rs | 4 +++- reporting/Cargo.toml | 1 + reporting/tests/test_reporting.rs | 6 +++--- 13 files changed, 29 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ff94289d0..53fc08cafd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3690,6 +3690,7 @@ dependencies = [ "roc_problem", "roc_region", "roc_solve", + "roc_target", "roc_test_utils", "roc_types", "ven_pretty", @@ -3712,6 +3713,7 @@ dependencies = [ "roc_problem", "roc_region", "roc_solve", + "roc_target", "roc_types", "roc_unify", "tempfile", @@ -4280,6 +4282,7 @@ dependencies = [ "roc_reporting", "roc_solve", "roc_std", + "roc_target", "roc_types", "roc_unify", "target-lexicon", @@ -4301,6 +4304,7 @@ dependencies = [ "roc_load", "roc_module", "roc_mono", + "roc_target", "test_mono_macros", ] diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index 7a72b7b98b..b0196c44e0 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -28,6 +28,8 @@ mod test_load { use roc_types::subs::Subs; use std::collections::HashMap; + const TARGET_INFO: roc_target::TargetInfo = roc_target::TargetInfo::default_x86_64(); + // HELPERS fn multiple_modules(files: Vec<(&str, &str)>) -> Result { @@ -110,7 +112,7 @@ mod test_load { arena.alloc(stdlib), dir.path(), exposed_types, - 8, + TARGET_INFO, builtin_defs_map, ) }; @@ -134,7 +136,7 @@ mod test_load { arena.alloc(roc_builtins::std::standard_stdlib()), src_dir.as_path(), subs_by_module, - 8, + TARGET_INFO, builtin_defs_map, ); let mut loaded_module = match loaded { @@ -305,7 +307,7 @@ mod test_load { arena.alloc(roc_builtins::std::standard_stdlib()), src_dir.as_path(), subs_by_module, - 8, + TARGET_INFO, builtin_defs_map, ); diff --git a/compiler/solve/Cargo.toml b/compiler/solve/Cargo.toml index 89da067e95..7b776821a3 100644 --- a/compiler/solve/Cargo.toml +++ b/compiler/solve/Cargo.toml @@ -21,6 +21,7 @@ roc_builtins = { path = "../builtins" } roc_problem = { path = "../problem" } roc_parse = { path = "../parse" } roc_solve = { path = "../solve" } +roc_target = { path = "../target" } pretty_assertions = "1.0.0" indoc = "1.0.3" tempfile = "3.2.0" diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index df5d36580f..b86caf3c81 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -63,7 +63,7 @@ mod solve_expr { &stdlib, dir.path(), exposed_types, - 8, + roc_target::TargetInfo::default_x86_64(), builtin_defs_map, ); diff --git a/compiler/test_gen/Cargo.toml b/compiler/test_gen/Cargo.toml index 162b058fd8..07fd0c91e7 100644 --- a/compiler/test_gen/Cargo.toml +++ b/compiler/test_gen/Cargo.toml @@ -31,6 +31,7 @@ roc_load = { path = "../load" } roc_can = { path = "../can" } roc_parse = { path = "../parse" } roc_build = { path = "../build" } +roc_target = { path = "../target" } roc_std = { path = "../../roc_std" } bumpalo = { version = "3.8.0", features = ["collections"] } either = "1.6.1" diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index bbbc9fa490..b4abaf711f 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -23,9 +23,9 @@ fn width_and_alignment_u8_u8() { let layout = Layout::Union(UnionLayout::NonRecursive(&tt)); - let ptr_width = 8; - assert_eq!(layout.alignment_bytes(ptr_width), 1); - assert_eq!(layout.stack_size(ptr_width), 2); + let target_info = roc_target::TargetInfo::default_x86_64(); + assert_eq!(layout.alignment_bytes(target_info), 1); + assert_eq!(layout.stack_size(target_info), 2); } #[test] diff --git a/compiler/test_gen/src/helpers/dev.rs b/compiler/test_gen/src/helpers/dev.rs index 31a87d9c57..3a562cbf8c 100644 --- a/compiler/test_gen/src/helpers/dev.rs +++ b/compiler/test_gen/src/helpers/dev.rs @@ -57,7 +57,7 @@ pub fn helper( &stdlib, src_dir, exposed_types, - 8, + roc_target::TargetInfo::default_x86_64(), builtin_defs_map, ); diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index 6dc6c2b7f4..1392b1ab13 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -42,6 +42,8 @@ fn create_llvm_module<'a>( ) -> (&'static str, String, &'a Module<'a>) { use std::path::{Path, PathBuf}; + let target_info = roc_target::TargetInfo::from(target); + let filename = PathBuf::from("Test.roc"); let src_dir = Path::new("fake/test/path"); @@ -56,8 +58,6 @@ fn create_llvm_module<'a>( module_src = &temp; } - let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; - let exposed_types = MutMap::default(); let loaded = roc_load::file::load_and_monomorphize_from_str( arena, @@ -66,7 +66,7 @@ fn create_llvm_module<'a>( stdlib, src_dir, exposed_types, - ptr_bytes, + target_info, test_builtin_defs, ); @@ -213,7 +213,7 @@ fn create_llvm_module<'a>( context, interns, module, - target_info: ptr_bytes, + target_info, is_gen_test, // important! we don't want any procedures to get the C calling convention exposed_to_host: MutSet::default(), diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index f68f6b5da8..5d756fe921 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -94,7 +94,6 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( } let exposed_types = MutMap::default(); - let ptr_bytes = 4; let loaded = roc_load::file::load_and_monomorphize_from_str( arena, filename, @@ -102,7 +101,7 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( stdlib, src_dir, exposed_types, - ptr_bytes, + roc_target::TargetInfo::default_wasm32(), builtin_defs_map, ); diff --git a/compiler/test_mono/Cargo.toml b/compiler/test_mono/Cargo.toml index d599b1b64c..a2225349cb 100644 --- a/compiler/test_mono/Cargo.toml +++ b/compiler/test_mono/Cargo.toml @@ -16,6 +16,7 @@ roc_builtins = { path = "../builtins" } roc_load = { path = "../load" } roc_can = { path = "../can" } roc_mono = { path = "../mono" } +roc_target = { path = "../target" } test_mono_macros = { path = "../test_mono_macros" } pretty_assertions = "1.0.0" bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/compiler/test_mono/src/tests.rs b/compiler/test_mono/src/tests.rs index 4a398e6d38..2cf783baa2 100644 --- a/compiler/test_mono/src/tests.rs +++ b/compiler/test_mono/src/tests.rs @@ -25,6 +25,8 @@ use roc_mono::ir::Proc; use roc_mono::ir::ProcLayout; +const TARGET_INFO: roc_target::TargetInfo = roc_target::TargetInfo::default_x86_64(); + /// 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 @@ -104,7 +106,7 @@ fn compiles_to_ir(test_name: &str, src: &str) { &stdlib, src_dir, exposed_types, - 8, + TARGET_INFO, builtin_defs_map, ); diff --git a/reporting/Cargo.toml b/reporting/Cargo.toml index 195406c968..be96603493 100644 --- a/reporting/Cargo.toml +++ b/reporting/Cargo.toml @@ -24,6 +24,7 @@ roc_constrain = { path = "../compiler/constrain" } roc_builtins = { path = "../compiler/builtins" } roc_problem = { path = "../compiler/problem" } roc_parse = { path = "../compiler/parse" } +roc_target = { path = "../compiler/target" } roc_test_utils = { path = "../test_utils" } pretty_assertions = "1.0.0" indoc = "1.0.3" diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index d0c6946def..45215dd437 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -96,8 +96,8 @@ mod test_reporting { let mut update_mode_ids = UpdateModeIds::new(); // Populate Procs and Subs, and get the low-level Expr from the canonical Expr - let ptr_bytes = 8; - let mut layout_cache = LayoutCache::new(ptr_bytes); + let target_info = roc_target::TargetInfo::default_x86_64(); + let mut layout_cache = LayoutCache::new(target_info); let mut mono_env = roc_mono::ir::Env { arena: &arena, subs: &mut subs, @@ -105,7 +105,7 @@ mod test_reporting { home, ident_ids: &mut ident_ids, update_mode_ids: &mut update_mode_ids, - target_info: ptr_bytes, + target_info, // call_specialization_counter=0 is reserved call_specialization_counter: 1, }; From 0298013346f8f9866b96f44f03b8e192d26d1aa4 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Jan 2022 17:03:49 +0100 Subject: [PATCH 324/541] fix logical error --- compiler/can/src/module.rs | 2 +- compiler/gen_llvm/src/llvm/convert.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 7a292807bb..2cdd6e8e4e 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -124,7 +124,7 @@ where // This is a type alias // the symbol should already be added to the scope when this module is canonicalized - debug_assert!(scope.contains_alias(symbol)); + debug_assert!(scope.contains_alias(symbol), "{:?}", symbol); // but now we know this symbol by a different identifier, so we still need to add it to // the scope diff --git a/compiler/gen_llvm/src/llvm/convert.rs b/compiler/gen_llvm/src/llvm/convert.rs index 00d98ff5e3..c7d3142d40 100644 --- a/compiler/gen_llvm/src/llvm/convert.rs +++ b/compiler/gen_llvm/src/llvm/convert.rs @@ -256,8 +256,8 @@ fn block_of_memory_help(context: &Context, union_size: u32) -> BasicTypeEnum<'_> /// The int type that the C ABI turns our RocList/RocStr into pub fn str_list_int(ctx: &Context, target_info: TargetInfo) -> IntType<'_> { match target_info.ptr_width() { - roc_target::PtrWidth::Bytes4 => ctx.i32_type(), - roc_target::PtrWidth::Bytes8 => ctx.i64_type(), + roc_target::PtrWidth::Bytes4 => ctx.i64_type(), + roc_target::PtrWidth::Bytes8 => ctx.i128_type(), } } From fbd26c598e4d9e1844f6f719e7a837106ea84ac9 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Jan 2022 17:19:53 +0100 Subject: [PATCH 325/541] provide target info to number alignment function --- compiler/builtins/src/bitcode.rs | 6 +++--- compiler/mono/src/layout.rs | 6 +++--- compiler/mono/src/layout_soa.rs | 36 +++++++++++++++++++++++--------- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index e1a2a9f8f4..9321d08637 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -1,4 +1,5 @@ use roc_module::symbol::Symbol; +use roc_target::TargetInfo; use std::ops::Index; pub const BUILTINS_HOST_OBJ_PATH: &str = env!( @@ -46,7 +47,7 @@ impl FloatWidth { } } - pub const fn alignment_bytes(&self) -> u32 { + pub const fn alignment_bytes(&self, target_info: TargetInfo) -> u32 { use std::mem::align_of; use FloatWidth::*; @@ -106,11 +107,10 @@ impl IntWidth { } } - pub const fn alignment_bytes(&self) -> u32 { + pub const fn alignment_bytes(&self, target_info: TargetInfo) -> u32 { use std::mem::align_of; use IntWidth::*; - // TODO actually alignment is architecture-specific match self { U8 | I8 => align_of::() as u32, U16 | I16 => align_of::() as u32, diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 12d759f8b8..2055b03fd2 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -1341,10 +1341,10 @@ impl<'a> Builtin<'a> { // since both of those are one pointer size, the alignment of that structure is a pointer // size match self { - Int(int_width) => int_width.alignment_bytes(), - Float(float_width) => float_width.alignment_bytes(), + Int(int_width) => int_width.alignment_bytes(target_info), + Float(float_width) => float_width.alignment_bytes(target_info), Bool => align_of::() as u32, - Decimal => IntWidth::I128.alignment_bytes(), + Decimal => IntWidth::I128.alignment_bytes(target_info), Dict(_, _) => ptr_width, Set(_) => ptr_width, // we often treat these as i128 (64-bit systems) diff --git a/compiler/mono/src/layout_soa.rs b/compiler/mono/src/layout_soa.rs index 1481b38723..fe37cc385d 100644 --- a/compiler/mono/src/layout_soa.rs +++ b/compiler/mono/src/layout_soa.rs @@ -3,6 +3,7 @@ use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::MutMap; use roc_module::ident::TagName; use roc_module::symbol::Symbol; +use roc_target::TargetInfo; use roc_types::subs::{Content, FlatType, Subs, Variable}; use roc_types::types::RecordField; use std::collections::hash_map::Entry; @@ -99,7 +100,7 @@ pub struct Layouts { lambda_sets: Vec, symbols: Vec, recursion_variable_to_structure_variable_map: MutMap>, - usize_int_width: IntWidth, + target_info: TargetInfo, } pub struct FunctionLayout { @@ -402,7 +403,7 @@ impl Layouts { const VOID_TUPLE: Index<(Layout, Layout)> = Index::new(0); const UNIT_INDEX: Index = Index::new(2); - pub fn new(usize_int_width: IntWidth) -> Self { + pub fn new(target_info: TargetInfo) -> Self { let mut layouts = Vec::with_capacity(64); layouts.push(Layout::VOID); @@ -420,7 +421,7 @@ impl Layouts { lambda_sets: Vec::default(), symbols: Vec::default(), recursion_variable_to_structure_variable_map: MutMap::default(), - usize_int_width, + target_info, } } @@ -443,7 +444,12 @@ impl Layouts { } fn usize(&self) -> Layout { - Layout::Int(self.usize_int_width) + let usize_int_width = match self.target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => IntWidth::U32, + roc_target::PtrWidth::Bytes8 => IntWidth::U64, + }; + + Layout::Int(usize_int_width) } fn align_of_layout_index(&self, index: Index) -> u16 { @@ -453,18 +459,23 @@ impl Layouts { } fn align_of_layout(&self, layout: Layout) -> u16 { - let ptr_alignment = self.usize_int_width.alignment_bytes() as u16; + let usize_int_width = match self.target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => IntWidth::U32, + roc_target::PtrWidth::Bytes8 => IntWidth::U64, + }; + + let ptr_alignment = usize_int_width.alignment_bytes(self.target_info) as u16; match layout { Layout::Reserved => unreachable!(), - Layout::Int(int_width) => int_width.alignment_bytes() as u16, - Layout::Float(float_width) => float_width.alignment_bytes() as u16, - Layout::Decimal => IntWidth::U128.alignment_bytes() as u16, + Layout::Int(int_width) => int_width.alignment_bytes(self.target_info) as u16, + Layout::Float(float_width) => float_width.alignment_bytes(self.target_info) as u16, + Layout::Decimal => IntWidth::U128.alignment_bytes(self.target_info) as u16, Layout::Str | Layout::Dict(_) | Layout::Set(_) | Layout::List(_) => ptr_alignment, Layout::Struct(slice) => self.align_of_layout_slice(slice), Layout::Boxed(_) | Layout::UnionRecursive(_) => ptr_alignment, Layout::UnionNonRecursive(slices) => { - let tag_id_align = IntWidth::I64.alignment_bytes() as u16; + let tag_id_align = IntWidth::I64.alignment_bytes(self.target_info) as u16; self.align_of_layout_slices(slices).max(tag_id_align) } @@ -518,7 +529,12 @@ impl Layouts { } pub fn size_of_layout(&self, layout: Layout) -> u16 { - let ptr_width = self.usize_int_width.stack_size() as u16; + let usize_int_width = match self.target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => IntWidth::U32, + roc_target::PtrWidth::Bytes8 => IntWidth::U64, + }; + + let ptr_width = usize_int_width.stack_size() as u16; match layout { Layout::Reserved => unreachable!(), From 7e383093644b819e7951268ae2e88dc588346413 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Jan 2022 17:24:52 +0100 Subject: [PATCH 326/541] make alignment target-specific --- compiler/builtins/src/bitcode.rs | 18 ++++++++++++++++-- compiler/target/src/lib.rs | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 9321d08637..0284e9e774 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -48,13 +48,20 @@ impl FloatWidth { } pub const fn alignment_bytes(&self, target_info: TargetInfo) -> u32 { + use roc_target::Architecture; use std::mem::align_of; use FloatWidth::*; // TODO actually alignment is architecture-specific match self { F32 => align_of::() as u32, - F64 => align_of::() as u32, + F64 => match target_info.architecture { + Architecture::X86_64 + | Architecture::Aarch64 + | Architecture::Arm + | Architecture::Wasm32 => 8, + Architecture::X86_32 => 4, + }, F128 => align_of::() as u32, } } @@ -108,6 +115,7 @@ impl IntWidth { } pub const fn alignment_bytes(&self, target_info: TargetInfo) -> u32 { + use roc_target::Architecture; use std::mem::align_of; use IntWidth::*; @@ -115,7 +123,13 @@ impl IntWidth { U8 | I8 => align_of::() as u32, U16 | I16 => align_of::() as u32, U32 | I32 => align_of::() as u32, - U64 | I64 => align_of::() as u32, + U64 | I64 => match target_info.architecture { + Architecture::X86_64 + | Architecture::Aarch64 + | Architecture::Arm + | Architecture::Wasm32 => 8, + Architecture::X86_32 => 4, + }, U128 | I128 => align_of::() as u32, } } diff --git a/compiler/target/src/lib.rs b/compiler/target/src/lib.rs index e62cefb710..68269a3849 100644 --- a/compiler/target/src/lib.rs +++ b/compiler/target/src/lib.rs @@ -4,7 +4,7 @@ #[derive(Debug, Clone, Copy)] pub struct TargetInfo { - architecture: Architecture, + pub architecture: Architecture, } impl TargetInfo { From 816cbdbe653973476f6df54eab15509a72f7794a Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 26 Jan 2022 13:37:49 +0000 Subject: [PATCH 327/541] repl: Create FromMemory trait --- cli/src/repl.rs | 1 + cli/src/repl/from_memory.rs | 51 +++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 cli/src/repl/from_memory.rs diff --git a/cli/src/repl.rs b/cli/src/repl.rs index 0ca12fba55..8e3c4d8933 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -29,6 +29,7 @@ pub const CONT_PROMPT: &str = concatcp!(BLUE, "…", END_COL, " "); #[cfg(feature = "llvm")] mod eval; +mod from_memory; #[cfg(feature = "llvm")] mod gen; diff --git a/cli/src/repl/from_memory.rs b/cli/src/repl/from_memory.rs new file mode 100644 index 0000000000..381ded1737 --- /dev/null +++ b/cli/src/repl/from_memory.rs @@ -0,0 +1,51 @@ +use std::mem::size_of; + +pub trait FromMemory { + fn from_memory(memory: M, address: usize) -> Self; +} + +/// A block of app memory in the same address space as the compiler +pub struct AppMemoryInternal; + +/// A block of app memory in a separate address space from the compiler +/// (e.g. compiler and app are in separate Wasm modules) +pub struct AppMemoryExternal { + bytes: Vec, +} + +macro_rules! impl_primitive { + ($t: ty) => { + impl FromMemory for $t { + fn from_memory(_: AppMemoryInternal, address: usize) -> Self { + let ptr = address as *const _; + unsafe { *ptr } + } + } + + impl FromMemory for $t { + fn from_memory(memory: AppMemoryExternal, address: usize) -> Self { + const N: usize = size_of::<$t>(); + let mut array = [0; N]; + array.copy_from_slice(&memory.bytes[address..][..N]); + Self::from_le_bytes(array) + } + } + }; +} + +impl_primitive!(u8); +impl_primitive!(u16); +impl_primitive!(u32); +impl_primitive!(u64); +impl_primitive!(u128); +impl_primitive!(usize); + +impl_primitive!(i8); +impl_primitive!(i16); +impl_primitive!(i32); +impl_primitive!(i64); +impl_primitive!(i128); +impl_primitive!(isize); + +impl_primitive!(f32); +impl_primitive!(f64); From 17b960bae0d65618eefc0365a3694abb23167ac2 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 26 Jan 2022 18:05:11 +0000 Subject: [PATCH 328/541] repl: Add memory parameter to Env --- cli/src/repl/eval.rs | 59 +++++++++++++++++++++++--------------------- cli/src/repl/gen.rs | 2 ++ 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index bbd0e93a38..569a4ff551 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -17,10 +17,11 @@ use roc_region::all::{Loc, Region}; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; use std::cmp::{max_by_key, min_by_key}; -struct Env<'a, 'env> { +struct Env<'a, 'env, M> { arena: &'a Bump, subs: &'env Subs, ptr_bytes: u32, + app_memory: M, interns: &'env Interns, home: ModuleId, } @@ -38,7 +39,7 @@ pub enum ToAstProblem { /// we get to a struct or tag, we know what the labels are and can turn them /// back into the appropriate user-facing literals. #[allow(clippy::too_many_arguments)] -pub unsafe fn jit_to_ast<'a>( +pub unsafe fn jit_to_ast<'a, M>( arena: &'a Bump, lib: Library, main_fn_name: &str, @@ -48,11 +49,13 @@ pub unsafe fn jit_to_ast<'a>( home: ModuleId, subs: &'a Subs, ptr_bytes: u32, + app_memory: M, ) -> Result, ToAstProblem> { let env = Env { arena, subs, ptr_bytes, + app_memory, interns, home, }; @@ -83,8 +86,8 @@ enum NewtypeKind<'a> { /// /// The returned list of newtype containers is ordered by increasing depth. As an example, /// `A ({b : C 123})` will have the unrolled list `[Tag(A), RecordField(b), Tag(C)]`. -fn unroll_newtypes<'a>( - env: &Env<'a, 'a>, +fn unroll_newtypes<'a, M>( + env: &Env<'a, 'a, M>, mut content: &'a Content, ) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) { let mut newtype_containers = Vec::with_capacity_in(1, env.arena); @@ -117,8 +120,8 @@ fn unroll_newtypes<'a>( } } -fn apply_newtypes<'a>( - env: &Env<'a, '_>, +fn apply_newtypes<'a, M>( + env: &Env<'a, '_, M>, newtype_containers: Vec<'a, NewtypeKind<'a>>, mut expr: Expr<'a>, ) -> Expr<'a> { @@ -145,22 +148,22 @@ fn apply_newtypes<'a>( expr } -fn unroll_aliases<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content { +fn unroll_aliases<'a, M>(env: &Env<'a, 'a, M>, mut content: &'a Content) -> &'a Content { while let Content::Alias(_, _, real) = content { content = env.subs.get_content_without_compacting(*real); } content } -fn unroll_recursion_var<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content { +fn unroll_recursion_var<'a, M>(env: &Env<'a, 'a, M>, mut content: &'a Content) -> &'a Content { while let Content::RecursionVar { structure, .. } = content { content = env.subs.get_content_without_compacting(*structure); } content } -fn get_tags_vars_and_variant<'a>( - env: &Env<'a, '_>, +fn get_tags_vars_and_variant<'a, M>( + env: &Env<'a, '_, M>, tags: &UnionTags, opt_rec_var: Option, ) -> (MutMap>, UnionVariant<'a>) { @@ -177,8 +180,8 @@ fn get_tags_vars_and_variant<'a>( (vars_of_tag, union_variant) } -fn expr_of_tag<'a>( - env: &Env<'a, 'a>, +fn expr_of_tag<'a, M>( + env: &Env<'a, 'a, M>, ptr_to_data: *const u8, tag_name: &TagName, arg_layouts: &'a [Layout<'a>], @@ -259,8 +262,8 @@ const OPAQUE_FUNCTION: Expr = Expr::Var { ident: "", }; -fn jit_to_ast_help<'a>( - env: &Env<'a, 'a>, +fn jit_to_ast_help<'a, M>( + env: &Env<'a, 'a, M>, lib: Library, main_fn_name: &str, layout: &Layout<'a>, @@ -430,7 +433,7 @@ fn jit_to_ast_help<'a>( result.map(|e| apply_newtypes(env, newtype_containers, e)) } -fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> { +fn tag_name_to_expr<'a, M>(env: &Env<'a, '_, M>, tag_name: &TagName) -> Expr<'a> { match tag_name { TagName::Global(_) => Expr::GlobalTag( env.arena @@ -452,8 +455,8 @@ enum WhenRecursive<'a> { Loop(Layout<'a>), } -fn ptr_to_ast<'a>( - env: &Env<'a, 'a>, +fn ptr_to_ast<'a, M>( + env: &Env<'a, 'a, M>, ptr: *const u8, layout: &Layout<'a>, when_recursive: WhenRecursive<'a>, @@ -723,8 +726,8 @@ fn ptr_to_ast<'a>( apply_newtypes(env, newtype_containers, expr) } -fn list_to_ast<'a>( - env: &Env<'a, 'a>, +fn list_to_ast<'a, M>( + env: &Env<'a, 'a, M>, ptr: *const u8, len: usize, elem_layout: &Layout<'a>, @@ -772,8 +775,8 @@ fn list_to_ast<'a>( Expr::List(Collection::with_items(output)) } -fn single_tag_union_to_ast<'a>( - env: &Env<'a, 'a>, +fn single_tag_union_to_ast<'a, M>( + env: &Env<'a, 'a, M>, ptr: *const u8, field_layouts: &'a [Layout<'a>], tag_name: &TagName, @@ -798,8 +801,8 @@ fn single_tag_union_to_ast<'a>( Expr::Apply(loc_tag_expr, output, CalledVia::Space) } -fn sequence_of_expr<'a, I>( - env: &Env<'a, 'a>, +fn sequence_of_expr<'a, I, M>( + env: &Env<'a, 'a, M>, ptr: *const u8, sequence: I, when_recursive: WhenRecursive<'a>, @@ -829,8 +832,8 @@ where output } -fn struct_to_ast<'a>( - env: &Env<'a, 'a>, +fn struct_to_ast<'a, M>( + env: &Env<'a, 'a, M>, ptr: *const u8, field_layouts: &'a [Layout<'a>], record_fields: RecordFields, @@ -947,7 +950,7 @@ fn unpack_two_element_tag_union( (tag_name1, payload_vars1, tag_name2, payload_vars2) } -fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a> { +fn bool_to_ast<'a, M>(env: &Env<'a, '_, M>, value: bool, content: &Content) -> Expr<'a> { use Content::*; let arena = env.arena; @@ -1025,7 +1028,7 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a } } -fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> { +fn byte_to_ast<'a, M>(env: &Env<'a, '_, M>, value: u8, content: &Content) -> Expr<'a> { use Content::*; let arena = env.arena; @@ -1112,7 +1115,7 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> } } -fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> Expr<'a> { +fn num_to_ast<'a, M>(env: &Env<'a, '_, M>, num_expr: Expr<'a>, content: &Content) -> Expr<'a> { use Content::*; let arena = env.arena; diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index 5a52f74420..4d6b2c67a7 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -1,4 +1,5 @@ use crate::repl::eval; +use crate::repl::from_memory::AppMemoryInternal; use bumpalo::Bump; use inkwell::context::Context; use inkwell::module::Linkage; @@ -238,6 +239,7 @@ pub fn gen_and_eval<'a>( home, &subs, ptr_bytes, + AppMemoryInternal, ) }; let mut expr = roc_fmt::Buf::new_in(&arena); From 490bbf381202aa7ef38313ade290c23c97e9a150 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 25 Jan 2022 22:09:12 +0100 Subject: [PATCH 329/541] ignore 'normal' functions when running i386 tests --- cli/tests/cli_run.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index bd7de64fed..149a632d73 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -421,9 +421,11 @@ mod cli_run { macro_rules! benchmarks { ($($test_name:ident => $benchmark:expr,)+) => { + $( #[test] #[cfg_attr(not(debug_assertions), serial(benchmark))] + #[cfg(all(not(feature = "wasm32-cli-run"), not(feature = "i386-cli-run")))] fn $test_name() { let benchmark = $benchmark; let file_name = examples_dir("benchmarks").join(benchmark.filename); From 4ab9a3302b26b3eb5d7e46952a30b346c2745cb0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 21 Jan 2022 20:01:45 +0100 Subject: [PATCH 330/541] use Task.loop in Deriv --- examples/benchmarks/Deriv.roc | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/examples/benchmarks/Deriv.roc b/examples/benchmarks/Deriv.roc index 5e5a81f8b2..2cad781765 100644 --- a/examples/benchmarks/Deriv.roc +++ b/examples/benchmarks/Deriv.roc @@ -21,6 +21,22 @@ main = |> Task.map (\_ -> {}) +nest : (I64, Expr -> IO Expr), I64, Expr -> IO Expr +nest = \f, n, e -> Task.loop { s: n, f, m: n, x: e } nestHelp + +State : { s : I64, f : I64, Expr -> IO Expr, m : I64, x : Expr } + +nestHelp : State -> IO [ Step State, Done Expr ] +nestHelp = \{ s, f, m, x } -> + when m is + 0 -> + Task.succeed (Done x) + + _ -> + w <- Task.after (f (s - m) x) + + Task.succeed (Step { s, f, m: (m - 1), x: w }) + Expr : [ Val I64, Var Str, Add Expr Expr, Mul Expr Expr, Pow Expr Expr, Ln Expr ] divmod : I64, I64 -> Result { div : I64, mod : I64 } [ DivByZero ]* @@ -180,18 +196,6 @@ count = \expr -> Ln f -> count f -nest : (I64, Expr -> IO Expr), I64, Expr -> IO Expr -nest = \f, n, e -> nestHelp n f n e - -nestHelp : I64, (I64, Expr -> IO Expr), I64, Expr -> IO Expr -nestHelp = \s, f, m, x -> - when m is - 0 -> - Task.succeed x - - _ -> - f (s - m) x |> Task.after \w -> nestHelp s f (m - 1) w - deriv : I64, Expr -> IO Expr deriv = \i, f -> fprime = d "x" f From ddf2a09cbfdf56f1faec7db3dc00b5194568f70f Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 21 Jan 2022 20:57:03 +0100 Subject: [PATCH 331/541] use Task.loop in False --- examples/false-interpreter/False.roc | 42 +++++++++++--------- examples/false-interpreter/platform/Task.roc | 20 +++++++++- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/examples/false-interpreter/False.roc b/examples/false-interpreter/False.roc index 57fc3c14a9..d7e0ac0c4f 100644 --- a/examples/false-interpreter/False.roc +++ b/examples/false-interpreter/False.roc @@ -89,6 +89,10 @@ isWhitespace = \char -> == 0x9# tab interpretCtx : Context -> Task Context InterpreterErrors interpretCtx = \ctx -> + Task.loop ctx interpretCtxLoop + +interpretCtxLoop : Context -> Task [ Step Context, Done Context ] InterpreterErrors +interpretCtxLoop = \ctx -> when ctx.state is Executing if Context.inWhileScope ctx -> # Deal with the current while loop potentially looping. @@ -104,11 +108,11 @@ interpretCtx = \ctx -> if n == 0 then newScope = { scope & whileInfo: None } - interpretCtx { popCtx & scopes: List.set ctx.scopes last newScope } + Task.succeed (Step { popCtx & scopes: List.set ctx.scopes last newScope }) else newScope = { scope & whileInfo: Some { state: InBody, body, cond } } - interpretCtx { popCtx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: body, index: 0, whileInfo: None } } + Task.succeed (Step { popCtx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: body, index: 0, whileInfo: None } }) Err e -> Task.fail e @@ -117,7 +121,7 @@ interpretCtx = \ctx -> # Just rand the body. Run the condition again. newScope = { scope & whileInfo: Some { state: InCond, body, cond } } - interpretCtx { ctx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: cond, index: 0, whileInfo: None } } + Task.succeed (Step { ctx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: cond, index: 0, whileInfo: None } }) None -> Task.fail NoScope @@ -131,7 +135,7 @@ interpretCtx = \ctx -> when result is Ok (T val newCtx) -> execCtx <- Task.await (stepExecCtx newCtx val) - interpretCtx execCtx + Task.succeed (Step execCtx) Err NoScope -> Task.fail NoScope @@ -143,9 +147,9 @@ interpretCtx = \ctx -> # If no scopes left, all execution complete. if List.isEmpty dropCtx.scopes then - Task.succeed dropCtx + Task.succeed (Done dropCtx) else - interpretCtx dropCtx + Task.succeed (Step dropCtx) InComment -> result <- Task.attempt (Context.getChar ctx) @@ -153,9 +157,9 @@ interpretCtx = \ctx -> Ok (T val newCtx) -> if val == 0x7D then # `}` end of comment - interpretCtx { newCtx & state: Executing } + Task.succeed (Step { newCtx & state: Executing }) else - interpretCtx { newCtx & state: InComment } + Task.succeed (Step { newCtx & state: InComment }) Err NoScope -> Task.fail NoScope @@ -174,13 +178,13 @@ interpretCtx = \ctx -> # so this is make i64 mul by 10 then convert back to i32. nextAccum = (10 * Num.intCast accum) + Num.intCast (val - 0x30) - interpretCtx { newCtx & state: InNumber (Num.intCast nextAccum) } + Task.succeed (Step { newCtx & state: InNumber (Num.intCast nextAccum) }) else # outside of number now, this needs to be executed. pushCtx = Context.pushStack newCtx (Number accum) execCtx <- Task.await (stepExecCtx { pushCtx & state: Executing } val) - interpretCtx execCtx + Task.succeed (Step execCtx) Err NoScope -> Task.fail NoScope @@ -197,12 +201,12 @@ interpretCtx = \ctx -> when Str.fromUtf8 bytes is Ok str -> { } <- Task.await (Stdout.raw str) - interpretCtx { newCtx & state: Executing } + Task.succeed (Step { newCtx & state: Executing }) Err _ -> Task.fail BadUtf8 else - interpretCtx { newCtx & state: InString (List.append bytes val) } + Task.succeed (Step { newCtx & state: InString (List.append bytes val) }) Err NoScope -> Task.fail NoScope @@ -216,17 +220,17 @@ interpretCtx = \ctx -> Ok (T val newCtx) -> if val == 0x5B then # start of a nested lambda `[` - interpretCtx { newCtx & state: InLambda (depth + 1) (List.append bytes val) } + Task.succeed (Step { newCtx & state: InLambda (depth + 1) (List.append bytes val) }) else if val == 0x5D then # `]` end of current lambda if depth == 0 then # end of all lambdas - interpretCtx (Context.pushStack { newCtx & state: Executing } (Lambda bytes)) + Task.succeed (Step (Context.pushStack { newCtx & state: Executing } (Lambda bytes))) else # end of nested lambda - interpretCtx { newCtx & state: InLambda (depth - 1) (List.append bytes val) } + Task.succeed (Step { newCtx & state: InLambda (depth - 1) (List.append bytes val) }) else - interpretCtx { newCtx & state: InLambda depth (List.append bytes val) } + Task.succeed (Step { newCtx & state: InLambda depth (List.append bytes val) }) Err NoScope -> Task.fail NoScope @@ -252,14 +256,14 @@ interpretCtx = \ctx -> when result2 is Ok a -> - interpretCtx a + Task.succeed (Step a) Err e -> Task.fail e Ok (T 0x9F newCtx) -> # This is supposed to flush io buffers. We don't buffer, so it does nothing - interpretCtx newCtx + Task.succeed (Step newCtx) Ok (T x _) -> data = Num.toStr (Num.intCast x) @@ -276,7 +280,7 @@ interpretCtx = \ctx -> result <- Task.attempt (Context.getChar { ctx & state: Executing }) when result is Ok (T x newCtx) -> - interpretCtx (Context.pushStack newCtx (Number (Num.intCast x))) + Task.succeed (Step (Context.pushStack newCtx (Number (Num.intCast x)))) Err NoScope -> Task.fail NoScope diff --git a/examples/false-interpreter/platform/Task.roc b/examples/false-interpreter/platform/Task.roc index 520eba4976..0ad7c223b2 100644 --- a/examples/false-interpreter/platform/Task.roc +++ b/examples/false-interpreter/platform/Task.roc @@ -1,9 +1,27 @@ interface Task - exposes [ Task, succeed, fail, await, map, onFail, attempt, fromResult ] + exposes [ Task, succeed, fail, await, map, onFail, attempt, fromResult, loop ] imports [ fx.Effect ] Task ok err : Effect.Effect (Result ok err) +loop : state, (state -> Task [ Step state, Done done ] err) -> Task done err +loop = \state, step -> + looper = \current -> + step current + |> Effect.map + \res -> + when res is + Ok (Step newState) -> + Step newState + + Ok (Done result) -> + Done (Ok result) + + Err e -> + Done (Err e) + + Effect.loop state looper + succeed : val -> Task val * succeed = \val -> Effect.always (Ok val) From b3d605cade2eac8391fa1d629de69e7b46881f68 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Jan 2022 20:22:18 +0100 Subject: [PATCH 332/541] fix failing debug_assert --- compiler/can/src/module.rs | 6 +++++- examples/benchmarks/platform/Package-Config.roc | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 2cdd6e8e4e..e164fb8680 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -124,7 +124,11 @@ where // This is a type alias // the symbol should already be added to the scope when this module is canonicalized - debug_assert!(scope.contains_alias(symbol), "{:?}", symbol); + debug_assert!( + scope.contains_alias(symbol), + "apparently, {:?} is not actually a type alias", + symbol + ); // but now we know this symbol by a different identifier, so we still need to add it to // the scope diff --git a/examples/benchmarks/platform/Package-Config.roc b/examples/benchmarks/platform/Package-Config.roc index d6f3a4452e..e2fa48be9b 100644 --- a/examples/benchmarks/platform/Package-Config.roc +++ b/examples/benchmarks/platform/Package-Config.roc @@ -1,5 +1,5 @@ platform "folkertdev/foo" - requires { Model, Msg } { main : Effect {} } + requires { } { main : Effect {} } exposes [] packages {} imports [ Task.{ Task } ] From c27f2a25926d71d4cb4d41952874f8709e2c3a2f Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 26 Jan 2022 19:22:39 +0000 Subject: [PATCH 333/541] repl: delete redundant casts --- cli/src/repl/eval.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 569a4ff551..f6552a9900 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -397,7 +397,7 @@ fn jit_to_ast_help<'a, M>( lib, main_fn_name, result_stack_size as usize, - |bytes: *const u8| { ptr_to_ast(bytes as *const u8) } + |bytes: *const u8| { ptr_to_ast(bytes) } ) } Layout::Union(UnionLayout::NonRecursive(_)) => { @@ -789,11 +789,11 @@ fn single_tag_union_to_ast<'a, M>( let output = if field_layouts.len() == payload_vars.len() { let it = payload_vars.iter().copied().zip(field_layouts); - sequence_of_expr(env, ptr as *const u8, it, WhenRecursive::Unreachable).into_bump_slice() + sequence_of_expr(env, ptr, it, WhenRecursive::Unreachable).into_bump_slice() } else if field_layouts.is_empty() && !payload_vars.is_empty() { // happens for e.g. `Foo Bar` where unit structures are nested and the inner one is dropped let it = payload_vars.iter().copied().zip([&Layout::Struct(&[])]); - sequence_of_expr(env, ptr as *const u8, it, WhenRecursive::Unreachable).into_bump_slice() + sequence_of_expr(env, ptr, it, WhenRecursive::Unreachable).into_bump_slice() } else { unreachable!() }; @@ -816,7 +816,7 @@ where let mut output = Vec::with_capacity_in(sequence.len(), arena); // We'll advance this as we iterate through the fields - let mut field_ptr = ptr as *const u8; + let mut field_ptr = ptr; for (var, layout) in sequence { let content = subs.get_content_without_compacting(var); From 27d960f7200bc21f0ee6eb61918cb7b7b74ca310 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Jan 2022 20:25:31 +0100 Subject: [PATCH 334/541] formatting --- examples/benchmarks/platform/Package-Config.roc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/benchmarks/platform/Package-Config.roc b/examples/benchmarks/platform/Package-Config.roc index e2fa48be9b..619534f070 100644 --- a/examples/benchmarks/platform/Package-Config.roc +++ b/examples/benchmarks/platform/Package-Config.roc @@ -1,5 +1,5 @@ platform "folkertdev/foo" - requires { } { main : Effect {} } + requires {} { main : Effect {} } exposes [] packages {} imports [ Task.{ Task } ] From afd11e1cb15db226ce1204b1018fcd27786f8ca0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Jan 2022 23:33:02 +0100 Subject: [PATCH 335/541] move target -> roc_target --- Cargo.toml | 2 +- ast/Cargo.toml | 2 +- cli/Cargo.toml | 2 +- compiler/build/Cargo.toml | 2 +- compiler/builtins/Cargo.toml | 2 +- compiler/gen_dev/Cargo.toml | 2 +- compiler/gen_llvm/Cargo.toml | 2 +- compiler/gen_wasm/Cargo.toml | 2 +- compiler/load/Cargo.toml | 2 +- compiler/mono/Cargo.toml | 2 +- compiler/{target => roc_target}/Cargo.toml | 0 compiler/{target => roc_target}/src/lib.rs | 0 compiler/solve/Cargo.toml | 2 +- compiler/test_gen/Cargo.toml | 2 +- compiler/test_mono/Cargo.toml | 2 +- docs/Cargo.toml | 2 +- reporting/Cargo.toml | 2 +- 17 files changed, 15 insertions(+), 15 deletions(-) rename compiler/{target => roc_target}/Cargo.toml (100%) rename compiler/{target => roc_target}/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 28a4f6193e..93974ca757 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ members = [ "compiler/build", "compiler/arena_pool", "compiler/test_gen", - "compiler/target", + "compiler/roc_target", "vendor/ena", "vendor/inkwell", "vendor/pathfinding", diff --git a/ast/Cargo.toml b/ast/Cargo.toml index f0edcc1fab..f0b82d7ca4 100644 --- a/ast/Cargo.toml +++ b/ast/Cargo.toml @@ -17,7 +17,7 @@ roc_problem = { path = "../compiler/problem" } roc_types = { path = "../compiler/types" } roc_unify = { path = "../compiler/unify"} roc_load = { path = "../compiler/load" } -roc_target = { path = "../compiler/target" } +roc_target = { path = "../compiler/roc_target" } roc_error_macros = { path = "../error_macros" } arrayvec = "0.7.2" bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index f889f4b16b..d680a24888 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -61,7 +61,7 @@ roc_load = { path = "../compiler/load" } roc_gen_llvm = { path = "../compiler/gen_llvm", optional = true } roc_build = { path = "../compiler/build", default-features = false } roc_fmt = { path = "../compiler/fmt" } -roc_target = { path = "../compiler/target" } +roc_target = { path = "../compiler/roc_target" } roc_reporting = { path = "../reporting" } roc_error_macros = { path = "../error_macros" } roc_editor = { path = "../editor", optional = true } diff --git a/compiler/build/Cargo.toml b/compiler/build/Cargo.toml index 4ef876bbf6..ab61cf2637 100644 --- a/compiler/build/Cargo.toml +++ b/compiler/build/Cargo.toml @@ -19,7 +19,7 @@ roc_unify = { path = "../unify" } roc_solve = { path = "../solve" } roc_mono = { path = "../mono" } roc_load = { path = "../load" } -roc_target = { path = "../target" } +roc_target = { path = "../roc_target" } roc_gen_llvm = { path = "../gen_llvm", optional = true } roc_gen_wasm = { path = "../gen_wasm", optional = true } roc_gen_dev = { path = "../gen_dev", default-features = false } diff --git a/compiler/builtins/Cargo.toml b/compiler/builtins/Cargo.toml index 4b73584449..91879456d7 100644 --- a/compiler/builtins/Cargo.toml +++ b/compiler/builtins/Cargo.toml @@ -10,4 +10,4 @@ roc_collections = { path = "../collections" } roc_region = { path = "../region" } roc_module = { path = "../module" } roc_types = { path = "../types" } -roc_target = { path = "../target" } +roc_target = { path = "../roc_target" } diff --git a/compiler/gen_dev/Cargo.toml b/compiler/gen_dev/Cargo.toml index d454e3ffbb..ca81c78e67 100644 --- a/compiler/gen_dev/Cargo.toml +++ b/compiler/gen_dev/Cargo.toml @@ -16,7 +16,7 @@ roc_builtins = { path = "../builtins" } roc_unify = { path = "../unify" } roc_solve = { path = "../solve" } roc_mono = { path = "../mono" } -roc_target = { path = "../target" } +roc_target = { path = "../roc_target" } roc_error_macros = { path = "../../error_macros" } bumpalo = { version = "3.8.0", features = ["collections"] } target-lexicon = "0.12.2" diff --git a/compiler/gen_llvm/Cargo.toml b/compiler/gen_llvm/Cargo.toml index 591f4ff92c..e87ed1c434 100644 --- a/compiler/gen_llvm/Cargo.toml +++ b/compiler/gen_llvm/Cargo.toml @@ -12,7 +12,7 @@ roc_module = { path = "../module" } roc_builtins = { path = "../builtins" } roc_error_macros = { path = "../../error_macros" } roc_mono = { path = "../mono" } -roc_target = { path = "../target" } +roc_target = { path = "../roc_target" } roc_std = { path = "../../roc_std" } morphic_lib = { path = "../../vendor/morphic_lib" } bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/compiler/gen_wasm/Cargo.toml b/compiler/gen_wasm/Cargo.toml index a40dd13703..b2223ee48a 100644 --- a/compiler/gen_wasm/Cargo.toml +++ b/compiler/gen_wasm/Cargo.toml @@ -11,6 +11,6 @@ roc_builtins = { path = "../builtins" } roc_collections = { path = "../collections" } roc_module = { path = "../module" } roc_mono = { path = "../mono" } -roc_target = { path = "../target" } +roc_target = { path = "../roc_target" } roc_std = { path = "../../roc_std" } roc_error_macros = { path = "../../error_macros" } diff --git a/compiler/load/Cargo.toml b/compiler/load/Cargo.toml index 27da9138af..329a3f5386 100644 --- a/compiler/load/Cargo.toml +++ b/compiler/load/Cargo.toml @@ -18,7 +18,7 @@ roc_unify = { path = "../unify" } roc_parse = { path = "../parse" } roc_solve = { path = "../solve" } roc_mono = { path = "../mono" } -roc_target = { path = "../target" } +roc_target = { path = "../roc_target" } roc_reporting = { path = "../../reporting" } morphic_lib = { path = "../../vendor/morphic_lib" } ven_pretty = { path = "../../vendor/pretty" } diff --git a/compiler/mono/Cargo.toml b/compiler/mono/Cargo.toml index 88b33a8d82..969bd4e256 100644 --- a/compiler/mono/Cargo.toml +++ b/compiler/mono/Cargo.toml @@ -16,7 +16,7 @@ roc_solve = { path = "../solve" } roc_std = { path = "../../roc_std" } roc_problem = { path = "../problem" } roc_builtins = { path = "../builtins" } -roc_target = { path = "../target" } +roc_target = { path = "../roc_target" } ven_pretty = { path = "../../vendor/pretty" } morphic_lib = { path = "../../vendor/morphic_lib" } bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/compiler/target/Cargo.toml b/compiler/roc_target/Cargo.toml similarity index 100% rename from compiler/target/Cargo.toml rename to compiler/roc_target/Cargo.toml diff --git a/compiler/target/src/lib.rs b/compiler/roc_target/src/lib.rs similarity index 100% rename from compiler/target/src/lib.rs rename to compiler/roc_target/src/lib.rs diff --git a/compiler/solve/Cargo.toml b/compiler/solve/Cargo.toml index 7b776821a3..ebb9e4a615 100644 --- a/compiler/solve/Cargo.toml +++ b/compiler/solve/Cargo.toml @@ -21,7 +21,7 @@ roc_builtins = { path = "../builtins" } roc_problem = { path = "../problem" } roc_parse = { path = "../parse" } roc_solve = { path = "../solve" } -roc_target = { path = "../target" } +roc_target = { path = "../roc_target" } pretty_assertions = "1.0.0" indoc = "1.0.3" tempfile = "3.2.0" diff --git a/compiler/test_gen/Cargo.toml b/compiler/test_gen/Cargo.toml index 07fd0c91e7..607b14b7ff 100644 --- a/compiler/test_gen/Cargo.toml +++ b/compiler/test_gen/Cargo.toml @@ -31,7 +31,7 @@ roc_load = { path = "../load" } roc_can = { path = "../can" } roc_parse = { path = "../parse" } roc_build = { path = "../build" } -roc_target = { path = "../target" } +roc_target = { path = "../roc_target" } roc_std = { path = "../../roc_std" } bumpalo = { version = "3.8.0", features = ["collections"] } either = "1.6.1" diff --git a/compiler/test_mono/Cargo.toml b/compiler/test_mono/Cargo.toml index a2225349cb..2f56668cb1 100644 --- a/compiler/test_mono/Cargo.toml +++ b/compiler/test_mono/Cargo.toml @@ -16,7 +16,7 @@ roc_builtins = { path = "../builtins" } roc_load = { path = "../load" } roc_can = { path = "../can" } roc_mono = { path = "../mono" } -roc_target = { path = "../target" } +roc_target = { path = "../roc_target" } test_mono_macros = { path = "../test_mono_macros" } pretty_assertions = "1.0.0" bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/docs/Cargo.toml b/docs/Cargo.toml index 77b1bf7972..0f92636b9a 100644 --- a/docs/Cargo.toml +++ b/docs/Cargo.toml @@ -18,7 +18,7 @@ roc_module = { path = "../compiler/module" } roc_region = { path = "../compiler/region" } roc_types = { path = "../compiler/types" } roc_parse = { path = "../compiler/parse" } -roc_target = { path = "../compiler/target" } +roc_target = { path = "../compiler/roc_target" } roc_collections = { path = "../compiler/collections" } bumpalo = { version = "3.8.0", features = ["collections"] } snafu = { version = "0.6.10", features = ["backtraces"] } diff --git a/reporting/Cargo.toml b/reporting/Cargo.toml index be96603493..2add55b827 100644 --- a/reporting/Cargo.toml +++ b/reporting/Cargo.toml @@ -24,7 +24,7 @@ roc_constrain = { path = "../compiler/constrain" } roc_builtins = { path = "../compiler/builtins" } roc_problem = { path = "../compiler/problem" } roc_parse = { path = "../compiler/parse" } -roc_target = { path = "../compiler/target" } +roc_target = { path = "../compiler/roc_target" } roc_test_utils = { path = "../test_utils" } pretty_assertions = "1.0.0" indoc = "1.0.3" From 0f30704839d4c1b3215f5adf90bb167bc449730d Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Jan 2022 23:34:17 +0100 Subject: [PATCH 336/541] revert gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d5960bf0cb..87635559c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -target/ +target generated-docs zig-cache .direnv From 0079f8f45ca5b99894465b791f48d3b987cff521 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Sat, 22 Jan 2022 00:41:39 -0700 Subject: [PATCH 337/541] Rename name example app to "form" --- examples/cli/.gitignore | 1 + examples/cli/{Echo.roc => form.roc} | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) rename examples/cli/{Echo.roc => form.roc} (97%) diff --git a/examples/cli/.gitignore b/examples/cli/.gitignore index fa11a6a9c5..5773a9c4eb 100644 --- a/examples/cli/.gitignore +++ b/examples/cli/.gitignore @@ -1 +1,2 @@ echo +form diff --git a/examples/cli/Echo.roc b/examples/cli/form.roc similarity index 97% rename from examples/cli/Echo.roc rename to examples/cli/form.roc index fcb9cf5acf..d3dda31965 100644 --- a/examples/cli/Echo.roc +++ b/examples/cli/form.roc @@ -1,4 +1,4 @@ -app "echo" +app "form" packages { pf: "platform" } imports [ pf.Task.{ Task, await }, pf.Stdout, pf.Stdin ] provides [ main ] to pf From 97149d453070352b3cb267154d8a76c6e7a6293f Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Sat, 22 Jan 2022 01:00:51 -0700 Subject: [PATCH 338/541] Add countdown example --- examples/cli/.gitignore | 1 + examples/cli/countdown.roc | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 examples/cli/countdown.roc diff --git a/examples/cli/.gitignore b/examples/cli/.gitignore index 5773a9c4eb..a87a68d9e2 100644 --- a/examples/cli/.gitignore +++ b/examples/cli/.gitignore @@ -1,2 +1,3 @@ +countdown echo form diff --git a/examples/cli/countdown.roc b/examples/cli/countdown.roc new file mode 100644 index 0000000000..79f9decb7c --- /dev/null +++ b/examples/cli/countdown.roc @@ -0,0 +1,18 @@ +app "countdown" + packages { pf: "platform" } + imports [ pf.Stdin, pf.Stdout, pf.Task.{ await, loop, succeed } ] + provides [ main ] to pf + +main = + _ <- await (Stdout.line "\nLet's count down from 10 together - all you have to do is press .") + _ <- await Stdin.line + loop 10 tick + +tick = \n -> + if n == 0 then + _ <- await (Stdout.line "🎉 SURPRISE! Happy Birthday! 🎂") + succeed (Done {}) + else + _ <- await (n |> Num.toStr |> \s -> "\(s)..." |> Stdout.line) + _ <- await Stdin.line + succeed (Step (n - 1)) From 3866723e3662bb55a4a112055ec0733326eb5357 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Sat, 22 Jan 2022 01:08:41 -0700 Subject: [PATCH 339/541] Add echo example --- examples/cli/echo.roc | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 examples/cli/echo.roc diff --git a/examples/cli/echo.roc b/examples/cli/echo.roc new file mode 100644 index 0000000000..f50456060f --- /dev/null +++ b/examples/cli/echo.roc @@ -0,0 +1,13 @@ +app "echo" + packages { pf: "platform" } + imports [ pf.Stdin, pf.Stdout, pf.Task ] + provides [ main ] to pf + +main : Task.Task {} * +main = + _ <- Task.await (Stdout.line "Shout into this cave and hear the echo!") + Task.forever tick + +tick = + shout <- Task.await Stdin.line + Stdout.line shout From accee83a3fd0cde50cb73532e9557c887a392d5b Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 22 Jan 2022 12:12:25 +0100 Subject: [PATCH 340/541] cli platform: use generic version of mainForHost --- examples/cli/platform/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cli/platform/src/lib.rs b/examples/cli/platform/src/lib.rs index e072cf5d21..d584592716 100644 --- a/examples/cli/platform/src/lib.rs +++ b/examples/cli/platform/src/lib.rs @@ -9,7 +9,7 @@ use std::ffi::CStr; use std::os::raw::c_char; extern "C" { - #[link_name = "roc__mainForHost_1_exposed"] + #[link_name = "roc__mainForHost_1_exposed_generic"] fn roc_main(output: *mut u8) -> (); #[link_name = "roc__mainForHost_size"] From de84698890750cacdd2231f261962dec94175d57 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 22 Jan 2022 15:05:56 +0100 Subject: [PATCH 341/541] use generic mainForHost in false platform --- examples/false-interpreter/platform/src/lib.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/examples/false-interpreter/platform/src/lib.rs b/examples/false-interpreter/platform/src/lib.rs index 18584164d6..4f97dfb72d 100644 --- a/examples/false-interpreter/platform/src/lib.rs +++ b/examples/false-interpreter/platform/src/lib.rs @@ -12,7 +12,7 @@ use std::io::{BufRead, BufReader, Read, Write}; use std::os::raw::c_char; extern "C" { - #[link_name = "roc__mainForHost_1_exposed"] + #[link_name = "roc__mainForHost_1_exposed_generic"] fn roc_main(args: RocStr, output: *mut u8) -> (); #[link_name = "roc__mainForHost_size"] @@ -188,10 +188,16 @@ pub extern "C" fn roc_fx_closeFile(br_ptr: *mut BufReader) { #[no_mangle] pub extern "C" fn roc_fx_openFile(name: ManuallyDrop) -> *mut BufReader { - let f = File::open(name.as_str()).expect("Unable to open file"); - let br = BufReader::new(f); + match File::open(name.as_str()) { + Ok(f) => { + let br = BufReader::new(f); - Box::into_raw(Box::new(br)) + Box::into_raw(Box::new(br)) + } + Err(_) => { + panic!("unable to open file {:?}", name) + } + } } #[no_mangle] From 5b5b9e5eb844a2c344f2a51cb4bbcfbd1b8404a3 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 22 Jan 2022 15:06:49 +0100 Subject: [PATCH 342/541] fix cli cli_run test (echo file got moved) --- cli/tests/cli_run.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index bd7de64fed..bb07877da1 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -376,8 +376,8 @@ mod cli_run { // use_valgrind: true, // }, cli:"cli" => Example { - filename: "Echo.roc", - executable_filename: "echo", + filename: "form.roc", + executable_filename: "form", stdin: &["Giovanni\n", "Giorgio\n"], input_file: None, expected_ending: "Hi, Giovanni Giorgio!\n", From 7e28d557ecc640592e40e82f6ec7e7547650b77c Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 22 Jan 2022 15:08:02 +0100 Subject: [PATCH 343/541] improve how we generate the mainForHost function --- compiler/gen_llvm/src/llvm/build.rs | 266 ++++++++++++++-------------- 1 file changed, 134 insertions(+), 132 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index d1a08a4bd8..354437298d 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -3554,6 +3554,93 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( c_function } +fn expose_function_to_host_help_c_abi_xx<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + roc_function: FunctionValue<'ctx>, + arguments: &[Layout<'a>], + return_layout: Layout<'a>, + c_function_name: &str, +) -> FunctionValue<'ctx> { + let it = arguments.iter().map(|l| basic_type_from_layout(env, l)); + let argument_types = Vec::from_iter_in(it, env.arena); + let return_type = basic_type_from_layout(env, &return_layout); + + let cc_return = to_cc_return(env, &return_layout); + let roc_return = RocReturn::from_layout(env, &return_layout); + + let c_function_type = cc_return.to_signature(env, return_type, argument_types.as_slice()); + + let c_function = add_func( + env.module, + c_function_name, + c_function_type, + Linkage::External, + C_CALL_CONV, + ); + + let subprogram = env.new_subprogram(c_function_name); + c_function.set_subprogram(subprogram); + + // STEP 2: build the exposed function's body + let builder = env.builder; + let context = env.context; + + let entry = context.append_basic_block(c_function, "entry"); + builder.position_at_end(entry); + + let params = c_function.get_params(); + + let param_types = Vec::from_iter_in(roc_function.get_type().get_param_types(), env.arena); + + // drop the "return pointer" if it exists on the roc function + // and the c function does not return via pointer + let param_types = match (&roc_return, &cc_return) { + (RocReturn::ByPointer, CCReturn::Return) => ¶m_types[1..], + _ => ¶m_types, + }; + + debug_assert_eq!(params.len(), param_types.len()); + + let it = params.iter().zip(param_types).map(|(arg, fastcc_type)| { + let arg_type = arg.get_type(); + if arg_type == *fastcc_type { + // the C and Fast calling conventions agree + *arg + } else { + complex_bitcast_check_size(env, *arg, *fastcc_type, "to_fastcc_type") + } + }); + + let arguments = Vec::from_iter_in(it, env.arena); + + let value = call_roc_function(env, roc_function, &return_layout, arguments.as_slice()); + + match cc_return { + CCReturn::Return => match roc_return { + RocReturn::Return => { + env.builder.build_return(Some(&value)); + } + RocReturn::ByPointer => { + let loaded = env + .builder + .build_load(value.into_pointer_value(), "load_result"); + env.builder.build_return(Some(&loaded)); + } + }, + CCReturn::ByPointer => { + let out_ptr = c_function.get_nth_param(0).unwrap().into_pointer_value(); + + env.builder.build_store(out_ptr, value); + env.builder.build_return(None); + } + CCReturn::Void => { + env.builder.build_return(None); + } + } + + c_function +} + fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, ident_string: &str, @@ -3582,122 +3669,14 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( &format!("{}_generic", c_function_name), ); - let wrapper_return_type = if env.is_gen_test { - roc_result_type(env, roc_function.get_type().get_return_type().unwrap()).into() - } else { - // roc_function.get_type().get_return_type().unwrap() - basic_type_from_layout(env, &return_layout) - }; - - let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena); - for layout in arguments { - cc_argument_types.push(to_cc_type(env, layout)); - } - - // STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}` if the C abi demands it - let mut argument_types = cc_argument_types; - let return_type = wrapper_return_type; - - let cc_return = to_cc_return(env, &return_layout); - - let c_function_type = match cc_return { - CCReturn::Void => env - .context - .void_type() - .fn_type(&function_arguments(env, &argument_types), false), - CCReturn::Return => return_type.fn_type(&function_arguments(env, &argument_types), false), - CCReturn::ByPointer => { - let output_type = return_type.ptr_type(AddressSpace::Generic); - argument_types.push(output_type.into()); - env.context - .void_type() - .fn_type(&function_arguments(env, &argument_types), false) - } - }; - - let c_function = add_func( - env.module, + let c_function = expose_function_to_host_help_c_abi_xx( + env, + roc_function, + arguments, + return_layout, c_function_name, - c_function_type, - Linkage::External, - C_CALL_CONV, ); - let subprogram = env.new_subprogram(c_function_name); - c_function.set_subprogram(subprogram); - - // STEP 2: build the exposed function's body - let builder = env.builder; - let context = env.context; - - let entry = context.append_basic_block(c_function, "entry"); - - builder.position_at_end(entry); - - debug_info_init!(env, c_function); - - // drop the final argument, which is the pointer we write the result into - let args_vector = c_function.get_params(); - let mut args = args_vector.as_slice(); - let args_length = args.len(); - - match cc_return { - CCReturn::Return => { - debug_assert_eq!(args.len(), roc_function.get_params().len()); - } - CCReturn::Void => { - debug_assert_eq!(args.len(), roc_function.get_params().len()); - } - CCReturn::ByPointer => match RocReturn::from_layout(env, &return_layout) { - RocReturn::ByPointer => { - debug_assert_eq!(args.len(), roc_function.get_params().len()); - } - RocReturn::Return => { - args = &args[..args.len() - 1]; - debug_assert_eq!(args.len(), roc_function.get_params().len()); - } - }, - } - - let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena); - - let it = args.iter().zip(roc_function.get_type().get_param_types()); - for (arg, fastcc_type) in it { - let arg_type = arg.get_type(); - if arg_type == fastcc_type { - // the C and Fast calling conventions agree - arguments_for_call.push(*arg); - } else { - let cast = complex_bitcast_check_size(env, *arg, fastcc_type, "to_fastcc_type"); - arguments_for_call.push(cast); - } - } - - let arguments_for_call = arguments_for_call.into_bump_slice(); - - let call_result = call_roc_function(env, roc_function, &return_layout, arguments_for_call); - - match cc_return { - CCReturn::Void => { - // TODO return empty struct here? - builder.build_return(None); - } - CCReturn::Return => { - builder.build_return(Some(&call_result)); - } - CCReturn::ByPointer => { - let output_arg_index = args_length - 1; - - let output_arg = c_function - .get_nth_param(output_arg_index as u32) - .unwrap() - .into_pointer_value(); - - builder.build_store(output_arg, call_result); - builder.build_return(None); - } - } - // STEP 3: build a {} -> u64 function that gives the size of the return type let size_function_type = env.context.i64_type().fn_type(&[], false); let size_function_name: String = format!("roc__{}_size", ident_string); @@ -3713,14 +3692,21 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( let subprogram = env.new_subprogram(&size_function_name); size_function.set_subprogram(subprogram); - let entry = context.append_basic_block(size_function, "entry"); + let entry = env.context.append_basic_block(size_function, "entry"); - builder.position_at_end(entry); + env.builder.position_at_end(entry); debug_info_init!(env, size_function); + let return_type = if env.is_gen_test { + roc_result_type(env, roc_function.get_type().get_return_type().unwrap()).into() + } else { + // roc_function.get_type().get_return_type().unwrap() + basic_type_from_layout(env, &return_layout) + }; + let size: BasicValueEnum = return_type.size_of().unwrap().into(); - builder.build_return(Some(&size)); + env.builder.build_return(Some(&size)); c_function } @@ -6181,6 +6167,7 @@ impl RocReturn { } } +#[derive(Debug)] enum CCReturn { /// Return as normal Return, @@ -6192,6 +6179,36 @@ enum CCReturn { Void, } +impl CCReturn { + fn to_signature<'a, 'ctx, 'env>( + &self, + env: &Env<'a, 'ctx, 'env>, + return_type: BasicTypeEnum<'ctx>, + argument_types: &[BasicTypeEnum<'ctx>], + ) -> FunctionType<'ctx> { + match self { + CCReturn::ByPointer => { + // turn the output type into a pointer type. Make it the first argument to the function + let output_type = return_type.ptr_type(AddressSpace::Generic); + let mut arguments: Vec<'_, BasicTypeEnum> = + bumpalo::vec![in env.arena; output_type.into()]; + arguments.extend(argument_types); + + let arguments = function_arguments(env, &arguments); + env.context.void_type().fn_type(&arguments, false) + } + CCReturn::Return => { + let arguments = function_arguments(env, argument_types); + return_type.fn_type(&arguments, false) + } + CCReturn::Void => { + let arguments = function_arguments(env, argument_types); + env.context.void_type().fn_type(&arguments, false) + } + } + } +} + /// According to the C ABI, how should we return a value with the given layout? fn to_cc_return<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> CCReturn { let return_size = layout.stack_size(env.ptr_bytes); @@ -6268,22 +6285,7 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( arguments.push(value); } - let cc_type = match cc_return { - CCReturn::Void => env - .context - .void_type() - .fn_type(&function_arguments(env, &cc_argument_types), false), - CCReturn::ByPointer => { - cc_argument_types.insert(0, return_type.ptr_type(AddressSpace::Generic).into()); - env.context - .void_type() - .fn_type(&function_arguments(env, &cc_argument_types), false) - } - CCReturn::Return => { - return_type.fn_type(&function_arguments(env, &cc_argument_types), false) - } - }; - + let cc_type = cc_return.to_signature(env, return_type, cc_argument_types.as_slice()); let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type); let fastcc_type = match roc_return { From d3b51ea8ea3b5ceba48cc3ab2f25452de252ca8f Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 22 Jan 2022 15:09:28 +0100 Subject: [PATCH 344/541] make echo example work (with loop, not yet with forever) --- examples/cli/echo.roc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/cli/echo.roc b/examples/cli/echo.roc index f50456060f..d49e49a462 100644 --- a/examples/cli/echo.roc +++ b/examples/cli/echo.roc @@ -3,11 +3,13 @@ app "echo" imports [ pf.Stdin, pf.Stdout, pf.Task ] provides [ main ] to pf -main : Task.Task {} * +main : Task.Task {} [] main = _ <- Task.await (Stdout.line "Shout into this cave and hear the echo!") - Task.forever tick + Task.loop {} (\{} -> Task.map tick Step) + # Task.forever tick # still does not work; loops for a while, then stack overflows for me +tick : Task.Task {} [] tick = shout <- Task.await Stdin.line Stdout.line shout From ea10c1f665df936eb4a8e09608b27f744ab53c0f Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 22 Jan 2022 16:11:31 +0100 Subject: [PATCH 345/541] formatting --- examples/cli/echo.roc | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/cli/echo.roc b/examples/cli/echo.roc index d49e49a462..a7d58cc847 100644 --- a/examples/cli/echo.roc +++ b/examples/cli/echo.roc @@ -7,7 +7,6 @@ main : Task.Task {} [] main = _ <- Task.await (Stdout.line "Shout into this cave and hear the echo!") Task.loop {} (\{} -> Task.map tick Step) - # Task.forever tick # still does not work; loops for a while, then stack overflows for me tick : Task.Task {} [] tick = From 1ccf7f6b344c62ef14da0221834b79d688517fde Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 23 Jan 2022 01:21:25 +0100 Subject: [PATCH 346/541] more formatting --- examples/cli/echo.roc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/cli/echo.roc b/examples/cli/echo.roc index a7d58cc847..e0d0e4fdd2 100644 --- a/examples/cli/echo.roc +++ b/examples/cli/echo.roc @@ -4,9 +4,9 @@ app "echo" provides [ main ] to pf main : Task.Task {} [] -main = +main = _ <- Task.await (Stdout.line "Shout into this cave and hear the echo!") - Task.loop {} (\{} -> Task.map tick Step) + Task.loop {} (\{ } -> Task.map tick Step) tick : Task.Task {} [] tick = From 8e0c2408c7330b460bf110bc4b3eb4df47cd465c Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Sun, 23 Jan 2022 00:28:19 -0700 Subject: [PATCH 347/541] Make echo example more fun --- examples/cli/echo.roc | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/examples/cli/echo.roc b/examples/cli/echo.roc index e0d0e4fdd2..8418521540 100644 --- a/examples/cli/echo.roc +++ b/examples/cli/echo.roc @@ -6,9 +6,24 @@ app "echo" main : Task.Task {} [] main = _ <- Task.await (Stdout.line "Shout into this cave and hear the echo!") - Task.loop {} (\{ } -> Task.map tick Step) + Task.loop {} (\_ -> Task.map tick Step) tick : Task.Task {} [] tick = shout <- Task.await Stdin.line - Stdout.line shout + Stdout.line (echo shout) + +echo : Str -> Str +echo = \shout -> + silence = \length -> + spaceInUtf8 = 32 + List.repeat length spaceInUtf8 + shout + |> Str.toUtf8 + |> List.mapWithIndex (\i, _ -> + length = (List.len (Str.toUtf8 shout) - i) + phrase = (List.split (Str.toUtf8 shout) length).before + List.concat (silence (if i == 0 then 2 * length else length)) phrase) + |> List.join + |> Str.fromUtf8 + |> Result.withDefault "" From e1f52057cc78cca28469f513b49245752de88cfb Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Sun, 23 Jan 2022 00:35:05 -0700 Subject: [PATCH 348/541] Reformat form example for simplicity --- examples/cli/form.roc | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/examples/cli/form.roc b/examples/cli/form.roc index d3dda31965..4cf731761a 100644 --- a/examples/cli/form.roc +++ b/examples/cli/form.roc @@ -1,16 +1,12 @@ app "form" packages { pf: "platform" } - imports [ pf.Task.{ Task, await }, pf.Stdout, pf.Stdin ] + imports [ pf.Stdin, pf.Stdout, pf.Task.{ await, Task } ] provides [ main ] to pf main : Task {} * main = - { } <- await (Stdout.line "What's your first name?") - + _ <- await (Stdout.line "What's your first name?") firstName <- await Stdin.line - - { } <- await (Stdout.line "What's your last name?") - + _ <- await (Stdout.line "What's your last name?") lastName <- await Stdin.line - Stdout.line "Hi, \(firstName) \(lastName)!" From 78be94291873a169a40078bafaddfbe96934f45e Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Wed, 26 Jan 2022 19:08:11 -0700 Subject: [PATCH 349/541] Add emojis to echo example --- examples/cli/echo.roc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cli/echo.roc b/examples/cli/echo.roc index 8418521540..9f3914c164 100644 --- a/examples/cli/echo.roc +++ b/examples/cli/echo.roc @@ -5,7 +5,7 @@ app "echo" main : Task.Task {} [] main = - _ <- Task.await (Stdout.line "Shout into this cave and hear the echo!") + _ <- Task.await (Stdout.line "🗣 Shout into this cave and hear the echo! 👂👂👂") Task.loop {} (\_ -> Task.map tick Step) tick : Task.Task {} [] From e36b94fc53d6f81ef2a8ff6bb0e65bcd370364a4 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Wed, 26 Jan 2022 19:08:26 -0700 Subject: [PATCH 350/541] Add emoji to form example --- cli/tests/cli_run.rs | 2 +- examples/cli/form.roc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index bb07877da1..98d8c0f147 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -380,7 +380,7 @@ mod cli_run { executable_filename: "form", stdin: &["Giovanni\n", "Giorgio\n"], input_file: None, - expected_ending: "Hi, Giovanni Giorgio!\n", + expected_ending: "Hi, Giovanni Giorgio! 👋\n", use_valgrind: true, }, tui:"tui" => Example { diff --git a/examples/cli/form.roc b/examples/cli/form.roc index 4cf731761a..735c752527 100644 --- a/examples/cli/form.roc +++ b/examples/cli/form.roc @@ -9,4 +9,4 @@ main = firstName <- await Stdin.line _ <- await (Stdout.line "What's your last name?") lastName <- await Stdin.line - Stdout.line "Hi, \(firstName) \(lastName)!" + Stdout.line "Hi, \(firstName) \(lastName)! 👋" From 4a08fead1a6f0f11fa850d1c35c8dc407127aa27 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 27 Jan 2022 09:40:18 +0000 Subject: [PATCH 351/541] repl: Change lots of pointers to addresses --- cli/src/repl/eval.rs | 162 ++++++++++++++++++------------------ cli/src/repl/from_memory.rs | 93 ++++++++++++++++----- compiler/mono/src/layout.rs | 8 ++ 3 files changed, 158 insertions(+), 105 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index f6552a9900..75ba8a23fb 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -1,9 +1,9 @@ +use crate::repl::from_memory::FromMemory; use bumpalo::collections::Vec; use bumpalo::Bump; use libloading::Library; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::MutMap; -use roc_gen_llvm::llvm::build::tag_pointer_tag_id_bits_and_mask; use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type}; use roc_module::called_via::CalledVia; use roc_module::ident::TagName; @@ -182,7 +182,7 @@ fn get_tags_vars_and_variant<'a, M>( fn expr_of_tag<'a, M>( env: &Env<'a, 'a, M>, - ptr_to_data: *const u8, + data_addr: usize, tag_name: &TagName, arg_layouts: &'a [Layout<'a>], arg_vars: &[Variable], @@ -195,7 +195,7 @@ fn expr_of_tag<'a, M>( // NOTE assumes the data bytes are the first bytes let it = arg_vars.iter().copied().zip(arg_layouts.iter()); - let output = sequence_of_expr(env, ptr_to_data, it, when_recursive); + let output = sequence_of_expr(env, data_addr, it, when_recursive); let output = output.into_bump_slice(); Expr::Apply(loc_tag_expr, output, CalledVia::Space) @@ -203,57 +203,56 @@ fn expr_of_tag<'a, M>( /// Gets the tag ID of a union variant, assuming that the tag ID is stored alongside (after) the /// tag data. The caller is expected to check that the tag ID is indeed stored this way. -fn tag_id_from_data(union_layout: UnionLayout, data_ptr: *const u8, ptr_bytes: u32) -> i64 { - let offset = union_layout.data_size_without_tag_id(ptr_bytes).unwrap(); +fn tag_id_from_data<'a, M>( + env: &Env<'a, 'a, M>, + union_layout: UnionLayout, + data_addr: usize, +) -> i64 { + let offset = union_layout + .data_size_without_tag_id(env.ptr_bytes) + .unwrap(); - unsafe { - match union_layout.tag_id_builtin() { - Builtin::Bool => *(data_ptr.add(offset as usize) as *const i8) as i64, - Builtin::Int(IntWidth::U8) => *(data_ptr.add(offset as usize) as *const i8) as i64, - Builtin::Int(IntWidth::U16) => *(data_ptr.add(offset as usize) as *const i16) as i64, - Builtin::Int(IntWidth::U64) => { - // used by non-recursive unions at the - // moment, remove if that is no longer the case - *(data_ptr.add(offset as usize) as *const i64) as i64 - } - _ => unreachable!("invalid tag id layout"), + let tag_id_addr = data_addr + offset as usize; + + match union_layout.tag_id_builtin() { + Builtin::Bool => u8::from_memory(&env.app_memory, tag_id_addr) as i64, + Builtin::Int(IntWidth::U8) => u8::from_memory(&env.app_memory, tag_id_addr) as i64, + Builtin::Int(IntWidth::U16) => u16::from_memory(&env.app_memory, tag_id_addr) as i64, + Builtin::Int(IntWidth::U64) => { + // used by non-recursive unions at the + // moment, remove if that is no longer the case + i64::from_memory(&env.app_memory, tag_id_addr) } + _ => unreachable!("invalid tag id layout"), } } -fn deref_ptr_of_ptr(ptr_of_ptr: *const u8, ptr_bytes: u32) -> *const u8 { - unsafe { - match ptr_bytes { - // Our LLVM codegen represents pointers as i32/i64s. - 4 => *(ptr_of_ptr as *const i32) as *const u8, - 8 => *(ptr_of_ptr as *const i64) as *const u8, - _ => unreachable!(), - } - } +fn deref_addr_of_addr<'a, M>(env: &Env<'a, 'a, M>, addr_of_addr: usize) -> usize { + usize::from_memory(&env.app_memory, addr_of_addr) } /// Gets the tag ID of a union variant from its recursive pointer (that is, the pointer to the /// pointer to the data of the union variant). Returns /// - the tag ID /// - the pointer to the data of the union variant, unmasked if the pointer held the tag ID -fn tag_id_from_recursive_ptr( +fn tag_id_from_recursive_ptr<'a, M>( + env: &Env<'a, 'a, M>, union_layout: UnionLayout, - rec_ptr: *const u8, - ptr_bytes: u32, -) -> (i64, *const u8) { - let tag_in_ptr = union_layout.stores_tag_id_in_pointer(ptr_bytes); + rec_addr: usize, +) -> (i64, usize) { + let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.ptr_bytes); if tag_in_ptr { - let masked_ptr_to_data = deref_ptr_of_ptr(rec_ptr, ptr_bytes) as i64; - let (tag_id_bits, tag_id_mask) = tag_pointer_tag_id_bits_and_mask(ptr_bytes); - let tag_id = masked_ptr_to_data & (tag_id_mask as i64); + let masked_data_addr = deref_addr_of_addr(env, rec_addr); + let (tag_id_bits, tag_id_mask) = UnionLayout::tag_id_pointer_bits_and_mask(env.ptr_bytes); + let tag_id = masked_data_addr & tag_id_mask; // Clear the tag ID data from the pointer - let ptr_to_data = ((masked_ptr_to_data >> tag_id_bits) << tag_id_bits) as *const u8; - (tag_id as i64, ptr_to_data) + let data_addr = (masked_data_addr >> tag_id_bits) << tag_id_bits; + (tag_id as i64, data_addr) } else { - let ptr_to_data = deref_ptr_of_ptr(rec_ptr, ptr_bytes); - let tag_id = tag_id_from_data(union_layout, ptr_to_data, ptr_bytes); - (tag_id, ptr_to_data) + let data_addr = deref_addr_of_addr(env, rec_addr); + let tag_id = tag_id_from_data(env, union_layout, data_addr); + (tag_id, data_addr) } } @@ -335,8 +334,8 @@ fn jit_to_ast_help<'a, M>( Layout::Builtin(Builtin::List(elem_layout)) => Ok(run_jit_function!( lib, main_fn_name, - (*const u8, usize), - |(ptr, len): (*const u8, usize)| { list_to_ast(env, ptr, len, elem_layout, content) } + (usize, usize), + |(addr, len): (usize, usize)| { list_to_ast(env, addr, len, elem_layout, content) } )), Layout::Builtin(other) => { todo!("add support for rendering builtin {:?} to the REPL", other) @@ -407,7 +406,7 @@ fn jit_to_ast_help<'a, M>( main_fn_name, size as usize, |ptr: *const u8| { - ptr_to_ast(env, ptr, layout, WhenRecursive::Unreachable, content) + addr_to_ast(env, ptr, layout, WhenRecursive::Unreachable, content) } )) } @@ -421,7 +420,7 @@ fn jit_to_ast_help<'a, M>( main_fn_name, size as usize, |ptr: *const u8| { - ptr_to_ast(env, ptr, layout, WhenRecursive::Loop(*layout), content) + addr_to_ast(env, ptr, layout, WhenRecursive::Loop(*layout), content) } )) } @@ -455,16 +454,16 @@ enum WhenRecursive<'a> { Loop(Layout<'a>), } -fn ptr_to_ast<'a, M>( +fn addr_to_ast<'a, M>( env: &Env<'a, 'a, M>, - ptr: *const u8, + addr: usize, layout: &Layout<'a>, when_recursive: WhenRecursive<'a>, content: &'a Content, ) -> Expr<'a> { macro_rules! helper { ($ty:ty) => {{ - let num = unsafe { *(ptr as *const $ty) }; + let num = <$ty>::from_memory(&env.app_memory, addr); num_to_ast(env, number_literal_to_ast(env.arena, num), content) }}; @@ -478,7 +477,7 @@ fn ptr_to_ast<'a, M>( (_, Layout::Builtin(Builtin::Bool)) => { // TODO: bits are not as expected here. // num is always false at the moment. - let num = unsafe { *(ptr as *const bool) }; + let num = bool::from_memory(&env.app_memory, addr); bool_to_ast(env, num, content) } @@ -508,33 +507,32 @@ fn ptr_to_ast<'a, M>( } } (_, Layout::Builtin(Builtin::List(elem_layout))) => { - // Turn the (ptr, len) wrapper struct into actual ptr and len values. - let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) }; - let ptr = unsafe { *(ptr as *const *const u8) }; + let elem_addr = usize::from_memory(&env.app_memory, addr); + let len = usize::from_memory(&env.app_memory, addr + env.ptr_bytes as usize); - list_to_ast(env, ptr, len, elem_layout, content) + list_to_ast(env, elem_addr, len, elem_layout, content) } (_, Layout::Builtin(Builtin::Str)) => { - let arena_str = unsafe { *(ptr as *const &'static str) }; + let arena_str = <&'a str>::from_memory(&env.app_memory, addr); str_to_ast(env.arena, arena_str) } (_, Layout::Struct(field_layouts)) => match content { Content::Structure(FlatType::Record(fields, _)) => { - struct_to_ast(env, ptr, field_layouts, *fields) + struct_to_ast(env, addr, field_layouts, *fields) } Content::Structure(FlatType::TagUnion(tags, _)) => { debug_assert_eq!(tags.len(), 1); let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); - single_tag_union_to_ast(env, ptr, field_layouts, tag_name, payload_vars) + single_tag_union_to_ast(env, addr, field_layouts, tag_name, payload_vars) } Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => { let tag_name = &env.subs[*tag_name]; - single_tag_union_to_ast(env, ptr, field_layouts, tag_name, &[]) + single_tag_union_to_ast(env, addr, field_layouts, tag_name, &[]) } Content::Structure(FlatType::EmptyRecord) => { - struct_to_ast(env, ptr, &[], RecordFields::empty()) + struct_to_ast(env, addr, &[], RecordFields::empty()) } other => { unreachable!( @@ -550,7 +548,7 @@ fn ptr_to_ast<'a, M>( opt_name: _, }, WhenRecursive::Loop(union_layout)) => { let content = env.subs.get_content_without_compacting(*structure); - ptr_to_ast(env, ptr, &union_layout, when_recursive, content) + addr_to_ast(env, addr, &union_layout, when_recursive, content) } other => unreachable!("Something had a RecursivePointer layout, but instead of being a RecursionVar and having a known recursive layout, I found {:?}", other), } @@ -575,8 +573,7 @@ fn ptr_to_ast<'a, M>( }; // Because this is a `NonRecursive`, the tag ID is definitely after the data. - let tag_id = - tag_id_from_data(union_layout, ptr, env.ptr_bytes); + let tag_id = tag_id_from_data(env, union_layout, addr); // use the tag ID as an index, to get its name and layout of any arguments let (tag_name, arg_layouts) = @@ -584,7 +581,7 @@ fn ptr_to_ast<'a, M>( expr_of_tag( env, - ptr, + addr, tag_name, arg_layouts, &vars_of_tag[tag_name], @@ -608,7 +605,7 @@ fn ptr_to_ast<'a, M>( _ => unreachable!("any other variant would have a different layout"), }; - let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(*union_layout, ptr, env.ptr_bytes); + let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(env, *union_layout, addr); let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; expr_of_tag( @@ -636,7 +633,7 @@ fn ptr_to_ast<'a, M>( _ => unreachable!("any other variant would have a different layout"), }; - let ptr_to_data = deref_ptr_of_ptr(ptr, env.ptr_bytes); + let ptr_to_data = deref_addr_of_addr(addr, env.ptr_bytes); expr_of_tag( env, @@ -666,7 +663,7 @@ fn ptr_to_ast<'a, M>( _ => unreachable!("any other variant would have a different layout"), }; - let ptr_to_data = deref_ptr_of_ptr(ptr, env.ptr_bytes); + let ptr_to_data = deref_addr_of_addr(addr, env.ptr_bytes); if ptr_to_data.is_null() { tag_name_to_expr(env, &nullable_name) } else { @@ -697,11 +694,11 @@ fn ptr_to_ast<'a, M>( _ => unreachable!("any other variant would have a different layout"), }; - let ptr_to_data = deref_ptr_of_ptr(ptr, env.ptr_bytes); + let ptr_to_data = deref_addr_of_addr(addr, env.ptr_bytes); if ptr_to_data.is_null() { tag_name_to_expr(env, &nullable_name) } else { - let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(*union_layout, ptr, env.ptr_bytes); + let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(env, *union_layout, addr); let tag_id = if tag_id > nullable_id.into() { tag_id - 1 } else { tag_id }; @@ -728,7 +725,7 @@ fn ptr_to_ast<'a, M>( fn list_to_ast<'a, M>( env: &Env<'a, 'a, M>, - ptr: *const u8, + addr: usize, len: usize, elem_layout: &Layout<'a>, content: &Content, @@ -756,11 +753,11 @@ fn list_to_ast<'a, M>( for index in 0..len { let offset_bytes = index * elem_size; - let elem_ptr = unsafe { ptr.add(offset_bytes) }; + let elem_addr = addr + offset_bytes; let (newtype_containers, elem_content) = unroll_newtypes(env, elem_content); - let expr = ptr_to_ast( + let expr = addr_to_ast( env, - elem_ptr, + elem_addr, elem_layout, WhenRecursive::Unreachable, elem_content, @@ -777,7 +774,7 @@ fn list_to_ast<'a, M>( fn single_tag_union_to_ast<'a, M>( env: &Env<'a, 'a, M>, - ptr: *const u8, + addr: usize, field_layouts: &'a [Layout<'a>], tag_name: &TagName, payload_vars: &[Variable], @@ -789,11 +786,11 @@ fn single_tag_union_to_ast<'a, M>( let output = if field_layouts.len() == payload_vars.len() { let it = payload_vars.iter().copied().zip(field_layouts); - sequence_of_expr(env, ptr, it, WhenRecursive::Unreachable).into_bump_slice() + sequence_of_expr(env, addr, it, WhenRecursive::Unreachable).into_bump_slice() } else if field_layouts.is_empty() && !payload_vars.is_empty() { // happens for e.g. `Foo Bar` where unit structures are nested and the inner one is dropped let it = payload_vars.iter().copied().zip([&Layout::Struct(&[])]); - sequence_of_expr(env, ptr, it, WhenRecursive::Unreachable).into_bump_slice() + sequence_of_expr(env, addr, it, WhenRecursive::Unreachable).into_bump_slice() } else { unreachable!() }; @@ -803,7 +800,7 @@ fn single_tag_union_to_ast<'a, M>( fn sequence_of_expr<'a, I, M>( env: &Env<'a, 'a, M>, - ptr: *const u8, + addr: usize, sequence: I, when_recursive: WhenRecursive<'a>, ) -> Vec<'a, &'a Loc>> @@ -816,17 +813,17 @@ where let mut output = Vec::with_capacity_in(sequence.len(), arena); // We'll advance this as we iterate through the fields - let mut field_ptr = ptr; + let mut field_addr = addr; for (var, layout) in sequence { let content = subs.get_content_without_compacting(var); - let expr = ptr_to_ast(env, field_ptr, layout, when_recursive, content); + let expr = addr_to_ast(env, field_addr, layout, when_recursive, content); let loc_expr = Loc::at_zero(expr); output.push(&*arena.alloc(loc_expr)); // Advance the field pointer to the next field. - field_ptr = unsafe { field_ptr.offset(layout.stack_size(env.ptr_bytes) as isize) }; + field_addr += layout.stack_size(env.ptr_bytes) as usize; } output @@ -834,7 +831,7 @@ where fn struct_to_ast<'a, M>( env: &Env<'a, 'a, M>, - ptr: *const u8, + addr: usize, field_layouts: &'a [Layout<'a>], record_fields: RecordFields, ) -> Expr<'a> { @@ -854,9 +851,9 @@ fn struct_to_ast<'a, M>( let inner_content = env.subs.get_content_without_compacting(field.into_inner()); let loc_expr = &*arena.alloc(Loc { - value: ptr_to_ast( + value: addr_to_ast( env, - ptr, + addr, &Layout::Struct(field_layouts), WhenRecursive::Unreachable, inner_content, @@ -880,7 +877,7 @@ fn struct_to_ast<'a, M>( debug_assert_eq!(sorted_fields.len(), field_layouts.len()); // We'll advance this as we iterate through the fields - let mut field_ptr = ptr; + let mut field_addr = addr; for ((label, field), field_layout) in sorted_fields.into_iter().zip(field_layouts.iter()) { let var = field.into_inner(); @@ -888,9 +885,9 @@ fn struct_to_ast<'a, M>( let content = subs.get_content_without_compacting(var); let loc_expr = &*arena.alloc(Loc { - value: ptr_to_ast( + value: addr_to_ast( env, - field_ptr, + field_addr, field_layout, WhenRecursive::Unreachable, content, @@ -910,8 +907,7 @@ fn struct_to_ast<'a, M>( output.push(loc_field); // Advance the field pointer to the next field. - field_ptr = - unsafe { field_ptr.offset(field_layout.stack_size(env.ptr_bytes) as isize) }; + field_addr += field_layout.stack_size(env.ptr_bytes) as usize; } let output = output.into_bump_slice(); diff --git a/cli/src/repl/from_memory.rs b/cli/src/repl/from_memory.rs index 381ded1737..1634933a6e 100644 --- a/cli/src/repl/from_memory.rs +++ b/cli/src/repl/from_memory.rs @@ -1,29 +1,33 @@ use std::mem::size_of; -pub trait FromMemory { - fn from_memory(memory: M, address: usize) -> Self; +pub trait FromMemory { + fn from_memory(memory: &M, address: usize) -> Self; } +pub trait AppMemory {} +impl AppMemory for AppMemoryInternal {} +impl<'a> AppMemory for AppMemoryExternal<'a> {} + /// A block of app memory in the same address space as the compiler pub struct AppMemoryInternal; /// A block of app memory in a separate address space from the compiler /// (e.g. compiler and app are in separate Wasm modules) -pub struct AppMemoryExternal { - bytes: Vec, +pub struct AppMemoryExternal<'a> { + bytes: &'a [u8], } -macro_rules! impl_primitive { +macro_rules! impl_number_type { ($t: ty) => { impl FromMemory for $t { - fn from_memory(_: AppMemoryInternal, address: usize) -> Self { + fn from_memory(_: &AppMemoryInternal, address: usize) -> Self { let ptr = address as *const _; unsafe { *ptr } } } - impl FromMemory for $t { - fn from_memory(memory: AppMemoryExternal, address: usize) -> Self { + impl FromMemory> for $t { + fn from_memory(memory: &AppMemoryExternal, address: usize) -> Self { const N: usize = size_of::<$t>(); let mut array = [0; N]; array.copy_from_slice(&memory.bytes[address..][..N]); @@ -33,19 +37,64 @@ macro_rules! impl_primitive { }; } -impl_primitive!(u8); -impl_primitive!(u16); -impl_primitive!(u32); -impl_primitive!(u64); -impl_primitive!(u128); -impl_primitive!(usize); +impl FromMemory for u8 { + fn from_memory(_: &AppMemoryInternal, address: usize) -> Self { + let ptr = address as *const _; + unsafe { *ptr } + } +} -impl_primitive!(i8); -impl_primitive!(i16); -impl_primitive!(i32); -impl_primitive!(i64); -impl_primitive!(i128); -impl_primitive!(isize); +impl FromMemory> for u8 { + fn from_memory(memory: &AppMemoryExternal, address: usize) -> Self { + const N: usize = size_of::(); + let mut array = [0; N]; + array.copy_from_slice(&memory.bytes[address..][..N]); + Self::from_le_bytes(array) + } +} -impl_primitive!(f32); -impl_primitive!(f64); +// impl_number_type!(u8); +impl_number_type!(u16); +impl_number_type!(u32); +impl_number_type!(u64); +impl_number_type!(u128); +impl_number_type!(usize); + +impl_number_type!(i8); +impl_number_type!(i16); +impl_number_type!(i32); +impl_number_type!(i64); +impl_number_type!(i128); +impl_number_type!(isize); + +impl_number_type!(f32); +impl_number_type!(f64); + +impl FromMemory for bool { + fn from_memory(_: &AppMemoryInternal, address: usize) -> Self { + let ptr = address as *const _; + unsafe { *ptr } + } +} + +impl FromMemory> for bool { + fn from_memory(memory: &AppMemoryExternal, address: usize) -> Self { + memory.bytes[address] != 0 + } +} + +impl FromMemory for &str { + fn from_memory(_: &AppMemoryInternal, address: usize) -> Self { + let ptr = address as *const _; + unsafe { *ptr } + } +} + +impl<'a> FromMemory> for &'a str { + fn from_memory(memory: &AppMemoryExternal<'a>, address: usize) -> Self { + let len = usize::from_memory(memory, address + std::mem::size_of::()); + let content_addr = usize::from_memory(memory, address); + let content_bytes: &'a [u8] = &memory.bytes[content_addr..][..len]; + std::str::from_utf8(content_bytes).unwrap() + } +} diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 3d19699399..fd29062f26 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -364,6 +364,14 @@ impl<'a> UnionLayout<'a> { tags.len() < ptr_bytes as usize } + pub fn tag_id_pointer_bits_and_mask(ptr_bytes: u32) -> (usize, usize) { + match ptr_bytes { + 8 => (3, 0b0000_0111), + 4 => (2, 0b0000_0011), + _ => unreachable!(), + } + } + // i.e. it is not implicit and not stored in the pointer bits pub fn stores_tag_id_as_data(&self, ptr_bytes: u32) -> bool { match self { From b2fed142e2903319ac590cc9e16da8e72ba0e7ce Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 27 Jan 2022 09:41:07 +0000 Subject: [PATCH 352/541] repl: use AppMemory trait in eval (attempting to fix compiler errors) --- cli/src/repl/eval.rs | 55 +++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 75ba8a23fb..917e57771f 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -1,4 +1,3 @@ -use crate::repl::from_memory::FromMemory; use bumpalo::collections::Vec; use bumpalo::Bump; use libloading::Library; @@ -17,7 +16,9 @@ use roc_region::all::{Loc, Region}; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; use std::cmp::{max_by_key, min_by_key}; -struct Env<'a, 'env, M> { +use super::from_memory::{AppMemory, FromMemory}; + +struct Env<'a, 'env, M: AppMemory> { arena: &'a Bump, subs: &'env Subs, ptr_bytes: u32, @@ -39,7 +40,7 @@ pub enum ToAstProblem { /// we get to a struct or tag, we know what the labels are and can turn them /// back into the appropriate user-facing literals. #[allow(clippy::too_many_arguments)] -pub unsafe fn jit_to_ast<'a, M>( +pub unsafe fn jit_to_ast<'a, M: AppMemory>( arena: &'a Bump, lib: Library, main_fn_name: &str, @@ -86,7 +87,7 @@ enum NewtypeKind<'a> { /// /// The returned list of newtype containers is ordered by increasing depth. As an example, /// `A ({b : C 123})` will have the unrolled list `[Tag(A), RecordField(b), Tag(C)]`. -fn unroll_newtypes<'a, M>( +fn unroll_newtypes<'a, M: AppMemory>( env: &Env<'a, 'a, M>, mut content: &'a Content, ) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) { @@ -120,7 +121,7 @@ fn unroll_newtypes<'a, M>( } } -fn apply_newtypes<'a, M>( +fn apply_newtypes<'a, M: AppMemory>( env: &Env<'a, '_, M>, newtype_containers: Vec<'a, NewtypeKind<'a>>, mut expr: Expr<'a>, @@ -148,21 +149,24 @@ fn apply_newtypes<'a, M>( expr } -fn unroll_aliases<'a, M>(env: &Env<'a, 'a, M>, mut content: &'a Content) -> &'a Content { +fn unroll_aliases<'a, M: AppMemory>(env: &Env<'a, 'a, M>, mut content: &'a Content) -> &'a Content { while let Content::Alias(_, _, real) = content { content = env.subs.get_content_without_compacting(*real); } content } -fn unroll_recursion_var<'a, M>(env: &Env<'a, 'a, M>, mut content: &'a Content) -> &'a Content { +fn unroll_recursion_var<'a, M: AppMemory>( + env: &Env<'a, 'a, M>, + mut content: &'a Content, +) -> &'a Content { while let Content::RecursionVar { structure, .. } = content { content = env.subs.get_content_without_compacting(*structure); } content } -fn get_tags_vars_and_variant<'a, M>( +fn get_tags_vars_and_variant<'a, M: AppMemory>( env: &Env<'a, '_, M>, tags: &UnionTags, opt_rec_var: Option, @@ -180,7 +184,7 @@ fn get_tags_vars_and_variant<'a, M>( (vars_of_tag, union_variant) } -fn expr_of_tag<'a, M>( +fn expr_of_tag<'a, M: AppMemory>( env: &Env<'a, 'a, M>, data_addr: usize, tag_name: &TagName, @@ -203,7 +207,7 @@ fn expr_of_tag<'a, M>( /// Gets the tag ID of a union variant, assuming that the tag ID is stored alongside (after) the /// tag data. The caller is expected to check that the tag ID is indeed stored this way. -fn tag_id_from_data<'a, M>( +fn tag_id_from_data<'a, M: AppMemory>( env: &Env<'a, 'a, M>, union_layout: UnionLayout, data_addr: usize, @@ -211,7 +215,6 @@ fn tag_id_from_data<'a, M>( let offset = union_layout .data_size_without_tag_id(env.ptr_bytes) .unwrap(); - let tag_id_addr = data_addr + offset as usize; match union_layout.tag_id_builtin() { @@ -227,15 +230,15 @@ fn tag_id_from_data<'a, M>( } } -fn deref_addr_of_addr<'a, M>(env: &Env<'a, 'a, M>, addr_of_addr: usize) -> usize { +fn deref_addr_of_addr<'a, M: AppMemory>(env: &Env<'a, 'a, M>, addr_of_addr: usize) -> usize { usize::from_memory(&env.app_memory, addr_of_addr) } /// Gets the tag ID of a union variant from its recursive pointer (that is, the pointer to the /// pointer to the data of the union variant). Returns /// - the tag ID -/// - the pointer to the data of the union variant, unmasked if the pointer held the tag ID -fn tag_id_from_recursive_ptr<'a, M>( +/// - the address of the data of the union variant, unmasked if the pointer held the tag ID +fn tag_id_from_recursive_ptr<'a, M: AppMemory>( env: &Env<'a, 'a, M>, union_layout: UnionLayout, rec_addr: usize, @@ -261,7 +264,7 @@ const OPAQUE_FUNCTION: Expr = Expr::Var { ident: "", }; -fn jit_to_ast_help<'a, M>( +fn jit_to_ast_help<'a, M: AppMemory>( env: &Env<'a, 'a, M>, lib: Library, main_fn_name: &str, @@ -432,7 +435,7 @@ fn jit_to_ast_help<'a, M>( result.map(|e| apply_newtypes(env, newtype_containers, e)) } -fn tag_name_to_expr<'a, M>(env: &Env<'a, '_, M>, tag_name: &TagName) -> Expr<'a> { +fn tag_name_to_expr<'a, M: AppMemory>(env: &Env<'a, '_, M>, tag_name: &TagName) -> Expr<'a> { match tag_name { TagName::Global(_) => Expr::GlobalTag( env.arena @@ -454,7 +457,7 @@ enum WhenRecursive<'a> { Loop(Layout<'a>), } -fn addr_to_ast<'a, M>( +fn addr_to_ast<'a, M: AppMemory>( env: &Env<'a, 'a, M>, addr: usize, layout: &Layout<'a>, @@ -723,7 +726,7 @@ fn addr_to_ast<'a, M>( apply_newtypes(env, newtype_containers, expr) } -fn list_to_ast<'a, M>( +fn list_to_ast<'a, M: AppMemory>( env: &Env<'a, 'a, M>, addr: usize, len: usize, @@ -772,7 +775,7 @@ fn list_to_ast<'a, M>( Expr::List(Collection::with_items(output)) } -fn single_tag_union_to_ast<'a, M>( +fn single_tag_union_to_ast<'a, M: AppMemory>( env: &Env<'a, 'a, M>, addr: usize, field_layouts: &'a [Layout<'a>], @@ -798,7 +801,7 @@ fn single_tag_union_to_ast<'a, M>( Expr::Apply(loc_tag_expr, output, CalledVia::Space) } -fn sequence_of_expr<'a, I, M>( +fn sequence_of_expr<'a, I, M: AppMemory>( env: &Env<'a, 'a, M>, addr: usize, sequence: I, @@ -829,7 +832,7 @@ where output } -fn struct_to_ast<'a, M>( +fn struct_to_ast<'a, M: AppMemory>( env: &Env<'a, 'a, M>, addr: usize, field_layouts: &'a [Layout<'a>], @@ -946,7 +949,7 @@ fn unpack_two_element_tag_union( (tag_name1, payload_vars1, tag_name2, payload_vars2) } -fn bool_to_ast<'a, M>(env: &Env<'a, '_, M>, value: bool, content: &Content) -> Expr<'a> { +fn bool_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: bool, content: &Content) -> Expr<'a> { use Content::*; let arena = env.arena; @@ -1024,7 +1027,7 @@ fn bool_to_ast<'a, M>(env: &Env<'a, '_, M>, value: bool, content: &Content) -> E } } -fn byte_to_ast<'a, M>(env: &Env<'a, '_, M>, value: u8, content: &Content) -> Expr<'a> { +fn byte_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: u8, content: &Content) -> Expr<'a> { use Content::*; let arena = env.arena; @@ -1111,7 +1114,11 @@ fn byte_to_ast<'a, M>(env: &Env<'a, '_, M>, value: u8, content: &Content) -> Exp } } -fn num_to_ast<'a, M>(env: &Env<'a, '_, M>, num_expr: Expr<'a>, content: &Content) -> Expr<'a> { +fn num_to_ast<'a, M: AppMemory>( + env: &Env<'a, '_, M>, + num_expr: Expr<'a>, + content: &Content, +) -> Expr<'a> { use Content::*; let arena = env.arena; From 33e5ecf8ba77c0c1ce6dac03b13c3a254a35af56 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Thu, 27 Jan 2022 06:26:32 -0700 Subject: [PATCH 353/541] Manually format echo example to pass tests --- examples/cli/echo.roc | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/examples/cli/echo.roc b/examples/cli/echo.roc index 9f3914c164..9f5401ab3c 100644 --- a/examples/cli/echo.roc +++ b/examples/cli/echo.roc @@ -17,13 +17,17 @@ echo : Str -> Str echo = \shout -> silence = \length -> spaceInUtf8 = 32 + List.repeat length spaceInUtf8 + shout - |> Str.toUtf8 - |> List.mapWithIndex (\i, _ -> - length = (List.len (Str.toUtf8 shout) - i) - phrase = (List.split (Str.toUtf8 shout) length).before - List.concat (silence (if i == 0 then 2 * length else length)) phrase) - |> List.join - |> Str.fromUtf8 - |> Result.withDefault "" + |> Str.toUtf8 + |> List.mapWithIndex + (\i, _ -> + length = (List.len (Str.toUtf8 shout) - i) + phrase = (List.split (Str.toUtf8 shout) length).before + + List.concat (silence (if i == 0 then 2 * length else length)) phrase) + |> List.join + |> Str.fromUtf8 + |> Result.withDefault "" From 04c2b5e88cd026ce683dc8a047cc47a82ef091e3 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Thu, 27 Jan 2022 06:48:27 -0700 Subject: [PATCH 354/541] Ignore tui executable --- examples/tui/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 examples/tui/.gitignore diff --git a/examples/tui/.gitignore b/examples/tui/.gitignore new file mode 100644 index 0000000000..ba0da0793e --- /dev/null +++ b/examples/tui/.gitignore @@ -0,0 +1 @@ +tui From e31532360b94fcb626e8cac6fedb5d1c5127a3db Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 27 Jan 2022 21:20:46 +0100 Subject: [PATCH 355/541] don't remove argument that is not pushed --- compiler/gen_llvm/src/llvm/build.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index ec8bade1e7..1947e4fd11 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -6324,7 +6324,6 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( if let CCReturn::ByPointer = cc_return { cc_arguments.push(return_pointer.into()); - cc_argument_types.remove(0); } let it = fastcc_parameters.into_iter().zip(cc_argument_types.iter()); From ee151c8f943fc44f30a54d8aab77aeb5333bbd15 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 27 Jan 2022 22:26:44 +0100 Subject: [PATCH 356/541] fix function name --- compiler/gen_llvm/src/llvm/build.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 1947e4fd11..64432ddd7f 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -3547,7 +3547,7 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( c_function } -fn expose_function_to_host_help_c_abi_xx<'a, 'ctx, 'env>( +fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, roc_function: FunctionValue<'ctx>, arguments: &[Layout<'a>], @@ -3662,7 +3662,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( &format!("{}_generic", c_function_name), ); - let c_function = expose_function_to_host_help_c_abi_xx( + let c_function = expose_function_to_host_help_c_abi( env, roc_function, arguments, From 684b873453c77ddfc4e0eb1e5bae478e00ee80a1 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 27 Jan 2022 23:20:35 +0100 Subject: [PATCH 357/541] change name back ?! --- compiler/gen_llvm/src/llvm/build.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 64432ddd7f..031ce7b1db 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -3547,7 +3547,7 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( c_function } -fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( +fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, roc_function: FunctionValue<'ctx>, arguments: &[Layout<'a>], @@ -3662,7 +3662,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( &format!("{}_generic", c_function_name), ); - let c_function = expose_function_to_host_help_c_abi( + let c_function = expose_function_to_host_help_c_abi_v2( env, roc_function, arguments, From 6a9d31ec040b1309892e8fa787a23e54db515a81 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 28 Jan 2022 09:39:52 +0000 Subject: [PATCH 358/541] repl: Fix more type errors, but Rust still can't infer the FromMemory impls --- cli/src/repl/eval.rs | 36 ++++++++++++--------- cli/src/repl/from_memory.rs | 63 ++++++++++++++++++++++++++++++------- 2 files changed, 72 insertions(+), 27 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 917e57771f..f7f32e26e6 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -344,13 +344,13 @@ fn jit_to_ast_help<'a, M: AppMemory>( todo!("add support for rendering builtin {:?} to the REPL", other) } Layout::Struct(field_layouts) => { - let ptr_to_ast = |ptr: *const u8| match content { + let struct_addr_to_ast = |addr: usize| match content { Content::Structure(FlatType::Record(fields, _)) => { - Ok(struct_to_ast(env, ptr, field_layouts, *fields)) + Ok(struct_to_ast(env, addr, field_layouts, *fields)) } Content::Structure(FlatType::EmptyRecord) => Ok(struct_to_ast( env, - ptr, + addr, field_layouts, RecordFields::empty(), )), @@ -361,7 +361,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( Ok(single_tag_union_to_ast( env, - ptr, + addr, field_layouts, tag_name, payload_vars, @@ -372,7 +372,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( Ok(single_tag_union_to_ast( env, - ptr, + addr, field_layouts, tag_name, &[], @@ -399,7 +399,8 @@ fn jit_to_ast_help<'a, M: AppMemory>( lib, main_fn_name, result_stack_size as usize, - |bytes: *const u8| { ptr_to_ast(bytes) } + // TODO: convert LLVM macro to use address + |bytes: *const u8| { struct_addr_to_ast(bytes as usize) } ) } Layout::Union(UnionLayout::NonRecursive(_)) => { @@ -409,7 +410,9 @@ fn jit_to_ast_help<'a, M: AppMemory>( main_fn_name, size as usize, |ptr: *const u8| { - addr_to_ast(env, ptr, layout, WhenRecursive::Unreachable, content) + // TODO: convert LLVM macro to use address + let addr = ptr as usize; + addr_to_ast(env, addr, layout, WhenRecursive::Unreachable, content) } )) } @@ -423,7 +426,9 @@ fn jit_to_ast_help<'a, M: AppMemory>( main_fn_name, size as usize, |ptr: *const u8| { - addr_to_ast(env, ptr, layout, WhenRecursive::Loop(*layout), content) + // TODO: convert LLVM macro to use address + let addr = ptr as usize; + addr_to_ast(env, addr, layout, WhenRecursive::Loop(*layout), content) } )) } @@ -516,7 +521,8 @@ fn addr_to_ast<'a, M: AppMemory>( list_to_ast(env, elem_addr, len, elem_layout, content) } (_, Layout::Builtin(Builtin::Str)) => { - let arena_str = <&'a str>::from_memory(&env.app_memory, addr); + // TODO: implement FromMemory! It's tricky! + let arena_str = unsafe { *(addr as *const &'static str) }; str_to_ast(env.arena, arena_str) } @@ -636,7 +642,7 @@ fn addr_to_ast<'a, M: AppMemory>( _ => unreachable!("any other variant would have a different layout"), }; - let ptr_to_data = deref_addr_of_addr(addr, env.ptr_bytes); + let ptr_to_data = deref_addr_of_addr(env, addr); expr_of_tag( env, @@ -666,13 +672,13 @@ fn addr_to_ast<'a, M: AppMemory>( _ => unreachable!("any other variant would have a different layout"), }; - let ptr_to_data = deref_addr_of_addr(addr, env.ptr_bytes); - if ptr_to_data.is_null() { + let data_addr = deref_addr_of_addr(env, addr); + if data_addr == 0 { tag_name_to_expr(env, &nullable_name) } else { expr_of_tag( env, - ptr_to_data, + data_addr, &other_name, other_arg_layouts, &vars_of_tag[&other_name], @@ -697,8 +703,8 @@ fn addr_to_ast<'a, M: AppMemory>( _ => unreachable!("any other variant would have a different layout"), }; - let ptr_to_data = deref_addr_of_addr(addr, env.ptr_bytes); - if ptr_to_data.is_null() { + let ptr_to_data = deref_addr_of_addr(env, addr); + if ptr_to_data == 0 { tag_name_to_expr(env, &nullable_name) } else { let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(env, *union_layout, addr); diff --git a/cli/src/repl/from_memory.rs b/cli/src/repl/from_memory.rs index 1634933a6e..4beb2b6006 100644 --- a/cli/src/repl/from_memory.rs +++ b/cli/src/repl/from_memory.rs @@ -4,6 +4,10 @@ pub trait FromMemory { fn from_memory(memory: &M, address: usize) -> Self; } +// TODO: check if AppMemory trait is really needed! +// I wrote it to try to help with Rust type inference but it didn't really help +// Rust is complaining in eval.rs that it can't find instances like `u8: FromMemory` +// I thought that the AppMemory trait might give it some more hints, but it's still not working. pub trait AppMemory {} impl AppMemory for AppMemoryInternal {} impl<'a> AppMemory for AppMemoryExternal<'a> {} @@ -83,18 +87,53 @@ impl FromMemory> for bool { } } -impl FromMemory for &str { - fn from_memory(_: &AppMemoryInternal, address: usize) -> Self { - let ptr = address as *const _; - unsafe { *ptr } - } -} +#[cfg(test)] +mod tests { + use super::*; -impl<'a> FromMemory> for &'a str { - fn from_memory(memory: &AppMemoryExternal<'a>, address: usize) -> Self { - let len = usize::from_memory(memory, address + std::mem::size_of::()); - let content_addr = usize::from_memory(memory, address); - let content_bytes: &'a [u8] = &memory.bytes[content_addr..][..len]; - std::str::from_utf8(content_bytes).unwrap() + #[test] + fn internal_u8() { + let value: u8 = 123; + let ptr = &value as *const u8; + let addr = ptr as usize; + let memory = AppMemoryInternal; + let recovered: u8 = u8::from_memory(&memory, addr); + assert_eq!(value, recovered); + } + + #[test] + fn external_u8() { + let value: u8 = 123; + let memory = AppMemoryExternal { + bytes: &[0, 0, value, 0, 0], + }; + let addr = 2; + let recovered: u8 = u8::from_memory(&memory, addr); + assert_eq!(value, recovered); + } + + #[test] + fn internal_i64() { + let value: i64 = -123 << 33; + let ptr = &value as *const i64; + let addr = ptr as usize; + let memory = AppMemoryInternal; + let recovered: i64 = i64::from_memory(&memory, addr); + assert_eq!(value, recovered); + } + + #[test] + fn external_i64() { + let value: i64 = -1 << 33; + let memory = AppMemoryExternal { + bytes: &[ + 0, 0, // + 0, 0, 0, 0, 0xfe, 0xff, 0xff, 0xff, // + 0, 0, + ], + }; + let addr = 2; + let recovered: i64 = i64::from_memory(&memory, addr); + assert_eq!(value, recovered); } } From 46a303575908ca9a9b8a4f8bd024646ec6329099 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 28 Jan 2022 11:00:49 +0000 Subject: [PATCH 359/541] repl: try putting the from_memory method on the AppMemory trait instead --- cli/src/repl/eval.rs | 72 +++++++++++++++++++++---------------- cli/src/repl/from_memory.rs | 64 +++++++++++---------------------- 2 files changed, 61 insertions(+), 75 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index f7f32e26e6..e2f53262f0 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -16,9 +16,9 @@ use roc_region::all::{Loc, Region}; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; use std::cmp::{max_by_key, min_by_key}; -use super::from_memory::{AppMemory, FromMemory}; +use super::from_memory::AppMemory; -struct Env<'a, 'env, M: AppMemory> { +struct Env<'a, 'env, M> { arena: &'a Bump, subs: &'env Subs, ptr_bytes: u32, @@ -40,7 +40,7 @@ pub enum ToAstProblem { /// we get to a struct or tag, we know what the labels are and can turn them /// back into the appropriate user-facing literals. #[allow(clippy::too_many_arguments)] -pub unsafe fn jit_to_ast<'a, M: AppMemory>( +pub unsafe fn jit_to_ast<'a, M>( arena: &'a Bump, lib: Library, main_fn_name: &str, @@ -87,7 +87,7 @@ enum NewtypeKind<'a> { /// /// The returned list of newtype containers is ordered by increasing depth. As an example, /// `A ({b : C 123})` will have the unrolled list `[Tag(A), RecordField(b), Tag(C)]`. -fn unroll_newtypes<'a, M: AppMemory>( +fn unroll_newtypes<'a, M>( env: &Env<'a, 'a, M>, mut content: &'a Content, ) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) { @@ -121,7 +121,7 @@ fn unroll_newtypes<'a, M: AppMemory>( } } -fn apply_newtypes<'a, M: AppMemory>( +fn apply_newtypes<'a, M>( env: &Env<'a, '_, M>, newtype_containers: Vec<'a, NewtypeKind<'a>>, mut expr: Expr<'a>, @@ -149,14 +149,14 @@ fn apply_newtypes<'a, M: AppMemory>( expr } -fn unroll_aliases<'a, M: AppMemory>(env: &Env<'a, 'a, M>, mut content: &'a Content) -> &'a Content { +fn unroll_aliases<'a, M>(env: &Env<'a, 'a, M>, mut content: &'a Content) -> &'a Content { while let Content::Alias(_, _, real) = content { content = env.subs.get_content_without_compacting(*real); } content } -fn unroll_recursion_var<'a, M: AppMemory>( +fn unroll_recursion_var<'a, M>( env: &Env<'a, 'a, M>, mut content: &'a Content, ) -> &'a Content { @@ -166,7 +166,7 @@ fn unroll_recursion_var<'a, M: AppMemory>( content } -fn get_tags_vars_and_variant<'a, M: AppMemory>( +fn get_tags_vars_and_variant<'a, M>( env: &Env<'a, '_, M>, tags: &UnionTags, opt_rec_var: Option, @@ -184,7 +184,7 @@ fn get_tags_vars_and_variant<'a, M: AppMemory>( (vars_of_tag, union_variant) } -fn expr_of_tag<'a, M: AppMemory>( +fn expr_of_tag<'a, M>( env: &Env<'a, 'a, M>, data_addr: usize, tag_name: &TagName, @@ -207,7 +207,7 @@ fn expr_of_tag<'a, M: AppMemory>( /// Gets the tag ID of a union variant, assuming that the tag ID is stored alongside (after) the /// tag data. The caller is expected to check that the tag ID is indeed stored this way. -fn tag_id_from_data<'a, M: AppMemory>( +fn tag_id_from_data<'a, M>( env: &Env<'a, 'a, M>, union_layout: UnionLayout, data_addr: usize, @@ -218,27 +218,37 @@ fn tag_id_from_data<'a, M: AppMemory>( let tag_id_addr = data_addr + offset as usize; match union_layout.tag_id_builtin() { - Builtin::Bool => u8::from_memory(&env.app_memory, tag_id_addr) as i64, - Builtin::Int(IntWidth::U8) => u8::from_memory(&env.app_memory, tag_id_addr) as i64, - Builtin::Int(IntWidth::U16) => u16::from_memory(&env.app_memory, tag_id_addr) as i64, + Builtin::Bool => { + let value: u8 = env.app_memory.from_memory(tag_id_addr); + value as i64 + } + Builtin::Int(IntWidth::U8) => { + let value: u8 = env.app_memory.from_memory(tag_id_addr); + value as i64 + } + Builtin::Int(IntWidth::U16) => { + let value: u16 = env.app_memory.from_memory(tag_id_addr); + value as i64 + } Builtin::Int(IntWidth::U64) => { // used by non-recursive unions at the // moment, remove if that is no longer the case - i64::from_memory(&env.app_memory, tag_id_addr) + env.app_memory.from_memory(tag_id_addr) } _ => unreachable!("invalid tag id layout"), } } -fn deref_addr_of_addr<'a, M: AppMemory>(env: &Env<'a, 'a, M>, addr_of_addr: usize) -> usize { - usize::from_memory(&env.app_memory, addr_of_addr) +// TODO: inline this at call sites +fn deref_addr_of_addr<'a, M>(env: &Env<'a, 'a, M>, addr_of_addr: usize) -> usize { + env.app_memory.from_memory(addr_of_addr) } /// Gets the tag ID of a union variant from its recursive pointer (that is, the pointer to the /// pointer to the data of the union variant). Returns /// - the tag ID /// - the address of the data of the union variant, unmasked if the pointer held the tag ID -fn tag_id_from_recursive_ptr<'a, M: AppMemory>( +fn tag_id_from_recursive_ptr<'a, M>( env: &Env<'a, 'a, M>, union_layout: UnionLayout, rec_addr: usize, @@ -264,7 +274,7 @@ const OPAQUE_FUNCTION: Expr = Expr::Var { ident: "", }; -fn jit_to_ast_help<'a, M: AppMemory>( +fn jit_to_ast_help<'a, M>( env: &Env<'a, 'a, M>, lib: Library, main_fn_name: &str, @@ -440,7 +450,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( result.map(|e| apply_newtypes(env, newtype_containers, e)) } -fn tag_name_to_expr<'a, M: AppMemory>(env: &Env<'a, '_, M>, tag_name: &TagName) -> Expr<'a> { +fn tag_name_to_expr<'a, M>(env: &Env<'a, '_, M>, tag_name: &TagName) -> Expr<'a> { match tag_name { TagName::Global(_) => Expr::GlobalTag( env.arena @@ -462,7 +472,7 @@ enum WhenRecursive<'a> { Loop(Layout<'a>), } -fn addr_to_ast<'a, M: AppMemory>( +fn addr_to_ast<'a, M>( env: &Env<'a, 'a, M>, addr: usize, layout: &Layout<'a>, @@ -471,7 +481,7 @@ fn addr_to_ast<'a, M: AppMemory>( ) -> Expr<'a> { macro_rules! helper { ($ty:ty) => {{ - let num = <$ty>::from_memory(&env.app_memory, addr); + let num: $ty = env.app_memory.from_memory(addr); num_to_ast(env, number_literal_to_ast(env.arena, num), content) }}; @@ -485,7 +495,7 @@ fn addr_to_ast<'a, M: AppMemory>( (_, Layout::Builtin(Builtin::Bool)) => { // TODO: bits are not as expected here. // num is always false at the moment. - let num = bool::from_memory(&env.app_memory, addr); + let num: bool = env.app_memory.from_memory(addr); bool_to_ast(env, num, content) } @@ -515,8 +525,8 @@ fn addr_to_ast<'a, M: AppMemory>( } } (_, Layout::Builtin(Builtin::List(elem_layout))) => { - let elem_addr = usize::from_memory(&env.app_memory, addr); - let len = usize::from_memory(&env.app_memory, addr + env.ptr_bytes as usize); + let elem_addr: usize = env.app_memory.from_memory(addr); + let len: usize = env.app_memory.from_memory(addr + env.ptr_bytes as usize); list_to_ast(env, elem_addr, len, elem_layout, content) } @@ -732,7 +742,7 @@ fn addr_to_ast<'a, M: AppMemory>( apply_newtypes(env, newtype_containers, expr) } -fn list_to_ast<'a, M: AppMemory>( +fn list_to_ast<'a, M>( env: &Env<'a, 'a, M>, addr: usize, len: usize, @@ -781,7 +791,7 @@ fn list_to_ast<'a, M: AppMemory>( Expr::List(Collection::with_items(output)) } -fn single_tag_union_to_ast<'a, M: AppMemory>( +fn single_tag_union_to_ast<'a, M>( env: &Env<'a, 'a, M>, addr: usize, field_layouts: &'a [Layout<'a>], @@ -807,7 +817,7 @@ fn single_tag_union_to_ast<'a, M: AppMemory>( Expr::Apply(loc_tag_expr, output, CalledVia::Space) } -fn sequence_of_expr<'a, I, M: AppMemory>( +fn sequence_of_expr<'a, I, M>( env: &Env<'a, 'a, M>, addr: usize, sequence: I, @@ -838,7 +848,7 @@ where output } -fn struct_to_ast<'a, M: AppMemory>( +fn struct_to_ast<'a, M>( env: &Env<'a, 'a, M>, addr: usize, field_layouts: &'a [Layout<'a>], @@ -955,7 +965,7 @@ fn unpack_two_element_tag_union( (tag_name1, payload_vars1, tag_name2, payload_vars2) } -fn bool_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: bool, content: &Content) -> Expr<'a> { +fn bool_to_ast<'a, M>(env: &Env<'a, '_, M>, value: bool, content: &Content) -> Expr<'a> { use Content::*; let arena = env.arena; @@ -1033,7 +1043,7 @@ fn bool_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: bool, content: &Co } } -fn byte_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: u8, content: &Content) -> Expr<'a> { +fn byte_to_ast<'a, M>(env: &Env<'a, '_, M>, value: u8, content: &Content) -> Expr<'a> { use Content::*; let arena = env.arena; @@ -1120,7 +1130,7 @@ fn byte_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: u8, content: &Cont } } -fn num_to_ast<'a, M: AppMemory>( +fn num_to_ast<'a, M>( env: &Env<'a, '_, M>, num_expr: Expr<'a>, content: &Content, diff --git a/cli/src/repl/from_memory.rs b/cli/src/repl/from_memory.rs index 4beb2b6006..a2c318f35f 100644 --- a/cli/src/repl/from_memory.rs +++ b/cli/src/repl/from_memory.rs @@ -1,17 +1,9 @@ use std::mem::size_of; -pub trait FromMemory { - fn from_memory(memory: &M, address: usize) -> Self; +pub trait AppMemory { + fn from_memory(&self, addr: usize) -> T; } -// TODO: check if AppMemory trait is really needed! -// I wrote it to try to help with Rust type inference but it didn't really help -// Rust is complaining in eval.rs that it can't find instances like `u8: FromMemory` -// I thought that the AppMemory trait might give it some more hints, but it's still not working. -pub trait AppMemory {} -impl AppMemory for AppMemoryInternal {} -impl<'a> AppMemory for AppMemoryExternal<'a> {} - /// A block of app memory in the same address space as the compiler pub struct AppMemoryInternal; @@ -23,41 +15,25 @@ pub struct AppMemoryExternal<'a> { macro_rules! impl_number_type { ($t: ty) => { - impl FromMemory for $t { - fn from_memory(_: &AppMemoryInternal, address: usize) -> Self { - let ptr = address as *const _; + impl AppMemory<$t> for AppMemoryInternal { + fn from_memory(&self, addr: usize) -> $t { + let ptr = addr as *const _; unsafe { *ptr } } } - impl FromMemory> for $t { - fn from_memory(memory: &AppMemoryExternal, address: usize) -> Self { + impl AppMemory<$t> for AppMemoryExternal<'_> { + fn from_memory(&self, address: usize) -> $t { const N: usize = size_of::<$t>(); let mut array = [0; N]; - array.copy_from_slice(&memory.bytes[address..][..N]); - Self::from_le_bytes(array) + array.copy_from_slice(&self.bytes[address..][..N]); + <$t>::from_le_bytes(array) } } }; } -impl FromMemory for u8 { - fn from_memory(_: &AppMemoryInternal, address: usize) -> Self { - let ptr = address as *const _; - unsafe { *ptr } - } -} - -impl FromMemory> for u8 { - fn from_memory(memory: &AppMemoryExternal, address: usize) -> Self { - const N: usize = size_of::(); - let mut array = [0; N]; - array.copy_from_slice(&memory.bytes[address..][..N]); - Self::from_le_bytes(array) - } -} - -// impl_number_type!(u8); +impl_number_type!(u8); impl_number_type!(u16); impl_number_type!(u32); impl_number_type!(u64); @@ -74,16 +50,16 @@ impl_number_type!(isize); impl_number_type!(f32); impl_number_type!(f64); -impl FromMemory for bool { - fn from_memory(_: &AppMemoryInternal, address: usize) -> Self { - let ptr = address as *const _; +impl AppMemory for AppMemoryInternal { + fn from_memory(&self, addr: usize) -> bool { + let ptr = addr as *const _; unsafe { *ptr } } } -impl FromMemory> for bool { - fn from_memory(memory: &AppMemoryExternal, address: usize) -> Self { - memory.bytes[address] != 0 +impl AppMemory for AppMemoryExternal<'_> { + fn from_memory(&self, address: usize) -> bool { + self.bytes[address] != 0 } } @@ -97,7 +73,7 @@ mod tests { let ptr = &value as *const u8; let addr = ptr as usize; let memory = AppMemoryInternal; - let recovered: u8 = u8::from_memory(&memory, addr); + let recovered: u8 = memory.from_memory(addr); assert_eq!(value, recovered); } @@ -108,7 +84,7 @@ mod tests { bytes: &[0, 0, value, 0, 0], }; let addr = 2; - let recovered: u8 = u8::from_memory(&memory, addr); + let recovered: u8 = memory.from_memory(addr); assert_eq!(value, recovered); } @@ -118,7 +94,7 @@ mod tests { let ptr = &value as *const i64; let addr = ptr as usize; let memory = AppMemoryInternal; - let recovered: i64 = i64::from_memory(&memory, addr); + let recovered: i64 = memory.from_memory(addr); assert_eq!(value, recovered); } @@ -133,7 +109,7 @@ mod tests { ], }; let addr = 2; - let recovered: i64 = i64::from_memory(&memory, addr); + let recovered: i64 = memory.from_memory(addr); assert_eq!(value, recovered); } } From 0986d47ec642ab9db3efc319dd6f9fa00b0d310b Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 28 Jan 2022 11:25:30 +0000 Subject: [PATCH 360/541] repl: Remove some generics and use macros instead --- cli/src/repl/eval.rs | 97 +++++++++++-------------- cli/src/repl/from_memory.rs | 139 +++++++++++++++++++++++------------- 2 files changed, 133 insertions(+), 103 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index e2f53262f0..6ee32cfb48 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -40,7 +40,7 @@ pub enum ToAstProblem { /// we get to a struct or tag, we know what the labels are and can turn them /// back into the appropriate user-facing literals. #[allow(clippy::too_many_arguments)] -pub unsafe fn jit_to_ast<'a, M>( +pub unsafe fn jit_to_ast<'a, M: AppMemory>( arena: &'a Bump, lib: Library, main_fn_name: &str, @@ -87,7 +87,7 @@ enum NewtypeKind<'a> { /// /// The returned list of newtype containers is ordered by increasing depth. As an example, /// `A ({b : C 123})` will have the unrolled list `[Tag(A), RecordField(b), Tag(C)]`. -fn unroll_newtypes<'a, M>( +fn unroll_newtypes<'a, M: AppMemory>( env: &Env<'a, 'a, M>, mut content: &'a Content, ) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) { @@ -121,7 +121,7 @@ fn unroll_newtypes<'a, M>( } } -fn apply_newtypes<'a, M>( +fn apply_newtypes<'a, M: AppMemory>( env: &Env<'a, '_, M>, newtype_containers: Vec<'a, NewtypeKind<'a>>, mut expr: Expr<'a>, @@ -149,24 +149,21 @@ fn apply_newtypes<'a, M>( expr } -fn unroll_aliases<'a, M>(env: &Env<'a, 'a, M>, mut content: &'a Content) -> &'a Content { +fn unroll_aliases<'a, M: AppMemory>(env: &Env<'a, 'a, M>, mut content: &'a Content) -> &'a Content { while let Content::Alias(_, _, real) = content { content = env.subs.get_content_without_compacting(*real); } content } -fn unroll_recursion_var<'a, M>( - env: &Env<'a, 'a, M>, - mut content: &'a Content, -) -> &'a Content { +fn unroll_recursion_var<'a, M: AppMemory>(env: &Env<'a, 'a, M>, mut content: &'a Content) -> &'a Content { while let Content::RecursionVar { structure, .. } = content { content = env.subs.get_content_without_compacting(*structure); } content } -fn get_tags_vars_and_variant<'a, M>( +fn get_tags_vars_and_variant<'a, M: AppMemory>( env: &Env<'a, '_, M>, tags: &UnionTags, opt_rec_var: Option, @@ -184,7 +181,7 @@ fn get_tags_vars_and_variant<'a, M>( (vars_of_tag, union_variant) } -fn expr_of_tag<'a, M>( +fn expr_of_tag<'a, M: AppMemory>( env: &Env<'a, 'a, M>, data_addr: usize, tag_name: &TagName, @@ -207,7 +204,7 @@ fn expr_of_tag<'a, M>( /// Gets the tag ID of a union variant, assuming that the tag ID is stored alongside (after) the /// tag data. The caller is expected to check that the tag ID is indeed stored this way. -fn tag_id_from_data<'a, M>( +fn tag_id_from_data<'a, M: AppMemory>( env: &Env<'a, 'a, M>, union_layout: UnionLayout, data_addr: usize, @@ -219,36 +216,33 @@ fn tag_id_from_data<'a, M>( match union_layout.tag_id_builtin() { Builtin::Bool => { - let value: u8 = env.app_memory.from_memory(tag_id_addr); - value as i64 + env.app_memory.deref_bool(tag_id_addr) as i64 } Builtin::Int(IntWidth::U8) => { - let value: u8 = env.app_memory.from_memory(tag_id_addr); - value as i64 + env.app_memory.deref_u8(tag_id_addr) as i64 } Builtin::Int(IntWidth::U16) => { - let value: u16 = env.app_memory.from_memory(tag_id_addr); - value as i64 + env.app_memory.deref_u16(tag_id_addr) as i64 } Builtin::Int(IntWidth::U64) => { // used by non-recursive unions at the // moment, remove if that is no longer the case - env.app_memory.from_memory(tag_id_addr) + env.app_memory.deref_i64(tag_id_addr) } _ => unreachable!("invalid tag id layout"), } } // TODO: inline this at call sites -fn deref_addr_of_addr<'a, M>(env: &Env<'a, 'a, M>, addr_of_addr: usize) -> usize { - env.app_memory.from_memory(addr_of_addr) +fn deref_addr_of_addr<'a, M: AppMemory>(env: &Env<'a, 'a, M>, addr_of_addr: usize) -> usize { + env.app_memory.deref_usize(addr_of_addr) } /// Gets the tag ID of a union variant from its recursive pointer (that is, the pointer to the /// pointer to the data of the union variant). Returns /// - the tag ID /// - the address of the data of the union variant, unmasked if the pointer held the tag ID -fn tag_id_from_recursive_ptr<'a, M>( +fn tag_id_from_recursive_ptr<'a, M: AppMemory>( env: &Env<'a, 'a, M>, union_layout: UnionLayout, rec_addr: usize, @@ -274,7 +268,7 @@ const OPAQUE_FUNCTION: Expr = Expr::Var { ident: "", }; -fn jit_to_ast_help<'a, M>( +fn jit_to_ast_help<'a, M: AppMemory>( env: &Env<'a, 'a, M>, lib: Library, main_fn_name: &str, @@ -450,7 +444,7 @@ fn jit_to_ast_help<'a, M>( result.map(|e| apply_newtypes(env, newtype_containers, e)) } -fn tag_name_to_expr<'a, M>(env: &Env<'a, '_, M>, tag_name: &TagName) -> Expr<'a> { +fn tag_name_to_expr<'a, M: AppMemory>(env: &Env<'a, '_, M>, tag_name: &TagName) -> Expr<'a> { match tag_name { TagName::Global(_) => Expr::GlobalTag( env.arena @@ -472,7 +466,7 @@ enum WhenRecursive<'a> { Loop(Layout<'a>), } -fn addr_to_ast<'a, M>( +fn addr_to_ast<'a, M: AppMemory>( env: &Env<'a, 'a, M>, addr: usize, layout: &Layout<'a>, @@ -480,8 +474,8 @@ fn addr_to_ast<'a, M>( content: &'a Content, ) -> Expr<'a> { macro_rules! helper { - ($ty:ty) => {{ - let num: $ty = env.app_memory.from_memory(addr); + ($method: ident, $ty: ty) => {{ + let num: $ty = env.app_memory.$method(addr); num_to_ast(env, number_literal_to_ast(env.arena, num), content) }}; @@ -495,7 +489,7 @@ fn addr_to_ast<'a, M>( (_, Layout::Builtin(Builtin::Bool)) => { // TODO: bits are not as expected here. // num is always false at the moment. - let num: bool = env.app_memory.from_memory(addr); + let num: bool = env.app_memory.deref_bool(addr); bool_to_ast(env, num, content) } @@ -503,36 +497,35 @@ fn addr_to_ast<'a, M>( use IntWidth::*; match int_width { - U8 => helper!(u8), - U16 => helper!(u16), - U32 => helper!(u32), - U64 => helper!(u64), - U128 => helper!(u128), - I8 => helper!(i8), - I16 => helper!(i16), - I32 => helper!(i32), - I64 => helper!(i64), - I128 => helper!(i128), + U8 => helper!(deref_u8, u8), + U16 => helper!(deref_u16, u16), + U32 => helper!(deref_u32, u32), + U64 => helper!(deref_u64, u64), + U128 => helper!(deref_u128, u128), + I8 => helper!(deref_i8, i8), + I16 => helper!(deref_i16, i16), + I32 => helper!(deref_i32, i32), + I64 => helper!(deref_i64, i64), + I128 => helper!(deref_i128, i128), } } (_, Layout::Builtin(Builtin::Float(float_width))) => { use FloatWidth::*; match float_width { - F32 => helper!(f32), - F64 => helper!(f64), + F32 => helper!(deref_f32, f32), + F64 => helper!(deref_f64, f64), F128 => todo!("F128 not implemented"), } } (_, Layout::Builtin(Builtin::List(elem_layout))) => { - let elem_addr: usize = env.app_memory.from_memory(addr); - let len: usize = env.app_memory.from_memory(addr + env.ptr_bytes as usize); + let elem_addr = env.app_memory.deref_usize(addr); + let len = env.app_memory.deref_usize(addr + env.ptr_bytes as usize); list_to_ast(env, elem_addr, len, elem_layout, content) } (_, Layout::Builtin(Builtin::Str)) => { - // TODO: implement FromMemory! It's tricky! - let arena_str = unsafe { *(addr as *const &'static str) }; + let arena_str = env.app_memory.deref_str(addr); str_to_ast(env.arena, arena_str) } @@ -742,7 +735,7 @@ fn addr_to_ast<'a, M>( apply_newtypes(env, newtype_containers, expr) } -fn list_to_ast<'a, M>( +fn list_to_ast<'a, M: AppMemory>( env: &Env<'a, 'a, M>, addr: usize, len: usize, @@ -791,7 +784,7 @@ fn list_to_ast<'a, M>( Expr::List(Collection::with_items(output)) } -fn single_tag_union_to_ast<'a, M>( +fn single_tag_union_to_ast<'a, M: AppMemory>( env: &Env<'a, 'a, M>, addr: usize, field_layouts: &'a [Layout<'a>], @@ -817,7 +810,7 @@ fn single_tag_union_to_ast<'a, M>( Expr::Apply(loc_tag_expr, output, CalledVia::Space) } -fn sequence_of_expr<'a, I, M>( +fn sequence_of_expr<'a, I, M: AppMemory>( env: &Env<'a, 'a, M>, addr: usize, sequence: I, @@ -848,7 +841,7 @@ where output } -fn struct_to_ast<'a, M>( +fn struct_to_ast<'a, M: AppMemory>( env: &Env<'a, 'a, M>, addr: usize, field_layouts: &'a [Layout<'a>], @@ -965,7 +958,7 @@ fn unpack_two_element_tag_union( (tag_name1, payload_vars1, tag_name2, payload_vars2) } -fn bool_to_ast<'a, M>(env: &Env<'a, '_, M>, value: bool, content: &Content) -> Expr<'a> { +fn bool_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: bool, content: &Content) -> Expr<'a> { use Content::*; let arena = env.arena; @@ -1043,7 +1036,7 @@ fn bool_to_ast<'a, M>(env: &Env<'a, '_, M>, value: bool, content: &Content) -> E } } -fn byte_to_ast<'a, M>(env: &Env<'a, '_, M>, value: u8, content: &Content) -> Expr<'a> { +fn byte_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: u8, content: &Content) -> Expr<'a> { use Content::*; let arena = env.arena; @@ -1130,11 +1123,7 @@ fn byte_to_ast<'a, M>(env: &Env<'a, '_, M>, value: u8, content: &Content) -> Exp } } -fn num_to_ast<'a, M>( - env: &Env<'a, '_, M>, - num_expr: Expr<'a>, - content: &Content, -) -> Expr<'a> { +fn num_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, num_expr: Expr<'a>, content: &Content) -> Expr<'a> { use Content::*; let arena = env.arena; diff --git a/cli/src/repl/from_memory.rs b/cli/src/repl/from_memory.rs index a2c318f35f..2ed52c31d2 100644 --- a/cli/src/repl/from_memory.rs +++ b/cli/src/repl/from_memory.rs @@ -1,66 +1,107 @@ use std::mem::size_of; -pub trait AppMemory { - fn from_memory(&self, addr: usize) -> T; +pub trait AppMemory { + fn deref_bool(&self, addr: usize) -> bool; + + fn deref_u8(&self, addr: usize) -> u8; + fn deref_u16(&self, addr: usize) -> u16; + fn deref_u32(&self, addr: usize) -> u32; + fn deref_u64(&self, addr: usize) -> u64; + fn deref_u128(&self, addr: usize) -> u128; + fn deref_usize(&self, addr: usize) -> usize; + + fn deref_i8(&self, addr: usize) -> i8; + fn deref_i16(&self, addr: usize) -> i16; + fn deref_i32(&self, addr: usize) -> i32; + fn deref_i64(&self, addr: usize) -> i64; + fn deref_i128(&self, addr: usize) -> i128; + fn deref_isize(&self, addr: usize) -> isize; + + fn deref_f32(&self, addr: usize) -> f32; + fn deref_f64(&self, addr: usize) -> f64; + + fn deref_str(&self, addr: usize) -> &str; } /// A block of app memory in the same address space as the compiler pub struct AppMemoryInternal; -/// A block of app memory in a separate address space from the compiler +macro_rules! internal_number_type { + ($name: ident, $t: ty) => { + fn $name(&self, addr: usize) -> $t { + let ptr = addr as *const _; + unsafe { *ptr } + } + }; +} + +impl AppMemory for AppMemoryInternal { + internal_number_type!(deref_bool, bool); + + internal_number_type!(deref_u8, u8); + internal_number_type!(deref_u16, u16); + internal_number_type!(deref_u32, u32); + internal_number_type!(deref_u64, u64); + internal_number_type!(deref_u128, u128); + internal_number_type!(deref_usize, usize); + + internal_number_type!(deref_i8, i8); + internal_number_type!(deref_i16, i16); + internal_number_type!(deref_i32, i32); + internal_number_type!(deref_i64, i64); + internal_number_type!(deref_i128, i128); + internal_number_type!(deref_isize, isize); + + internal_number_type!(deref_f32, f32); + internal_number_type!(deref_f64, f64); + + fn deref_str(&self, addr: usize) -> &str { + unsafe { *(addr as *const &'static str) } + } +} + +/// A block of app memory copied from an exteral address space outside the compiler /// (e.g. compiler and app are in separate Wasm modules) pub struct AppMemoryExternal<'a> { bytes: &'a [u8], } -macro_rules! impl_number_type { - ($t: ty) => { - impl AppMemory<$t> for AppMemoryInternal { - fn from_memory(&self, addr: usize) -> $t { - let ptr = addr as *const _; - unsafe { *ptr } - } - } - - impl AppMemory<$t> for AppMemoryExternal<'_> { - fn from_memory(&self, address: usize) -> $t { - const N: usize = size_of::<$t>(); - let mut array = [0; N]; - array.copy_from_slice(&self.bytes[address..][..N]); - <$t>::from_le_bytes(array) - } +macro_rules! external_number_type { + ($name: ident, $t: ty) => { + fn $name(&self, address: usize) -> $t { + const N: usize = size_of::<$t>(); + let mut array = [0; N]; + array.copy_from_slice(&self.bytes[address..][..N]); + <$t>::from_le_bytes(array) } }; } -impl_number_type!(u8); -impl_number_type!(u16); -impl_number_type!(u32); -impl_number_type!(u64); -impl_number_type!(u128); -impl_number_type!(usize); - -impl_number_type!(i8); -impl_number_type!(i16); -impl_number_type!(i32); -impl_number_type!(i64); -impl_number_type!(i128); -impl_number_type!(isize); - -impl_number_type!(f32); -impl_number_type!(f64); - -impl AppMemory for AppMemoryInternal { - fn from_memory(&self, addr: usize) -> bool { - let ptr = addr as *const _; - unsafe { *ptr } - } -} - -impl AppMemory for AppMemoryExternal<'_> { - fn from_memory(&self, address: usize) -> bool { +impl<'a> AppMemory for AppMemoryExternal<'a> { + fn deref_bool(&self, address: usize) -> bool { self.bytes[address] != 0 } + + external_number_type!(deref_u8, u8); + external_number_type!(deref_u16, u16); + external_number_type!(deref_u32, u32); + external_number_type!(deref_u64, u64); + external_number_type!(deref_u128, u128); + external_number_type!(deref_usize, usize); + + external_number_type!(deref_i8, i8); + external_number_type!(deref_i16, i16); + external_number_type!(deref_i32, i32); + external_number_type!(deref_i64, i64); + external_number_type!(deref_i128, i128); + external_number_type!(deref_isize, isize); + + external_number_type!(deref_f32, f32); + external_number_type!(deref_f64, f64); + + fn deref_str(&self, _addr: usize) -> &str { + todo!("deref_str") + } } #[cfg(test)] @@ -73,7 +114,7 @@ mod tests { let ptr = &value as *const u8; let addr = ptr as usize; let memory = AppMemoryInternal; - let recovered: u8 = memory.from_memory(addr); + let recovered: u8 = memory.deref_u8(addr); assert_eq!(value, recovered); } @@ -84,7 +125,7 @@ mod tests { bytes: &[0, 0, value, 0, 0], }; let addr = 2; - let recovered: u8 = memory.from_memory(addr); + let recovered: u8 = memory.deref_u8(addr); assert_eq!(value, recovered); } @@ -94,7 +135,7 @@ mod tests { let ptr = &value as *const i64; let addr = ptr as usize; let memory = AppMemoryInternal; - let recovered: i64 = memory.from_memory(addr); + let recovered: i64 = memory.deref_i64(addr); assert_eq!(value, recovered); } @@ -109,7 +150,7 @@ mod tests { ], }; let addr = 2; - let recovered: i64 = memory.from_memory(addr); + let recovered: i64 = memory.deref_i64(addr); assert_eq!(value, recovered); } } From 3e358805136950b039364e6e589aa825034bec16 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 28 Jan 2022 13:05:06 +0000 Subject: [PATCH 361/541] repl: fix lifetime problem with app_memory in Env --- cli/src/repl/eval.rs | 4 ++-- cli/src/repl/gen.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 85f2bd6879..66381c7cc9 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -22,7 +22,7 @@ use super::from_memory::AppMemory; struct Env<'a, 'env, M> { arena: &'a Bump, subs: &'env Subs, - app_memory: M, + app_memory: &'a M, target_info: TargetInfo, interns: &'env Interns, home: ModuleId, @@ -51,7 +51,7 @@ pub unsafe fn jit_to_ast<'a, M: AppMemory>( home: ModuleId, subs: &'a Subs, target_info: TargetInfo, - app_memory: M, + app_memory: &'a M, ) -> Result, ToAstProblem> { let env = Env { arena, diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index 2ad4075baa..f2f18878fb 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -239,7 +239,7 @@ pub fn gen_and_eval<'a>( home, &subs, target_info, - AppMemoryInternal, + &AppMemoryInternal, ) }; let mut expr = roc_fmt::Buf::new_in(&arena); From b466857b5ef220fb6cc38a476a3aef8723fddad4 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 28 Jan 2022 13:12:25 +0000 Subject: [PATCH 362/541] repl: rename module from_memory -> app_memory --- cli/src/repl.rs | 2 +- cli/src/repl/{from_memory.rs => app_memory.rs} | 0 cli/src/repl/eval.rs | 2 +- cli/src/repl/gen.rs | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename cli/src/repl/{from_memory.rs => app_memory.rs} (100%) diff --git a/cli/src/repl.rs b/cli/src/repl.rs index 8e3c4d8933..149ce0bab4 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -29,7 +29,7 @@ pub const CONT_PROMPT: &str = concatcp!(BLUE, "…", END_COL, " "); #[cfg(feature = "llvm")] mod eval; -mod from_memory; +mod app_memory; #[cfg(feature = "llvm")] mod gen; diff --git a/cli/src/repl/from_memory.rs b/cli/src/repl/app_memory.rs similarity index 100% rename from cli/src/repl/from_memory.rs rename to cli/src/repl/app_memory.rs diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 66381c7cc9..5292fd397b 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -17,7 +17,7 @@ use roc_target::TargetInfo; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; use std::cmp::{max_by_key, min_by_key}; -use super::from_memory::AppMemory; +use super::app_memory::AppMemory; struct Env<'a, 'env, M> { arena: &'a Bump, diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index f2f18878fb..3f3ab72256 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -1,5 +1,5 @@ use crate::repl::eval; -use crate::repl::from_memory::AppMemoryInternal; +use crate::repl::app_memory::AppMemoryInternal; use bumpalo::Bump; use inkwell::context::Context; use inkwell::module::Linkage; From e43cd8c3994d1c877f02a80f99ac4d73fb04c280 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 28 Jan 2022 13:30:26 +0000 Subject: [PATCH 363/541] repl: implement AppMemoryExternal::from_str --- cli/src/repl/app_memory.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cli/src/repl/app_memory.rs b/cli/src/repl/app_memory.rs index 2ed52c31d2..a7612dbb4d 100644 --- a/cli/src/repl/app_memory.rs +++ b/cli/src/repl/app_memory.rs @@ -99,8 +99,11 @@ impl<'a> AppMemory for AppMemoryExternal<'a> { external_number_type!(deref_f32, f32); external_number_type!(deref_f64, f64); - fn deref_str(&self, _addr: usize) -> &str { - todo!("deref_str") + fn deref_str(&self, addr: usize) -> &str { + let elems_addr = self.deref_usize(addr); + let len = self.deref_usize(addr + size_of::()); + let bytes = &self.bytes[elems_addr..][..len]; + std::str::from_utf8(bytes).unwrap() } } From 39e67747e68e19bf8fad78c4442505636cec4c3c Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Fri, 28 Jan 2022 17:47:17 +0100 Subject: [PATCH 364/541] added instructions for commit signing --- CONTRIBUTING.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2858e4402d..870abe3af9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,13 @@ Earthly may temporarily use a lot of disk space, up to 90 GB. This disk space is - Before making your first pull request, definitely talk to an existing contributor on [Roc Zulip](https://roc.zulipchat.com) first about what you plan to do! This can not only avoid duplicated effort, it can also avoid making a whole PR only to discover it won't be accepted because the change doesn't fit with the goals of the language's design or implementation. - It's a good idea to open a work-in-progress pull request as you begin working on something. This way, others can see that you're working on it, which avoids duplicate effort, and others can give feedback sooner rather than later if they notice a problem in the direction things are going. Be sure to include "WIP" in the title of the PR as long as it's not ready for review! - Make sure to create a branch on the roc repository for your changes. We do not allow CI to be run on forks for security. +- All your commits need to be signed to prevent impersonation: + 1. [Make a key to sign your commits.](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key). + 2. [Configure git to use your key.](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key) + 3. Make git sign your commits automatically: + ``` + git config --global commit.gpgsign true + ``` - You find good first issues [here](https://github.com/rtfeldman/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). ## Can we do better? From 929ee7f560974d7aedba0fb69dc336d05f61b32d Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 28 Jan 2022 16:50:05 +0000 Subject: [PATCH 365/541] Formatting --- cli/src/repl.rs | 2 +- cli/src/repl/eval.rs | 23 ++++++++++++----------- cli/src/repl/gen.rs | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/cli/src/repl.rs b/cli/src/repl.rs index 149ce0bab4..d045028bd9 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -27,9 +27,9 @@ pub const INSTRUCTIONS: &str = "Enter an expression, or :help, or :exit/:q.\n"; pub const PROMPT: &str = concatcp!("\n", BLUE, "»", END_COL, " "); pub const CONT_PROMPT: &str = concatcp!(BLUE, "…", END_COL, " "); +mod app_memory; #[cfg(feature = "llvm")] mod eval; -mod app_memory; #[cfg(feature = "llvm")] mod gen; diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 5292fd397b..7bee58acbe 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -157,7 +157,10 @@ fn unroll_aliases<'a, M: AppMemory>(env: &Env<'a, 'a, M>, mut content: &'a Conte content } -fn unroll_recursion_var<'a, M: AppMemory>(env: &Env<'a, 'a, M>, mut content: &'a Content) -> &'a Content { +fn unroll_recursion_var<'a, M: AppMemory>( + env: &Env<'a, 'a, M>, + mut content: &'a Content, +) -> &'a Content { while let Content::RecursionVar { structure, .. } = content { content = env.subs.get_content_without_compacting(*structure); } @@ -216,15 +219,9 @@ fn tag_id_from_data<'a, M: AppMemory>( let tag_id_addr = data_addr + offset as usize; match union_layout.tag_id_builtin() { - Builtin::Bool => { - env.app_memory.deref_bool(tag_id_addr) as i64 - } - Builtin::Int(IntWidth::U8) => { - env.app_memory.deref_u8(tag_id_addr) as i64 - } - Builtin::Int(IntWidth::U16) => { - env.app_memory.deref_u16(tag_id_addr) as i64 - } + Builtin::Bool => env.app_memory.deref_bool(tag_id_addr) as i64, + Builtin::Int(IntWidth::U8) => env.app_memory.deref_u8(tag_id_addr) as i64, + Builtin::Int(IntWidth::U16) => env.app_memory.deref_u16(tag_id_addr) as i64, Builtin::Int(IntWidth::U64) => { // used by non-recursive unions at the // moment, remove if that is no longer the case @@ -1129,7 +1126,11 @@ fn byte_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: u8, content: &Cont } } -fn num_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, num_expr: Expr<'a>, content: &Content) -> Expr<'a> { +fn num_to_ast<'a, M: AppMemory>( + env: &Env<'a, '_, M>, + num_expr: Expr<'a>, + content: &Content, +) -> Expr<'a> { use Content::*; let arena = env.arena; diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index 3f3ab72256..c09a981b72 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -1,5 +1,5 @@ -use crate::repl::eval; use crate::repl::app_memory::AppMemoryInternal; +use crate::repl::eval; use bumpalo::Bump; use inkwell::context::Context; use inkwell::module::Linkage; From 72f2fac1a743461c5c5cda36c118e3af70daa444 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Fri, 28 Jan 2022 17:51:58 +0100 Subject: [PATCH 366/541] added Yubikey instructions --- CONTRIBUTING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 870abe3af9..194f13fd20 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,12 +23,15 @@ Earthly may temporarily use a lot of disk space, up to 90 GB. This disk space is - It's a good idea to open a work-in-progress pull request as you begin working on something. This way, others can see that you're working on it, which avoids duplicate effort, and others can give feedback sooner rather than later if they notice a problem in the direction things are going. Be sure to include "WIP" in the title of the PR as long as it's not ready for review! - Make sure to create a branch on the roc repository for your changes. We do not allow CI to be run on forks for security. - All your commits need to be signed to prevent impersonation: + 0. If you have a Yubikey, follow [guide 1](https://dev.to/paulmicheli/using-your-yubikey-to-get-started-with-gpg-3h4k), [guide 2](https://dev.to/paulmicheli/using-your-yubikey-for-signed-git-commits-4l73) and skip the steps below. 1. [Make a key to sign your commits.](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key). 2. [Configure git to use your key.](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key) 3. Make git sign your commits automatically: ``` git config --global commit.gpgsign true ``` + +- If you have a Yubikey, check this - You find good first issues [here](https://github.com/rtfeldman/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). ## Can we do better? From 548c0327c2a037bf9bb3bcb02e2b547fb0e87a72 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Fri, 28 Jan 2022 17:53:27 +0100 Subject: [PATCH 367/541] cleanup --- CONTRIBUTING.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 194f13fd20..f40058fdcd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,7 +31,6 @@ Earthly may temporarily use a lot of disk space, up to 90 GB. This disk space is git config --global commit.gpgsign true ``` -- If you have a Yubikey, check this - You find good first issues [here](https://github.com/rtfeldman/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). ## Can we do better? From 6a0535d688b8f91ff2aa197491a13e7f7f4a14f6 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Fri, 28 Jan 2022 17:56:19 +0100 Subject: [PATCH 368/541] Fixed number list --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f40058fdcd..bbe7a3822e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,10 +23,10 @@ Earthly may temporarily use a lot of disk space, up to 90 GB. This disk space is - It's a good idea to open a work-in-progress pull request as you begin working on something. This way, others can see that you're working on it, which avoids duplicate effort, and others can give feedback sooner rather than later if they notice a problem in the direction things are going. Be sure to include "WIP" in the title of the PR as long as it's not ready for review! - Make sure to create a branch on the roc repository for your changes. We do not allow CI to be run on forks for security. - All your commits need to be signed to prevent impersonation: - 0. If you have a Yubikey, follow [guide 1](https://dev.to/paulmicheli/using-your-yubikey-to-get-started-with-gpg-3h4k), [guide 2](https://dev.to/paulmicheli/using-your-yubikey-for-signed-git-commits-4l73) and skip the steps below. - 1. [Make a key to sign your commits.](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key). - 2. [Configure git to use your key.](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key) - 3. Make git sign your commits automatically: + 1. If you have a Yubikey, follow [guide 1](https://dev.to/paulmicheli/using-your-yubikey-to-get-started-with-gpg-3h4k), [guide 2](https://dev.to/paulmicheli/using-your-yubikey-for-signed-git-commits-4l73) and skip the steps below. + 2. [Make a key to sign your commits.](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key). + 3. [Configure git to use your key.](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key) + 4. Make git sign your commits automatically: ``` git config --global commit.gpgsign true ``` From 5943873654c2c3458af46f18bfd265a3c10bd24e Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Fri, 28 Jan 2022 23:37:44 -0500 Subject: [PATCH 369/541] Inline polymorphic calls at usage sites This is a bit.. ugly, or at least seems suboptimal, but I can't think of a better way to do it currently aside from demanding a uniform representation, which we probably don't want to do. Another option is something like the defunctionalization we perform today, except also capturing potential uses of nested functions in the closure tag of an encompassing lambda. So for example, ``` f = \x -> \y -> 1 ``` would now record a lambdaset with the data `[Test.f [TypeOfInnerClos1]]`, where `TypeOfInnerClos1` is e.g. `[Test.f.innerClos1 I8, Test.f.innerClos1 I16]`, symbolizing that the inner closure may be specialized to take an I8 or I16. Then at the time that we create the capture set for `f`, we create a tag noting what specialization should be used for the inner closure, and apply the current defunctionalization algorithm. So effectively, the type of the inner closure becomes a capture. I'm not sure if this is any better, or if it has more problems. @folkertdev any thoughts? Closes #2322 --- compiler/mono/src/ir.rs | 47 +++++++++++++- compiler/test_gen/src/gen_primitives.rs | 17 +++++ .../generated/aliased_polymorphic_closure.txt | 17 +++++ .../test_mono/generated/closure_in_list.txt | 20 +++--- .../test_mono/generated/nested_closure.txt | 8 +-- .../test_mono/generated/optional_when.txt | 62 +++++++++---------- compiler/test_mono/src/tests.rs | 13 ++++ 7 files changed, 136 insertions(+), 48 deletions(-) create mode 100644 compiler/test_mono/generated/aliased_polymorphic_closure.txt diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index d048a0f4c2..ff51aab7fd 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -4179,9 +4179,6 @@ pub fn with_hole<'a>( LocalFunction(_) => { unreachable!("if this was known to be a function, we would not be here") } - UnspecializedExpr(_) => { - unreachable!("if this was known to be an unspecialized expression, we would not be here") - } Imported(thunk_name) => { debug_assert!(procs.is_imported_module_thunk(thunk_name)); @@ -4240,6 +4237,49 @@ pub fn with_hole<'a>( unreachable!("calling a non-closure layout") } }, + UnspecializedExpr(symbol) => match full_layout { + RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout) => { + let closure_data_symbol = env.unique_symbol(); + + result = match_on_lambda_set( + env, + lambda_set, + closure_data_symbol, + arg_symbols, + arg_layouts, + ret_layout, + assigned, + hole, + ); + + let (lambda_expr, lambda_expr_var) = + procs.partial_exprs.get(symbol).unwrap(); + + let snapshot = env.subs.snapshot(); + let cache_snapshot = layout_cache.snapshot(); + let _unified = roc_unify::unify::unify( + env.subs, + fn_var, + lambda_expr_var, + roc_unify::unify::Mode::Eq, + ); + + result = with_hole( + env, + lambda_expr.clone(), + fn_var, + procs, + layout_cache, + closure_data_symbol, + env.arena.alloc(result), + ); + env.subs.rollback_to(snapshot); + layout_cache.rollback_to(cache_snapshot); + } + RawFunctionLayout::ZeroArgumentThunk(_) => { + unreachable!("calling a non-closure layout") + } + }, NotASymbol => { // the expression is not a symbol. That means it's an expression // evaluating to a function value. @@ -5184,6 +5224,7 @@ fn is_literal_like(expr: &roc_can::expr::Expr) -> bool { | ZeroArgumentTag { .. } | Tag { .. } | Record { .. } + | Call(..) ) } diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index 3c4a6a26cb..9292224e9f 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -3207,3 +3207,20 @@ fn polymophic_expression_captured_inside_closure() { u8 ); } + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn issue_2322() { + assert_evals_to!( + indoc!( + r#" + double = \x -> x * 2 + doubleBind = \x -> (\_ -> double x) + doubleThree = doubleBind 3 + doubleThree {} + "# + ), + 6, + u8 + ) +} diff --git a/compiler/test_mono/generated/aliased_polymorphic_closure.txt b/compiler/test_mono/generated/aliased_polymorphic_closure.txt new file mode 100644 index 0000000000..bf1eb3d95d --- /dev/null +++ b/compiler/test_mono/generated/aliased_polymorphic_closure.txt @@ -0,0 +1,17 @@ +procedure Test.2 (Test.6, #Attr.12): + let Test.1 : Builtin(Int(U8)) = StructAtIndex 0 #Attr.12; + let Test.11 : LambdaSet(LambdaSet { set: [( Test.4, [Builtin(Int(U8))])], representation: Struct([Builtin(Int(U8))]) }) = Struct {Test.1}; + ret Test.11; + +procedure Test.4 (Test.5, #Attr.12): + let Test.1 : Builtin(Int(U8)) = StructAtIndex 0 #Attr.12; + ret Test.1; + +procedure Test.0 (): + let Test.1 : Builtin(Int(U8)) = 1i64; + let Test.8 : Struct([]) = Struct {}; + let Test.10 : Struct([]) = Struct {}; + let Test.14 : LambdaSet(LambdaSet { set: [( Test.2, [Builtin(Int(U8))])], representation: Struct([Builtin(Int(U8))]) }) = Struct {Test.1}; + let Test.9 : LambdaSet(LambdaSet { set: [( Test.4, [Builtin(Int(U8))])], representation: Struct([Builtin(Int(U8))]) }) = CallByName Test.2 Test.10 Test.14; + let Test.7 : Builtin(Int(U8)) = CallByName Test.4 Test.8 Test.9; + ret Test.7; diff --git a/compiler/test_mono/generated/closure_in_list.txt b/compiler/test_mono/generated/closure_in_list.txt index 7ac145a0d4..0347966c3e 100644 --- a/compiler/test_mono/generated/closure_in_list.txt +++ b/compiler/test_mono/generated/closure_in_list.txt @@ -1,21 +1,21 @@ procedure List.7 (#Attr.2): - let Test.7 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; - ret Test.7; + let Test.8 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + ret Test.8; procedure Test.1 (Test.5): let Test.2 : Builtin(Int(I64)) = 41i64; - let Test.11 : LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; - let Test.10 : Builtin(List(LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }))) = Array [Test.11]; - ret Test.10; + let Test.12 : LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; + let Test.11 : Builtin(List(LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }))) = Array [Test.12]; + ret Test.11; -procedure Test.3 (Test.9, #Attr.12): +procedure Test.3 (Test.10, #Attr.12): let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 #Attr.12; let Test.2 : Builtin(Int(I64)) = 41i64; ret Test.2; procedure Test.0 (): - let Test.8 : Struct([]) = Struct {}; - let Test.4 : Builtin(List(LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }))) = CallByName Test.1 Test.8; - let Test.6 : Builtin(Int(U64)) = CallByName List.7 Test.4; - dec Test.4; + let Test.9 : Struct([]) = Struct {}; + let Test.7 : Builtin(List(LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }))) = CallByName Test.1 Test.9; + let Test.6 : Builtin(Int(U64)) = CallByName List.7 Test.7; + dec Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/nested_closure.txt b/compiler/test_mono/generated/nested_closure.txt index af44f33389..9b15123922 100644 --- a/compiler/test_mono/generated/nested_closure.txt +++ b/compiler/test_mono/generated/nested_closure.txt @@ -3,14 +3,14 @@ procedure Test.1 (Test.5): let Test.3 : LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; ret Test.3; -procedure Test.3 (Test.9, #Attr.12): +procedure Test.3 (Test.10, #Attr.12): let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 #Attr.12; let Test.2 : Builtin(Int(I64)) = 42i64; ret Test.2; procedure Test.0 (): - let Test.8 : Struct([]) = Struct {}; - let Test.4 : LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = CallByName Test.1 Test.8; let Test.7 : Struct([]) = Struct {}; - let Test.6 : Builtin(Int(I64)) = CallByName Test.3 Test.7 Test.4; + let Test.9 : Struct([]) = Struct {}; + let Test.8 : LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = CallByName Test.1 Test.9; + let Test.6 : Builtin(Int(I64)) = CallByName Test.3 Test.7 Test.8; ret Test.6; diff --git a/compiler/test_mono/generated/optional_when.txt b/compiler/test_mono/generated/optional_when.txt index 9c98133311..1dc1fdca6b 100644 --- a/compiler/test_mono/generated/optional_when.txt +++ b/compiler/test_mono/generated/optional_when.txt @@ -1,42 +1,42 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.17 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; - ret Test.17; + let Test.18 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; + ret Test.18; procedure Test.1 (Test.6): - let Test.22 : Builtin(Bool) = StructAtIndex 1 Test.6; - let Test.23 : Builtin(Bool) = false; - let Test.24 : Builtin(Bool) = lowlevel Eq Test.23 Test.22; - if Test.24 then - let Test.8 : Builtin(Int(I64)) = StructAtIndex 0 Test.6; - ret Test.8; - else - let Test.10 : Builtin(Int(I64)) = StructAtIndex 0 Test.6; - ret Test.10; - -procedure Test.1 (Test.6): - let Test.33 : Builtin(Bool) = false; - let Test.34 : Builtin(Bool) = lowlevel Eq Test.33 Test.6; - if Test.34 then + let Test.22 : Builtin(Bool) = false; + let Test.23 : Builtin(Bool) = lowlevel Eq Test.22 Test.6; + if Test.23 then let Test.8 : Builtin(Int(I64)) = 3i64; ret Test.8; else let Test.10 : Builtin(Int(I64)) = 5i64; ret Test.10; +procedure Test.1 (Test.6): + let Test.30 : Builtin(Bool) = StructAtIndex 1 Test.6; + let Test.31 : Builtin(Bool) = false; + let Test.32 : Builtin(Bool) = lowlevel Eq Test.31 Test.30; + if Test.32 then + let Test.8 : Builtin(Int(I64)) = StructAtIndex 0 Test.6; + ret Test.8; + else + let Test.10 : Builtin(Int(I64)) = StructAtIndex 0 Test.6; + ret Test.10; + procedure Test.0 (): - let Test.37 : Builtin(Bool) = true; - let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.37; - let Test.35 : Builtin(Bool) = false; - let Test.3 : Builtin(Int(I64)) = CallByName Test.1 Test.35; - let Test.28 : Builtin(Int(I64)) = 11i64; - let Test.29 : Builtin(Bool) = true; - let Test.27 : Struct([Builtin(Int(I64)), Builtin(Bool)]) = Struct {Test.28, Test.29}; - let Test.4 : Builtin(Int(I64)) = CallByName Test.1 Test.27; - let Test.25 : Builtin(Int(I64)) = 7i64; - let Test.26 : Builtin(Bool) = false; - let Test.19 : Struct([Builtin(Int(I64)), Builtin(Bool)]) = Struct {Test.25, Test.26}; - let Test.2 : Builtin(Int(I64)) = CallByName Test.1 Test.19; - let Test.18 : Builtin(Int(I64)) = CallByName Num.24 Test.2 Test.3; - let Test.16 : Builtin(Int(I64)) = CallByName Num.24 Test.18 Test.4; - let Test.15 : Builtin(Int(I64)) = CallByName Num.24 Test.16 Test.5; + let Test.40 : Builtin(Int(I64)) = 7i64; + let Test.41 : Builtin(Bool) = false; + let Test.39 : Struct([Builtin(Int(I64)), Builtin(Bool)]) = Struct {Test.40, Test.41}; + let Test.35 : Builtin(Int(I64)) = CallByName Test.1 Test.39; + let Test.38 : Builtin(Bool) = false; + let Test.36 : Builtin(Int(I64)) = CallByName Test.1 Test.38; + let Test.25 : Builtin(Int(I64)) = CallByName Num.24 Test.35 Test.36; + let Test.33 : Builtin(Int(I64)) = 11i64; + let Test.34 : Builtin(Bool) = true; + let Test.27 : Struct([Builtin(Int(I64)), Builtin(Bool)]) = Struct {Test.33, Test.34}; + let Test.26 : Builtin(Int(I64)) = CallByName Test.1 Test.27; + let Test.16 : Builtin(Int(I64)) = CallByName Num.24 Test.25 Test.26; + let Test.24 : Builtin(Bool) = true; + let Test.17 : Builtin(Int(I64)) = CallByName Test.1 Test.24; + let Test.15 : Builtin(Int(I64)) = CallByName Num.24 Test.16 Test.17; ret Test.15; diff --git a/compiler/test_mono/src/tests.rs b/compiler/test_mono/src/tests.rs index 2cf783baa2..34001fe46a 100644 --- a/compiler/test_mono/src/tests.rs +++ b/compiler/test_mono/src/tests.rs @@ -1238,6 +1238,19 @@ fn monomorphized_applied_tag() { ) } +#[mono_test] +fn aliased_polymorphic_closure() { + indoc!( + r#" + n : U8 + n = 1 + f = \{} -> (\a -> n) + g = f {} + g {} + "# + ) +} + // #[ignore] // #[mono_test] // fn static_str_closure() { From 06079ccde59aaf6bab7c33fb21822e0a14f895d0 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 29 Jan 2022 11:17:20 +0000 Subject: [PATCH 370/541] repl: inline a helper function --- cli/src/repl/eval.rs | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 7bee58acbe..f2a3d77f0a 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -231,11 +231,6 @@ fn tag_id_from_data<'a, M: AppMemory>( } } -// TODO: inline this at call sites -fn deref_addr_of_addr<'a, M: AppMemory>(env: &Env<'a, 'a, M>, addr_of_addr: usize) -> usize { - env.app_memory.deref_usize(addr_of_addr) -} - /// Gets the tag ID of a union variant from its recursive pointer (that is, the pointer to the /// pointer to the data of the union variant). Returns /// - the tag ID @@ -247,15 +242,13 @@ fn tag_id_from_recursive_ptr<'a, M: AppMemory>( ) -> (i64, usize) { let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.target_info); if tag_in_ptr { - let masked_data_addr = deref_addr_of_addr(env, rec_addr); + let addr_with_id = env.app_memory.deref_usize(rec_addr); let (tag_id_bits, tag_id_mask) = UnionLayout::tag_id_pointer_bits_and_mask(env.target_info); - let tag_id = masked_data_addr & tag_id_mask; - - // Clear the tag ID data from the pointer - let data_addr = (masked_data_addr >> tag_id_bits) << tag_id_bits; + let tag_id = addr_with_id & tag_id_mask; + let data_addr = addr_with_id & !tag_id_mask; (tag_id as i64, data_addr) } else { - let data_addr = deref_addr_of_addr(env, rec_addr); + let data_addr = env.app_memory.deref_usize(rec_addr); let tag_id = tag_id_from_data(env, union_layout, data_addr); (tag_id, data_addr) } @@ -643,11 +636,11 @@ fn addr_to_ast<'a, M: AppMemory>( _ => unreachable!("any other variant would have a different layout"), }; - let ptr_to_data = deref_addr_of_addr(env, addr); + let data_addr = env.app_memory.deref_usize(addr); expr_of_tag( env, - ptr_to_data, + data_addr, &tag_name, arg_layouts, &vars_of_tag[&tag_name], @@ -673,7 +666,7 @@ fn addr_to_ast<'a, M: AppMemory>( _ => unreachable!("any other variant would have a different layout"), }; - let data_addr = deref_addr_of_addr(env, addr); + let data_addr = env.app_memory.deref_usize(addr); if data_addr == 0 { tag_name_to_expr(env, &nullable_name) } else { @@ -704,18 +697,18 @@ fn addr_to_ast<'a, M: AppMemory>( _ => unreachable!("any other variant would have a different layout"), }; - let ptr_to_data = deref_addr_of_addr(env, addr); - if ptr_to_data == 0 { + let data_addr = env.app_memory.deref_usize(addr); + if data_addr == 0 { tag_name_to_expr(env, &nullable_name) } else { - let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(env, *union_layout, addr); + let (tag_id, data_addr) = tag_id_from_recursive_ptr(env, *union_layout, addr); let tag_id = if tag_id > nullable_id.into() { tag_id - 1 } else { tag_id }; let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; expr_of_tag( env, - ptr_to_data, + data_addr, tag_name, arg_layouts, &vars_of_tag[tag_name], From d7f10d80ae5697a66b6486c48724e4071b6f457e Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 29 Jan 2022 11:24:13 +0000 Subject: [PATCH 371/541] repl: Change LLVM macro to use address instead of pointer --- cli/src/repl/eval.rs | 13 ++++--------- compiler/gen_llvm/src/run_roc.rs | 2 +- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index f2a3d77f0a..51fb280877 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -243,7 +243,7 @@ fn tag_id_from_recursive_ptr<'a, M: AppMemory>( let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.target_info); if tag_in_ptr { let addr_with_id = env.app_memory.deref_usize(rec_addr); - let (tag_id_bits, tag_id_mask) = UnionLayout::tag_id_pointer_bits_and_mask(env.target_info); + let (_, tag_id_mask) = UnionLayout::tag_id_pointer_bits_and_mask(env.target_info); let tag_id = addr_with_id & tag_id_mask; let data_addr = addr_with_id & !tag_id_mask; (tag_id as i64, data_addr) @@ -394,8 +394,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( lib, main_fn_name, result_stack_size as usize, - // TODO: convert LLVM macro to use address - |bytes: *const u8| { struct_addr_to_ast(bytes as usize) } + |bytes_addr: usize| { struct_addr_to_ast(bytes_addr as usize) } ) } Layout::Union(UnionLayout::NonRecursive(_)) => { @@ -404,9 +403,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( lib, main_fn_name, size as usize, - |ptr: *const u8| { - // TODO: convert LLVM macro to use address - let addr = ptr as usize; + |addr: usize| { addr_to_ast(env, addr, layout, WhenRecursive::Unreachable, content) } )) @@ -420,9 +417,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( lib, main_fn_name, size as usize, - |ptr: *const u8| { - // TODO: convert LLVM macro to use address - let addr = ptr as usize; + |addr: usize| { addr_to_ast(env, addr, layout, WhenRecursive::Loop(*layout), content) } )) diff --git a/compiler/gen_llvm/src/run_roc.rs b/compiler/gen_llvm/src/run_roc.rs index 4428f68fbf..d6237ce629 100644 --- a/compiler/gen_llvm/src/run_roc.rs +++ b/compiler/gen_llvm/src/run_roc.rs @@ -95,7 +95,7 @@ macro_rules! run_jit_function_dynamic_type { let flag = *result; if flag == 0 { - $transform(result.add(std::mem::size_of::>()) as *const u8) + $transform(result.add(std::mem::size_of::>()) as usize) } else { use std::ffi::CString; use std::os::raw::c_char; From 47a59c560cb6737462fea6bf62c52513a1d8691e Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sat, 29 Jan 2022 15:04:22 -0500 Subject: [PATCH 372/541] Make test use an i64 --- compiler/test_gen/src/gen_primitives.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index 9292224e9f..24782ef1a5 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -3221,6 +3221,6 @@ fn issue_2322() { "# ), 6, - u8 + i64 ) } From 227f06c4a51e65c17d6770f7ff9345acca7d1d4e Mon Sep 17 00:00:00 2001 From: Drew Date: Sat, 29 Jan 2022 17:27:17 -0600 Subject: [PATCH 373/541] Indent + remove old comments --- examples/cli/platform/Package-Config.roc | 10 +++++----- examples/cli/platform/Stdout.roc | 2 -- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/examples/cli/platform/Package-Config.roc b/examples/cli/platform/Package-Config.roc index 93a045a908..4c871363e6 100644 --- a/examples/cli/platform/Package-Config.roc +++ b/examples/cli/platform/Package-Config.roc @@ -1,14 +1,14 @@ platform "examples/cli" - requires {} { main : Task {} [] }# TODO FIXME + requires {} { main : Task {} [] } exposes [] packages {} imports [ Task.{ Task } ] provides [ mainForHost ] effects fx.Effect - { - putLine : Str -> Effect {}, - getLine : Effect Str - } + { + putLine : Str -> Effect {}, + getLine : Effect Str + } mainForHost : Task {} [] as Fx mainForHost = main diff --git a/examples/cli/platform/Stdout.roc b/examples/cli/platform/Stdout.roc index c843296ce2..1df3a1f7c4 100644 --- a/examples/cli/platform/Stdout.roc +++ b/examples/cli/platform/Stdout.roc @@ -2,7 +2,5 @@ interface Stdout exposes [ line ] imports [ fx.Effect, Task.{ Task } ] -# line : Str -> Task.Task {} * -# line = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {}) line : Str -> Task {} * line = \str -> Effect.map (Effect.putLine str) (\_ -> Ok {}) From 8ae2d9d407514b8738fdfaa45f8762f55771e7b6 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sat, 29 Jan 2022 23:52:00 -0500 Subject: [PATCH 374/541] Parse interfaces with leading newlines Closes #2420 --- compiler/parse/src/module.rs | 71 ++++++++++--------- .../interface_with_newline.header.result-ast | 15 ++++ .../pass/interface_with_newline.header.roc | 2 + compiler/parse/tests/test_parse.rs | 1 + 4 files changed, 55 insertions(+), 34 deletions(-) create mode 100644 compiler/parse/tests/snapshots/pass/interface_with_newline.header.result-ast create mode 100644 compiler/parse/tests/snapshots/pass/interface_with_newline.header.roc diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index 0852be36ee..42031bad83 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -52,41 +52,44 @@ pub fn parse_header<'a>( fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> { use crate::parser::keyword_e; - one_of![ - map!( - and!( - space0_e(0, EHeader::Space, EHeader::IndentStart), - skip_first!(keyword_e("app", EHeader::Start), app_header()) - ), - |(spaces, mut header): (&'a [CommentOrNewline], AppHeader<'a>)| { - header.before_header = spaces; - - Module::App { header } - } + map!( + and!( + space0_e(0, EHeader::Space, EHeader::IndentStart), + one_of![ + map!( + skip_first!(keyword_e("app", EHeader::Start), app_header()), + |mut header: AppHeader<'a>| -> Box Module<'a>> { + Box::new(|spaces| { + header.before_header = spaces; + Module::App { header } + }) + } + ), + map!( + skip_first!(keyword_e("platform", EHeader::Start), platform_header()), + |mut header: PlatformHeader<'a>| -> Box Module<'a>> { + Box::new(|spaces| { + header.before_header = spaces; + Module::Platform { header } + }) + } + ), + map!( + skip_first!(keyword_e("interface", EHeader::Start), interface_header()), + |mut header: InterfaceHeader<'a>| -> Box Module<'a>> { + Box::new(|spaces| { + header.before_header = spaces; + Module::Interface { header } + }) + } + ) + ] ), - map!( - and!( - space0_e(0, EHeader::Space, EHeader::IndentStart), - skip_first!(keyword_e("platform", EHeader::Start), platform_header()) - ), - |(spaces, mut header): (&'a [CommentOrNewline], PlatformHeader<'a>)| { - header.before_header = spaces; - - Module::Platform { header } - } - ), - map!( - and!( - space0_e(0, EHeader::Space, EHeader::IndentStart), - skip_first!(keyword_e("interface", EHeader::Start), interface_header()) - ), - |(spaces, mut header): (&'a [CommentOrNewline], InterfaceHeader<'a>)| { - header.before_header = spaces; - - Module::Interface { header } - } - ) - ] + |(spaces, make_header): ( + &'a [CommentOrNewline], + Box Module<'a>> + )| { make_header(spaces) } + ) } #[inline(always)] diff --git a/compiler/parse/tests/snapshots/pass/interface_with_newline.header.result-ast b/compiler/parse/tests/snapshots/pass/interface_with_newline.header.result-ast new file mode 100644 index 0000000000..fe67388046 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/interface_with_newline.header.result-ast @@ -0,0 +1,15 @@ +Interface { + header: InterfaceHeader { + name: @10-11 ModuleName( + "T", + ), + exposes: [], + imports: [], + before_header: [], + after_interface_keyword: [], + before_exposes: [], + after_exposes: [], + before_imports: [], + after_imports: [], + }, +} diff --git a/compiler/parse/tests/snapshots/pass/interface_with_newline.header.roc b/compiler/parse/tests/snapshots/pass/interface_with_newline.header.roc new file mode 100644 index 0000000000..696cdada15 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/interface_with_newline.header.roc @@ -0,0 +1,2 @@ + +interface T exposes [] imports [] diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index d575d677a1..0aaf95b559 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -148,6 +148,7 @@ mod test_parse { pass/highest_int.expr, pass/if_def.expr, pass/int_with_underscore.expr, + pass/interface_with_newline.header, pass/lowest_float.expr, pass/lowest_int.expr, pass/malformed_ident_due_to_underscore.expr, From 84f81525142ddf408f5047f3c51cb1e95205dd25 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sat, 29 Jan 2022 23:57:24 -0500 Subject: [PATCH 375/541] Remove debug_assert that may legally check a false value This may be false e.g. in the presence of a multiline string. Since the parser is in a more stable state than when this was introduced, I think this is OK to remove. Closes #2398 --- compiler/parse/src/state.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/parse/src/state.rs b/compiler/parse/src/state.rs index b6a198d6e7..b21bccc122 100644 --- a/compiler/parse/src/state.rs +++ b/compiler/parse/src/state.rs @@ -45,7 +45,6 @@ impl<'a> State<'a> { #[must_use] pub(crate) fn advance(&self, offset: usize) -> State<'a> { let mut state = self.clone(); - debug_assert!(!state.bytes()[..offset].iter().any(|b| *b == b'\n')); state.offset += offset; state } From ee05d13802fb5bcd9c4141949678901ed2a05e01 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 30 Jan 2022 00:11:55 -0500 Subject: [PATCH 376/541] Add test for type mismatch between optional and required fields This comes from #2167 which has been fixed on trunk for some time, but we didn't have a test for. Closes #2167 --- reporting/tests/test_reporting.rs | 38 +++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 45215dd437..3cd69c34d5 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -7163,4 +7163,42 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn issue_2167_record_field_optional_and_required_mismatch() { + report_problem_as( + indoc!( + r#" + Job : [ @Job { inputs : List Str } ] + job : { inputs ? List Str } -> Job + job = \{ inputs } -> + @Job { inputs } + + job { inputs: [ "build", "test" ] } + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 1st argument to `job` is weird: + + 3│ job = \{ inputs } -> + ^^^^^^^^^^ + + The argument is a pattern that matches record values of type: + + { inputs : List Str } + + But the annotation on `job` says the 1st argument should be: + + { inputs ? List Str } + + Tip: To extract the `.inputs` field it must be non-optional, but the + type says this field is optional. Learn more about optional fields at + TODO. + "# + ), + ) + } } From e54917a063af368e648d8f1b065076ff0fcf99fb Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 30 Jan 2022 00:21:45 -0500 Subject: [PATCH 377/541] Expose type mismatches between recursive types and types that aren't Closes #2166 --- compiler/unify/src/unify.rs | 7 +++++- reporting/tests/test_reporting.rs | 37 +++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 0a426b4a56..34693d704d 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -285,7 +285,12 @@ fn unify_structure( // unify the structure with this unrecursive tag union unify_pool(subs, pool, ctx.first, *structure, ctx.mode) } - _ => todo!("rec structure {:?}", &flat_type), + // Only tag unions can be recursive; everything else is an error. + _ => mismatch!( + "trying to unify {:?} with recursive type var {:?}", + &flat_type, + structure + ), }, Structure(ref other_flat_type) => { diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 3cd69c34d5..4df35ed1ff 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -7201,4 +7201,41 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn unify_recursive_with_nonrecursive() { + report_problem_as( + indoc!( + r#" + Job : [ @Job { inputs : List Job } ] + + job : { inputs : List Str } -> Job + job = \{ inputs } -> + @Job { inputs } + + job { inputs: [ "build", "test" ] } + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + Something is off with the body of the `job` definition: + + 3│ job : { inputs : List Str } -> Job + 4│ job = \{ inputs } -> + 5│ @Job { inputs } + ^^^^^^^^^^^^^^^ + + This `@Job` private tag application has the type: + + [ @Job { inputs : List Str } ] + + But the type annotation on `job` says it should be: + + [ @Job { inputs : List a } ] as a + "# + ), + ) + } } From b50bb9a4c72adc1b6b1020cc8fdf8034aaa2dd1f Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 30 Jan 2022 00:48:01 -0500 Subject: [PATCH 378/541] Centralize closure type --- compiler/parse/src/module.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index 42031bad83..2600af4a2a 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -52,13 +52,15 @@ pub fn parse_header<'a>( fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> { use crate::parser::keyword_e; + type Clos<'b> = Box<(dyn FnOnce(&'b [CommentOrNewline]) -> Module<'b> + 'b)>; + map!( and!( space0_e(0, EHeader::Space, EHeader::IndentStart), one_of![ map!( skip_first!(keyword_e("app", EHeader::Start), app_header()), - |mut header: AppHeader<'a>| -> Box Module<'a>> { + |mut header: AppHeader<'a>| -> Clos<'a> { Box::new(|spaces| { header.before_header = spaces; Module::App { header } @@ -67,7 +69,7 @@ fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> { ), map!( skip_first!(keyword_e("platform", EHeader::Start), platform_header()), - |mut header: PlatformHeader<'a>| -> Box Module<'a>> { + |mut header: PlatformHeader<'a>| -> Clos<'a> { Box::new(|spaces| { header.before_header = spaces; Module::Platform { header } @@ -76,7 +78,7 @@ fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> { ), map!( skip_first!(keyword_e("interface", EHeader::Start), interface_header()), - |mut header: InterfaceHeader<'a>| -> Box Module<'a>> { + |mut header: InterfaceHeader<'a>| -> Clos<'a> { Box::new(|spaces| { header.before_header = spaces; Module::Interface { header } @@ -85,10 +87,7 @@ fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> { ) ] ), - |(spaces, make_header): ( - &'a [CommentOrNewline], - Box Module<'a>> - )| { make_header(spaces) } + |(spaces, make_header): (&'a [CommentOrNewline], Clos<'a>)| { make_header(spaces) } ) } From 0f206121bdc01c6ef3dff1c0c44f971ce7f44b6b Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 30 Jan 2022 07:47:02 +0000 Subject: [PATCH 379/541] repl: move files from cli into repl-cli and repl-eval --- cli/src/repl.rs => repl-cli/src/main.rs | 0 {cli/src/repl => repl-eval/src}/app_memory.rs | 0 {cli/src/repl => repl-eval/src}/debug.rs | 0 {cli/src/repl => repl-eval/src}/eval.rs | 0 {cli/src/repl => repl-eval/src}/gen.rs | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename cli/src/repl.rs => repl-cli/src/main.rs (100%) rename {cli/src/repl => repl-eval/src}/app_memory.rs (100%) rename {cli/src/repl => repl-eval/src}/debug.rs (100%) rename {cli/src/repl => repl-eval/src}/eval.rs (100%) rename {cli/src/repl => repl-eval/src}/gen.rs (100%) diff --git a/cli/src/repl.rs b/repl-cli/src/main.rs similarity index 100% rename from cli/src/repl.rs rename to repl-cli/src/main.rs diff --git a/cli/src/repl/app_memory.rs b/repl-eval/src/app_memory.rs similarity index 100% rename from cli/src/repl/app_memory.rs rename to repl-eval/src/app_memory.rs diff --git a/cli/src/repl/debug.rs b/repl-eval/src/debug.rs similarity index 100% rename from cli/src/repl/debug.rs rename to repl-eval/src/debug.rs diff --git a/cli/src/repl/eval.rs b/repl-eval/src/eval.rs similarity index 100% rename from cli/src/repl/eval.rs rename to repl-eval/src/eval.rs diff --git a/cli/src/repl/gen.rs b/repl-eval/src/gen.rs similarity index 100% rename from cli/src/repl/gen.rs rename to repl-eval/src/gen.rs From f098c6cb9930ab076996dd2f7aa6b00cf87509b8 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 30 Jan 2022 09:04:48 +0000 Subject: [PATCH 380/541] repl: initial working version with new crate structure --- Cargo.lock | 39 +++++++++++++++++++++ Cargo.toml | 2 ++ cli/Cargo.toml | 1 + cli/src/lib.rs | 1 - cli/src/main.rs | 7 ++-- repl_cli/Cargo.toml | 21 +++++++++++ repl-cli/src/main.rs => repl_cli/src/lib.rs | 23 +++++------- repl_eval/Cargo.toml | 27 ++++++++++++++ {repl-eval => repl_eval}/src/app_memory.rs | 0 {repl-eval => repl_eval}/src/debug.rs | 0 {repl-eval => repl_eval}/src/eval.rs | 0 {repl-eval => repl_eval}/src/gen.rs | 20 +++++++---- repl_eval/src/lib.rs | 4 +++ 13 files changed, 120 insertions(+), 25 deletions(-) create mode 100644 repl_cli/Cargo.toml rename repl-cli/src/main.rs => repl_cli/src/lib.rs (95%) create mode 100644 repl_eval/Cargo.toml rename {repl-eval => repl_eval}/src/app_memory.rs (100%) rename {repl-eval => repl_eval}/src/debug.rs (100%) rename {repl-eval => repl_eval}/src/eval.rs (100%) rename {repl-eval => repl_eval}/src/gen.rs (93%) create mode 100644 repl_eval/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 53fc08cafd..e28afcd8ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3351,6 +3351,7 @@ dependencies = [ "roc_parse", "roc_problem", "roc_region", + "roc_repl_cli", "roc_reporting", "roc_solve", "roc_target", @@ -3672,6 +3673,44 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "roc_repl_cli" +version = "0.1.0" +dependencies = [ + "bumpalo", + "const_format", + "roc_mono", + "roc_parse", + "roc_repl_eval", + "rustyline", + "rustyline-derive", + "target-lexicon", +] + +[[package]] +name = "roc_repl_eval" +version = "0.1.0" +dependencies = [ + "bumpalo", + "inkwell 0.1.0", + "libloading 0.7.1", + "roc_build", + "roc_builtins", + "roc_can", + "roc_collections", + "roc_fmt", + "roc_gen_llvm", + "roc_load", + "roc_module", + "roc_mono", + "roc_parse", + "roc_region", + "roc_reporting", + "roc_target", + "roc_types", + "target-lexicon", +] + [[package]] name = "roc_reporting" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 93974ca757..f685a9058c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,8 @@ members = [ "code_markup", "error_macros", "reporting", + "repl_cli", + "repl_eval", "roc_std", "test_utils", "utils", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index d680a24888..0712366e82 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -66,6 +66,7 @@ roc_reporting = { path = "../reporting" } roc_error_macros = { path = "../error_macros" } roc_editor = { path = "../editor", optional = true } roc_linker = { path = "../linker" } +roc_repl_cli = { path = "../repl_cli" } clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] } const_format = "0.2.22" rustyline = { git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1" } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index b8e5abc6fb..136f99eab7 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -18,7 +18,6 @@ use target_lexicon::{Architecture, OperatingSystem, Triple, X86_32Architecture}; pub mod build; mod format; -pub mod repl; pub use format::format; pub const CMD_BUILD: &str = "build"; diff --git a/cli/src/main.rs b/cli/src/main.rs index a59bd0465f..5e6ba134be 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,9 +1,10 @@ use roc_cli::build::check_file; use roc_cli::{ - build_app, docs, format, repl, BuildConfig, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, - CMD_FORMAT, CMD_REPL, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_TIME, ROC_FILE, + build_app, docs, format, BuildConfig, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, CMD_FORMAT, + CMD_REPL, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_TIME, ROC_FILE, }; use roc_load::file::LoadingProblem; +use roc_repl_cli; use std::fs::{self, FileType}; use std::io; use std::path::{Path, PathBuf}; @@ -63,7 +64,7 @@ fn main() -> io::Result<()> { } } Some((CMD_REPL, _)) => { - repl::main()?; + roc_repl_cli::main()?; // Exit 0 if the repl exited normally Ok(0) diff --git a/repl_cli/Cargo.toml b/repl_cli/Cargo.toml new file mode 100644 index 0000000000..99b33cc5d8 --- /dev/null +++ b/repl_cli/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2021" +name = "roc_repl_cli" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bumpalo = {version = "3.8.0", features = ["collections"]} +const_format = "0.2.22" +rustyline = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"} +rustyline-derive = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"} +target-lexicon = "0.12.2" + +roc_mono = {path = "../compiler/mono"} +roc_parse = {path = "../compiler/parse"} +roc_repl_eval = {path = "../repl_eval"} + +[lib] +name = "roc_repl_cli" +path = "src/lib.rs" diff --git a/repl-cli/src/main.rs b/repl_cli/src/lib.rs similarity index 95% rename from repl-cli/src/main.rs rename to repl_cli/src/lib.rs index d045028bd9..0247b7b8d8 100644 --- a/repl-cli/src/main.rs +++ b/repl_cli/src/lib.rs @@ -1,13 +1,13 @@ use const_format::concatcp; -#[cfg(feature = "llvm")] -use gen::{gen_and_eval, ReplOutput}; -use roc_parse::parser::{EExpr, ELambda, SyntaxError}; use rustyline::highlight::{Highlighter, PromptInfo}; use rustyline::validate::{self, ValidationContext, ValidationResult, Validator}; use rustyline_derive::{Completer, Helper, Hinter}; use std::borrow::Cow; use std::io; +use roc_parse::parser::{EExpr, ELambda, SyntaxError}; +use roc_repl_eval::gen::{gen_and_eval, ReplOutput}; + const BLUE: &str = "\u{001b}[36m"; const PINK: &str = "\u{001b}[35m"; const END_COL: &str = "\u{001b}[0m"; @@ -27,11 +27,11 @@ pub const INSTRUCTIONS: &str = "Enter an expression, or :help, or :exit/:q.\n"; pub const PROMPT: &str = concatcp!("\n", BLUE, "»", END_COL, " "); pub const CONT_PROMPT: &str = concatcp!(BLUE, "…", END_COL, " "); -mod app_memory; -#[cfg(feature = "llvm")] -mod eval; -#[cfg(feature = "llvm")] -mod gen; +// mod app_memory; +// #[cfg(feature = "llvm")] +// mod eval; +// #[cfg(feature = "llvm")] +// mod gen; #[derive(Completer, Helper, Hinter)] struct ReplHelper { @@ -108,12 +108,6 @@ impl Validator for InputValidator { } } -#[cfg(not(feature = "llvm"))] -pub fn main() -> io::Result<()> { - panic!("The REPL currently requires being built with LLVM."); -} - -#[cfg(feature = "llvm")] pub fn main() -> io::Result<()> { use rustyline::error::ReadlineError; use rustyline::Editor; @@ -236,7 +230,6 @@ fn report_parse_error(fail: SyntaxError) { println!("TODO Gracefully report parse error in repl: {:?}", fail); } -#[cfg(feature = "llvm")] fn eval_and_format<'a>(src: &str) -> Result> { use roc_mono::ir::OptLevel; use target_lexicon::Triple; diff --git a/repl_eval/Cargo.toml b/repl_eval/Cargo.toml new file mode 100644 index 0000000000..acc7cefd0c --- /dev/null +++ b/repl_eval/Cargo.toml @@ -0,0 +1,27 @@ +[package] +edition = "2021" +name = "roc_repl_eval" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bumpalo = {version = "3.8.0", features = ["collections"]} +inkwell = {path = "../vendor/inkwell"}# TODO +libloading = "0.7.1" # TODO +target-lexicon = "0.12.2" + +roc_build = {path = "../compiler/build", default-features = false, features = ["llvm"]}# TODO +roc_builtins = {path = "../compiler/builtins"} +roc_can = {path = "../compiler/can"} +roc_collections = {path = "../compiler/collections"} +roc_fmt = {path = "../compiler/fmt"} +roc_gen_llvm = {path = "../compiler/gen_llvm"}# TODO +roc_load = {path = "../compiler/load"} +roc_module = {path = "../compiler/module"} +roc_mono = {path = "../compiler/mono"} +roc_parse = {path = "../compiler/parse"} +roc_region = {path = "../compiler/region"} +roc_reporting = {path = "../reporting"} +roc_target = {path = "../compiler/roc_target"} +roc_types = {path = "../compiler/types"} diff --git a/repl-eval/src/app_memory.rs b/repl_eval/src/app_memory.rs similarity index 100% rename from repl-eval/src/app_memory.rs rename to repl_eval/src/app_memory.rs diff --git a/repl-eval/src/debug.rs b/repl_eval/src/debug.rs similarity index 100% rename from repl-eval/src/debug.rs rename to repl_eval/src/debug.rs diff --git a/repl-eval/src/eval.rs b/repl_eval/src/eval.rs similarity index 100% rename from repl-eval/src/eval.rs rename to repl_eval/src/eval.rs diff --git a/repl-eval/src/gen.rs b/repl_eval/src/gen.rs similarity index 93% rename from repl-eval/src/gen.rs rename to repl_eval/src/gen.rs index c09a981b72..8c93c45bbb 100644 --- a/repl-eval/src/gen.rs +++ b/repl_eval/src/gen.rs @@ -1,10 +1,9 @@ -use crate::repl::app_memory::AppMemoryInternal; -use crate::repl::eval; +use crate::app_memory::AppMemoryInternal; +use crate::eval; use bumpalo::Bump; -use inkwell::context::Context; -use inkwell::module::Linkage; -use roc_build::link::module_to_dylib; -use roc_build::program::FunctionIterator; +use inkwell::context::Context; // TODO +use inkwell::module::Linkage; // TODO +use roc_build::{link::module_to_dylib, program::FunctionIterator}; use roc_can::builtins::builtin_defs_map; use roc_collections::all::{MutMap, MutSet}; use roc_fmt::annotation::Formattable; @@ -167,6 +166,10 @@ pub fn gen_and_eval<'a>( } }; + /*-------------------------------------------------------------------- + START OF LLVM-SPECIFIC STUFF + --------------------------------------------------------------------*/ + let module = arena.alloc(module); let (module_pass, function_pass) = roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level); @@ -228,6 +231,11 @@ pub fn gen_and_eval<'a>( let lib = module_to_dylib(env.module, &target, opt_level) .expect("Error loading compiled dylib for test"); + + /*-------------------------------------------------------------------- + END OF LLVM-SPECIFIC STUFF + --------------------------------------------------------------------*/ + let res_answer = unsafe { eval::jit_to_ast( &arena, diff --git a/repl_eval/src/lib.rs b/repl_eval/src/lib.rs new file mode 100644 index 0000000000..ce104f147d --- /dev/null +++ b/repl_eval/src/lib.rs @@ -0,0 +1,4 @@ +mod app_memory; +// mod debug; TODO: Is this in the right place? Seems to be specifically for solve.rs +mod eval; +pub mod gen; From e168adfdf84f5d3e53541cbec0279468d5cf6dba Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 30 Jan 2022 09:08:44 +0000 Subject: [PATCH 381/541] repl: update Earthfile --- Earthfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Earthfile b/Earthfile index a1a1ce8c14..2c3482be9d 100644 --- a/Earthfile +++ b/Earthfile @@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt: copy-dirs: FROM +install-zig-llvm-valgrind-clippy-rustfmt - COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros utils test_utils reporting roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./ + COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros utils test_utils reporting repl_cli repl_eval roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./ test-zig: FROM +install-zig-llvm-valgrind-clippy-rustfmt From 29caf5d3018a65732d0c465672f49f2a6dcc4705 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 30 Jan 2022 11:01:31 -0500 Subject: [PATCH 382/541] Make macOS frameworks link properly --- compiler/build/src/link.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 80f09f901b..13f8f00a65 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -856,11 +856,23 @@ fn link_macos( "-lSystem", "-lresolv", "-lpthread", + // This `-F PATH` flag is needed for `-framework` flags to work + "-F", + "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/", + // These frameworks are needed for GUI examples to work + "-framework", + "Cocoa", + "-framework", + "CoreVideo", + "-framework", + "Metal", + "-framework", + "QuartzCore", // "-lrt", // TODO shouldn't we need this? // "-lc_nonshared", // TODO shouldn't we need this? // "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840 - // "-framework", // Uncomment this line & the following ro run the `rand` crate in examples/cli - // "Security", + "-framework", + "Security", // Output "-o", output_path.to_str().unwrap(), // app From c8c51f0cdc10985c7b86c296574ba077aab52d90 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 30 Jan 2022 16:20:48 +0000 Subject: [PATCH 383/541] repl: Move tests from roc_cli to roc_repl_cli --- Cargo.lock | 3 + cli/tests/repl_eval.rs | 962 ---------------------------------- cli_utils/src/helpers.rs | 86 --- repl_cli/Cargo.toml | 5 + repl_cli/src/lib.rs | 9 +- repl_cli/src/tests.rs | 1078 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 1089 insertions(+), 1054 deletions(-) delete mode 100644 cli/tests/repl_eval.rs create mode 100644 repl_cli/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index e28afcd8ee..964cd0cd6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3679,11 +3679,14 @@ version = "0.1.0" dependencies = [ "bumpalo", "const_format", + "indoc", "roc_mono", "roc_parse", "roc_repl_eval", + "roc_test_utils", "rustyline", "rustyline-derive", + "strip-ansi-escapes", "target-lexicon", ] diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs deleted file mode 100644 index 5069c29f98..0000000000 --- a/cli/tests/repl_eval.rs +++ /dev/null @@ -1,962 +0,0 @@ -#[macro_use] -extern crate indoc; - -#[cfg(test)] -mod repl_eval { - use cli_utils::helpers; - use roc_test_utils::assert_multiline_str_eq; - - const ERROR_MESSAGE_START: char = '─'; - - fn expect_success(input: &str, expected: &str) { - let out = helpers::repl_eval(input); - - assert_multiline_str_eq!("", out.stderr.as_str()); - assert_multiline_str_eq!(expected, out.stdout.as_str()); - assert!(out.status.success()); - } - - fn expect_failure(input: &str, expected: &str) { - let out = helpers::repl_eval(input); - - // there may be some other stuff printed (e.g. unification errors) - // so skip till the header of the first error - match out.stdout.find(ERROR_MESSAGE_START) { - Some(index) => { - assert_multiline_str_eq!("", out.stderr.as_str()); - assert_multiline_str_eq!(expected, &out.stdout[index..]); - assert!(out.status.success()); - } - None => { - assert_multiline_str_eq!("", out.stderr.as_str()); - assert!(out.status.success()); - panic!( - "I expected a failure, but there is no error message in stdout:\n\n{}", - &out.stdout - ); - } - } - } - - #[test] - fn literal_0() { - expect_success("0", "0 : Num *"); - } - - #[test] - fn literal_42() { - expect_success("42", "42 : Num *"); - } - - #[test] - fn literal_0x0() { - expect_success("0x0", "0 : Int *"); - } - - #[test] - fn literal_0x42() { - expect_success("0x42", "66 : Int *"); - } - - #[test] - fn literal_0point0() { - expect_success("0.0", "0 : Float *"); - } - - #[test] - fn literal_4point2() { - expect_success("4.2", "4.2 : Float *"); - } - - #[test] - fn num_addition() { - expect_success("1 + 2", "3 : Num *"); - } - - #[test] - fn int_addition() { - expect_success("0x1 + 2", "3 : I64"); - } - - #[test] - fn float_addition() { - expect_success("1.1 + 2", "3.1 : F64"); - } - - #[test] - fn num_rem() { - expect_success("299 % 10", "Ok 9 : Result (Int *) [ DivByZero ]*"); - } - - #[test] - fn num_floor_division_success() { - expect_success("Num.divFloor 4 3", "Ok 1 : Result (Int *) [ DivByZero ]*"); - } - - #[test] - fn num_floor_division_divby_zero() { - expect_success( - "Num.divFloor 4 0", - "Err DivByZero : Result (Int *) [ DivByZero ]*", - ); - } - - #[test] - fn num_ceil_division_success() { - expect_success("Num.divCeil 4 3", "Ok 2 : Result (Int *) [ DivByZero ]*") - } - - #[test] - fn bool_in_record() { - expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }"); - expect_success( - "{ z: { y: { x: 1 == 1 } } }", - "{ z: { y: { x: True } } } : { z : { y : { x : Bool } } }", - ); - expect_success("{ x: 1 != 1 }", "{ x: False } : { x : Bool }"); - expect_success( - "{ x: 1 == 1, y: 1 != 1 }", - "{ x: True, y: False } : { x : Bool, y : Bool }", - ); - } - - #[test] - fn bool_basic_equality() { - expect_success("1 == 1", "True : Bool"); - expect_success("1 != 1", "False : Bool"); - } - - #[test] - fn arbitrary_tag_unions() { - expect_success("if 1 == 1 then Red else Green", "Red : [ Green, Red ]*"); - expect_success("if 1 != 1 then Red else Green", "Green : [ Green, Red ]*"); - } - - #[test] - fn tag_without_arguments() { - expect_success("True", "True : [ True ]*"); - expect_success("False", "False : [ False ]*"); - } - - #[test] - fn byte_tag_union() { - expect_success( - "if 1 == 1 then Red else if 1 == 1 then Green else Blue", - "Red : [ Blue, Green, Red ]*", - ); - - expect_success( - "{ y: { x: if 1 == 1 then Red else if 1 == 1 then Green else Blue } }", - "{ y: { x: Red } } : { y : { x : [ Blue, Green, Red ]* } }", - ); - } - - #[test] - fn tag_in_record() { - expect_success( - "{ x: Foo 1 2 3, y : 4 }", - "{ x: Foo 1 2 3, y: 4 } : { x : [ Foo (Num *) (Num *) (Num *) ]*, y : Num * }", - ); - expect_success( - "{ x: Foo 1 2 3 }", - "{ x: Foo 1 2 3 } : { x : [ Foo (Num *) (Num *) (Num *) ]* }", - ); - expect_success("{ x: Unit }", "{ x: Unit } : { x : [ Unit ]* }"); - } - - #[test] - fn single_element_tag_union() { - expect_success("True 1", "True 1 : [ True (Num *) ]*"); - expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) (Float *) ]*"); - } - - #[test] - fn newtype_of_unit() { - expect_success("Foo Bar", "Foo Bar : [ Foo [ Bar ]* ]*"); - } - - #[test] - fn newtype_of_big_data() { - expect_success( - indoc!( - r#" - Either a b : [ Left a, Right b ] - lefty : Either Str Str - lefty = Left "loosey" - A lefty - "# - ), - r#"A (Left "loosey") : [ A (Either Str Str) ]*"#, - ) - } - - #[test] - fn newtype_nested() { - expect_success( - indoc!( - r#" - Either a b : [ Left a, Right b ] - lefty : Either Str Str - lefty = Left "loosey" - A (B (C lefty)) - "# - ), - r#"A (B (C (Left "loosey"))) : [ A [ B [ C (Either Str Str) ]* ]* ]*"#, - ) - } - - #[test] - fn newtype_of_big_of_newtype() { - expect_success( - indoc!( - r#" - Big a : [ Big a [ Wrapper [ Newtype a ] ] ] - big : Big Str - big = Big "s" (Wrapper (Newtype "t")) - A big - "# - ), - r#"A (Big "s" (Wrapper (Newtype "t"))) : [ A (Big Str) ]*"#, - ) - } - - #[test] - fn tag_with_arguments() { - expect_success("True 1", "True 1 : [ True (Num *) ]*"); - - expect_success( - "if 1 == 1 then True 3 else False 3.14", - "True 3 : [ False (Float *), True (Num *) ]*", - ) - } - - #[test] - fn literal_empty_str() { - expect_success("\"\"", "\"\" : Str"); - } - - #[test] - fn literal_ascii_str() { - expect_success("\"Hello, World!\"", "\"Hello, World!\" : Str"); - } - - #[test] - fn literal_utf8_str() { - expect_success("\"👩‍👩‍👦‍👦\"", "\"👩‍👩‍👦‍👦\" : Str"); - } - - #[test] - fn str_concat() { - expect_success( - "Str.concat \"Hello, \" \"World!\"", - "\"Hello, World!\" : Str", - ); - } - - #[test] - fn str_count_graphemes() { - expect_success("Str.countGraphemes \"å🤔\"", "2 : Nat"); - } - - #[test] - fn literal_empty_list() { - expect_success("[]", "[] : List *"); - } - - #[test] - fn literal_empty_list_empty_record() { - expect_success("[ {} ]", "[ {} ] : List {}"); - } - - #[test] - fn literal_num_list() { - expect_success("[ 1, 2, 3 ]", "[ 1, 2, 3 ] : List (Num *)"); - } - - #[test] - fn literal_int_list() { - expect_success("[ 0x1, 0x2, 0x3 ]", "[ 1, 2, 3 ] : List (Int *)"); - } - - #[test] - fn literal_float_list() { - expect_success("[ 1.1, 2.2, 3.3 ]", "[ 1.1, 2.2, 3.3 ] : List (Float *)"); - } - - #[test] - fn literal_string_list() { - expect_success(r#"[ "a", "b", "cd" ]"#, r#"[ "a", "b", "cd" ] : List Str"#); - } - - #[test] - fn nested_string_list() { - expect_success( - r#"[ [ [ "a", "b", "cd" ], [ "y", "z" ] ], [ [] ], [] ]"#, - r#"[ [ [ "a", "b", "cd" ], [ "y", "z" ] ], [ [] ], [] ] : List (List (List Str))"#, - ); - } - - #[test] - fn nested_num_list() { - expect_success( - r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ]"#, - r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List (Num *)))"#, - ); - } - - #[test] - fn nested_int_list() { - expect_success( - r#"[ [ [ 4, 3, 2 ], [ 1, 0x0 ] ], [ [] ], [] ]"#, - r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List I64))"#, - ); - } - - #[test] - fn nested_float_list() { - expect_success( - r#"[ [ [ 4, 3, 2 ], [ 1, 0.0 ] ], [ [] ], [] ]"#, - r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List F64))"#, - ); - } - - #[test] - fn num_bitwise_and() { - expect_success("Num.bitwiseAnd 20 20", "20 : Int *"); - - expect_success("Num.bitwiseAnd 25 10", "8 : Int *"); - - expect_success("Num.bitwiseAnd 200 0", "0 : Int *") - } - - #[test] - fn num_bitwise_xor() { - expect_success("Num.bitwiseXor 20 20", "0 : Int *"); - - expect_success("Num.bitwiseXor 15 14", "1 : Int *"); - - expect_success("Num.bitwiseXor 7 15", "8 : Int *"); - - expect_success("Num.bitwiseXor 200 0", "200 : Int *") - } - - #[test] - fn num_add_wrap() { - expect_success( - "Num.addWrap Num.maxI64 1", - "-9223372036854775808 : Int Signed64", - ); - } - - #[test] - fn num_sub_wrap() { - expect_success( - "Num.subWrap Num.minI64 1", - "9223372036854775807 : Int Signed64", - ); - } - - #[test] - fn num_mul_wrap() { - expect_success("Num.mulWrap Num.maxI64 2", "-2 : Int Signed64"); - } - - #[test] - fn num_add_checked() { - expect_success("Num.addChecked 1 1", "Ok 2 : Result (Num *) [ Overflow ]*"); - expect_success( - "Num.addChecked Num.maxI64 1", - "Err Overflow : Result I64 [ Overflow ]*", - ); - } - - #[test] - fn num_sub_checked() { - expect_success("Num.subChecked 1 1", "Ok 0 : Result (Num *) [ Overflow ]*"); - expect_success( - "Num.subChecked Num.minI64 1", - "Err Overflow : Result I64 [ Overflow ]*", - ); - } - - #[test] - fn num_mul_checked() { - expect_success( - "Num.mulChecked 20 2", - "Ok 40 : Result (Num *) [ Overflow ]*", - ); - expect_success( - "Num.mulChecked Num.maxI64 2", - "Err Overflow : Result I64 [ Overflow ]*", - ); - } - - #[test] - fn list_concat() { - expect_success( - "List.concat [ 1.1, 2.2 ] [ 3.3, 4.4, 5.5 ]", - "[ 1.1, 2.2, 3.3, 4.4, 5.5 ] : List (Float *)", - ); - } - - #[test] - fn list_contains() { - expect_success("List.contains [] 0", "False : Bool"); - expect_success("List.contains [ 1, 2, 3 ] 2", "True : Bool"); - expect_success("List.contains [ 1, 2, 3 ] 4", "False : Bool"); - } - - #[test] - fn list_sum() { - expect_success("List.sum []", "0 : Num *"); - expect_success("List.sum [ 1, 2, 3 ]", "6 : Num *"); - expect_success("List.sum [ 1.1, 2.2, 3.3 ]", "6.6 : F64"); - } - - #[test] - fn list_first() { - expect_success( - "List.first [ 12, 9, 6, 3 ]", - "Ok 12 : Result (Num *) [ ListWasEmpty ]*", - ); - expect_success( - "List.first []", - "Err ListWasEmpty : Result * [ ListWasEmpty ]*", - ); - } - - #[test] - fn list_last() { - expect_success( - "List.last [ 12, 9, 6, 3 ]", - "Ok 3 : Result (Num *) [ ListWasEmpty ]*", - ); - - expect_success( - "List.last []", - "Err ListWasEmpty : Result * [ ListWasEmpty ]*", - ); - } - - #[test] - fn empty_record() { - expect_success("{}", "{} : {}"); - } - - #[test] - fn basic_1_field_i64_record() { - // Even though this gets unwrapped at runtime, the repl should still - // report it as a record - expect_success("{ foo: 42 }", "{ foo: 42 } : { foo : Num * }"); - } - - #[test] - fn basic_1_field_f64_record() { - // Even though this gets unwrapped at runtime, the repl should still - // report it as a record - expect_success("{ foo: 4.2 }", "{ foo: 4.2 } : { foo : Float * }"); - } - - #[test] - fn nested_1_field_i64_record() { - // Even though this gets unwrapped at runtime, the repl should still - // report it as a record - expect_success( - "{ foo: { bar: { baz: 42 } } }", - "{ foo: { bar: { baz: 42 } } } : { foo : { bar : { baz : Num * } } }", - ); - } - - #[test] - fn nested_1_field_f64_record() { - // Even though this gets unwrapped at runtime, the repl should still - // report it as a record - expect_success( - "{ foo: { bar: { baz: 4.2 } } }", - "{ foo: { bar: { baz: 4.2 } } } : { foo : { bar : { baz : Float * } } }", - ); - } - - #[test] - fn basic_2_field_i64_record() { - expect_success( - "{ foo: 0x4, bar: 0x2 }", - "{ bar: 2, foo: 4 } : { bar : Int *, foo : Int * }", - ); - } - - #[test] - fn basic_2_field_f64_record() { - expect_success( - "{ foo: 4.1, bar: 2.3 }", - "{ bar: 2.3, foo: 4.1 } : { bar : Float *, foo : Float * }", - ); - } - - #[test] - fn basic_2_field_mixed_record() { - expect_success( - "{ foo: 4.1, bar: 2 }", - "{ bar: 2, foo: 4.1 } : { bar : Num *, foo : Float * }", - ); - } - - #[test] - fn basic_3_field_record() { - expect_success( - "{ foo: 4.1, bar: 2, baz: 0x5 }", - "{ bar: 2, baz: 5, foo: 4.1 } : { bar : Num *, baz : Int *, foo : Float * }", - ); - } - - #[test] - fn list_of_1_field_records() { - // Even though these get unwrapped at runtime, the repl should still - // report them as records - expect_success("[ { foo: 42 } ]", "[ { foo: 42 } ] : List { foo : Num * }"); - } - - #[test] - fn list_of_2_field_records() { - expect_success( - "[ { foo: 4.1, bar: 2 } ]", - "[ { bar: 2, foo: 4.1 } ] : List { bar : Num *, foo : Float * }", - ); - } - - #[test] - fn three_element_record() { - // if this tests turns out to fail on 32-bit platforms, look at jit_to_ast_help - expect_success( - "{ a: 1, b: 2, c: 3 }", - "{ a: 1, b: 2, c: 3 } : { a : Num *, b : Num *, c : Num * }", - ); - } - - #[test] - fn four_element_record() { - // if this tests turns out to fail on 32-bit platforms, look at jit_to_ast_help - expect_success( - "{ a: 1, b: 2, c: 3, d: 4 }", - "{ a: 1, b: 2, c: 3, d: 4 } : { a : Num *, b : Num *, c : Num *, d : Num * }", - ); - } - - // #[test] - // fn multiline_string() { - // // If a string contains newlines, format it as a multiline string in the output - // expect_success(r#""\n\nhi!\n\n""#, "\"\"\"\n\nhi!\n\n\"\"\""); - // } - - #[test] - fn list_of_3_field_records() { - expect_success( - "[ { foo: 4.1, bar: 2, baz: 0x3 } ]", - "[ { bar: 2, baz: 3, foo: 4.1 } ] : List { bar : Num *, baz : Int *, foo : Float * }", - ); - } - - #[test] - fn identity_lambda() { - expect_success("\\x -> x", " : a -> a"); - } - - #[test] - fn sum_lambda() { - expect_success("\\x, y -> x + y", " : Num a, Num a -> Num a"); - } - - #[test] - fn stdlib_function() { - expect_success("Num.abs", " : Num a -> Num a"); - } - - #[test] - fn too_few_args() { - expect_failure( - "Num.add 2", - indoc!( - r#" - ── TOO FEW ARGS ──────────────────────────────────────────────────────────────── - - The add function expects 2 arguments, but it got only 1: - - 4│ Num.add 2 - ^^^^^^^ - - Roc does not allow functions to be partially applied. Use a closure to - make partial application explicit. - "# - ), - ); - } - - #[test] - fn type_problem() { - expect_failure( - "1 + \"\"", - indoc!( - r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── - - The 2nd argument to add is not what I expect: - - 4│ 1 + "" - ^^ - - This argument is a string of type: - - Str - - But add needs the 2nd argument to be: - - Num a - "# - ), - ); - } - - #[test] - fn issue_2149() { - expect_success(r#"Str.toI8 "127""#, "Ok 127 : Result I8 [ InvalidNumStr ]*"); - expect_success( - r#"Str.toI8 "128""#, - "Err InvalidNumStr : Result I8 [ InvalidNumStr ]*", - ); - expect_success( - r#"Str.toI16 "32767""#, - "Ok 32767 : Result I16 [ InvalidNumStr ]*", - ); - expect_success( - r#"Str.toI16 "32768""#, - "Err InvalidNumStr : Result I16 [ InvalidNumStr ]*", - ); - } - - #[test] - fn multiline_input() { - expect_success( - indoc!( - r#" - a : Str - a = "123" - a - "# - ), - r#""123" : Str"#, - ) - } - - #[test] - fn recursive_tag_union_flat_variant() { - expect_success( - indoc!( - r#" - Expr : [ Sym Str, Add Expr Expr ] - s : Expr - s = Sym "levitating" - s - "# - ), - r#"Sym "levitating" : Expr"#, - ) - } - - #[test] - fn large_recursive_tag_union_flat_variant() { - expect_success( - // > 7 variants so that to force tag storage alongside the data - indoc!( - r#" - Item : [ A Str, B Str, C Str, D Str, E Str, F Str, G Str, H Str, I Str, J Str, K Item ] - s : Item - s = H "woo" - s - "# - ), - r#"H "woo" : Item"#, - ) - } - - #[test] - fn recursive_tag_union_recursive_variant() { - expect_success( - indoc!( - r#" - Expr : [ Sym Str, Add Expr Expr ] - s : Expr - s = Add (Add (Sym "one") (Sym "two")) (Sym "four") - s - "# - ), - r#"Add (Add (Sym "one") (Sym "two")) (Sym "four") : Expr"#, - ) - } - - #[test] - fn large_recursive_tag_union_recursive_variant() { - expect_success( - // > 7 variants so that to force tag storage alongside the data - indoc!( - r#" - Item : [ A Str, B Str, C Str, D Str, E Str, F Str, G Str, H Str, I Str, J Str, K Item, L Item ] - s : Item - s = K (L (E "woo")) - s - "# - ), - r#"K (L (E "woo")) : Item"#, - ) - } - - #[test] - fn recursive_tag_union_into_flat_tag_union() { - expect_success( - indoc!( - r#" - Item : [ One [ A Str, B Str ], Deep Item ] - i : Item - i = Deep (One (A "woo")) - i - "# - ), - r#"Deep (One (A "woo")) : Item"#, - ) - } - - #[test] - fn non_nullable_unwrapped_tag_union() { - expect_success( - indoc!( - r#" - RoseTree a : [ Tree a (List (RoseTree a)) ] - e1 : RoseTree Str - e1 = Tree "e1" [] - e2 : RoseTree Str - e2 = Tree "e2" [] - combo : RoseTree Str - combo = Tree "combo" [e1, e2] - combo - "# - ), - r#"Tree "combo" [ Tree "e1" [], Tree "e2" [] ] : RoseTree Str"#, - ) - } - - #[test] - fn nullable_unwrapped_tag_union() { - expect_success( - indoc!( - r#" - LinkedList a : [ Nil, Cons a (LinkedList a) ] - c1 : LinkedList Str - c1 = Cons "Red" Nil - c2 : LinkedList Str - c2 = Cons "Yellow" c1 - c3 : LinkedList Str - c3 = Cons "Green" c2 - c3 - "# - ), - r#"Cons "Green" (Cons "Yellow" (Cons "Red" Nil)) : LinkedList Str"#, - ) - } - - #[test] - fn nullable_wrapped_tag_union() { - expect_success( - indoc!( - r#" - Container a : [ Empty, Whole a, Halved (Container a) (Container a) ] - - meats : Container Str - meats = Halved (Whole "Brisket") (Whole "Ribs") - - sides : Container Str - sides = Halved (Whole "Coleslaw") Empty - - bbqPlate : Container Str - bbqPlate = Halved meats sides - - bbqPlate - "# - ), - r#"Halved (Halved (Whole "Brisket") (Whole "Ribs")) (Halved (Whole "Coleslaw") Empty) : Container Str"#, - ) - } - - #[test] - fn large_nullable_wrapped_tag_union() { - // > 7 non-empty variants so that to force tag storage alongside the data - expect_success( - indoc!( - r#" - Cont a : [ Empty, S1 a, S2 a, S3 a, S4 a, S5 a, S6 a, S7 a, Tup (Cont a) (Cont a) ] - - fst : Cont Str - fst = Tup (S1 "S1") (S2 "S2") - - snd : Cont Str - snd = Tup (S5 "S5") Empty - - tup : Cont Str - tup = Tup fst snd - - tup - "# - ), - r#"Tup (Tup (S1 "S1") (S2 "S2")) (Tup (S5 "S5") Empty) : Cont Str"#, - ) - } - - #[test] - fn issue_2300() { - expect_success( - r#"\Email str -> str == """#, - r#" : [ Email Str ] -> Bool"#, - ) - } - - #[test] - fn function_in_list() { - expect_success( - r#"[\x -> x + 1, \s -> s * 2]"#, - r#"[ , ] : List (Num a -> Num a)"#, - ) - } - - #[test] - fn function_in_record() { - expect_success( - r#"{ n: 1, adder: \x -> x + 1 }"#, - r#"{ adder: , n: } : { adder : Num a -> Num a, n : Num * }"#, - ) - } - - #[test] - fn function_in_unwrapped_record() { - expect_success( - r#"{ adder: \x -> x + 1 }"#, - r#"{ adder: } : { adder : Num a -> Num a }"#, - ) - } - - #[test] - fn function_in_tag() { - expect_success( - r#"Adder (\x -> x + 1)"#, - r#"Adder : [ Adder (Num a -> Num a) ]*"#, - ) - } - - #[test] - fn newtype_of_record_of_tag_of_record_of_tag() { - expect_success( - r#"A {b: C {d: 1}}"#, - r#"A { b: C { d: 1 } } : [ A { b : [ C { d : Num * } ]* } ]*"#, - ) - } - - #[test] - fn print_u8s() { - expect_success( - indoc!( - r#" - x : U8 - x = 129 - x - "# - ), - "129 : U8", - ) - } - - #[test] - fn parse_problem() { - expect_failure( - "add m n = m + n", - indoc!( - r#" - ── ARGUMENTS BEFORE EQUALS ───────────────────────────────────────────────────── - - I am partway through parsing a definition, but I got stuck here: - - 1│ app "app" provides [ replOutput ] to "./platform" - 2│ - 3│ replOutput = - 4│ add m n = m + n - ^^^ - - Looks like you are trying to define a function. In roc, functions are - always written as a lambda, like increment = \n -> n + 1. - "# - ), - ); - } - - #[test] - fn mono_problem() { - expect_failure( - r#" - t : [A, B, C] - t = A - - when t is - A -> "a" - "#, - indoc!( - r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── - - This when does not cover all the possibilities: - - 7│> when t is - 8│> A -> "a" - - Other possibilities include: - - B - C - - I would have to crash if I saw one of those! Add branches for them! - - - Enter an expression, or :help, or :exit/:q."# - ), - ); - } - - #[test] - fn issue_2343_complete_mono_with_shadowed_vars() { - expect_failure( - indoc!( - r#" - b = False - f = \b -> - when b is - True -> 5 - False -> 15 - f b - "# - ), - indoc!( - r#" - ── DUPLICATE NAME ────────────────────────────────────────────────────────────── - - The b name is first defined here: - - 4│ b = False - ^ - - But then it's defined a second time here: - - 5│ f = \b -> - ^ - - Since these variables have the same name, it's easy to use the wrong - one on accident. Give one of them a new name. - "# - ), - ); - } -} diff --git a/cli_utils/src/helpers.rs b/cli_utils/src/helpers.rs index 8167fe38eb..e83556176e 100644 --- a/cli_utils/src/helpers.rs +++ b/cli_utils/src/helpers.rs @@ -4,7 +4,6 @@ extern crate roc_load; extern crate roc_module; extern crate tempfile; -use roc_cli::repl::{INSTRUCTIONS, WELCOME_MESSAGE}; use serde::Deserialize; use serde_xml_rs::from_str; use std::env; @@ -306,88 +305,3 @@ pub fn known_bad_file(file_name: &str) -> PathBuf { path } - -#[allow(dead_code)] -pub fn repl_eval(input: &str) -> Out { - let mut cmd = Command::new(path_to_roc_binary()); - - cmd.arg("repl"); - - let mut child = cmd - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .expect("failed to execute compiled `roc` binary in CLI test"); - - { - let stdin = child.stdin.as_mut().expect("Failed to open stdin"); - - // Send the input expression - stdin - .write_all(input.as_bytes()) - .expect("Failed to write input to stdin"); - - // Evaluate the expression - stdin - .write_all(b"\n") - .expect("Failed to write newline to stdin"); - - // Gracefully exit the repl - stdin - .write_all(b":exit\n") - .expect("Failed to write :exit to stdin"); - } - - let output = child - .wait_with_output() - .expect("Error waiting for REPL child process to exit."); - - // Remove the initial instructions from the output. - - let expected_instructions = format!("{}{}", WELCOME_MESSAGE, INSTRUCTIONS); - let stdout = String::from_utf8(output.stdout).unwrap(); - - assert!( - stdout.starts_with(&expected_instructions), - "Unexpected repl output: {}", - stdout - ); - - let (_, answer) = stdout.split_at(expected_instructions.len()); - let answer = if answer.is_empty() { - // The repl crashed before completing the evaluation. - // This is most likely due to a segfault. - if output.status.to_string() == "signal: 11" { - panic!( - "repl segfaulted during the test. Stderr was {:?}", - String::from_utf8(output.stderr).unwrap() - ); - } else { - panic!("repl exited unexpectedly before finishing evaluation. Exit status was {:?} and stderr was {:?}", output.status, String::from_utf8(output.stderr).unwrap()); - } - } else { - let expected_after_answer = "\n".to_string(); - - assert!( - answer.ends_with(&expected_after_answer), - "Unexpected repl output after answer: {}", - answer - ); - - // Use [1..] to trim the leading '\n' - // and (len - 1) to trim the trailing '\n' - let (answer, _) = answer[1..].split_at(answer.len() - expected_after_answer.len() - 1); - - // Remove ANSI escape codes from the answer - for example: - // - // Before: "42 \u{1b}[35m:\u{1b}[0m Num *" - // After: "42 : Num *" - strip_ansi_escapes::strip(answer).unwrap() - }; - - Out { - stdout: String::from_utf8(answer).unwrap(), - stderr: String::from_utf8(output.stderr).unwrap(), - status: output.status, - } -} diff --git a/repl_cli/Cargo.toml b/repl_cli/Cargo.toml index 99b33cc5d8..00ab87922c 100644 --- a/repl_cli/Cargo.toml +++ b/repl_cli/Cargo.toml @@ -16,6 +16,11 @@ roc_mono = {path = "../compiler/mono"} roc_parse = {path = "../compiler/parse"} roc_repl_eval = {path = "../repl_eval"} +[dev-dependencies] +indoc = "1.0.3" +roc_test_utils = {path = "../test_utils"} +strip-ansi-escapes = "0.1.1" + [lib] name = "roc_repl_cli" path = "src/lib.rs" diff --git a/repl_cli/src/lib.rs b/repl_cli/src/lib.rs index 0247b7b8d8..1d1f8b2901 100644 --- a/repl_cli/src/lib.rs +++ b/repl_cli/src/lib.rs @@ -8,6 +8,9 @@ use std::io; use roc_parse::parser::{EExpr, ELambda, SyntaxError}; use roc_repl_eval::gen::{gen_and_eval, ReplOutput}; +#[cfg(test)] +mod tests; + const BLUE: &str = "\u{001b}[36m"; const PINK: &str = "\u{001b}[35m"; const END_COL: &str = "\u{001b}[0m"; @@ -27,12 +30,6 @@ pub const INSTRUCTIONS: &str = "Enter an expression, or :help, or :exit/:q.\n"; pub const PROMPT: &str = concatcp!("\n", BLUE, "»", END_COL, " "); pub const CONT_PROMPT: &str = concatcp!(BLUE, "…", END_COL, " "); -// mod app_memory; -// #[cfg(feature = "llvm")] -// mod eval; -// #[cfg(feature = "llvm")] -// mod gen; - #[derive(Completer, Helper, Hinter)] struct ReplHelper { validator: InputValidator, diff --git a/repl_cli/src/tests.rs b/repl_cli/src/tests.rs new file mode 100644 index 0000000000..aa0022384f --- /dev/null +++ b/repl_cli/src/tests.rs @@ -0,0 +1,1078 @@ +use indoc::indoc; +use std::env; +use std::io::Write; +use std::path::PathBuf; +use std::process::{Command, ExitStatus, Stdio}; + +use roc_test_utils::assert_multiline_str_eq; + +use crate::{INSTRUCTIONS, WELCOME_MESSAGE}; + +const ERROR_MESSAGE_START: char = '─'; + +#[derive(Debug)] +pub struct Out { + pub stdout: String, + pub stderr: String, + pub status: ExitStatus, +} + +pub fn path_to_roc_binary() -> PathBuf { + // Adapted from https://github.com/volta-cli/volta/blob/cefdf7436a15af3ce3a38b8fe53bb0cfdb37d3dd/tests/acceptance/support/sandbox.rs#L680 + // by the Volta Contributors - license information can be found in + // the LEGAL_DETAILS file in the root directory of this distribution. + // + // Thank you, Volta contributors! + let mut path = env::var_os("CARGO_BIN_PATH") + .map(PathBuf::from) + .or_else(|| { + env::current_exe().ok().map(|mut path| { + path.pop(); + if path.ends_with("deps") { + path.pop(); + } + path + }) + }) + .unwrap_or_else(|| panic!("CARGO_BIN_PATH wasn't set, and couldn't be inferred from context. Can't run CLI tests.")); + + path.push("roc"); + + path +} + +fn repl_eval(input: &str) -> Out { + let mut cmd = Command::new(path_to_roc_binary()); + + cmd.arg("repl"); + + let mut child = cmd + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("failed to execute compiled `roc` binary in CLI test"); + + { + let stdin = child.stdin.as_mut().expect("Failed to open stdin"); + + // Send the input expression + stdin + .write_all(input.as_bytes()) + .expect("Failed to write input to stdin"); + + // Evaluate the expression + stdin + .write_all(b"\n") + .expect("Failed to write newline to stdin"); + + // Gracefully exit the repl + stdin + .write_all(b":exit\n") + .expect("Failed to write :exit to stdin"); + } + + let output = child + .wait_with_output() + .expect("Error waiting for REPL child process to exit."); + + // Remove the initial instructions from the output. + + let expected_instructions = format!("{}{}", WELCOME_MESSAGE, INSTRUCTIONS); + let stdout = String::from_utf8(output.stdout).unwrap(); + + assert!( + stdout.starts_with(&expected_instructions), + "Unexpected repl output: {}", + stdout + ); + + let (_, answer) = stdout.split_at(expected_instructions.len()); + let answer = if answer.is_empty() { + // The repl crashed before completing the evaluation. + // This is most likely due to a segfault. + if output.status.to_string() == "signal: 11" { + panic!( + "repl segfaulted during the test. Stderr was {:?}", + String::from_utf8(output.stderr).unwrap() + ); + } else { + panic!("repl exited unexpectedly before finishing evaluation. Exit status was {:?} and stderr was {:?}", output.status, String::from_utf8(output.stderr).unwrap()); + } + } else { + let expected_after_answer = "\n".to_string(); + + assert!( + answer.ends_with(&expected_after_answer), + "Unexpected repl output after answer: {}", + answer + ); + + // Use [1..] to trim the leading '\n' + // and (len - 1) to trim the trailing '\n' + let (answer, _) = answer[1..].split_at(answer.len() - expected_after_answer.len() - 1); + + // Remove ANSI escape codes from the answer - for example: + // + // Before: "42 \u{1b}[35m:\u{1b}[0m Num *" + // After: "42 : Num *" + strip_ansi_escapes::strip(answer).unwrap() + }; + + Out { + stdout: String::from_utf8(answer).unwrap(), + stderr: String::from_utf8(output.stderr).unwrap(), + status: output.status, + } +} + +fn expect_success(input: &str, expected: &str) { + let out = repl_eval(input); + + assert_multiline_str_eq!("", out.stderr.as_str()); + assert_multiline_str_eq!(expected, out.stdout.as_str()); + assert!(out.status.success()); +} + +fn expect_failure(input: &str, expected: &str) { + let out = repl_eval(input); + + // there may be some other stuff printed (e.g. unification errors) + // so skip till the header of the first error + match out.stdout.find(ERROR_MESSAGE_START) { + Some(index) => { + assert_multiline_str_eq!("", out.stderr.as_str()); + assert_multiline_str_eq!(expected, &out.stdout[index..]); + assert!(out.status.success()); + } + None => { + assert_multiline_str_eq!("", out.stderr.as_str()); + assert!(out.status.success()); + panic!( + "I expected a failure, but there is no error message in stdout:\n\n{}", + &out.stdout + ); + } + } +} + +#[test] +fn literal_0() { + expect_success("0", "0 : Num *"); +} + +#[test] +fn literal_42() { + expect_success("42", "42 : Num *"); +} + +#[test] +fn literal_0x0() { + expect_success("0x0", "0 : Int *"); +} + +#[test] +fn literal_0x42() { + expect_success("0x42", "66 : Int *"); +} + +#[test] +fn literal_0point0() { + expect_success("0.0", "0 : Float *"); +} + +#[test] +fn literal_4point2() { + expect_success("4.2", "4.2 : Float *"); +} + +#[test] +fn num_addition() { + expect_success("1 + 2", "3 : Num *"); +} + +#[test] +fn int_addition() { + expect_success("0x1 + 2", "3 : I64"); +} + +#[test] +fn float_addition() { + expect_success("1.1 + 2", "3.1 : F64"); +} + +#[test] +fn num_rem() { + expect_success("299 % 10", "Ok 9 : Result (Int *) [ DivByZero ]*"); +} + +#[test] +fn num_floor_division_success() { + expect_success("Num.divFloor 4 3", "Ok 1 : Result (Int *) [ DivByZero ]*"); +} + +#[test] +fn num_floor_division_divby_zero() { + expect_success( + "Num.divFloor 4 0", + "Err DivByZero : Result (Int *) [ DivByZero ]*", + ); +} + +#[test] +fn num_ceil_division_success() { + expect_success("Num.divCeil 4 3", "Ok 2 : Result (Int *) [ DivByZero ]*") +} + +#[test] +fn bool_in_record() { + expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }"); + expect_success( + "{ z: { y: { x: 1 == 1 } } }", + "{ z: { y: { x: True } } } : { z : { y : { x : Bool } } }", + ); + expect_success("{ x: 1 != 1 }", "{ x: False } : { x : Bool }"); + expect_success( + "{ x: 1 == 1, y: 1 != 1 }", + "{ x: True, y: False } : { x : Bool, y : Bool }", + ); +} + +#[test] +fn bool_basic_equality() { + expect_success("1 == 1", "True : Bool"); + expect_success("1 != 1", "False : Bool"); +} + +#[test] +fn arbitrary_tag_unions() { + expect_success("if 1 == 1 then Red else Green", "Red : [ Green, Red ]*"); + expect_success("if 1 != 1 then Red else Green", "Green : [ Green, Red ]*"); +} + +#[test] +fn tag_without_arguments() { + expect_success("True", "True : [ True ]*"); + expect_success("False", "False : [ False ]*"); +} + +#[test] +fn byte_tag_union() { + expect_success( + "if 1 == 1 then Red else if 1 == 1 then Green else Blue", + "Red : [ Blue, Green, Red ]*", + ); + + expect_success( + "{ y: { x: if 1 == 1 then Red else if 1 == 1 then Green else Blue } }", + "{ y: { x: Red } } : { y : { x : [ Blue, Green, Red ]* } }", + ); +} + +#[test] +fn tag_in_record() { + expect_success( + "{ x: Foo 1 2 3, y : 4 }", + "{ x: Foo 1 2 3, y: 4 } : { x : [ Foo (Num *) (Num *) (Num *) ]*, y : Num * }", + ); + expect_success( + "{ x: Foo 1 2 3 }", + "{ x: Foo 1 2 3 } : { x : [ Foo (Num *) (Num *) (Num *) ]* }", + ); + expect_success("{ x: Unit }", "{ x: Unit } : { x : [ Unit ]* }"); +} + +#[test] +fn single_element_tag_union() { + expect_success("True 1", "True 1 : [ True (Num *) ]*"); + expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) (Float *) ]*"); +} + +#[test] +fn newtype_of_unit() { + expect_success("Foo Bar", "Foo Bar : [ Foo [ Bar ]* ]*"); +} + +#[test] +fn newtype_of_big_data() { + expect_success( + indoc!( + r#" + Either a b : [ Left a, Right b ] + lefty : Either Str Str + lefty = Left "loosey" + A lefty + "# + ), + r#"A (Left "loosey") : [ A (Either Str Str) ]*"#, + ) +} + +#[test] +fn newtype_nested() { + expect_success( + indoc!( + r#" + Either a b : [ Left a, Right b ] + lefty : Either Str Str + lefty = Left "loosey" + A (B (C lefty)) + "# + ), + r#"A (B (C (Left "loosey"))) : [ A [ B [ C (Either Str Str) ]* ]* ]*"#, + ) +} + +#[test] +fn newtype_of_big_of_newtype() { + expect_success( + indoc!( + r#" + Big a : [ Big a [ Wrapper [ Newtype a ] ] ] + big : Big Str + big = Big "s" (Wrapper (Newtype "t")) + A big + "# + ), + r#"A (Big "s" (Wrapper (Newtype "t"))) : [ A (Big Str) ]*"#, + ) +} + +#[test] +fn tag_with_arguments() { + expect_success("True 1", "True 1 : [ True (Num *) ]*"); + + expect_success( + "if 1 == 1 then True 3 else False 3.14", + "True 3 : [ False (Float *), True (Num *) ]*", + ) +} + +#[test] +fn literal_empty_str() { + expect_success("\"\"", "\"\" : Str"); +} + +#[test] +fn literal_ascii_str() { + expect_success("\"Hello, World!\"", "\"Hello, World!\" : Str"); +} + +#[test] +fn literal_utf8_str() { + expect_success("\"👩‍👩‍👦‍👦\"", "\"👩‍👩‍👦‍👦\" : Str"); +} + +#[test] +fn str_concat() { + expect_success( + "Str.concat \"Hello, \" \"World!\"", + "\"Hello, World!\" : Str", + ); +} + +#[test] +fn str_count_graphemes() { + expect_success("Str.countGraphemes \"å🤔\"", "2 : Nat"); +} + +#[test] +fn literal_empty_list() { + expect_success("[]", "[] : List *"); +} + +#[test] +fn literal_empty_list_empty_record() { + expect_success("[ {} ]", "[ {} ] : List {}"); +} + +#[test] +fn literal_num_list() { + expect_success("[ 1, 2, 3 ]", "[ 1, 2, 3 ] : List (Num *)"); +} + +#[test] +fn literal_int_list() { + expect_success("[ 0x1, 0x2, 0x3 ]", "[ 1, 2, 3 ] : List (Int *)"); +} + +#[test] +fn literal_float_list() { + expect_success("[ 1.1, 2.2, 3.3 ]", "[ 1.1, 2.2, 3.3 ] : List (Float *)"); +} + +#[test] +fn literal_string_list() { + expect_success(r#"[ "a", "b", "cd" ]"#, r#"[ "a", "b", "cd" ] : List Str"#); +} + +#[test] +fn nested_string_list() { + expect_success( + r#"[ [ [ "a", "b", "cd" ], [ "y", "z" ] ], [ [] ], [] ]"#, + r#"[ [ [ "a", "b", "cd" ], [ "y", "z" ] ], [ [] ], [] ] : List (List (List Str))"#, + ); +} + +#[test] +fn nested_num_list() { + expect_success( + r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ]"#, + r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List (Num *)))"#, + ); +} + +#[test] +fn nested_int_list() { + expect_success( + r#"[ [ [ 4, 3, 2 ], [ 1, 0x0 ] ], [ [] ], [] ]"#, + r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List I64))"#, + ); +} + +#[test] +fn nested_float_list() { + expect_success( + r#"[ [ [ 4, 3, 2 ], [ 1, 0.0 ] ], [ [] ], [] ]"#, + r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List F64))"#, + ); +} + +#[test] +fn num_bitwise_and() { + expect_success("Num.bitwiseAnd 20 20", "20 : Int *"); + + expect_success("Num.bitwiseAnd 25 10", "8 : Int *"); + + expect_success("Num.bitwiseAnd 200 0", "0 : Int *") +} + +#[test] +fn num_bitwise_xor() { + expect_success("Num.bitwiseXor 20 20", "0 : Int *"); + + expect_success("Num.bitwiseXor 15 14", "1 : Int *"); + + expect_success("Num.bitwiseXor 7 15", "8 : Int *"); + + expect_success("Num.bitwiseXor 200 0", "200 : Int *") +} + +#[test] +fn num_add_wrap() { + expect_success( + "Num.addWrap Num.maxI64 1", + "-9223372036854775808 : Int Signed64", + ); +} + +#[test] +fn num_sub_wrap() { + expect_success( + "Num.subWrap Num.minI64 1", + "9223372036854775807 : Int Signed64", + ); +} + +#[test] +fn num_mul_wrap() { + expect_success("Num.mulWrap Num.maxI64 2", "-2 : Int Signed64"); +} + +#[test] +fn num_add_checked() { + expect_success("Num.addChecked 1 1", "Ok 2 : Result (Num *) [ Overflow ]*"); + expect_success( + "Num.addChecked Num.maxI64 1", + "Err Overflow : Result I64 [ Overflow ]*", + ); +} + +#[test] +fn num_sub_checked() { + expect_success("Num.subChecked 1 1", "Ok 0 : Result (Num *) [ Overflow ]*"); + expect_success( + "Num.subChecked Num.minI64 1", + "Err Overflow : Result I64 [ Overflow ]*", + ); +} + +#[test] +fn num_mul_checked() { + expect_success( + "Num.mulChecked 20 2", + "Ok 40 : Result (Num *) [ Overflow ]*", + ); + expect_success( + "Num.mulChecked Num.maxI64 2", + "Err Overflow : Result I64 [ Overflow ]*", + ); +} + +#[test] +fn list_concat() { + expect_success( + "List.concat [ 1.1, 2.2 ] [ 3.3, 4.4, 5.5 ]", + "[ 1.1, 2.2, 3.3, 4.4, 5.5 ] : List (Float *)", + ); +} + +#[test] +fn list_contains() { + expect_success("List.contains [] 0", "False : Bool"); + expect_success("List.contains [ 1, 2, 3 ] 2", "True : Bool"); + expect_success("List.contains [ 1, 2, 3 ] 4", "False : Bool"); +} + +#[test] +fn list_sum() { + expect_success("List.sum []", "0 : Num *"); + expect_success("List.sum [ 1, 2, 3 ]", "6 : Num *"); + expect_success("List.sum [ 1.1, 2.2, 3.3 ]", "6.6 : F64"); +} + +#[test] +fn list_first() { + expect_success( + "List.first [ 12, 9, 6, 3 ]", + "Ok 12 : Result (Num *) [ ListWasEmpty ]*", + ); + expect_success( + "List.first []", + "Err ListWasEmpty : Result * [ ListWasEmpty ]*", + ); +} + +#[test] +fn list_last() { + expect_success( + "List.last [ 12, 9, 6, 3 ]", + "Ok 3 : Result (Num *) [ ListWasEmpty ]*", + ); + + expect_success( + "List.last []", + "Err ListWasEmpty : Result * [ ListWasEmpty ]*", + ); +} + +#[test] +fn empty_record() { + expect_success("{}", "{} : {}"); +} + +#[test] +fn basic_1_field_i64_record() { + // Even though this gets unwrapped at runtime, the repl should still + // report it as a record + expect_success("{ foo: 42 }", "{ foo: 42 } : { foo : Num * }"); +} + +#[test] +fn basic_1_field_f64_record() { + // Even though this gets unwrapped at runtime, the repl should still + // report it as a record + expect_success("{ foo: 4.2 }", "{ foo: 4.2 } : { foo : Float * }"); +} + +#[test] +fn nested_1_field_i64_record() { + // Even though this gets unwrapped at runtime, the repl should still + // report it as a record + expect_success( + "{ foo: { bar: { baz: 42 } } }", + "{ foo: { bar: { baz: 42 } } } : { foo : { bar : { baz : Num * } } }", + ); +} + +#[test] +fn nested_1_field_f64_record() { + // Even though this gets unwrapped at runtime, the repl should still + // report it as a record + expect_success( + "{ foo: { bar: { baz: 4.2 } } }", + "{ foo: { bar: { baz: 4.2 } } } : { foo : { bar : { baz : Float * } } }", + ); +} + +#[test] +fn basic_2_field_i64_record() { + expect_success( + "{ foo: 0x4, bar: 0x2 }", + "{ bar: 2, foo: 4 } : { bar : Int *, foo : Int * }", + ); +} + +#[test] +fn basic_2_field_f64_record() { + expect_success( + "{ foo: 4.1, bar: 2.3 }", + "{ bar: 2.3, foo: 4.1 } : { bar : Float *, foo : Float * }", + ); +} + +#[test] +fn basic_2_field_mixed_record() { + expect_success( + "{ foo: 4.1, bar: 2 }", + "{ bar: 2, foo: 4.1 } : { bar : Num *, foo : Float * }", + ); +} + +#[test] +fn basic_3_field_record() { + expect_success( + "{ foo: 4.1, bar: 2, baz: 0x5 }", + "{ bar: 2, baz: 5, foo: 4.1 } : { bar : Num *, baz : Int *, foo : Float * }", + ); +} + +#[test] +fn list_of_1_field_records() { + // Even though these get unwrapped at runtime, the repl should still + // report them as records + expect_success("[ { foo: 42 } ]", "[ { foo: 42 } ] : List { foo : Num * }"); +} + +#[test] +fn list_of_2_field_records() { + expect_success( + "[ { foo: 4.1, bar: 2 } ]", + "[ { bar: 2, foo: 4.1 } ] : List { bar : Num *, foo : Float * }", + ); +} + +#[test] +fn three_element_record() { + // if this tests turns out to fail on 32-bit platforms, look at jit_to_ast_help + expect_success( + "{ a: 1, b: 2, c: 3 }", + "{ a: 1, b: 2, c: 3 } : { a : Num *, b : Num *, c : Num * }", + ); +} + +#[test] +fn four_element_record() { + // if this tests turns out to fail on 32-bit platforms, look at jit_to_ast_help + expect_success( + "{ a: 1, b: 2, c: 3, d: 4 }", + "{ a: 1, b: 2, c: 3, d: 4 } : { a : Num *, b : Num *, c : Num *, d : Num * }", + ); +} + +// #[test] +// fn multiline_string() { +// // If a string contains newlines, format it as a multiline string in the output +// expect_success(r#""\n\nhi!\n\n""#, "\"\"\"\n\nhi!\n\n\"\"\""); +// } + +#[test] +fn list_of_3_field_records() { + expect_success( + "[ { foo: 4.1, bar: 2, baz: 0x3 } ]", + "[ { bar: 2, baz: 3, foo: 4.1 } ] : List { bar : Num *, baz : Int *, foo : Float * }", + ); +} + +#[test] +fn identity_lambda() { + expect_success("\\x -> x", " : a -> a"); +} + +#[test] +fn sum_lambda() { + expect_success("\\x, y -> x + y", " : Num a, Num a -> Num a"); +} + +#[test] +fn stdlib_function() { + expect_success("Num.abs", " : Num a -> Num a"); +} + +#[test] +fn too_few_args() { + expect_failure( + "Num.add 2", + indoc!( + r#" + ── TOO FEW ARGS ──────────────────────────────────────────────────────────────── + + The add function expects 2 arguments, but it got only 1: + + 4│ Num.add 2 + ^^^^^^^ + + Roc does not allow functions to be partially applied. Use a closure to + make partial application explicit. + "# + ), + ); +} + +#[test] +fn type_problem() { + expect_failure( + "1 + \"\"", + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 2nd argument to add is not what I expect: + + 4│ 1 + "" + ^^ + + This argument is a string of type: + + Str + + But add needs the 2nd argument to be: + + Num a + "# + ), + ); +} + +#[test] +fn issue_2149() { + expect_success(r#"Str.toI8 "127""#, "Ok 127 : Result I8 [ InvalidNumStr ]*"); + expect_success( + r#"Str.toI8 "128""#, + "Err InvalidNumStr : Result I8 [ InvalidNumStr ]*", + ); + expect_success( + r#"Str.toI16 "32767""#, + "Ok 32767 : Result I16 [ InvalidNumStr ]*", + ); + expect_success( + r#"Str.toI16 "32768""#, + "Err InvalidNumStr : Result I16 [ InvalidNumStr ]*", + ); +} + +#[test] +fn multiline_input() { + expect_success( + indoc!( + r#" + a : Str + a = "123" + a + "# + ), + r#""123" : Str"#, + ) +} + +#[test] +fn recursive_tag_union_flat_variant() { + expect_success( + indoc!( + r#" + Expr : [ Sym Str, Add Expr Expr ] + s : Expr + s = Sym "levitating" + s + "# + ), + r#"Sym "levitating" : Expr"#, + ) +} + +#[test] +fn large_recursive_tag_union_flat_variant() { + expect_success( + // > 7 variants so that to force tag storage alongside the data + indoc!( + r#" + Item : [ A Str, B Str, C Str, D Str, E Str, F Str, G Str, H Str, I Str, J Str, K Item ] + s : Item + s = H "woo" + s + "# + ), + r#"H "woo" : Item"#, + ) +} + +#[test] +fn recursive_tag_union_recursive_variant() { + expect_success( + indoc!( + r#" + Expr : [ Sym Str, Add Expr Expr ] + s : Expr + s = Add (Add (Sym "one") (Sym "two")) (Sym "four") + s + "# + ), + r#"Add (Add (Sym "one") (Sym "two")) (Sym "four") : Expr"#, + ) +} + +#[test] +fn large_recursive_tag_union_recursive_variant() { + expect_success( + // > 7 variants so that to force tag storage alongside the data + indoc!( + r#" + Item : [ A Str, B Str, C Str, D Str, E Str, F Str, G Str, H Str, I Str, J Str, K Item, L Item ] + s : Item + s = K (L (E "woo")) + s + "# + ), + r#"K (L (E "woo")) : Item"#, + ) +} + +#[test] +fn recursive_tag_union_into_flat_tag_union() { + expect_success( + indoc!( + r#" + Item : [ One [ A Str, B Str ], Deep Item ] + i : Item + i = Deep (One (A "woo")) + i + "# + ), + r#"Deep (One (A "woo")) : Item"#, + ) +} + +#[test] +fn non_nullable_unwrapped_tag_union() { + expect_success( + indoc!( + r#" + RoseTree a : [ Tree a (List (RoseTree a)) ] + e1 : RoseTree Str + e1 = Tree "e1" [] + e2 : RoseTree Str + e2 = Tree "e2" [] + combo : RoseTree Str + combo = Tree "combo" [e1, e2] + combo + "# + ), + r#"Tree "combo" [ Tree "e1" [], Tree "e2" [] ] : RoseTree Str"#, + ) +} + +#[test] +fn nullable_unwrapped_tag_union() { + expect_success( + indoc!( + r#" + LinkedList a : [ Nil, Cons a (LinkedList a) ] + c1 : LinkedList Str + c1 = Cons "Red" Nil + c2 : LinkedList Str + c2 = Cons "Yellow" c1 + c3 : LinkedList Str + c3 = Cons "Green" c2 + c3 + "# + ), + r#"Cons "Green" (Cons "Yellow" (Cons "Red" Nil)) : LinkedList Str"#, + ) +} + +#[test] +fn nullable_wrapped_tag_union() { + expect_success( + indoc!( + r#" + Container a : [ Empty, Whole a, Halved (Container a) (Container a) ] + + meats : Container Str + meats = Halved (Whole "Brisket") (Whole "Ribs") + + sides : Container Str + sides = Halved (Whole "Coleslaw") Empty + + bbqPlate : Container Str + bbqPlate = Halved meats sides + + bbqPlate + "# + ), + r#"Halved (Halved (Whole "Brisket") (Whole "Ribs")) (Halved (Whole "Coleslaw") Empty) : Container Str"#, + ) +} + +#[test] +fn large_nullable_wrapped_tag_union() { + // > 7 non-empty variants so that to force tag storage alongside the data + expect_success( + indoc!( + r#" + Cont a : [ Empty, S1 a, S2 a, S3 a, S4 a, S5 a, S6 a, S7 a, Tup (Cont a) (Cont a) ] + + fst : Cont Str + fst = Tup (S1 "S1") (S2 "S2") + + snd : Cont Str + snd = Tup (S5 "S5") Empty + + tup : Cont Str + tup = Tup fst snd + + tup + "# + ), + r#"Tup (Tup (S1 "S1") (S2 "S2")) (Tup (S5 "S5") Empty) : Cont Str"#, + ) +} + +#[test] +fn issue_2300() { + expect_success( + r#"\Email str -> str == """#, + r#" : [ Email Str ] -> Bool"#, + ) +} + +#[test] +fn function_in_list() { + expect_success( + r#"[\x -> x + 1, \s -> s * 2]"#, + r#"[ , ] : List (Num a -> Num a)"#, + ) +} + +#[test] +fn function_in_record() { + expect_success( + r#"{ n: 1, adder: \x -> x + 1 }"#, + r#"{ adder: , n: } : { adder : Num a -> Num a, n : Num * }"#, + ) +} + +#[test] +fn function_in_unwrapped_record() { + expect_success( + r#"{ adder: \x -> x + 1 }"#, + r#"{ adder: } : { adder : Num a -> Num a }"#, + ) +} + +#[test] +fn function_in_tag() { + expect_success( + r#"Adder (\x -> x + 1)"#, + r#"Adder : [ Adder (Num a -> Num a) ]*"#, + ) +} + +#[test] +fn newtype_of_record_of_tag_of_record_of_tag() { + expect_success( + r#"A {b: C {d: 1}}"#, + r#"A { b: C { d: 1 } } : [ A { b : [ C { d : Num * } ]* } ]*"#, + ) +} + +#[test] +fn print_u8s() { + expect_success( + indoc!( + r#" + x : U8 + x = 129 + x + "# + ), + "129 : U8", + ) +} + +#[test] +fn parse_problem() { + expect_failure( + "add m n = m + n", + indoc!( + r#" + ── ARGUMENTS BEFORE EQUALS ───────────────────────────────────────────────────── + + I am partway through parsing a definition, but I got stuck here: + + 1│ app "app" provides [ replOutput ] to "./platform" + 2│ + 3│ replOutput = + 4│ add m n = m + n + ^^^ + + Looks like you are trying to define a function. In roc, functions are + always written as a lambda, like increment = \n -> n + 1. + "# + ), + ); +} + +#[test] +fn mono_problem() { + expect_failure( + r#" + t : [A, B, C] + t = A + + when t is + A -> "a" + "#, + indoc!( + r#" + ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + + This when does not cover all the possibilities: + + 7│> when t is + 8│> A -> "a" + + Other possibilities include: + + B + C + + I would have to crash if I saw one of those! Add branches for them! + + + Enter an expression, or :help, or :exit/:q."# + ), + ); +} + +#[test] +fn issue_2343_complete_mono_with_shadowed_vars() { + expect_failure( + indoc!( + r#" + b = False + f = \b -> + when b is + True -> 5 + False -> 15 + f b + "# + ), + indoc!( + r#" + ── DUPLICATE NAME ────────────────────────────────────────────────────────────── + + The b name is first defined here: + + 4│ b = False + ^ + + But then it's defined a second time here: + + 5│ f = \b -> + ^ + + Since these variables have the same name, it's easy to use the wrong + one on accident. Give one of them a new name. + "# + ), + ); +} From c6babb30bab809fbd0175f1cf71df714d1bf633f Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 30 Jan 2022 16:32:42 +0000 Subject: [PATCH 384/541] repl: Remove unused import --- cli/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 5e6ba134be..7fba8781c5 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -4,7 +4,6 @@ use roc_cli::{ CMD_REPL, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_TIME, ROC_FILE, }; use roc_load::file::LoadingProblem; -use roc_repl_cli; use std::fs::{self, FileType}; use std::io; use std::path::{Path, PathBuf}; From 4f8d0776b3373e72f13ac2e772af57c8afc598da Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Sun, 30 Jan 2022 18:55:09 -0600 Subject: [PATCH 385/541] We got a test working for panicking with the appropriate number fo failures. Ultimatly we want: + An error maessage that says what the failures were + Not panicking (so these are effectively error productions) --- compiler/builtins/bitcode/src/utils.zig | 11 +++++++- compiler/gen_llvm/src/llvm/build.rs | 12 ++++++-- compiler/gen_llvm/src/llvm/externs.rs | 37 +++++++++++++++++++++++++ compiler/gen_llvm/src/run_roc.rs | 35 +++++++++++++++++++---- compiler/test_gen/src/gen_primitives.rs | 1 + compiler/test_gen/src/helpers/dev.rs | 3 +- compiler/test_gen/src/helpers/llvm.rs | 23 +++++++-------- compiler/test_gen/src/tests.rs | 5 ++++ 8 files changed, 103 insertions(+), 24 deletions(-) diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 44ca582deb..72f6e4f754 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -23,12 +23,13 @@ extern fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; comptime { const builtin = @import("builtin"); - // During tetsts, use the testing allocators to satisfy these functions. + // During tests, use the testing allocators to satisfy these functions. if (builtin.is_test) { @export(testing_roc_alloc, .{ .name = "roc_alloc", .linkage = .Strong }); @export(testing_roc_realloc, .{ .name = "roc_realloc", .linkage = .Strong }); @export(testing_roc_dealloc, .{ .name = "roc_dealloc", .linkage = .Strong }); @export(testing_roc_panic, .{ .name = "roc_panic", .linkage = .Strong }); + @export(testing_roc_memcpy, .{ .name = "roc_memcpy", .linkage = .Strong }); } } @@ -56,6 +57,14 @@ fn testing_roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { @panic("Roc panicked"); } +fn testing_roc_memcpy(dest: *c_void, src: *c_void, bytes: usize) callconv(.C) ?*c_void { + const zig_dest = @ptrCast([*]u8, dest); + const zig_src = @ptrCast([*]u8, src); + + @memcpy(zig_dest, zig_src, bytes); + return dest; +} + pub fn alloc(size: usize, alignment: u32) [*]u8 { return @ptrCast([*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment })); } diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 0d230a6bb1..aee0be81b8 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -6030,7 +6030,10 @@ fn run_low_level<'a, 'ctx, 'env>( { bd.position_at_end(throw_block); - let func = env.module.get_function("roc_builtins.utils.expect_failed").unwrap(); + let func = env + .module + .get_function("roc_builtins.utils.expect_failed") + .unwrap(); let callable = CallableValue::try_from(func).unwrap(); let start_line = context.i32_type().const_int(0, false); let end_line = context.i32_type().const_int(0, false); @@ -6039,7 +6042,12 @@ fn run_low_level<'a, 'ctx, 'env>( bd.build_call( callable, - &[start_line.into(), end_line.into(), start_col.into(), end_col.into()], + &[ + start_line.into(), + end_line.into(), + start_col.into(), + end_col.into(), + ], "call_expect_failed", ); diff --git a/compiler/gen_llvm/src/llvm/externs.rs b/compiler/gen_llvm/src/llvm/externs.rs index 75bc506cc2..9092e211bb 100644 --- a/compiler/gen_llvm/src/llvm/externs.rs +++ b/compiler/gen_llvm/src/llvm/externs.rs @@ -42,6 +42,43 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) { } } + // roc_memcpy + { + // The type of this function (but not the implementation) should have + // already been defined by the builtins, which rely on it. + let fn_val = module.get_function("roc_memcpy").unwrap(); + let mut params = fn_val.get_param_iter(); + let dest_arg = params.next().unwrap(); + let dest_alignment = 1; + let src_arg = params.next().unwrap(); + let src_alignment = 1; + let bytes_arg = params.next().unwrap(); + + debug_assert!(params.next().is_none()); + + // Add a basic block for the entry point + let entry = ctx.append_basic_block(fn_val, "entry"); + + builder.position_at_end(entry); + + // Call libc memcpy() + let _retval = builder + .build_memcpy( + dest_arg.into_pointer_value(), + dest_alignment, + src_arg.into_pointer_value(), + src_alignment, + bytes_arg.into_int_value(), + ) + .unwrap(); + + builder.build_return(None); + + if cfg!(debug_assertions) { + crate::llvm::build::verify_fn(fn_val); + } + } + // roc_realloc { let libc_realloc_val = { diff --git a/compiler/gen_llvm/src/run_roc.rs b/compiler/gen_llvm/src/run_roc.rs index b607a6a281..bd3b2e1a9c 100644 --- a/compiler/gen_llvm/src/run_roc.rs +++ b/compiler/gen_llvm/src/run_roc.rs @@ -45,22 +45,45 @@ macro_rules! run_jit_function { use roc_gen_llvm::run_roc::RocCallResult; use std::mem::MaybeUninit; + #[derive(Debug, Copy, Clone)] + struct Failure { + start_line: u32, + end_line: u32, + start_col: u16, + end_col: u16, + } + unsafe { let main: libloading::Symbol) -> ()> = $lib.get($main_fn_name.as_bytes()) .ok() .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) .expect("errored"); - let get_expect_failures: libloading::Symbol (usize, usize)> = - $lib.get("roc_builtins.utils.get_expect_failures".as_bytes()) + let get_expect_failures: libloading::Symbol< + unsafe extern "C" fn() -> (*const Failure, usize), + > = $lib + .get("roc_builtins.utils.get_expect_failures".as_bytes()) .ok() - .ok_or(format!("Unable to JIT compile `{}`", "roc_builtins.utils.get_expect_failures")) + .ok_or(format!( + "Unable to JIT compile `{}`", + "roc_builtins.utils.get_expect_failures" + )) .expect("errored"); - let mut result = MaybeUninit::uninit(); + let mut main_result = MaybeUninit::uninit(); - main(result.as_mut_ptr()); + main(main_result.as_mut_ptr()); + let (failures_ptr, num_failures) = get_expect_failures(); + let mut failures = std::vec::Vec::new(); - match result.assume_init().into() { + for index in 0..num_failures { + failures.push(*failures_ptr.add(index)); + } + + if (num_failures > 0) { + panic!("Failed with {} failures. Failures: ", num_failures); + } + + match main_result.assume_init().into() { Ok(success) => { // only if there are no exceptions thrown, check for errors assert!($errors.is_empty(), "Encountered errors:\n{}", $errors); diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index f79a27cc77..4966a3edb5 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -2513,6 +2513,7 @@ fn call_invalid_layout() { #[test] #[cfg(any(feature = "gen-llvm"))] +#[should_panic(expected = "Failed with 1 failures. Failures: ")] fn expect_fail() { assert_expect_failed!( indoc!( diff --git a/compiler/test_gen/src/helpers/dev.rs b/compiler/test_gen/src/helpers/dev.rs index 867c2c6cf3..9f05e1cff6 100644 --- a/compiler/test_gen/src/helpers/dev.rs +++ b/compiler/test_gen/src/helpers/dev.rs @@ -272,10 +272,9 @@ macro_rules! assert_expect_failed { }; run_jit_function_raw!(lib, main_fn_name, $ty, transform, errors); todo!("Actually look up the failures and check them") - }; + }}; } - #[allow(unused_imports)] pub(crate) use assert_evals_to; pub(crate) use assert_expect_failed; diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index edea6be011..f825d9ef0a 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -192,7 +192,11 @@ fn create_llvm_module<'a>( for function in FunctionIterator::from_module(module) { let name = function.get_name().to_str().unwrap(); if name.starts_with("roc_builtins") { - function.set_linkage(Linkage::Internal); + if name.starts_with("roc_builtins.utils") { + function.set_linkage(Linkage::External); + } else { + function.set_linkage(Linkage::Internal); + } } if name.starts_with("roc_builtins.dict") { @@ -613,19 +617,14 @@ macro_rules! assert_expect_failed { let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); let is_gen_test = true; - let (main_fn_name, errors, lib) = $crate::helpers::llvm::helper( - &arena, - $src, - stdlib, - is_gen_test, - false, - &context, - ); + let (main_fn_name, errors, lib) = + $crate::helpers::llvm::helper(&arena, $src, stdlib, is_gen_test, false, &context); let transform = |success| { let expected = $expected; assert_eq!(&success, &expected, "LLVM test failed"); }; + run_jit_function!(lib, main_fn_name, $ty, transform, errors) }; @@ -644,7 +643,6 @@ macro_rules! assert_expect_failed { }; } - #[allow(dead_code)] pub fn identity(value: T) -> T { value @@ -674,11 +672,10 @@ macro_rules! assert_non_opt_evals_to { #[allow(unused_imports)] pub(crate) use assert_evals_to; #[allow(unused_imports)] +pub(crate) use assert_expect_failed; +#[allow(unused_imports)] pub(crate) use assert_llvm_evals_to; #[allow(unused_imports)] pub(crate) use assert_non_opt_evals_to; #[allow(unused_imports)] pub(crate) use assert_wasm_evals_to; -#[allow(unused_imports)] -pub(crate) use assert_expect_failed; - diff --git a/compiler/test_gen/src/tests.rs b/compiler/test_gen/src/tests.rs index 7e324f608b..c2f702ec7c 100644 --- a/compiler/test_gen/src/tests.rs +++ b/compiler/test_gen/src/tests.rs @@ -23,6 +23,11 @@ pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { libc::malloc(size) } +#[no_mangle] +pub unsafe fn roc_memcpy(dest: *mut c_void, src: *const c_void, bytes: usize) -> *mut c_void { + libc::memcpy(dest, src, bytes) +} + #[no_mangle] pub unsafe fn roc_realloc( c_ptr: *mut c_void, From 484ce2fbc96d6c10ecea114aac62ff73b525699a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 29 Jan 2022 21:10:50 -0500 Subject: [PATCH 386/541] Parse hosted modules --- compiler/load/src/file.rs | 51 +++++++++++--- compiler/parse/src/ast.rs | 3 +- compiler/parse/src/header.rs | 21 ++++++ compiler/parse/src/module.rs | 126 ++++++++++++++++++++++++++++++++--- compiler/parse/src/parser.rs | 34 ++++++++++ 5 files changed, 217 insertions(+), 18 deletions(-) diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index fed406405e..eeeb79f42d 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -1801,7 +1801,6 @@ fn update<'a>( // // e.g. for `app "blah"` we should generate an output file named "blah" match &parsed.module_name { - ModuleNameEnum::PkgConfig => {} ModuleNameEnum::App(output_str) => match output_str { StrLiteral::PlainLine(path) => { state.output_path = Some(path); @@ -1810,7 +1809,9 @@ fn update<'a>( todo!("TODO gracefully handle a malformed string literal after `app` keyword."); } }, - ModuleNameEnum::Interface(_) => {} + ModuleNameEnum::PkgConfig + | ModuleNameEnum::Interface(_) + | ModuleNameEnum::Hosted(_) => {} } let module_id = parsed.module_id; @@ -2409,6 +2410,12 @@ fn load_pkg_config<'a>( Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg])) } + Ok((ast::Module::Hosted { header }, _parse_state)) => { + Err(LoadingProblem::UnexpectedHeader(format!( + "expected platform/package module, got Hosted module with header\n{:?}", + header + ))) + } Err(fail) => Err(LoadingProblem::ParsingFailed( fail.map_problem(SyntaxError::Header) .into_file_error(filename), @@ -2655,6 +2662,29 @@ fn parse_header<'a>( header, module_timing, )), + Ok((ast::Module::Hosted { header }, parse_state)) => { + let info = HeaderInfo { + loc_name: Loc { + region: header.name.region, + value: ModuleNameEnum::Hosted(header.name.value), + }, + filename, + is_root_module, + opt_shorthand, + packages: &[], + exposes: unspace(arena, header.exposes.items), + imports: unspace(arena, header.imports.items), + to_platform: None, + }; + + Ok(send_header( + info, + parse_state, + module_ids, + ident_ids_by_module, + module_timing, + )) + } Err(fail) => Err(LoadingProblem::ParsingFailed( fail.map_problem(SyntaxError::Header) .into_file_error(filename), @@ -2728,6 +2758,7 @@ enum ModuleNameEnum<'a> { /// A filename App(StrLiteral<'a>), Interface(roc_parse::header::ModuleName<'a>), + Hosted(roc_parse::header::ModuleName<'a>), PkgConfig, } @@ -2767,7 +2798,7 @@ fn send_header<'a>( let declared_name: ModuleName = match &loc_name.value { PkgConfig => unreachable!(), App(_) => ModuleName::APP.into(), - Interface(module_name) => { + Interface(module_name) | Hosted(module_name) => { // TODO check to see if module_name is consistent with filename. // If it isn't, report a problem! @@ -3663,12 +3694,14 @@ where let module_docs = match module_name { ModuleNameEnum::PkgConfig => None, ModuleNameEnum::App(_) => None, - ModuleNameEnum::Interface(name) => Some(crate::docs::generate_module_docs( - module_output.scope, - name.as_str().into(), - &module_output.ident_ids, - parsed_defs, - )), + ModuleNameEnum::Interface(name) | ModuleNameEnum::Hosted(name) => { + Some(crate::docs::generate_module_docs( + module_output.scope, + name.as_str().into(), + &module_output.ident_ids, + parsed_defs, + )) + } }; let constraint = constrain_module(&module_output.declarations, module_id); diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index c8844357cd..8dfc69bd3a 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use crate::header::{AppHeader, InterfaceHeader, PlatformHeader}; +use crate::header::{AppHeader, InterfaceHeader, PlatformHeader, HostedHeader}; use crate::ident::Ident; use bumpalo::collections::{String, Vec}; use bumpalo::Bump; @@ -70,6 +70,7 @@ pub enum Module<'a> { Interface { header: InterfaceHeader<'a> }, App { header: AppHeader<'a> }, Platform { header: PlatformHeader<'a> }, + Hosted { header: HostedHeader<'a> }, } #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/compiler/parse/src/header.rs b/compiler/parse/src/header.rs index 5f30370bae..17aa05950d 100644 --- a/compiler/parse/src/header.rs +++ b/compiler/parse/src/header.rs @@ -81,6 +81,27 @@ pub struct InterfaceHeader<'a> { pub after_imports: &'a [CommentOrNewline<'a>], } +#[derive(Clone, Debug, PartialEq)] +pub struct HostedHeader<'a> { + pub name: Loc>, + pub exposes: Collection<'a, Loc>>>, + pub imports: Collection<'a, Loc>>>, + pub generates: UppercaseIdent<'a>, + pub generates_with: Collection<'a, Loc>>>, + + // Potential comments and newlines - these will typically all be empty. + pub before_header: &'a [CommentOrNewline<'a>], + pub after_hosted_keyword: &'a [CommentOrNewline<'a>], + pub before_exposes: &'a [CommentOrNewline<'a>], + pub after_exposes: &'a [CommentOrNewline<'a>], + pub before_imports: &'a [CommentOrNewline<'a>], + pub after_imports: &'a [CommentOrNewline<'a>], + pub before_generates: &'a [CommentOrNewline<'a>], + pub after_generates: &'a [CommentOrNewline<'a>], + pub before_with: &'a [CommentOrNewline<'a>], + pub after_with: &'a [CommentOrNewline<'a>], +} + #[derive(Copy, Clone, Debug, PartialEq)] pub enum To<'a> { ExistingPackage(&'a str), diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index 2600af4a2a..8e6539f964 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -1,14 +1,17 @@ use crate::ast::{Collection, CommentOrNewline, Def, Module, Spaced}; use crate::blankspace::{space0_around_ee, space0_before_e, space0_e}; use crate::header::{ - package_entry, package_name, AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, - ModuleName, PackageEntry, PlatformHeader, PlatformRequires, To, TypedIdent, + package_entry, package_name, AppHeader, Effects, ExposedName, HostedHeader, ImportsEntry, + InterfaceHeader, ModuleName, PackageEntry, PlatformHeader, PlatformRequires, To, TypedIdent, +}; +use crate::ident::{ + self, lowercase_ident, unqualified_ident, uppercase, uppercase_ident, UppercaseIdent, }; -use crate::ident::{self, lowercase_ident, unqualified_ident, uppercase_ident, UppercaseIdent}; use crate::parser::Progress::{self, *}; use crate::parser::{ - backtrackable, optional, specialize, specialize_region, word1, EEffects, EExposes, EHeader, - EImports, EPackages, EProvides, ERequires, ETypedIdent, Parser, SourceError, SyntaxError, + backtrackable, optional, specialize, specialize_region, word1, EEffects, EExposes, EGenerates, + EGeneratesWith, EHeader, EImports, EPackages, EProvides, ERequires, ETypedIdent, Parser, + SourceError, SyntaxError, }; use crate::state::State; use crate::string_literal; @@ -58,6 +61,15 @@ fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> { and!( space0_e(0, EHeader::Space, EHeader::IndentStart), one_of![ + map!( + skip_first!(keyword_e("interface", EHeader::Start), interface_header()), + |mut header: InterfaceHeader<'a>| -> Clos<'a> { + Box::new(|spaces| { + header.before_header = spaces; + Module::Interface { header } + }) + } + ), map!( skip_first!(keyword_e("app", EHeader::Start), app_header()), |mut header: AppHeader<'a>| -> Clos<'a> { @@ -77,11 +89,11 @@ fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> { } ), map!( - skip_first!(keyword_e("interface", EHeader::Start), interface_header()), - |mut header: InterfaceHeader<'a>| -> Clos<'a> { + skip_first!(keyword_e("hosted", EHeader::Start), hosted_header()), + |mut header: HostedHeader<'a>| -> Clos<'a> { Box::new(|spaces| { header.before_header = spaces; - Module::Interface { header } + Module::Hosted { header } }) } ) @@ -121,6 +133,46 @@ fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>, EHeader<'a>> { } } +#[inline(always)] +fn hosted_header<'a>() -> impl Parser<'a, HostedHeader<'a>, EHeader<'a>> { + |arena, state| { + let min_indent = 1; + + let (_, after_hosted_keyword, state) = + space0_e(min_indent, EHeader::Space, EHeader::IndentStart).parse(arena, state)?; + let (_, name, state) = loc!(module_name_help(EHeader::ModuleName)).parse(arena, state)?; + + let (_, ((before_exposes, after_exposes), exposes), state) = + specialize(EHeader::Exposes, exposes_values()).parse(arena, state)?; + let (_, ((before_imports, after_imports), imports), state) = + specialize(EHeader::Imports, imports()).parse(arena, state)?; + let (_, ((before_generates, after_generates), generates), state) = + specialize(EHeader::Generates, generates()).parse(arena, state)?; + let (_, ((before_with, after_with), generates_with), state) = + specialize(EHeader::GeneratesWith, generates_with()).parse(arena, state)?; + + let header = HostedHeader { + name, + exposes, + imports, + generates, + generates_with, + before_header: &[] as &[_], + after_hosted_keyword, + before_exposes, + after_exposes, + before_imports, + after_imports, + before_generates, + after_generates, + before_with, + after_with, + }; + + Ok((MadeProgress, header, state)) + } +} + fn chomp_module_name(buffer: &[u8]) -> Result<&str, Progress> { use encode_unicode::CharExt; @@ -678,6 +730,64 @@ fn packages<'a>() -> impl Parser<'a, Packages<'a>, EPackages<'a>> { ) } +#[inline(always)] +fn generates<'a>() -> impl Parser< + 'a, + ( + (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), + UppercaseIdent<'a>, + ), + EGenerates, +> { + let min_indent = 1; + + and!( + spaces_around_keyword( + min_indent, + "generates", + EGenerates::Generates, + EGenerates::Space, + EGenerates::IndentGenerates, + EGenerates::IndentTypeStart + ), + specialize(|(), pos| EGenerates::Identifier(pos), uppercase()) + ) +} + +#[inline(always)] +fn generates_with<'a>() -> impl Parser< + 'a, + ( + (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), + Collection<'a, Loc>>>, + ), + EGeneratesWith, +> { + let min_indent = 1; + + and!( + spaces_around_keyword( + min_indent, + "with", + EGeneratesWith::With, + EGeneratesWith::Space, + EGeneratesWith::IndentWith, + EGeneratesWith::IndentListStart + ), + collection_trailing_sep_e!( + word1(b'[', EGeneratesWith::ListStart), + exposes_entry(EGeneratesWith::Identifier), + word1(b',', EGeneratesWith::ListEnd), + word1(b']', EGeneratesWith::ListEnd), + min_indent, + EGeneratesWith::Open, + EGeneratesWith::Space, + EGeneratesWith::IndentListEnd, + Spaced::SpaceBefore + ) + ) +} + #[inline(always)] fn imports<'a>() -> impl Parser< 'a, diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index 73f2bbd38c..9beb7759d0 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -72,6 +72,8 @@ pub enum EHeader<'a> { Requires(ERequires<'a>, Position), Packages(EPackages<'a>, Position), Effects(EEffects<'a>, Position), + Generates(EGenerates, Position), + GeneratesWith(EGeneratesWith, Position), Space(BadInputError, Position), Start(Position), @@ -202,6 +204,38 @@ pub enum EImports { SetEnd(Position), } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EGenerates { + Open(Position), + Generates(Position), + IndentGenerates(Position), + Identifier(Position), + Space(BadInputError, Position), + IndentTypeStart(Position), + IndentTypeEnd(Position), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EGeneratesWith { + Open(Position), + With(Position), + IndentWith(Position), + IndentListStart(Position), + IndentListEnd(Position), + ListStart(Position), + ListEnd(Position), + Identifier(Position), + ExposingDot(Position), + ShorthandDot(Position), + Shorthand(Position), + ModuleName(Position), + Space(BadInputError, Position), + IndentSetStart(Position), + IndentSetEnd(Position), + SetStart(Position), + SetEnd(Position), +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BadInputError { HasTab, From c3123de737722926de9131bcfd2cb256e898c607 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 29 Jan 2022 21:38:10 -0500 Subject: [PATCH 387/541] format hosted modules --- cli/src/format.rs | 23 +++++++++++++++++-- compiler/fmt/src/module.rs | 46 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/cli/src/format.rs b/cli/src/format.rs index 1c5b83c75c..365bb0034e 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -12,8 +12,8 @@ use roc_parse::ast::{ TypeAnnotation, WhenBranch, }; use roc_parse::header::{ - AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, - PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, + AppHeader, Effects, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, + PackageEntry, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, }; use roc_parse::{ ast::{Def, Module}, @@ -221,6 +221,25 @@ impl<'a> RemoveSpaces<'a> for Module<'a> { after_provides: &[], }, }, + Module::Hosted { header } => Module::Hosted { + header: HostedHeader { + name: header.name.remove_spaces(arena), + exposes: header.exposes.remove_spaces(arena), + imports: header.imports.remove_spaces(arena), + generates: header.generates.remove_spaces(arena), + generates_with: header.generates_with.remove_spaces(arena), + before_header: &[], + after_hosted_keyword: &[], + before_exposes: &[], + after_exposes: &[], + before_imports: &[], + after_imports: &[], + before_generates: &[], + after_generates: &[], + before_with: &[], + after_with: &[], + }, + }, } } } diff --git a/compiler/fmt/src/module.rs b/compiler/fmt/src/module.rs index 1b059cc7ad..9660d427fe 100644 --- a/compiler/fmt/src/module.rs +++ b/compiler/fmt/src/module.rs @@ -5,8 +5,8 @@ use crate::spaces::{fmt_default_spaces, fmt_spaces, INDENT}; use crate::Buf; use roc_parse::ast::{Collection, Module, Spaced}; use roc_parse::header::{ - AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, - PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, + AppHeader, Effects, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, + PackageEntry, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, }; use roc_parse::ident::UppercaseIdent; use roc_region::all::Loc; @@ -22,6 +22,9 @@ pub fn fmt_module<'a, 'buf>(buf: &mut Buf<'buf>, module: &'a Module<'a>) { Module::Platform { header } => { fmt_platform_header(buf, header); } + Module::Hosted { header } => { + fmt_hosted_header(buf, header); + } } } @@ -50,6 +53,45 @@ pub fn fmt_interface_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a Interface fmt_imports(buf, header.imports, indent); } +pub fn fmt_hosted_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a HostedHeader<'a>) { + let indent = INDENT; + + buf.indent(0); + buf.push_str("hosted"); + + // module name + fmt_default_spaces(buf, header.after_hosted_keyword, indent); + buf.push_str(header.name.value.as_str()); + + // exposes + fmt_default_spaces(buf, header.before_exposes, indent); + buf.indent(indent); + buf.push_str("exposes"); + fmt_default_spaces(buf, header.after_exposes, indent); + fmt_exposes(buf, header.exposes, indent); + + // imports + fmt_default_spaces(buf, header.before_imports, indent); + buf.indent(indent); + buf.push_str("imports"); + fmt_default_spaces(buf, header.after_imports, indent); + fmt_imports(buf, header.imports, indent); + + // generates + fmt_default_spaces(buf, header.before_generates, indent); + buf.indent(indent); + buf.push_str("generates"); + fmt_default_spaces(buf, header.after_generates, indent); + buf.push_str(header.generates.into()); + + // with + fmt_default_spaces(buf, header.before_with, indent); + buf.indent(indent); + buf.push_str("with"); + fmt_default_spaces(buf, header.after_with, indent); + fmt_exposes(buf, header.generates_with, indent); +} + pub fn fmt_app_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a AppHeader<'a>) { let indent = INDENT; buf.indent(0); From fbedc3ba130ebb4251da3de784b241f8ab5f49d4 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 29 Jan 2022 21:55:43 -0500 Subject: [PATCH 388/541] Extract common code from both branches --- cli/src/repl/eval.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 51fb280877..f0ce6c8f29 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -241,16 +241,16 @@ fn tag_id_from_recursive_ptr<'a, M: AppMemory>( rec_addr: usize, ) -> (i64, usize) { let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.target_info); + let addr_with_id = env.app_memory.deref_usize(rec_addr); + if tag_in_ptr { - let addr_with_id = env.app_memory.deref_usize(rec_addr); let (_, tag_id_mask) = UnionLayout::tag_id_pointer_bits_and_mask(env.target_info); let tag_id = addr_with_id & tag_id_mask; let data_addr = addr_with_id & !tag_id_mask; (tag_id as i64, data_addr) } else { - let data_addr = env.app_memory.deref_usize(rec_addr); - let tag_id = tag_id_from_data(env, union_layout, data_addr); - (tag_id, data_addr) + let tag_id = tag_id_from_data(env, union_layout, addr_with_id); + (tag_id, addr_with_id) } } From c68dfdf61eb0d3d10fc7b01d9d792232fce8a9aa Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 29 Jan 2022 21:59:12 -0500 Subject: [PATCH 389/541] Drop some unused variants --- compiler/parse/src/parser.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index 9beb7759d0..6542f83003 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -225,15 +225,7 @@ pub enum EGeneratesWith { ListStart(Position), ListEnd(Position), Identifier(Position), - ExposingDot(Position), - ShorthandDot(Position), - Shorthand(Position), - ModuleName(Position), Space(BadInputError, Position), - IndentSetStart(Position), - IndentSetEnd(Position), - SetStart(Position), - SetEnd(Position), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] From 121e387f5aa6ef5c180aa1cf18fb4ecf42ba3676 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 29 Jan 2022 21:59:22 -0500 Subject: [PATCH 390/541] Make some errors more specific --- reporting/src/error/parse.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index 6922a8e9ee..98d5518678 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -3182,7 +3182,7 @@ fn to_exposes_report<'a>( let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ - alloc.reflow(r"I am partway through parsing a exposes list, but I got stuck here:"), + alloc.reflow(r"I am partway through parsing an `exposes` list, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![alloc.reflow( "I was expecting a type name, value name or function name next, like", @@ -3227,7 +3227,7 @@ fn to_exposes_report<'a>( EExposes::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), - _ => todo!("unhandled parse error {:?}", parse_problem), + _ => todo!("unhandled `exposes` parsing error {:?}", parse_problem), } } From 02263dbf42e069d18b263b1a3951b225a2b1ba80 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 29 Jan 2022 22:08:11 -0500 Subject: [PATCH 391/541] Report `hosted` module parse errors --- reporting/src/error/parse.rs | 90 ++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index 98d5518678..cb1660597f 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -3097,6 +3097,96 @@ fn to_header_report<'a>( } EHeader::Space(error, pos) => to_space_report(alloc, lines, filename, error, *pos), + EHeader::Generates(_, pos) => { + let surroundings = Region::new(start, *pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat(vec![ + alloc.reflow("I am expecting a type name next, like "), + alloc.parser_suggestion("Effect"), + alloc.reflow(". Type names must start with an uppercase letter."), + ]), + ]); + + Report { + filename, + doc, + title: "WEIRD GENERATED TYPE NAME".to_string(), + severity: Severity::RuntimeError, + } + } + EHeader::GeneratesWith(generates_with, pos) => { + to_generates_with_report(alloc, lines, filename, generates_with, *pos) + } + } +} + +fn to_generates_with_report<'a>( + alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, + filename: PathBuf, + parse_problem: &roc_parse::parser::EGeneratesWith, + start: Position, +) -> Report<'a> { + use roc_parse::parser::EGeneratesWith; + + match *parse_problem { + EGeneratesWith::ListEnd(pos) | // TODO: give this its own error message + EGeneratesWith::Identifier(pos) => { + let surroundings = Region::new(start, pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); + + let doc = alloc.stack(vec![ + alloc + .reflow(r"I am partway through parsing a provides list, but I got stuck here:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat(vec![alloc.reflow( + "I was expecting a type name, value name or function name next, like", + )]), + alloc + .parser_suggestion("provides [ Animal, default, tame ]") + .indent(4), + ]); + + Report { + filename, + doc, + title: "WEIRD GENERATES".to_string(), + severity: Severity::RuntimeError, + } + } + + EGeneratesWith::With(pos) => { + let surroundings = Region::new(start, pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat(vec![ + alloc.reflow("I am expecting the "), + alloc.keyword("with"), + alloc.reflow(" keyword next, like "), + ]), + alloc + .parser_suggestion("with [ after, map ]") + .indent(4), + ]); + + Report { + filename, + doc, + title: "WEIRD GENERATES".to_string(), + severity: Severity::RuntimeError, + } + } + + EGeneratesWith::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), + + _ => todo!("unhandled parse error {:?}", parse_problem), } } From 2eb924394252de7d23a153dd5dcd3d77160eaf52 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 30 Jan 2022 11:09:40 -0500 Subject: [PATCH 392/541] Add parse tests for hosted modules --- .../empty_hosted_header.header.result-ast | 23 ++++ .../pass/empty_hosted_header.header.roc | 1 + .../nonempty_hosted_header.header.result-ast | 130 ++++++++++++++++++ .../pass/nonempty_hosted_header.header.roc | 18 +++ compiler/parse/tests/test_parse.rs | 2 + 5 files changed, 174 insertions(+) create mode 100644 compiler/parse/tests/snapshots/pass/empty_hosted_header.header.result-ast create mode 100644 compiler/parse/tests/snapshots/pass/empty_hosted_header.header.roc create mode 100644 compiler/parse/tests/snapshots/pass/nonempty_hosted_header.header.result-ast create mode 100644 compiler/parse/tests/snapshots/pass/nonempty_hosted_header.header.roc diff --git a/compiler/parse/tests/snapshots/pass/empty_hosted_header.header.result-ast b/compiler/parse/tests/snapshots/pass/empty_hosted_header.header.result-ast new file mode 100644 index 0000000000..8cf0174c92 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/empty_hosted_header.header.result-ast @@ -0,0 +1,23 @@ +Hosted { + header: HostedHeader { + name: @7-10 ModuleName( + "Foo", + ), + exposes: [], + imports: [], + generates: UppercaseIdent( + "Bar", + ), + generates_with: [], + before_header: [], + after_hosted_keyword: [], + before_exposes: [], + after_exposes: [], + before_imports: [], + after_imports: [], + before_generates: [], + after_generates: [], + before_with: [], + after_with: [], + }, +} diff --git a/compiler/parse/tests/snapshots/pass/empty_hosted_header.header.roc b/compiler/parse/tests/snapshots/pass/empty_hosted_header.header.roc new file mode 100644 index 0000000000..e43d69f4a1 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/empty_hosted_header.header.roc @@ -0,0 +1 @@ +hosted Foo exposes [] imports [] generates Bar with [] diff --git a/compiler/parse/tests/snapshots/pass/nonempty_hosted_header.header.result-ast b/compiler/parse/tests/snapshots/pass/nonempty_hosted_header.header.result-ast new file mode 100644 index 0000000000..c5b245f35f --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/nonempty_hosted_header.header.result-ast @@ -0,0 +1,130 @@ +Hosted { + header: HostedHeader { + name: @7-10 ModuleName( + "Foo", + ), + exposes: Collection { + items: [ + @45-50 SpaceBefore( + ExposedName( + "Stuff", + ), + [ + Newline, + ], + ), + @64-70 SpaceBefore( + ExposedName( + "Things", + ), + [ + Newline, + ], + ), + @84-97 SpaceBefore( + ExposedName( + "somethingElse", + ), + [ + Newline, + ], + ), + ], + final_comments: [ + Newline, + ], + }, + imports: Collection { + items: [ + @143-147 SpaceBefore( + Module( + ModuleName( + "Blah", + ), + [], + ), + [ + Newline, + ], + ), + @161-182 SpaceBefore( + Module( + ModuleName( + "Baz", + ), + [ + @167-172 ExposedName( + "stuff", + ), + @174-180 ExposedName( + "things", + ), + ], + ), + [ + Newline, + ], + ), + ], + final_comments: [ + Newline, + ], + }, + generates: UppercaseIdent( + "Bar", + ), + generates_with: Collection { + items: [ + @239-242 SpaceBefore( + ExposedName( + "map", + ), + [ + Newline, + ], + ), + @256-261 SpaceBefore( + ExposedName( + "after", + ), + [ + Newline, + ], + ), + @275-279 SpaceBefore( + ExposedName( + "loop", + ), + [ + Newline, + ], + ), + ], + final_comments: [ + Newline, + ], + }, + before_header: [], + after_hosted_keyword: [], + before_exposes: [ + Newline, + ], + after_exposes: [ + Newline, + ], + before_imports: [ + Newline, + ], + after_imports: [ + Newline, + ], + before_generates: [ + Newline, + ], + after_generates: [], + before_with: [], + after_with: [ + Newline, + ], + }, +} diff --git a/compiler/parse/tests/snapshots/pass/nonempty_hosted_header.header.roc b/compiler/parse/tests/snapshots/pass/nonempty_hosted_header.header.roc new file mode 100644 index 0000000000..2f417ee54b --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/nonempty_hosted_header.header.roc @@ -0,0 +1,18 @@ +hosted Foo + exposes + [ + Stuff, + Things, + somethingElse, + ] + imports + [ + Blah, + Baz.{ stuff, things }, + ] + generates Bar with + [ + map, + after, + loop, + ] diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 0aaf95b559..60c92b5b07 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -133,6 +133,8 @@ mod test_parse { pass/destructure_tag_assignment.expr, pass/empty_app_header.header, pass/empty_interface_header.header, + pass/empty_hosted_header.header, + pass/nonempty_hosted_header.header, pass/empty_list.expr, pass/empty_platform_header.header, pass/empty_record.expr, From 5c6d2a909ea06646f085d8f7d6a9f0556cebf8ec Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 30 Jan 2022 11:33:46 -0500 Subject: [PATCH 393/541] Make Collection formattable --- compiler/fmt/src/annotation.rs | 11 ++++++++++- compiler/fmt/src/collection.rs | 4 +--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs index 08b7dc68a4..66cf4e89e5 100644 --- a/compiler/fmt/src/annotation.rs +++ b/compiler/fmt/src/annotation.rs @@ -3,7 +3,7 @@ use crate::{ spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT}, Buf, }; -use roc_parse::ast::{AliasHeader, AssignedField, Expr, Tag, TypeAnnotation}; +use roc_parse::ast::{AliasHeader, AssignedField, Collection, Expr, Tag, TypeAnnotation}; use roc_parse::ident::UppercaseIdent; use roc_region::all::Loc; @@ -83,6 +83,15 @@ where } } +impl<'a, T> Formattable for Collection<'a, T> +where + T: Formattable, +{ + fn is_multiline(&self) -> bool { + self.items.iter().any(|item| item.is_multiline()) || !self.final_comments().is_empty() + } +} + /// A Located formattable value is also formattable impl Formattable for Loc where diff --git a/compiler/fmt/src/collection.rs b/compiler/fmt/src/collection.rs index 3f63ed7641..9b31bad527 100644 --- a/compiler/fmt/src/collection.rs +++ b/compiler/fmt/src/collection.rs @@ -17,10 +17,8 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>( >::Item: Formattable, { buf.indent(indent); - let is_multiline = - items.iter().any(|item| item.is_multiline()) || !items.final_comments().is_empty(); - if is_multiline { + if items.is_multiline() { let braces_indent = indent; let item_indent = braces_indent + INDENT; if newline == Newlines::Yes { From ea5365d6ccd9e9c29e0a5f278e8b8b0760b442e2 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 30 Jan 2022 11:33:49 -0500 Subject: [PATCH 394/541] Make Newlines optimize better to bool --- compiler/fmt/src/annotation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs index 66cf4e89e5..9a26793bc8 100644 --- a/compiler/fmt/src/annotation.rs +++ b/compiler/fmt/src/annotation.rs @@ -37,8 +37,8 @@ pub enum Parens { /// newlines are taken into account. #[derive(PartialEq, Eq, Clone, Copy)] pub enum Newlines { - Yes, No, + Yes, } pub trait Formattable { From 270374044fdd695e5954cf0ee42d82b97489d454 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 30 Jan 2022 11:52:05 -0500 Subject: [PATCH 395/541] Make Buf derive Debug --- compiler/fmt/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/fmt/src/lib.rs b/compiler/fmt/src/lib.rs index e7200d1413..1d99edbcd6 100644 --- a/compiler/fmt/src/lib.rs +++ b/compiler/fmt/src/lib.rs @@ -11,6 +11,7 @@ pub mod spaces; use bumpalo::{collections::String, Bump}; +#[derive(Debug)] pub struct Buf<'a> { text: String<'a>, spaces_to_flush: usize, From 099a0b5d4148e691bd6971f27eaad920c8f5b9a2 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 30 Jan 2022 12:14:52 -0500 Subject: [PATCH 396/541] Add formatting test for hosted modules --- compiler/fmt/tests/test_fmt.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index 92e8dee122..e6a182a809 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -2666,6 +2666,14 @@ mod test_fmt { ); } + #[test] + fn single_line_hosted() { + module_formats_same(indoc!( + r#" + hosted Foo exposes [] imports [] generates Bar with []"# + )); + } + /// Annotations and aliases #[test] From 755f4c01f3edb1d995a5648c8b9d843da3471788 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 30 Jan 2022 12:15:16 -0500 Subject: [PATCH 397/541] cargo fmt --- compiler/parse/src/ast.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index 8dfc69bd3a..879d62e98d 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use crate::header::{AppHeader, InterfaceHeader, PlatformHeader, HostedHeader}; +use crate::header::{AppHeader, HostedHeader, InterfaceHeader, PlatformHeader}; use crate::ident::Ident; use bumpalo::collections::{String, Vec}; use bumpalo::Bump; From f28ca65ac5812d684f98159a374d85e90f10c932 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sat, 29 Jan 2022 01:59:47 -0500 Subject: [PATCH 398/541] Error when platform package config cannot be found This avoid compiler hangs that occurred before Closes #1822 --- compiler/load/src/file.rs | 13 +++-- .../fixtures/build/app_with_deps/Primary.roc | 5 +- compiler/load/tests/test_load.rs | 47 ++++++++++++------- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index fed406405e..dc1a146317 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -1217,6 +1217,10 @@ impl<'a> LoadStart<'a> { let buf = to_parse_problem_report(problem, module_ids, root_exposed_ident_ids); return Err(LoadingProblem::FormattedReport(buf)); } + Err(LoadingProblem::FileProblem { filename, error }) => { + let buf = to_file_problem_report(&filename, error); + return Err(LoadingProblem::FormattedReport(buf)); + } Err(e) => return Err(e), } }; @@ -1367,7 +1371,7 @@ where // We need to allocate worker *queues* on the main thread and then move them // into the worker threads, because those workers' stealers need to be - // shared bet,een all threads, and this coordination work is much easier + // shared between all threads, and this coordination work is much easier // on the main thread. let mut worker_queues = bumpalo::collections::Vec::with_capacity_in(num_workers, arena); let mut stealers = bumpalo::collections::Vec::with_capacity_in(num_workers, arena); @@ -1745,7 +1749,7 @@ fn update<'a>( state.module_cache.module_names.insert(*id, name.clone()); } - // This was a dependency. Write it down and keep processing messaages. + // This was a dependency. Write it down and keep processing messages. let mut exposed_symbols: MutSet = HashSet::with_capacity_and_hasher(header.exposes.len(), default_hasher()); @@ -2638,7 +2642,10 @@ fn parse_header<'a>( Msg::Many(vec![app_module_header_msg, load_pkg_config_msg]), )) } else { - Ok((module_id, app_module_header_msg)) + Err(LoadingProblem::FileProblem { + filename: pkg_config_roc, + error: io::ErrorKind::NotFound.into(), + }) } } else { panic!("could not find base") diff --git a/compiler/load/tests/fixtures/build/app_with_deps/Primary.roc b/compiler/load/tests/fixtures/build/app_with_deps/Primary.roc index 6fc1d9ff3a..7d473dbc90 100644 --- a/compiler/load/tests/fixtures/build/app_with_deps/Primary.roc +++ b/compiler/load/tests/fixtures/build/app_with_deps/Primary.roc @@ -1,7 +1,6 @@ -app "primary" - packages { blah: "./blah" } +interface Primary + exposes [ blah2, blah3, str, alwaysThree, identity, z, w, succeed, withDefault, yay ] imports [ Dep1, Dep2.{ two, foo }, Dep3.Blah.{ bar }, Res ] - provides [ blah2, blah3, str, alwaysThree, identity, z, w, succeed, withDefault, yay ] to blah blah2 = Dep2.two blah3 = bar diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index b0196c44e0..136e7ca5cf 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -82,11 +82,6 @@ mod test_load { let app_module = files.pop().unwrap(); let interfaces = files; - debug_assert!( - app_module.1.starts_with("app"), - "The final module should be the application module" - ); - for (name, source) in interfaces { let mut filename = PathBuf::from(name); filename.set_extension("roc"); @@ -278,15 +273,10 @@ mod test_load { "Main", indoc!( r#" - app "test-app" - packages { blah: "./blah" } - imports [ RBTree ] - provides [ main ] to blah + interface Other exposes [ empty ] imports [ RBTree ] empty : RBTree.RedBlackTree I64 I64 empty = RBTree.empty - - main = empty "# ), ), @@ -530,10 +520,10 @@ mod test_load { "Main", indoc!( r#" - app "test-app" packages { blah: "./blah" } provides [ main ] to blah + interface Main exposes [ main ] imports [] - main = [ - "# + main = [ + "# ), )]; @@ -561,9 +551,7 @@ mod test_load { } #[test] - #[should_panic( - expected = "FileProblem { filename: \"tests/fixtures/build/interface_with_deps/invalid$name.roc\", error: NotFound }" - )] + #[should_panic(expected = "FILE NOT FOUND")] fn file_not_found() { let subs_by_module = MutMap::default(); let loaded_module = load_fixture("interface_with_deps", "invalid$name", subs_by_module); @@ -589,4 +577,29 @@ mod test_load { }, ); } + + #[test] + fn platform_does_not_exist() { + let modules = vec![( + "Main", + indoc!( + r#" + app "example" + packages { pf: "./zzz-does-not-exist" } + imports [ ] + provides [ main ] to pf + + main = "" + "# + ), + )]; + + match multiple_modules(modules) { + Err(report) => { + assert!(report.contains("FILE NOT FOUND")); + assert!(report.contains("zzz-does-not-exist/Package-Config.roc")); + } + Ok(_) => unreachable!("we expect failure here"), + } + } } From 2fa55269f1ab0ecd9d438fa2a423d6d1d3e1e7c7 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sat, 29 Jan 2022 10:05:07 -0500 Subject: [PATCH 399/541] Sate clippy --- compiler/load/src/file.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index dc1a146317..e5a2366d4c 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -2644,7 +2644,7 @@ fn parse_header<'a>( } else { Err(LoadingProblem::FileProblem { filename: pkg_config_roc, - error: io::ErrorKind::NotFound.into(), + error: io::ErrorKind::NotFound, }) } } else { From 5e1103dd917c49eab5b2387f814a4130df21fddb Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sat, 29 Jan 2022 12:50:36 -0500 Subject: [PATCH 400/541] Fix docs tests --- docs/tests/insert_syntax_highlighting.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/tests/insert_syntax_highlighting.rs b/docs/tests/insert_syntax_highlighting.rs index 0cc30c2b4a..44ea2054b6 100644 --- a/docs/tests/insert_syntax_highlighting.rs +++ b/docs/tests/insert_syntax_highlighting.rs @@ -56,11 +56,7 @@ mod insert_doc_syntax_highlighting { } } - pub const HELLO_WORLD: &str = r#" -app "test-app" - packages { pf: "platform" } - imports [] - provides [ main ] to pf + pub const HELLO_WORLD: &str = r#"interface Test exposes [ ] imports [ ] main = "Hello, world!" From 4e4986dfddd200a5113f3f8e41b2aac2b21391a3 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 30 Jan 2022 21:43:07 -0500 Subject: [PATCH 401/541] Fix editor tests --- editor/src/editor/mvc/ed_model.rs | 12 +++++++++++- editor/src/editor/resources/strings.rs | 13 +++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/editor/src/editor/mvc/ed_model.rs b/editor/src/editor/mvc/ed_model.rs index 51c12f7e7d..7419cf8331 100644 --- a/editor/src/editor/mvc/ed_model.rs +++ b/editor/src/editor/mvc/ed_model.rs @@ -206,7 +206,7 @@ impl<'a> EdModule<'a> { pub mod test_ed_model { use crate::editor::ed_error::EdResult; use crate::editor::mvc::ed_model; - use crate::editor::resources::strings::HELLO_WORLD; + use crate::editor::resources::strings::{HELLO_WORLD, PLATFORM_STR}; use crate::ui::text::caret_w_select::test_caret_w_select::convert_dsl_to_selection; use crate::ui::text::caret_w_select::test_caret_w_select::convert_selection_to_dsl; use crate::ui::text::caret_w_select::CaretPos; @@ -222,6 +222,7 @@ pub mod test_ed_model { use roc_module::symbol::IdentIds; use roc_module::symbol::ModuleIds; use roc_types::subs::VarStore; + use std::fs; use std::fs::File; use std::io::Write; use std::path::Path; @@ -290,6 +291,15 @@ pub mod test_ed_model { *clean_code_str = full_code.join("\n"); let temp_dir = tempdir().expect("Failed to create temporary directory for test."); + + let platform_dir = temp_dir.path().join("platform"); + fs::create_dir(platform_dir.clone()).expect("Failed to create platform directory"); + let package_config_path = platform_dir.join("Package-Config.roc"); + let mut package_config_file = + File::create(package_config_path).expect("Failed to create Package-Config.roc"); + writeln!(package_config_file, "{}", PLATFORM_STR) + .expect("Failed to write to Package-Config.roc"); + let temp_file_path_buf = PathBuf::from([Uuid::new_v4().to_string(), ".roc".to_string()].join("")); let temp_file_full_path = temp_dir.path().join(temp_file_path_buf); diff --git a/editor/src/editor/resources/strings.rs b/editor/src/editor/resources/strings.rs index df130f4bf0..4175ffc1c9 100644 --- a/editor/src/editor/resources/strings.rs +++ b/editor/src/editor/resources/strings.rs @@ -25,3 +25,16 @@ main = "Hello, world!" "#; + +pub const PLATFORM_STR: &str = r#" +platform "test-platform" + requires {} { main : Str } + exposes [] + packages {} + imports [] + provides [ mainForHost ] + effects fx.Effect {} + +mainForHost : Str +mainForHost = main +"#; From 842737297c01f037c33513b50569e590042a7225 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 30 Jan 2022 21:44:44 -0500 Subject: [PATCH 402/541] fix test --- reporting/tests/test_reporting.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 4df35ed1ff..de83bd2cc7 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -6173,7 +6173,7 @@ I need all branches in an `if` to have the same type! r#" ── WEIRD EXPOSES ─────────────────────────────────────────────────────────────── - I am partway through parsing a exposes list, but I got stuck here: + I am partway through parsing an `exposes` list, but I got stuck here: 1│ interface Foobar 2│ exposes [ main, @Foo ] From 2efe9f9107d6db1a359cb1f2ba2bf75c21cd572c Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 30 Jan 2022 21:45:14 -0500 Subject: [PATCH 403/541] Delete some trailing spaces --- reporting/tests/test_reporting.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index de83bd2cc7..4be661a40e 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -426,14 +426,14 @@ mod test_reporting { 3│ Booly : [ Yes, No, Maybe ] ^^^^^^^^^^^^^^^^^^^^^^^^^^ - + If you didn't intend on using `Booly` then remove it so future readers of your code don't wonder why it is there. - + ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── - + `Booly` is not used anywhere in your code. - + 1│ Booly : [ Yes, No ] ^^^^^^^^^^^^^^^^^^^ From 516b3ee296897d09c83c29e75463524870321c99 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 31 Jan 2022 06:50:08 +0000 Subject: [PATCH 404/541] repl: Delete unused debug file --- repl_eval/src/debug.rs | 109 ----------------------------------------- 1 file changed, 109 deletions(-) delete mode 100644 repl_eval/src/debug.rs diff --git a/repl_eval/src/debug.rs b/repl_eval/src/debug.rs deleted file mode 100644 index 8a26015036..0000000000 --- a/repl_eval/src/debug.rs +++ /dev/null @@ -1,109 +0,0 @@ - -// Check constraints -// -// Keep track of the used (in types or expectations) variables, and the declared variables (in -// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates -// and no variables that are used but not declared are allowed. -// -// There is one exception: the initial variable (that stores the type of the whole expression) is -// never declared, but is used. -pub fn assert_correct_variable_usage(constraint: &Constraint) { - // variables declared in constraint (flex_vars or rigid_vars) - // and variables actually used in constraints - let (declared, used) = variable_usage(constraint); - - let used: ImSet = used.into(); - let mut decl: ImSet = declared.rigid_vars.clone().into(); - - for var in declared.flex_vars.clone() { - decl.insert(var); - } - - let diff = used.clone().relative_complement(decl); - - // NOTE: this checks whether we're using variables that are not declared. For recursive type - // definitions, their rigid types are declared twice, which is correct! - if !diff.is_empty() { - println!("VARIABLE USAGE PROBLEM"); - - println!("used: {:?}", &used); - println!("rigids: {:?}", &declared.rigid_vars); - println!("flexs: {:?}", &declared.flex_vars); - - println!("difference: {:?}", &diff); - - panic!("variable usage problem (see stdout for details)"); - } -} - -#[derive(Default)] -pub struct SeenVariables { - pub rigid_vars: Vec, - pub flex_vars: Vec, -} - -pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec) { - let mut declared = SeenVariables::default(); - let mut used = ImSet::default(); - variable_usage_help(con, &mut declared, &mut used); - - used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) }); - - let mut used_vec: Vec = used.into_iter().collect(); - used_vec.sort(); - - declared.rigid_vars.sort(); - declared.flex_vars.sort(); - - (declared, used_vec) -} - -fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet) { - use Constraint::*; - - match con { - True | SaveTheEnvironment => (), - Eq(tipe, expectation, _, _) => { - for v in tipe.variables() { - used.insert(v); - } - - for v in expectation.get_type_ref().variables() { - used.insert(v); - } - } - Store(tipe, var, _, _) => { - for v in tipe.variables() { - used.insert(v); - } - - used.insert(*var); - } - Lookup(_, expectation, _) => { - for v in expectation.get_type_ref().variables() { - used.insert(v); - } - } - Pattern(_, _, tipe, pexpectation) => { - for v in tipe.variables() { - used.insert(v); - } - - for v in pexpectation.get_type_ref().variables() { - used.insert(v); - } - } - Let(letcon) => { - declared.rigid_vars.extend(letcon.rigid_vars.clone()); - declared.flex_vars.extend(letcon.flex_vars.clone()); - - variable_usage_help(&letcon.defs_constraint, declared, used); - variable_usage_help(&letcon.ret_constraint, declared, used); - } - And(constraints) => { - for sub in constraints { - variable_usage_help(sub, declared, used); - } - } - } -} \ No newline at end of file From 33e6afe83c9f5487274991fba2d1f2032604d7d5 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 31 Jan 2022 07:18:37 +0000 Subject: [PATCH 405/541] repl: remove unused dependencies from roc_cli crate --- Cargo.lock | 10 ---------- cli/Cargo.toml | 16 +--------------- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 964cd0cd6b..cfbd14e68d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3330,36 +3330,26 @@ dependencies = [ "const_format", "criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)", "indoc", - "inkwell 0.1.0", - "libloading 0.7.1", "mimalloc", "pretty_assertions", "roc_build", "roc_builtins", "roc_can", "roc_collections", - "roc_constrain", "roc_docs", "roc_editor", "roc_error_macros", "roc_fmt", - "roc_gen_llvm", "roc_linker", "roc_load", "roc_module", "roc_mono", "roc_parse", - "roc_problem", "roc_region", "roc_repl_cli", "roc_reporting", - "roc_solve", "roc_target", "roc_test_utils", - "roc_types", - "roc_unify", - "rustyline", - "rustyline-derive", "serial_test", "target-lexicon", "tempfile", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 0712366e82..dfffe25ee0 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -15,14 +15,11 @@ test = false bench = false [features] -default = ["target-aarch64", "target-x86_64", "target-wasm32", "llvm", "editor"] +default = ["target-aarch64", "target-x86_64", "target-wasm32", "editor"] wasm32-cli-run = ["target-wasm32", "run-wasm32"] i386-cli-run = ["target-x86"] -# This is a separate feature because when we generate docs on Netlify, -# it doesn't have LLVM installed. (Also, it doesn't need to do code gen.) -llvm = ["inkwell", "roc_gen_llvm", "roc_build/llvm"] editor = ["roc_editor"] run-wasm32 = ["wasmer", "wasmer-wasi"] @@ -50,15 +47,9 @@ roc_docs = { path = "../docs" } roc_parse = { path = "../compiler/parse" } roc_region = { path = "../compiler/region" } roc_module = { path = "../compiler/module" } -roc_problem = { path = "../compiler/problem" } -roc_types = { path = "../compiler/types" } roc_builtins = { path = "../compiler/builtins" } -roc_constrain = { path = "../compiler/constrain" } -roc_unify = { path = "../compiler/unify" } -roc_solve = { path = "../compiler/solve" } roc_mono = { path = "../compiler/mono" } roc_load = { path = "../compiler/load" } -roc_gen_llvm = { path = "../compiler/gen_llvm", optional = true } roc_build = { path = "../compiler/build", default-features = false } roc_fmt = { path = "../compiler/fmt" } roc_target = { path = "../compiler/roc_target" } @@ -69,13 +60,9 @@ roc_linker = { path = "../linker" } roc_repl_cli = { path = "../repl_cli" } clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] } const_format = "0.2.22" -rustyline = { git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1" } -rustyline-derive = { git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1" } bumpalo = { version = "3.8.0", features = ["collections"] } -libloading = "0.7.1" mimalloc = { version = "0.1.26", default-features = false } -inkwell = { path = "../vendor/inkwell", optional = true } target-lexicon = "0.12.2" tempfile = "3.2.0" @@ -89,7 +76,6 @@ pretty_assertions = "1.0.0" roc_test_utils = { path = "../test_utils" } indoc = "1.0.3" serial_test = "0.5.1" -tempfile = "3.2.0" criterion = { git = "https://github.com/Anton-4/criterion.rs"} cli_utils = { path = "../cli_utils" } From e7918e7d8a08dee92069bf7028aefab335d86c37 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 31 Jan 2022 14:59:01 +0100 Subject: [PATCH 406/541] nix: update old rust, use nix 21.11, update to zig 0.8.1 --- Earthfile | 2 +- nix/sources.json | 18 +++++++++++++++--- nix/zig.nix | 10 +++++----- shell.nix | 15 +++++++++------ 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/Earthfile b/Earthfile index a1a1ce8c14..a3da0c7ef6 100644 --- a/Earthfile +++ b/Earthfile @@ -1,4 +1,4 @@ -FROM rust:1.57.0-slim-bullseye +FROM rust:1.57.0-slim-bullseye # make sure to update nixpkgs-unstable in sources.json too so that it uses the same rust version > search for cargo on unstable here: https://search.nixos.org/packages WORKDIR /earthbuild prep-debian: diff --git a/nix/sources.json b/nix/sources.json index b126e3bdf2..a7c8c261c0 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -12,15 +12,27 @@ "url_template": "https://github.com///archive/.tar.gz" }, "nixpkgs": { + "branch": "release-21.11", + "description": "Nix Packages collection", + "homepage": "", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "fe6f208d68ac254873b659db9676d44dea9b0555", + "sha256": "0ybvy1zx97k811bz73xmgsb41d33i2kr2dfqcxzq9m9h958178nq", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/fe6f208d68ac254873b659db9676d44dea9b0555.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + }, + "nixpkgs-unstable": { "branch": "nixpkgs-unstable", "description": "Nix Packages collection", "homepage": "", "owner": "NixOS", "repo": "nixpkgs", - "rev": "47b35f569e84eb6dbbcf0a9fc75d8729ab8837fd", - "sha256": "0c5ny3yxlxixrd6z99g1wg8i6ysqpja763cxn9bh1g1c0byq8bdj", + "rev": "ea171bc81fcb3c6f21deeb46dbc10000087777ef", + "sha256": "15hh28c98kb6pf7wgydc07bx2ivq04a2cay5mhwnqk5cpa8dbiap", "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/47b35f569e84eb6dbbcf0a9fc75d8729ab8837fd.tar.gz", + "url": "https://github.com/NixOS/nixpkgs/archive/ea171bc81fcb3c6f21deeb46dbc10000087777ef.tar.gz", "url_template": "https://github.com///archive/.tar.gz" } } diff --git a/nix/zig.nix b/nix/zig.nix index 55fd1680e8..b9ac0a4111 100644 --- a/nix/zig.nix +++ b/nix/zig.nix @@ -1,7 +1,7 @@ { pkgs }: let - version = "0.8.0"; + version = "0.8.1"; osName = if pkgs.stdenv.isDarwin then "macos" else "linux"; @@ -14,13 +14,13 @@ let # If your system is not aarch64, we assume it's x86_64 sha256 = if pkgs.stdenv.isDarwin then if isAarch64 then - "b32d13f66d0e1ff740b3326d66a469ee6baddbd7211fa111c066d3bd57683111" + "5351297e3b8408213514b29c0a938002c5cf9f97eee28c2f32920e1227fd8423" # macos-aarch64 else - "279f9360b5cb23103f0395dc4d3d0d30626e699b1b4be55e98fd985b62bc6fbe" + "16b0e1defe4c1807f2e128f72863124bffdd906cefb21043c34b673bf85cd57f" # macos-x86_64 else if isAarch64 then - "ee204ca2c2037952cf3f8b10c609373a08a291efa4af7b3c73be0f2b27720470" + "2166dc9f2d8df387e8b4122883bb979d739281e1ff3f3d5483fec3a23b957510" # linux-aarch64 else - "502625d3da3ae595c5f44a809a87714320b7a40e6dff4a895b5fa7df3391d01e"; + "6c032fc61b5d77a3f3cf781730fa549f8f059ffdb3b3f6ad1c2994d2b2d87983"; # linux-x86_64 in pkgs.stdenv.mkDerivation { pname = "zig"; version = version; diff --git a/shell.nix b/shell.nix index 9902d2bd73..7f85013d3b 100644 --- a/shell.nix +++ b/shell.nix @@ -3,6 +3,7 @@ let sources = import nix/sources.nix { }; pkgs = import sources.nixpkgs { }; + unstable-pkgs = import sources.nixpkgs-unstable { }; darwinInputs = with pkgs; lib.optionals stdenv.isDarwin (with pkgs.darwin.apple_sdk.frameworks; [ @@ -35,12 +36,8 @@ let zig = import ./nix/zig.nix { inherit pkgs; }; debugir = import ./nix/debugir.nix { inherit pkgs; }; - inputs = with pkgs; [ + inputs = (with pkgs; [ # build libraries - rustc - cargo - clippy - rustfmt cmake git python3 @@ -68,7 +65,13 @@ let # tools for development environment less - ]; + ]) ++ (with unstable-pkgs; [ + rustc + cargo + clippy + rustfmt + ]); + in pkgs.mkShell { buildInputs = inputs ++ darwinInputs ++ linuxInputs; From 2f80d64056ec73f18bfe071822c3724d3881b945 Mon Sep 17 00:00:00 2001 From: Jan Van Bruggen Date: Mon, 31 Jan 2022 11:17:27 -0700 Subject: [PATCH 407/541] Link macOS with AudioUnit, CoreAudio, IOKit frameworks AudioUnit & CoreAudio are necessary to get [Bevy](https://docs.rs/bevy/latest/bevy/)'s "hello world" example working inside a Roc platform. IOKit is necessary to get [Bevy](https://docs.rs/bevy/latest/bevy/)'s "breakout" example working inside a Roc platform. Credit to @hafiz for discovering this solution. --- compiler/build/src/link.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 13f8f00a65..b3d5e52062 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -861,10 +861,16 @@ fn link_macos( "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/", // These frameworks are needed for GUI examples to work "-framework", + "AudioUnit", + "-framework", "Cocoa", "-framework", + "CoreAudio", + "-framework", "CoreVideo", "-framework", + "IOKit", + "-framework", "Metal", "-framework", "QuartzCore", From 4e942b3e5dbc98f1001153aa67dc9946e1e593b1 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 30 Jan 2022 21:23:53 -0500 Subject: [PATCH 408/541] Make nested datatypes into errors I was hoping to add nested datatypes into the language, but it turns out doing so is quite tricky and not all that useful with Roc's current compilation model. Basically every implementation strategy I could think of ended up requiring a uniform representation for the data layout (or some ugly workaround). Furhermore it increased the complexity of the checker/mono IR generator a little bit - basically, we must always pass around the alias definitions of nested datatypes and instantiate them at usage sites, rather than being able to unroll aliases as we currently do during canonicalization. So, especially because we don't support polymorphic recursion anyway, I think it may be better to simply disallow any kind of nested datatypes in the language. In any case, Stephanie Weirich [seems to think nested datatypes are not needed](https://www.cis.upenn.edu/~plclub/blog/2020-12-04-nested-datatypes/). Closes #2293 --- compiler/can/src/annotation.rs | 32 +++++++-- compiler/can/src/def.rs | 93 +++++++++++++++++++++---- compiler/constrain/src/builtins.rs | 2 +- compiler/parse/src/ast.rs | 10 +++ compiler/problem/src/can.rs | 5 ++ compiler/types/src/solved_types.rs | 4 +- compiler/types/src/types.rs | 102 +++++++++++++++++----------- reporting/src/error/canonicalize.rs | 29 ++++++++ reporting/tests/test_reporting.rs | 66 ++++++++++++++++++ 9 files changed, 284 insertions(+), 59 deletions(-) diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index a78ed60cb9..5cd1b22164 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -357,7 +357,7 @@ fn can_annotation_help( actual: Box::new(actual), } } - None => Type::Apply(symbol, args), + None => Type::Apply(symbol, args, region), } } BoundVariable(v) => { @@ -377,7 +377,8 @@ fn can_annotation_help( As( loc_inner, _spaces, - AliasHeader { + alias_header + @ AliasHeader { name, vars: loc_vars, }, @@ -439,20 +440,43 @@ fn can_annotation_help( } } + let alias_args = vars.iter().map(|(_, v)| v.clone()).collect::>(); + let alias_actual = if let Type::TagUnion(tags, ext) = inner_type { let rec_var = var_store.fresh(); let mut new_tags = Vec::with_capacity(tags.len()); + let mut is_nested_datatype = false; for (tag_name, args) in tags { let mut new_args = Vec::with_capacity(args.len()); for arg in args { let mut new_arg = arg.clone(); - new_arg.substitute_alias(symbol, &Type::Variable(rec_var)); + let substitution_result = + new_arg.substitute_alias(symbol, &alias_args, &Type::Variable(rec_var)); + + if let Err(differing_recursion_region) = substitution_result { + env.problems + .push(roc_problem::can::Problem::NestedDatatype { + alias: symbol, + def_region: alias_header.region(), + differing_recursion_region, + }); + is_nested_datatype = true; + } + + // Either way, add the argument; not doing so would only result in more + // confusing error messages later on. new_args.push(new_arg); } new_tags.push((tag_name.clone(), new_args)); } - Type::RecursiveTagUnion(rec_var, new_tags, ext) + if is_nested_datatype { + // We don't have a way to represent nested data types; hence, we don't actually + // use the recursion var in them, and should avoid marking them as such. + Type::TagUnion(new_tags, ext) + } else { + Type::RecursiveTagUnion(rec_var, new_tags, ext) + } } else { inner_type }; diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 43e7af2e95..42015839ec 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -277,7 +277,7 @@ pub fn canonicalize_defs<'a>( let mut can_vars: Vec> = Vec::with_capacity(vars.len()); let mut is_phantom = false; - for loc_lowercase in vars { + for loc_lowercase in vars.iter() { if let Some(var) = can_ann .introduced_variables .var_by_name(&loc_lowercase.value) @@ -303,10 +303,18 @@ pub fn canonicalize_defs<'a>( continue; } + let mut is_nested_datatype = false; if can_ann.typ.contains_symbol(symbol) { - make_tag_union_recursive( + let alias_args = can_vars + .iter() + .map(|l| (l.value.0.clone(), Type::Variable(l.value.1))) + .collect::>(); + let alias_region = + Region::across_all([name.region].iter().chain(vars.iter().map(|l| &l.region))); + + let made_recursive = make_tag_union_recursive( env, - symbol, + Loc::at(alias_region, (symbol, &alias_args)), name.region, vec![], &mut can_ann.typ, @@ -315,6 +323,13 @@ pub fn canonicalize_defs<'a>( // recursion errors after the sorted introductions are complete. &mut false, ); + + is_nested_datatype = made_recursive.is_err(); + } + + if is_nested_datatype { + // Bail out + continue; } scope.add_alias(symbol, name.region, can_vars.clone(), can_ann.typ.clone()); @@ -1624,9 +1639,16 @@ fn correct_mutual_recursive_type_alias<'a>( var_store, &mut ImSet::default(), ); - make_tag_union_recursive( + + let alias_args = &alias + .type_variables + .iter() + .map(|l| (l.value.0.clone(), Type::Variable(l.value.1))) + .collect::>(); + + let _made_recursive = make_tag_union_recursive( env, - *rec, + Loc::at(alias.header_region(), (*rec, &alias_args)), alias.region, others, &mut alias.typ, @@ -1640,25 +1662,71 @@ fn correct_mutual_recursive_type_alias<'a>( } } +/// Attempt to make a tag union recursive at the position of `recursive_alias`; for example, +/// +/// ```roc +/// [ Cons a (ConsList a), Nil ] as ConsList a +/// ``` +/// +/// can be made recursive at the position "ConsList a" with a fresh recursive variable, say r1: +/// +/// ```roc +/// [ Cons a r1, Nil ] as r1 +/// ``` +/// +/// Returns `Err` if the tag union is recursive, but there is no structure-preserving recursion +/// variable for it. This can happen when the type is a nested datatype, for example in either of +/// +/// ```roc +/// Nested a : [ Chain a (Nested (List a)), Term ] +/// DuoList a b : [ Cons a (DuoList b a), Nil ] +/// ``` +/// +/// When `Err` is returned, a problem will be added to `env`. fn make_tag_union_recursive<'a>( env: &mut Env<'a>, - symbol: Symbol, + recursive_alias: Loc<(Symbol, &[(Lowercase, Type)])>, region: Region, others: Vec, typ: &mut Type, var_store: &mut VarStore, can_report_error: &mut bool, -) { +) -> Result<(), ()> { + let Loc { + value: (symbol, args), + region: alias_region, + } = recursive_alias; + let vars = args.iter().map(|(_, t)| t.clone()).collect::>(); match typ { Type::TagUnion(tags, ext) => { let rec_var = var_store.fresh(); - *typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone()); - typ.substitute_alias(symbol, &Type::Variable(rec_var)); + let mut pending_typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone()); + let substitution_result = + pending_typ.substitute_alias(symbol, &vars, &Type::Variable(rec_var)); + match substitution_result { + Ok(()) => { + // We can substitute the alias presence for the variable exactly. + *typ = pending_typ; + Ok(()) + } + Err(differing_recursion_region) => { + env.problems.push(Problem::NestedDatatype { + alias: symbol, + def_region: alias_region, + differing_recursion_region, + }); + Err(()) + } + } } - Type::RecursiveTagUnion(_, _, _) => {} - Type::Alias { actual, .. } => make_tag_union_recursive( + Type::RecursiveTagUnion(_, _, _) => Ok(()), + Type::Alias { + actual, + type_arguments, + .. + } => make_tag_union_recursive( env, - symbol, + Loc::at_zero((symbol, &type_arguments)), region, others, actual, @@ -1676,6 +1744,7 @@ fn make_tag_union_recursive<'a>( let problem = Problem::CyclicAlias(symbol, region, others); env.problems.push(problem); } + Ok(()) } } } diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index c47a1e5796..8fe6bfeece 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -71,7 +71,7 @@ pub fn exists(flex_vars: Vec, constraint: Constraint) -> Constraint { #[inline(always)] pub fn builtin_type(symbol: Symbol, args: Vec) -> Type { - Type::Apply(symbol, args) + Type::Apply(symbol, args, Region::zero()) } #[inline(always)] diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index 879d62e98d..3fc72c72fd 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -232,6 +232,16 @@ pub struct AliasHeader<'a> { pub vars: &'a [Loc>], } +impl<'a> AliasHeader<'a> { + pub fn region(&self) -> Region { + Region::across_all( + [self.name.region] + .iter() + .chain(self.vars.iter().map(|v| &v.region)), + ) + } +} + #[derive(Debug, Clone, Copy, PartialEq)] pub enum Def<'a> { // TODO in canonicalization, validate the pattern; only certain patterns diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index d9e910becb..e2dce07a9a 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -78,6 +78,11 @@ pub enum Problem { InvalidInterpolation(Region), InvalidHexadecimal(Region), InvalidUnicodeCodePt(Region), + NestedDatatype { + alias: Symbol, + def_region: Region, + differing_recursion_region: Region, + }, } #[derive(Clone, Debug, PartialEq)] diff --git a/compiler/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index 92282b0e55..2174ace2f0 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -85,7 +85,7 @@ impl SolvedType { match typ { EmptyRec => SolvedType::EmptyRecord, EmptyTagUnion => SolvedType::EmptyTagUnion, - Apply(symbol, types) => { + Apply(symbol, types, _) => { let mut solved_types = Vec::with_capacity(types.len()); for typ in types { @@ -454,7 +454,7 @@ pub fn to_type( new_args.push(to_type(arg, free_vars, var_store)); } - Type::Apply(*symbol, new_args) + Type::Apply(*symbol, new_args, Region::zero()) } Rigid(lowercase) => { if let Some(var) = free_vars.named_vars.get(lowercase) { diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 9087ac80d6..648f1089f6 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -94,13 +94,18 @@ impl RecordField { } } - pub fn substitute_alias(&mut self, rep_symbol: Symbol, actual: &Type) { + pub fn substitute_alias( + &mut self, + rep_symbol: Symbol, + rep_args: &[Type], + actual: &Type, + ) -> Result<(), Region> { use RecordField::*; match self { - Optional(typ) => typ.substitute_alias(rep_symbol, actual), - Required(typ) => typ.substitute_alias(rep_symbol, actual), - Demanded(typ) => typ.substitute_alias(rep_symbol, actual), + Optional(typ) => typ.substitute_alias(rep_symbol, rep_args, actual), + Required(typ) => typ.substitute_alias(rep_symbol, rep_args, actual), + Demanded(typ) => typ.substitute_alias(rep_symbol, rep_args, actual), } } @@ -189,7 +194,7 @@ pub enum Type { }, RecursiveTagUnion(Variable, Vec<(TagName, Vec)>, Box), /// Applying a type to some arguments (e.g. Dict.Dict String Int) - Apply(Symbol, Vec), + Apply(Symbol, Vec, Region), Variable(Variable), /// A type error, which will code gen to a runtime error Erroneous(Problem), @@ -220,7 +225,7 @@ impl fmt::Debug for Type { } Type::Variable(var) => write!(f, "<{:?}>", var), - Type::Apply(symbol, args) => { + Type::Apply(symbol, args, _) => { write!(f, "({:?}", symbol)?; for arg in args { @@ -539,7 +544,7 @@ impl Type { } actual_type.substitute(substitutions); } - Apply(_, args) => { + Apply(_, args, _) => { for arg in args { arg.substitute(substitutions); } @@ -549,62 +554,69 @@ impl Type { } } - // swap Apply with Alias if their module and tag match - pub fn substitute_alias(&mut self, rep_symbol: Symbol, actual: &Type) { + /// Swap Apply(rep_symbol, rep_args) with `actual`. Returns `Err` if there is an + /// `Apply(rep_symbol, _)`, but the args don't match. + pub fn substitute_alias( + &mut self, + rep_symbol: Symbol, + rep_args: &[Type], + actual: &Type, + ) -> Result<(), Region> { use Type::*; match self { Function(args, closure, ret) => { for arg in args { - arg.substitute_alias(rep_symbol, actual); + arg.substitute_alias(rep_symbol, rep_args, actual)?; } - closure.substitute_alias(rep_symbol, actual); - ret.substitute_alias(rep_symbol, actual); - } - FunctionOrTagUnion(_, _, ext) => { - ext.substitute_alias(rep_symbol, actual); + closure.substitute_alias(rep_symbol, rep_args, actual)?; + ret.substitute_alias(rep_symbol, rep_args, actual) } + FunctionOrTagUnion(_, _, ext) => ext.substitute_alias(rep_symbol, rep_args, actual), RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { for (_, args) in tags { for x in args { - x.substitute_alias(rep_symbol, actual); + x.substitute_alias(rep_symbol, rep_args, actual)?; } } - ext.substitute_alias(rep_symbol, actual); + ext.substitute_alias(rep_symbol, rep_args, actual) } Record(fields, ext) => { for (_, x) in fields.iter_mut() { - x.substitute_alias(rep_symbol, actual); + x.substitute_alias(rep_symbol, rep_args, actual)?; } - ext.substitute_alias(rep_symbol, actual); + ext.substitute_alias(rep_symbol, rep_args, actual) } Alias { actual: alias_actual, .. - } => { - alias_actual.substitute_alias(rep_symbol, actual); - } + } => alias_actual.substitute_alias(rep_symbol, rep_args, actual), HostExposedAlias { actual: actual_type, .. - } => { - actual_type.substitute_alias(rep_symbol, actual); - } - Apply(symbol, _) if *symbol == rep_symbol => { - *self = actual.clone(); + } => actual_type.substitute_alias(rep_symbol, rep_args, actual), + Apply(symbol, args, region) if *symbol == rep_symbol => { + if args.len() == rep_args.len() + && args.iter().zip(rep_args.iter()).all(|(t1, t2)| t1 == t2) + { + *self = actual.clone(); - if let Apply(_, args) = self { - for arg in args { - arg.substitute_alias(rep_symbol, actual); + if let Apply(_, args, _) = self { + for arg in args { + arg.substitute_alias(rep_symbol, rep_args, actual)?; + } } + return Ok(()); } + Err(*region) } - Apply(_, args) => { + Apply(_, args, _) => { for arg in args { - arg.substitute_alias(rep_symbol, actual); + arg.substitute_alias(rep_symbol, rep_args, actual)?; } + Ok(()) } - EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {} + EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => Ok(()), } } @@ -639,8 +651,8 @@ impl Type { HostExposedAlias { name, actual, .. } => { name == &rep_symbol || actual.contains_symbol(rep_symbol) } - Apply(symbol, _) if *symbol == rep_symbol => true, - Apply(_, args) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)), + Apply(symbol, _, _) if *symbol == rep_symbol => true, + Apply(_, args, _) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)), EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => false, } } @@ -676,7 +688,7 @@ impl Type { .. } => actual_type.contains_variable(rep_variable), HostExposedAlias { actual, .. } => actual.contains_variable(rep_variable), - Apply(_, args) => args.iter().any(|arg| arg.contains_variable(rep_variable)), + Apply(_, args, _) => args.iter().any(|arg| arg.contains_variable(rep_variable)), EmptyRec | EmptyTagUnion | Erroneous(_) => false, } } @@ -753,7 +765,7 @@ impl Type { actual_type.instantiate_aliases(region, aliases, var_store, introduced); } - Apply(symbol, args) => { + Apply(symbol, args, _) => { if let Some(alias) = aliases.get(symbol) { if args.len() != alias.type_variables.len() { *self = Type::Erroneous(Problem::BadTypeArguments { @@ -882,7 +894,7 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet) { accum.insert(*name); symbols_help(actual, accum); } - Apply(symbol, args) => { + Apply(symbol, args, _) => { accum.insert(*symbol); args.iter().for_each(|arg| symbols_help(arg, accum)); } @@ -967,7 +979,7 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { } variables_help(actual, accum); } - Apply(_, args) => { + Apply(_, args, _) => { for x in args { variables_help(x, accum); } @@ -1071,7 +1083,7 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { } variables_help_detailed(actual, accum); } - Apply(_, args) => { + Apply(_, args, _) => { for x in args { variables_help_detailed(x, accum); } @@ -1241,6 +1253,16 @@ pub struct Alias { pub typ: Type, } +impl Alias { + pub fn header_region(&self) -> Region { + Region::across_all( + [self.region] + .iter() + .chain(self.type_variables.iter().map(|tv| &tv.region)), + ) + } +} + #[derive(PartialEq, Eq, Debug, Clone, Hash)] pub enum Problem { CanonicalizationProblem, diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index dd64d2e612..ed0c9d97e1 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -24,6 +24,7 @@ const CIRCULAR_DEF: &str = "CIRCULAR DEFINITION"; const DUPLICATE_NAME: &str = "DUPLICATE NAME"; const VALUE_NOT_EXPOSED: &str = "NOT EXPOSED"; const MODULE_NOT_IMPORTED: &str = "MODULE NOT IMPORTED"; +const NESTED_DATATYPE: &str = "NESTED DATATYPE"; pub fn can_problem<'b>( alloc: &'b RocDocAllocator<'b>, @@ -437,6 +438,34 @@ pub fn can_problem<'b>( title = answer.1.to_string(); severity = Severity::RuntimeError; } + Problem::NestedDatatype { + alias, + def_region, + differing_recursion_region, + } => { + doc = alloc.stack(vec![ + alloc.concat(vec![ + alloc.symbol_unqualified(alias), + alloc.reflow(" is a nested datatype. Here is one recursive usage of it:"), + ]), + alloc.region(lines.convert_region(differing_recursion_region)), + alloc.concat(vec![ + alloc.reflow("But recursive usages of "), + alloc.symbol_unqualified(alias), + alloc.reflow(" must match its definition:"), + ]), + alloc.region(lines.convert_region(def_region)), + alloc.reflow("Nested datatypes are not supported in Roc."), + alloc.concat(vec![ + alloc.hint("Consider rewriting the definition of "), + alloc.symbol_unqualified(alias), + alloc.text(" to use the recursive type with the same arguments."), + ]), + ]); + + title = NESTED_DATATYPE.to_string(); + severity = Severity::RuntimeError; + } }; Report { diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 4be661a40e..f736655cea 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -7238,4 +7238,70 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn nested_datatype() { + report_problem_as( + indoc!( + r#" + Nested a : [ Chain a (Nested (List a)), Term ] + + s : Nested Str + + s + "# + ), + indoc!( + r#" + ── NESTED DATATYPE ───────────────────────────────────────────────────────────── + + `Nested` is a nested datatype. Here is one recursive usage of it: + + 1│ Nested a : [ Chain a (Nested (List a)), Term ] + ^^^^^^^^^^^^^^^ + + But recursive usages of `Nested` must match its definition: + + 1│ Nested a : [ Chain a (Nested (List a)), Term ] + ^^^^^^^^ + + Nested datatypes are not supported in Roc. + + Hint: Consider rewriting the definition of `Nested` to use the recursive type with the same arguments. + "# + ), + ) + } + + #[test] + fn nested_datatype_inline() { + report_problem_as( + indoc!( + r#" + f : {} -> [ Chain a (Nested (List a)), Term ] as Nested a + + f + "# + ), + indoc!( + r#" + ── NESTED DATATYPE ───────────────────────────────────────────────────────────── + + `Nested` is a nested datatype. Here is one recursive usage of it: + + 1│ f : {} -> [ Chain a (Nested (List a)), Term ] as Nested a + ^^^^^^^^^^^^^^^ + + But recursive usages of `Nested` must match its definition: + + 1│ f : {} -> [ Chain a (Nested (List a)), Term ] as Nested a + ^^^^^^^^ + + Nested datatypes are not supported in Roc. + + Hint: Consider rewriting the definition of `Nested` to use the recursive type with the same arguments. + "# + ), + ) + } } From a9ed4ce4cee7fce66d58031e7ae99e3b1fae6977 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 30 Jan 2022 21:51:02 -0500 Subject: [PATCH 409/541] Removing references --- compiler/can/src/def.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 42015839ec..9343134c60 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1648,7 +1648,7 @@ fn correct_mutual_recursive_type_alias<'a>( let _made_recursive = make_tag_union_recursive( env, - Loc::at(alias.header_region(), (*rec, &alias_args)), + Loc::at(alias.header_region(), (*rec, alias_args)), alias.region, others, &mut alias.typ, @@ -1726,7 +1726,7 @@ fn make_tag_union_recursive<'a>( .. } => make_tag_union_recursive( env, - Loc::at_zero((symbol, &type_arguments)), + Loc::at_zero((symbol, type_arguments)), region, others, actual, From d10eb0f9a3a4ec440e843397084a9ad1b093d405 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 31 Jan 2022 23:00:37 -0500 Subject: [PATCH 410/541] Fix `Apply` usage --- compiler/solve/src/solve.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 00b2c19042..edb027a235 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -771,7 +771,7 @@ fn type_to_variable<'a>( match typ { Variable(var) => *var, - Apply(symbol, arguments) => { + Apply(symbol, arguments, _) => { let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { let var = type_to_variable(subs, rank, pools, arena, var_index); From bbe82fcf25640315e6814ed4f550c295c37003d8 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 1 Feb 2022 10:19:25 +0000 Subject: [PATCH 411/541] repl: refactor LLVM-specific code under an optional Cargo feature --- repl_eval/Cargo.toml | 18 +- repl_eval/src/eval.rs | 35 ++-- repl_eval/src/gen.rs | 384 +++++++++++++++++++++++------------------- repl_eval/src/lib.rs | 1 - 4 files changed, 244 insertions(+), 194 deletions(-) diff --git a/repl_eval/Cargo.toml b/repl_eval/Cargo.toml index acc7cefd0c..2f6ba54a65 100644 --- a/repl_eval/Cargo.toml +++ b/repl_eval/Cargo.toml @@ -5,18 +5,22 @@ version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[dependencies] -bumpalo = {version = "3.8.0", features = ["collections"]} -inkwell = {path = "../vendor/inkwell"}# TODO -libloading = "0.7.1" # TODO -target-lexicon = "0.12.2" +[features] +default = ["llvm"] +llvm = ["inkwell", "libloading", "roc_gen_llvm", "roc_build/llvm"] -roc_build = {path = "../compiler/build", default-features = false, features = ["llvm"]}# TODO +[dependencies] +bumpalo = {version = "3.8.0", features = ["collections"]} +inkwell = {path = "../vendor/inkwell", optional = true} +libloading = {version = "0.7.1", optional = true} +target-lexicon = "0.12.2" + +roc_build = {path = "../compiler/build", default-features = false} roc_builtins = {path = "../compiler/builtins"} roc_can = {path = "../compiler/can"} roc_collections = {path = "../compiler/collections"} roc_fmt = {path = "../compiler/fmt"} -roc_gen_llvm = {path = "../compiler/gen_llvm"}# TODO +roc_gen_llvm = {path = "../compiler/gen_llvm", optional = true} roc_load = {path = "../compiler/load"} roc_module = {path = "../compiler/module"} roc_mono = {path = "../compiler/mono"} diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index 51fb280877..b5be2e5b33 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -1,9 +1,9 @@ use bumpalo::collections::Vec; use bumpalo::Bump; -use libloading::Library; +use std::cmp::{max_by_key, min_by_key}; + use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::MutMap; -use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type}; use roc_module::called_via::CalledVia; use roc_module::ident::TagName; use roc_module::symbol::{Interns, ModuleId, Symbol}; @@ -15,7 +15,12 @@ use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral}; use roc_region::all::{Loc, Region}; use roc_target::TargetInfo; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; -use std::cmp::{max_by_key, min_by_key}; + +#[cfg(feature = "llvm")] +use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type}; + +#[cfg(feature = "llvm")] +type AppExecutable = libloading::Library; use super::app_memory::AppMemory; @@ -43,7 +48,7 @@ pub enum ToAstProblem { #[allow(clippy::too_many_arguments)] pub unsafe fn jit_to_ast<'a, M: AppMemory>( arena: &'a Bump, - lib: Library, + app: AppExecutable, main_fn_name: &str, layout: ProcLayout<'a>, content: &'a Content, @@ -68,7 +73,7 @@ pub unsafe fn jit_to_ast<'a, M: AppMemory>( result, } => { // this is a thunk - jit_to_ast_help(&env, lib, main_fn_name, &result, content) + jit_to_ast_help(&env, app, main_fn_name, &result, content) } _ => Err(ToAstProblem::FunctionLayout), } @@ -261,7 +266,7 @@ const OPAQUE_FUNCTION: Expr = Expr::Var { fn jit_to_ast_help<'a, M: AppMemory>( env: &Env<'a, 'a, M>, - lib: Library, + app: AppExecutable, main_fn_name: &str, layout: &Layout<'a>, content: &'a Content, @@ -269,7 +274,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( let (newtype_containers, content) = unroll_newtypes(env, content); let content = unroll_aliases(env, content); let result = match layout { - Layout::Builtin(Builtin::Bool) => Ok(run_jit_function!(lib, main_fn_name, bool, |num| { + Layout::Builtin(Builtin::Bool) => Ok(run_jit_function!(app, main_fn_name, bool, |num| { bool_to_ast(env, num, content) })), Layout::Builtin(Builtin::Int(int_width)) => { @@ -277,7 +282,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( macro_rules! helper { ($ty:ty) => { - run_jit_function!(lib, main_fn_name, $ty, |num| num_to_ast( + run_jit_function!(app, main_fn_name, $ty, |num| num_to_ast( env, number_literal_to_ast(env.arena, num), content @@ -288,7 +293,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( let result = match int_width { U8 | I8 => { // NOTE: this is does not handle 8-bit numbers yet - run_jit_function!(lib, main_fn_name, u8, |num| byte_to_ast(env, num, content)) + run_jit_function!(app, main_fn_name, u8, |num| byte_to_ast(env, num, content)) } U16 => helper!(u16), U32 => helper!(u32), @@ -307,7 +312,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( macro_rules! helper { ($ty:ty) => { - run_jit_function!(lib, main_fn_name, $ty, |num| num_to_ast( + run_jit_function!(app, main_fn_name, $ty, |num| num_to_ast( env, number_literal_to_ast(env.arena, num), content @@ -324,13 +329,13 @@ fn jit_to_ast_help<'a, M: AppMemory>( Ok(result) } Layout::Builtin(Builtin::Str) => Ok(run_jit_function!( - lib, + app, main_fn_name, &'static str, |string: &'static str| { str_to_ast(env.arena, env.arena.alloc(string)) } )), Layout::Builtin(Builtin::List(elem_layout)) => Ok(run_jit_function!( - lib, + app, main_fn_name, (usize, usize), |(addr, len): (usize, usize)| { list_to_ast(env, addr, len, elem_layout, content) } @@ -391,7 +396,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( let result_stack_size = layout.stack_size(env.target_info); run_jit_function_dynamic_type!( - lib, + app, main_fn_name, result_stack_size as usize, |bytes_addr: usize| { struct_addr_to_ast(bytes_addr as usize) } @@ -400,7 +405,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( Layout::Union(UnionLayout::NonRecursive(_)) => { let size = layout.stack_size(env.target_info); Ok(run_jit_function_dynamic_type!( - lib, + app, main_fn_name, size as usize, |addr: usize| { @@ -414,7 +419,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( | Layout::Union(UnionLayout::NullableWrapped { .. }) => { let size = layout.stack_size(env.target_info); Ok(run_jit_function_dynamic_type!( - lib, + app, main_fn_name, size as usize, |addr: usize| { diff --git a/repl_eval/src/gen.rs b/repl_eval/src/gen.rs index 8c93c45bbb..d7fa969862 100644 --- a/repl_eval/src/gen.rs +++ b/repl_eval/src/gen.rs @@ -1,23 +1,30 @@ -use crate::app_memory::AppMemoryInternal; -use crate::eval; use bumpalo::Bump; -use inkwell::context::Context; // TODO -use inkwell::module::Linkage; // TODO -use roc_build::{link::module_to_dylib, program::FunctionIterator}; +use std::path::{Path, PathBuf}; +use std::str::from_utf8_unchecked; +use target_lexicon::Triple; + use roc_can::builtins::builtin_defs_map; use roc_collections::all::{MutMap, MutSet}; use roc_fmt::annotation::Formattable; use roc_fmt::annotation::{Newlines, Parens}; use roc_gen_llvm::llvm::externs::add_default_roc_externs; use roc_load::file::LoadingProblem; +use roc_load::file::MonomorphizedModule; use roc_mono::ir::OptLevel; +use roc_parse::ast::Expr; use roc_parse::parser::SyntaxError; use roc_region::all::LineInfo; use roc_target::TargetInfo; use roc_types::pretty_print::{content_to_string, name_all_type_vars}; -use std::path::{Path, PathBuf}; -use std::str::from_utf8_unchecked; -use target_lexicon::Triple; + +#[cfg(feature = "llvm")] +use { + inkwell::context::Context, inkwell::module::Linkage, roc_build::link::module_to_dylib, + roc_build::program::FunctionIterator, +}; + +use crate::app_memory::AppMemoryInternal; +use crate::eval::{self, ToAstProblem}; pub enum ReplOutput { Problems(Vec), @@ -29,29 +36,201 @@ pub fn gen_and_eval<'a>( target: Triple, opt_level: OptLevel, ) -> Result> { + if cfg!(feature = "llvm") { + gen_and_eval_llvm(src, target, opt_level) + } else { + todo!("REPL must be compiled with LLVM feature for now") + } +} + +pub fn gen_and_eval_llvm<'a>( + src: &[u8], + target: Triple, + opt_level: OptLevel, +) -> Result> { + let arena = Bump::new(); + let target_info = TargetInfo::from(&target); + + let loaded = match compile_to_mono(&arena, src, target_info) { + Ok(x) => x, + Err(prob_strings) => { + return Ok(ReplOutput::Problems(prob_strings)); + } + }; + + let MonomorphizedModule { + procedures, + entry_point, + interns, + exposed_to_host, + mut subs, + module_id: home, + .. + } = loaded; + + let context = Context::create(); + let builder = context.create_builder(); + let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins( + &target, &context, "", + )); + + // mark our zig-defined builtins as internal + for function in FunctionIterator::from_module(module) { + let name = function.get_name().to_str().unwrap(); + if name.starts_with("roc_builtins") { + function.set_linkage(Linkage::Internal); + } + } + + debug_assert_eq!(exposed_to_host.values.len(), 1); + let (main_fn_symbol, main_fn_var) = exposed_to_host.values.iter().next().unwrap(); + let main_fn_symbol = *main_fn_symbol; + let main_fn_var = *main_fn_var; + + // pretty-print the expr type string for later. + name_all_type_vars(main_fn_var, &mut subs); + let content = subs.get_content_without_compacting(main_fn_var); + let expr_type_str = content_to_string(content, &subs, home, &interns); + + let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { + Some(layout) => *layout, + None => { + return Ok(ReplOutput::NoProblems { + expr: "".to_string(), + expr_type: expr_type_str, + }); + } + }; + + let module = arena.alloc(module); + let (module_pass, function_pass) = + roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level); + + let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module); + + // Compile and add all the Procs before adding main + let env = roc_gen_llvm::llvm::build::Env { + arena: &arena, + builder: &builder, + dibuilder: &dibuilder, + compile_unit: &compile_unit, + context: &context, + interns, + module, + target_info, + is_gen_test: true, // so roc_panic is generated + // important! we don't want any procedures to get the C calling convention + exposed_to_host: MutSet::default(), + }; + + // Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no + // platform to provide them. + add_default_roc_externs(&env); + + let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main( + &env, + opt_level, + procedures, + entry_point, + ); + + env.dibuilder.finalize(); + + // we don't use the debug info, and it causes weird errors. + module.strip_debug_info(); + + // Uncomment this to see the module's un-optimized LLVM instruction output: + // env.module.print_to_stderr(); + + if main_fn.verify(true) { + function_pass.run_on(&main_fn); + } else { + panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name); + } + + module_pass.run_on(env.module); + + // Uncomment this to see the module's optimized LLVM instruction output: + // env.module.print_to_stderr(); + + // Verify the module + if let Err(errors) = env.module.verify() { + panic!( + "Errors defining module:\n{}\n\nUncomment things nearby to see more details.", + errors.to_string() + ); + } + + let lib = module_to_dylib(env.module, &target, opt_level) + .expect("Error loading compiled dylib for test"); + + let res_answer = unsafe { + eval::jit_to_ast( + &arena, + lib, + main_fn_name, + main_fn_layout, + content, + &env.interns, + home, + &subs, + target_info, + &AppMemoryInternal, + ) + }; + + let formatted = format_answer(&arena, res_answer, expr_type_str); + Ok(formatted) +} + +fn format_answer( + arena: &Bump, + res_answer: Result, + expr_type_str: String, +) -> ReplOutput { + let mut expr = roc_fmt::Buf::new_in(arena); + + use eval::ToAstProblem::*; + match res_answer { + Ok(answer) => { + answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0); + } + Err(FunctionLayout) => { + expr.indent(0); + expr.push_str(""); + } + } + + ReplOutput::NoProblems { + expr: expr.into_bump_str().to_string(), + expr_type: expr_type_str, + } +} + +fn compile_to_mono<'a>( + arena: &'a Bump, + src: &[u8], + target_info: TargetInfo, +) -> Result, Vec> { use roc_reporting::report::{ can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE, }; - let arena = Bump::new(); - // SAFETY: we've already verified that this is valid UTF-8 during parsing. let src_str: &str = unsafe { from_utf8_unchecked(src) }; - let stdlib = roc_builtins::std::standard_stdlib(); + let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); let filename = PathBuf::from("REPL.roc"); let src_dir = Path::new("fake/test/path"); - let module_src = promote_expr_to_module(src_str); - - let target_info = TargetInfo::from(&target); + let module_src = arena.alloc(promote_expr_to_module(src_str)); let exposed_types = MutMap::default(); let loaded = roc_load::file::load_and_monomorphize_from_str( - &arena, + arena, filename, - &module_src, - &stdlib, + module_src, + stdlib, src_dir, exposed_types, target_info, @@ -61,46 +240,43 @@ pub fn gen_and_eval<'a>( let mut loaded = match loaded { Ok(v) => v, Err(LoadingProblem::FormattedReport(report)) => { - return Ok(ReplOutput::Problems(vec![report])); + return Err(vec![report]); } Err(e) => { panic!("error while loading module: {:?}", e) } }; - use roc_load::file::MonomorphizedModule; let MonomorphizedModule { - procedures, - entry_point, interns, - exposed_to_host, - mut subs, - module_id: home, sources, + can_problems, + type_problems, + mono_problems, .. - } = loaded; + } = &mut loaded; let mut lines = Vec::new(); - for (home, (module_path, src)) in sources { - let can_problems = loaded.can_problems.remove(&home).unwrap_or_default(); - let type_problems = loaded.type_problems.remove(&home).unwrap_or_default(); - let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default(); + for (home, (module_path, src)) in sources.iter() { + let can_probs = can_problems.remove(home).unwrap_or_default(); + let type_probs = type_problems.remove(home).unwrap_or_default(); + let mono_probs = mono_problems.remove(home).unwrap_or_default(); - let error_count = can_problems.len() + type_problems.len() + mono_problems.len(); + let error_count = can_probs.len() + type_probs.len() + mono_probs.len(); if error_count == 0 { continue; } - let line_info = LineInfo::new(&module_src); + let line_info = LineInfo::new(module_src); let src_lines: Vec<&str> = src.split('\n').collect(); let palette = DEFAULT_PALETTE; // Report parsing and canonicalization problems - let alloc = RocDocAllocator::new(&src_lines, home, &interns); + let alloc = RocDocAllocator::new(&src_lines, *home, interns); - for problem in can_problems.into_iter() { + for problem in can_probs.into_iter() { let report = can_problem(&alloc, &line_info, module_path.clone(), problem); let mut buf = String::new(); @@ -109,7 +285,7 @@ pub fn gen_and_eval<'a>( lines.push(buf); } - for problem in type_problems { + for problem in type_probs { if let Some(report) = type_problem(&alloc, &line_info, module_path.clone(), problem) { let mut buf = String::new(); @@ -119,7 +295,7 @@ pub fn gen_and_eval<'a>( } } - for problem in mono_problems { + for problem in mono_probs { let report = mono_problem(&alloc, &line_info, module_path.clone(), problem); let mut buf = String::new(); @@ -130,143 +306,9 @@ pub fn gen_and_eval<'a>( } if !lines.is_empty() { - Ok(ReplOutput::Problems(lines)) + Err(lines) } else { - let context = Context::create(); - let builder = context.create_builder(); - let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins( - &target, &context, "", - )); - - // mark our zig-defined builtins as internal - for function in FunctionIterator::from_module(module) { - let name = function.get_name().to_str().unwrap(); - if name.starts_with("roc_builtins") { - function.set_linkage(Linkage::Internal); - } - } - - debug_assert_eq!(exposed_to_host.values.len(), 1); - let (main_fn_symbol, main_fn_var) = exposed_to_host.values.iter().next().unwrap(); - let main_fn_symbol = *main_fn_symbol; - let main_fn_var = *main_fn_var; - - // pretty-print the expr type string for later. - name_all_type_vars(main_fn_var, &mut subs); - let content = subs.get_content_without_compacting(main_fn_var); - let expr_type_str = content_to_string(content, &subs, home, &interns); - - let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { - Some(layout) => *layout, - None => { - return Ok(ReplOutput::NoProblems { - expr: "".to_string(), - expr_type: expr_type_str, - }); - } - }; - - /*-------------------------------------------------------------------- - START OF LLVM-SPECIFIC STUFF - --------------------------------------------------------------------*/ - - let module = arena.alloc(module); - let (module_pass, function_pass) = - roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level); - - let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module); - - // Compile and add all the Procs before adding main - let env = roc_gen_llvm::llvm::build::Env { - arena: &arena, - builder: &builder, - dibuilder: &dibuilder, - compile_unit: &compile_unit, - context: &context, - interns, - module, - target_info, - is_gen_test: true, // so roc_panic is generated - // important! we don't want any procedures to get the C calling convention - exposed_to_host: MutSet::default(), - }; - - // Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no - // platform to provide them. - add_default_roc_externs(&env); - - let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main( - &env, - opt_level, - procedures, - entry_point, - ); - - env.dibuilder.finalize(); - - // we don't use the debug info, and it causes weird errors. - module.strip_debug_info(); - - // Uncomment this to see the module's un-optimized LLVM instruction output: - // env.module.print_to_stderr(); - - if main_fn.verify(true) { - function_pass.run_on(&main_fn); - } else { - panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name); - } - - module_pass.run_on(env.module); - - // Uncomment this to see the module's optimized LLVM instruction output: - // env.module.print_to_stderr(); - - // Verify the module - if let Err(errors) = env.module.verify() { - panic!( - "Errors defining module:\n{}\n\nUncomment things nearby to see more details.", - errors.to_string() - ); - } - - let lib = module_to_dylib(env.module, &target, opt_level) - .expect("Error loading compiled dylib for test"); - - /*-------------------------------------------------------------------- - END OF LLVM-SPECIFIC STUFF - --------------------------------------------------------------------*/ - - let res_answer = unsafe { - eval::jit_to_ast( - &arena, - lib, - main_fn_name, - main_fn_layout, - content, - &env.interns, - home, - &subs, - target_info, - &AppMemoryInternal, - ) - }; - let mut expr = roc_fmt::Buf::new_in(&arena); - - use eval::ToAstProblem::*; - match res_answer { - Ok(answer) => { - answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0); - } - Err(FunctionLayout) => { - expr.indent(0); - expr.push_str(""); - } - } - - Ok(ReplOutput::NoProblems { - expr: expr.into_bump_str().to_string(), - expr_type: expr_type_str, - }) + Ok(loaded) } } diff --git a/repl_eval/src/lib.rs b/repl_eval/src/lib.rs index ce104f147d..358e05e325 100644 --- a/repl_eval/src/lib.rs +++ b/repl_eval/src/lib.rs @@ -1,4 +1,3 @@ mod app_memory; -// mod debug; TODO: Is this in the right place? Seems to be specifically for solve.rs mod eval; pub mod gen; From c21741a7e0a5d46921b1e47e7a6ca61959895d51 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 1 Feb 2022 08:36:19 -0500 Subject: [PATCH 412/541] zig fmt --- compiler/builtins/bitcode/build.zig | 2 +- compiler/builtins/bitcode/src/utils.zig | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/compiler/builtins/bitcode/build.zig b/compiler/builtins/bitcode/build.zig index 7dfd533ede..377f975018 100644 --- a/compiler/builtins/bitcode/build.zig +++ b/compiler/builtins/bitcode/build.zig @@ -26,7 +26,7 @@ pub fn build(b: *Builder) void { .default_target = CrossTarget{ .cpu_model = .baseline, // TODO allow for native target for maximum speed - } + }, }); const i386_target = makeI386Target(); const wasm32_target = makeWasm32Target(); diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index d6aca7b857..5995db3339 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -319,10 +319,9 @@ const CSlice = extern struct { len: usize, }; pub fn getExpectFailuresC() callconv(.C) CSlice { - var bytes = @ptrCast(*c_void, failures); - return .{.pointer = bytes, .len = failure_length}; + return .{ .pointer = bytes, .len = failure_length }; } pub fn deinitFailures() void { From 65244d6383afb04b9877a4f43c671fa800bd0d77 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 1 Feb 2022 08:43:47 -0500 Subject: [PATCH 413/541] Fix unclosed brace --- compiler/test_gen/src/helpers/llvm.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index 858c4210df..338172ffd6 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -643,6 +643,8 @@ macro_rules! assert_expect_failed { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { $crate::helpers::llvm::assert_llvm_evals_to!($src, $expected, $ty, $transform, false); }; +} + macro_rules! expect_runtime_error_panic { ($src:expr) => {{ #[cfg(feature = "wasm-cli-run")] From e65e6c97d193c69c33fa89702250e355bd0aba32 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 1 Feb 2022 09:17:28 -0500 Subject: [PATCH 414/541] Use constant instead of hardcoded string --- compiler/gen_llvm/src/run_roc.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/gen_llvm/src/run_roc.rs b/compiler/gen_llvm/src/run_roc.rs index 009dbaa66c..4f46e97de0 100644 --- a/compiler/gen_llvm/src/run_roc.rs +++ b/compiler/gen_llvm/src/run_roc.rs @@ -42,6 +42,7 @@ macro_rules! run_jit_function { }}; ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr, $expect_failures:expr) => {{ use inkwell::context::Context; + use roc_builtins::bitcode; use roc_gen_llvm::run_roc::RocCallResult; use std::mem::MaybeUninit; @@ -62,11 +63,11 @@ macro_rules! run_jit_function { let get_expect_failures: libloading::Symbol< unsafe extern "C" fn() -> (*const Failure, usize), > = $lib - .get("roc_builtins.utils.get_expect_failures".as_bytes()) + .get(bitcode::UTILS_GET_EXPECT_FAILURES.as_bytes()) .ok() .ok_or(format!( "Unable to JIT compile `{}`", - "roc_builtins.utils.get_expect_failures" + bitcode::UTILS_GET_EXPECT_FAILURES )) .expect("errored"); let mut main_result = MaybeUninit::uninit(); From 5b6d457aa3cc22ab46872f5ed76b7a1e46802a6a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 1 Feb 2022 11:52:47 -0500 Subject: [PATCH 415/541] Fix typo in Num.roc --- compiler/builtins/docs/Num.roc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index 70b7eab5ad..c4672f0c7f 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -1143,7 +1143,7 @@ shr : Int a, Int a -> Int a ## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) right. ## -## This is called `shlWrap` because any bits shifted +## This is called `shrWrap` because any bits shifted ## off the end of the number will be wrapped around to ## the beginning. (In contrast, [shr] replaces discarded bits with zeroes.) shrWrap : Int a, Int a -> Int a From f8eec65229c6ad0c98cad3f41e3b3ac3ae5557d6 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 1 Feb 2022 20:43:43 -0500 Subject: [PATCH 416/541] Drop an obsolete todo --- compiler/test_gen/src/helpers/dev.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/test_gen/src/helpers/dev.rs b/compiler/test_gen/src/helpers/dev.rs index 212d4bd836..d17c326161 100644 --- a/compiler/test_gen/src/helpers/dev.rs +++ b/compiler/test_gen/src/helpers/dev.rs @@ -273,7 +273,6 @@ macro_rules! assert_expect_failed { assert_eq!(&success, &expected); }; run_jit_function_raw!(lib, main_fn_name, $ty, transform, errors); - todo!("Actually look up the failures and check them") }}; } From 15969af53cb77067e290f62e67be9d453be935bb Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 1 Feb 2022 20:44:00 -0500 Subject: [PATCH 417/541] Use constant over string literal --- compiler/gen_llvm/src/llvm/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index a19bda6c8c..800f67a3c4 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -6059,7 +6059,7 @@ fn run_low_level<'a, 'ctx, 'env>( roc_target::PtrWidth::Bytes8 => { let func = env .module - .get_function("roc_builtins.utils.expect_failed") + .get_function(bitcode::UTILS_EXPECT_FAILED) .unwrap(); let callable = CallableValue::try_from(func).unwrap(); let start_line = context.i32_type().const_int(0, false); From fde7a94372d8fef1e4f4df530a906397d8afc362 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 1 Feb 2022 20:51:53 -0500 Subject: [PATCH 418/541] Don't mark builtins as internal linkage. This is necessary for `expect` to work properly in the repl. --- cli/src/repl/gen.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index c09a981b72..223c778afe 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -2,9 +2,7 @@ use crate::repl::app_memory::AppMemoryInternal; use crate::repl::eval; use bumpalo::Bump; use inkwell::context::Context; -use inkwell::module::Linkage; use roc_build::link::module_to_dylib; -use roc_build::program::FunctionIterator; use roc_can::builtins::builtin_defs_map; use roc_collections::all::{MutMap, MutSet}; use roc_fmt::annotation::Formattable; @@ -139,14 +137,6 @@ pub fn gen_and_eval<'a>( &target, &context, "", )); - // mark our zig-defined builtins as internal - for function in FunctionIterator::from_module(module) { - let name = function.get_name().to_str().unwrap(); - if name.starts_with("roc_builtins") { - function.set_linkage(Linkage::Internal); - } - } - debug_assert_eq!(exposed_to_host.values.len(), 1); let (main_fn_symbol, main_fn_var) = exposed_to_host.values.iter().next().unwrap(); let main_fn_symbol = *main_fn_symbol; From 3b1ca5c310b6a508f7018a8ef663244e96481bf7 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 1 Feb 2022 21:18:27 -0500 Subject: [PATCH 419/541] Refactor is_multiline for collections --- compiler/fmt/src/annotation.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs index 9a26793bc8..98c15b292a 100644 --- a/compiler/fmt/src/annotation.rs +++ b/compiler/fmt/src/annotation.rs @@ -88,7 +88,12 @@ where T: Formattable, { fn is_multiline(&self) -> bool { - self.items.iter().any(|item| item.is_multiline()) || !self.final_comments().is_empty() + // if there are any comments, they must go on their own line + // because otherwise they'd comment out the closing delimiter + !self.final_comments().is_empty() || + // if any of the items in the collection are multiline, + // then the whole collection must be multiline + self.items.iter().any(Formattable::is_multiline) } } From 271112fec17dfc99f9f5613e9084b66310d2c9a4 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 1 Feb 2022 21:18:54 -0500 Subject: [PATCH 420/541] Finish implementing is_multiline for Spaced --- compiler/fmt/src/module.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/fmt/src/module.rs b/compiler/fmt/src/module.rs index 9660d427fe..c25b67427d 100644 --- a/compiler/fmt/src/module.rs +++ b/compiler/fmt/src/module.rs @@ -222,8 +222,14 @@ fn fmt_package_name<'buf>(buf: &mut Buf<'buf>, name: PackageName, _indent: u16) impl<'a, T: Formattable> Formattable for Spaced<'a, T> { fn is_multiline(&self) -> bool { - // TODO - false + use Spaced::*; + + match self { + Item(formattable) => formattable.is_multiline(), + SpaceBefore(formattable, spaces) | SpaceAfter(formattable, spaces) => { + !spaces.is_empty() || formattable.is_multiline() + } + } } fn format_with_options<'buf>( From 34e9b1b73d28d6590bf68bd6517edeecaab32921 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 1 Feb 2022 21:20:14 -0500 Subject: [PATCH 421/541] Fix module formatting --- compiler/fmt/src/collection.rs | 9 ++++--- compiler/fmt/src/module.rs | 17 ++++++++---- .../benchmarks/platform/Package-Config.roc | 10 +++---- examples/cli/platform/Package-Config.roc | 8 ++---- .../thing/platform-dir/Package-Config.roc | 10 +++---- .../platform/Package-Config.roc | 26 +++++++++---------- 6 files changed, 42 insertions(+), 38 deletions(-) diff --git a/compiler/fmt/src/collection.rs b/compiler/fmt/src/collection.rs index 9b31bad527..5765f0f4d1 100644 --- a/compiler/fmt/src/collection.rs +++ b/compiler/fmt/src/collection.rs @@ -16,8 +16,6 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>( ) where >::Item: Formattable, { - buf.indent(indent); - if items.is_multiline() { let braces_indent = indent; let item_indent = braces_indent + INDENT; @@ -50,9 +48,12 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>( item_indent, ); buf.newline(); + buf.indent(braces_indent); + buf.push(end); } else { // is_multiline == false // there is no comment to add + buf.indent(indent); buf.push(start); let mut iter = items.iter().peekable(); while let Some(item) = iter.next() { @@ -66,7 +67,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>( if !items.is_empty() { buf.spaces(1); } + + buf.push(end); } - buf.indent(indent); - buf.push(end); } diff --git a/compiler/fmt/src/module.rs b/compiler/fmt/src/module.rs index c25b67427d..669edc61af 100644 --- a/compiler/fmt/src/module.rs +++ b/compiler/fmt/src/module.rs @@ -196,7 +196,14 @@ fn fmt_effects<'a, 'buf>(buf: &mut Buf<'buf>, effects: &Effects<'a>, indent: u16 fmt_default_spaces(buf, effects.spaces_after_type_name, indent); - fmt_collection(buf, indent, '{', '}', effects.entries, Newlines::No) + fmt_collection( + buf, + indent + INDENT, + '{', + '}', + effects.entries, + Newlines::No, + ) } impl<'a> Formattable for TypedIdent<'a> { @@ -260,7 +267,7 @@ fn fmt_imports<'a, 'buf>( loc_entries: Collection<'a, Loc>>>, indent: u16, ) { - fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No) + fmt_collection(buf, indent + INDENT, '[', ']', loc_entries, Newlines::No) } fn fmt_provides<'a, 'buf>( @@ -270,9 +277,9 @@ fn fmt_provides<'a, 'buf>( indent: u16, ) { fmt_collection(buf, indent, '[', ']', loc_exposed_names, Newlines::No); - if let Some(loc_provided_types) = loc_provided_types { + if let Some(loc_provided) = loc_provided_types { fmt_default_spaces(buf, &[], indent); - fmt_collection(buf, indent, '{', '}', loc_provided_types, Newlines::No); + fmt_collection(buf, indent + INDENT, '{', '}', loc_provided, Newlines::No); } } @@ -290,7 +297,7 @@ fn fmt_exposes<'buf, N: Formattable + Copy>( loc_entries: Collection<'_, Loc>>, indent: u16, ) { - fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No) + fmt_collection(buf, indent + INDENT, '[', ']', loc_entries, Newlines::No) } pub trait FormatName { diff --git a/examples/benchmarks/platform/Package-Config.roc b/examples/benchmarks/platform/Package-Config.roc index 619534f070..07583550da 100644 --- a/examples/benchmarks/platform/Package-Config.roc +++ b/examples/benchmarks/platform/Package-Config.roc @@ -5,11 +5,11 @@ platform "folkertdev/foo" imports [ Task.{ Task } ] provides [ mainForHost ] effects fx.Effect - { - putLine : Str -> Effect {}, - putInt : I64 -> Effect {}, - getInt : Effect { value : I64, errorCode : [ A, B ], isError : Bool } - } + { + putLine : Str -> Effect {}, + putInt : I64 -> Effect {}, + getInt : Effect { value : I64, errorCode : [ A, B ], isError : Bool }, + } mainForHost : Task {} [] as Fx mainForHost = main diff --git a/examples/cli/platform/Package-Config.roc b/examples/cli/platform/Package-Config.roc index 93a045a908..f82a01c43b 100644 --- a/examples/cli/platform/Package-Config.roc +++ b/examples/cli/platform/Package-Config.roc @@ -1,14 +1,10 @@ platform "examples/cli" - requires {} { main : Task {} [] }# TODO FIXME + requires {} { main : Task {} [] } exposes [] packages {} imports [ Task.{ Task } ] provides [ mainForHost ] - effects fx.Effect - { - putLine : Str -> Effect {}, - getLine : Effect Str - } + effects fx.Effect { putLine : Str -> Effect {}, getLine : Effect Str } mainForHost : Task {} [] as Fx mainForHost = main diff --git a/examples/effect/thing/platform-dir/Package-Config.roc b/examples/effect/thing/platform-dir/Package-Config.roc index 667e960a28..17d8a92f5d 100644 --- a/examples/effect/thing/platform-dir/Package-Config.roc +++ b/examples/effect/thing/platform-dir/Package-Config.roc @@ -1,14 +1,14 @@ -platform "folkertdev/foo" +platform "roc-examples/cli" requires {} { main : Effect {} } exposes [] packages {} imports [ fx.Effect ] provides [ mainForHost ] effects fx.Effect - { - putLine : Str -> Effect {}, - getLine : Effect Str - } + { + putLine : Str -> Effect {}, + getLine : Effect Str, + } mainForHost : Effect.Effect {} as Fx mainForHost = main diff --git a/examples/false-interpreter/platform/Package-Config.roc b/examples/false-interpreter/platform/Package-Config.roc index 55003feeea..da5868f3b3 100644 --- a/examples/false-interpreter/platform/Package-Config.roc +++ b/examples/false-interpreter/platform/Package-Config.roc @@ -1,22 +1,22 @@ platform "examples/cli" - requires {} { main : Str -> Task {} [] }# TODO FIXME + requires {} { main : Str -> Task {} [] } exposes [] packages {} imports [ Task.{ Task } ] provides [ mainForHost ] effects fx.Effect - { - openFile : Str -> Effect U64, - closeFile : U64 -> Effect {}, - withFileOpen : Str, (U64 -> Effect (Result ok err)) -> Effect {}, - getFileLine : U64 -> Effect Str, - getFileBytes : U64 -> Effect (List U8), - putLine : Str -> Effect {}, - putRaw : Str -> Effect {}, - # Is there a limit to the number of effect, uncomment the next line and it crashes - # getLine : Effect Str, - getChar : Effect U8 - } + { + openFile : Str -> Effect U64, + closeFile : U64 -> Effect {}, + withFileOpen : Str, (U64 -> Effect (Result ok err)) -> Effect {}, + getFileLine : U64 -> Effect Str, + getFileBytes : U64 -> Effect (List U8), + putLine : Str -> Effect {}, + putRaw : Str -> Effect {}, + # Is there a limit to the number of effect, uncomment the next line and it crashes + # getLine : Effect Str, + getChar : Effect U8, + } mainForHost : Str -> Task {} [] as Fx mainForHost = \file -> main file From 200e237393391f122345f928dba3eea30139bbb8 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 1 Feb 2022 21:20:35 -0500 Subject: [PATCH 422/541] Add tests for multiline interface and hosted modules --- compiler/fmt/tests/test_fmt.rs | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index e6a182a809..0ff058cc88 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -2640,6 +2640,25 @@ mod test_fmt { )); } + #[test] + fn multi_line_interface() { + module_formats_same(indoc!( + r#" + interface Foo + exposes + [ + Stuff, + Things, + somethingElse, + ] + imports + [ + Blah, + Baz.{ stuff, things }, + ]"# + )); + } + #[test] fn single_line_app() { module_formats_same(indoc!( @@ -2674,6 +2693,31 @@ mod test_fmt { )); } + #[test] + fn multi_line_hosted() { + module_formats_same(indoc!( + r#" + hosted Foo + exposes + [ + Stuff, + Things, + somethingElse, + ] + imports + [ + Blah, + Baz.{ stuff, things }, + ] + generates Bar with + [ + map, + after, + loop, + ]"# + )); + } + /// Annotations and aliases #[test] From bfb938914f13a0dfdaa3c7752e4fa5d7adce6500 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 1 Feb 2022 21:34:18 -0500 Subject: [PATCH 423/541] Fix some missing indentations --- compiler/fmt/src/module.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/fmt/src/module.rs b/compiler/fmt/src/module.rs index 669edc61af..28a4a26918 100644 --- a/compiler/fmt/src/module.rs +++ b/compiler/fmt/src/module.rs @@ -331,7 +331,8 @@ impl<'a> Formattable for ExposedName<'a> { false } - fn format<'buf>(&self, buf: &mut Buf<'buf>, _indent: u16) { + fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) { + buf.indent(indent); buf.push_str(self.as_str()); } } @@ -379,6 +380,8 @@ fn fmt_packages_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &PackageEntry<'a>, i fn fmt_imports_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &ImportsEntry<'a>, indent: u16) { use roc_parse::header::ImportsEntry::*; + buf.indent(indent); + match entry { Module(module, loc_exposes_entries) => { buf.push_str(module.as_str()); From 320827167fb9484b8e8c04d70a5055a619c5858f Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 31 Jan 2022 00:30:15 -0500 Subject: [PATCH 424/541] Parse number literal width suffixes Supports [u,i][8,16,32,64,128] and [nat,dec] Part of #2350 --- ast/src/lang/core/expr/expr_to_expr2.rs | 5 +- ast/src/lang/core/pattern.rs | 5 +- cli/src/format.rs | 12 +- compiler/can/src/builtins.rs | 24 +- compiler/can/src/def.rs | 6 +- compiler/can/src/expr.rs | 40 +- compiler/can/src/module.rs | 12 +- compiler/can/src/num.rs | 24 +- compiler/can/src/operator.rs | 4 +- compiler/can/src/pattern.rs | 41 +- compiler/constrain/src/expr.rs | 9 +- compiler/constrain/src/pattern.rs | 15 +- compiler/fmt/src/expr.rs | 30 +- compiler/fmt/src/pattern.rs | 21 +- compiler/mono/src/ir.rs | 20 +- compiler/parse/src/ast.rs | 89 ++- compiler/parse/src/expr.rs | 38 +- compiler/parse/src/number_literal.rs | 108 +++- compiler/parse/src/pattern.rs | 6 +- .../pass/add_var_with_spaces.expr.result-ast | 1 + .../pass/add_with_spaces.expr.result-ast | 2 + ...notated_record_destructure.expr.result-ast | 1 + .../annotated_tag_destructure.expr.result-ast | 1 + .../pass/apply_global_tag.expr.result-ast | 2 + ...enthetical_global_tag_args.expr.result-ast | 2 + .../pass/apply_private_tag.expr.result-ast | 2 + .../pass/apply_two_args.expr.result-ast | 2 + .../pass/apply_unary_negation.expr.result-ast | 1 + .../pass/apply_unary_not.expr.result-ast | 1 + .../pass/basic_apply.expr.result-ast | 1 + .../snapshots/pass/basic_docs.expr.result-ast | 2 + .../closure_with_underscores.expr.result-ast | 1 + .../pass/comment_after_op.expr.result-ast | 2 + .../pass/comment_before_op.expr.result-ast | 2 + .../comment_with_non_ascii.expr.result-ast | 2 + .../snapshots/pass/expect.expr.result-ast | 3 + .../float_with_underscores.expr.result-ast | 1 + .../pass/highest_float.expr.result-ast | 1 + .../pass/highest_int.expr.result-ast | 1 + .../snapshots/pass/if_def.expr.result-ast | 2 + .../pass/int_with_underscore.expr.result-ast | 1 + .../pass/lowest_float.expr.result-ast | 1 + .../snapshots/pass/lowest_int.expr.result-ast | 1 + ...ed_ident_due_to_underscore.expr.result-ast | 1 + ...ormed_pattern_field_access.expr.result-ast | 2 + ...formed_pattern_module_name.expr.result-ast | 2 + .../minus_twelve_minus_five.expr.result-ast | 2 + .../snapshots/pass/mixed_docs.expr.result-ast | 2 + .../pass/module_def_newline.module.result-ast | 1 + .../multiline_type_signature.expr.result-ast | 1 + ...ype_signature_with_comment.expr.result-ast | 1 + .../pass/multiple_operators.expr.result-ast | 3 + .../pass/negative_float.expr.result-ast | 1 + .../pass/negative_int.expr.result-ast | 1 + .../nested_def_annotation.module.result-ast | 2 + .../snapshots/pass/nested_if.expr.result-ast | 3 + .../pass/newline_after_equals.expr.result-ast | 2 + .../pass/newline_after_mul.expr.result-ast | 2 + .../pass/newline_after_sub.expr.result-ast | 2 + ...nd_spaces_before_less_than.expr.result-ast | 3 + .../pass/newline_before_add.expr.result-ast | 2 + .../pass/newline_before_sub.expr.result-ast | 2 + .../newline_singleton_list.expr.result-ast | 1 + .../snapshots/pass/not_docs.expr.result-ast | 2 + .../number_literal_suffixes.expr.result-ast | 573 ++++++++++++++++++ .../pass/number_literal_suffixes.expr.roc | 38 ++ .../snapshots/pass/one_def.expr.result-ast | 2 + .../pass/one_minus_two.expr.result-ast | 2 + .../pass/one_plus_two.expr.result-ast | 2 + .../pass/one_spaced_def.expr.result-ast | 2 + .../pass/ops_with_newlines.expr.result-ast | 2 + .../packed_singleton_list.expr.result-ast | 1 + .../pass/parenthetical_apply.expr.result-ast | 1 + .../pass/parse_alias.expr.result-ast | 1 + .../pass/parse_as_ann.expr.result-ast | 1 + ...ttern_with_space_in_parens.expr.result-ast | 2 + .../pass/positive_float.expr.result-ast | 1 + .../pass/positive_int.expr.result-ast | 1 + .../record_destructure_def.expr.result-ast | 3 + .../record_func_type_decl.expr.result-ast | 1 + .../pass/record_update.expr.result-ast | 2 + .../pass/record_with_if.expr.result-ast | 3 + .../pass/single_arg_closure.expr.result-ast | 1 + .../single_underscore_closure.expr.result-ast | 1 + .../spaced_singleton_list.expr.result-ast | 1 + .../standalone_module_defs.module.result-ast | 1 + .../pass/sub_var_with_spaces.expr.result-ast | 1 + .../pass/sub_with_spaces.expr.result-ast | 2 + .../pass/tag_pattern.expr.result-ast | 1 + .../pass/ten_times_eleven.expr.result-ast | 2 + .../pass/three_arg_closure.expr.result-ast | 1 + .../pass/two_arg_closure.expr.result-ast | 1 + .../pass/two_branch_when.expr.result-ast | 2 + .../pass/two_spaced_def.expr.result-ast | 3 + .../type_decl_with_underscore.expr.result-ast | 1 + .../pass/unary_negation_arg.expr.result-ast | 1 + ...unary_negation_with_parens.expr.result-ast | 1 + .../unary_not_with_parens.expr.result-ast | 1 + .../underscore_backpassing.expr.result-ast | 1 + .../pass/var_minus_two.expr.result-ast | 1 + .../pass/when_if_guard.expr.result-ast | 3 + .../pass/when_in_parens.expr.result-ast | 1 + .../when_in_parens_indented.expr.result-ast | 1 + ..._with_alternative_patterns.expr.result-ast | 2 + ..._with_function_application.expr.result-ast | 3 + ...when_with_negative_numbers.expr.result-ast | 4 + .../pass/when_with_numbers.expr.result-ast | 4 + .../pass/when_with_records.expr.result-ast | 2 + .../snapshots/pass/zero_float.expr.result-ast | 1 + .../snapshots/pass/zero_int.expr.result-ast | 1 + compiler/parse/tests/test_parse.rs | 11 +- repl_eval/src/eval.rs | 9 +- 112 files changed, 1159 insertions(+), 127 deletions(-) create mode 100644 compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.result-ast create mode 100644 compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.roc diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index a83fd39ed1..8b35a7158f 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -50,7 +50,7 @@ pub fn expr_to_expr2<'a>( use roc_parse::ast::Expr::*; match parse_expr { - Float(string) => { + Float(string, _bound) => { match finish_parsing_float(string) { Ok(float) => { let expr = Expr2::Float { @@ -72,7 +72,7 @@ pub fn expr_to_expr2<'a>( } } } - Num(string) => { + Num(string, _bound) => { match finish_parsing_int(string) { Ok(int) => { let expr = Expr2::SmallInt { @@ -105,6 +105,7 @@ pub fn expr_to_expr2<'a>( string, base, is_negative, + bound: _, } => { match finish_parsing_base(string, *base, *is_negative) { Ok(int) => { diff --git a/ast/src/lang/core/pattern.rs b/ast/src/lang/core/pattern.rs index 19a956bcd8..ee0c8ca72a 100644 --- a/ast/src/lang/core/pattern.rs +++ b/ast/src/lang/core/pattern.rs @@ -177,7 +177,7 @@ pub fn to_pattern2<'a>( TopLevelDef | DefExpr => underscore_in_def(env, region), }, - FloatLiteral(ref string) => match pattern_type { + FloatLiteral(ref string, _bound) => match pattern_type { WhenBranch => match finish_parsing_float(string) { Err(_error) => { let problem = MalformedPatternProblem::MalformedFloat; @@ -188,7 +188,7 @@ pub fn to_pattern2<'a>( ptype => unsupported_pattern(env, ptype, region), }, - NumLiteral(string) => match pattern_type { + NumLiteral(string, _bound) => match pattern_type { WhenBranch => match finish_parsing_int(string) { Err(_error) => { let problem = MalformedPatternProblem::MalformedInt; @@ -203,6 +203,7 @@ pub fn to_pattern2<'a>( string, base, is_negative, + bound: _, } => match pattern_type { WhenBranch => match finish_parsing_base(string, *base, *is_negative) { Err(_error) => { diff --git a/cli/src/format.rs b/cli/src/format.rs index 365bb0034e..9c62b228a1 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -484,16 +484,18 @@ impl<'a> RemoveSpaces<'a> for StrSegment<'a> { impl<'a> RemoveSpaces<'a> for Expr<'a> { fn remove_spaces(&self, arena: &'a Bump) -> Self { match *self { - Expr::Float(a) => Expr::Float(a), - Expr::Num(a) => Expr::Num(a), + Expr::Float(a, bound) => Expr::Float(a, bound), + Expr::Num(a, bound) => Expr::Num(a, bound), Expr::NonBase10Int { string, base, is_negative, + bound, } => Expr::NonBase10Int { string, base, is_negative, + bound, }, Expr::Str(a) => Expr::Str(a.remove_spaces(arena)), Expr::Access(a, b) => Expr::Access(arena.alloc(a.remove_spaces(arena)), b), @@ -569,17 +571,19 @@ impl<'a> RemoveSpaces<'a> for Pattern<'a> { Pattern::OptionalField(a, b) => { Pattern::OptionalField(a, arena.alloc(b.remove_spaces(arena))) } - Pattern::NumLiteral(a) => Pattern::NumLiteral(a), + Pattern::NumLiteral(a, bound) => Pattern::NumLiteral(a, bound), Pattern::NonBase10Literal { string, base, is_negative, + bound, } => Pattern::NonBase10Literal { string, base, is_negative, + bound, }, - Pattern::FloatLiteral(a) => Pattern::FloatLiteral(a), + Pattern::FloatLiteral(a, bound) => Pattern::FloatLiteral(a, bound), Pattern::StrLiteral(a) => Pattern::StrLiteral(a), Pattern::Underscore(a) => Pattern::Underscore(a), Pattern::Malformed(a) => Pattern::Malformed(a), diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index cd90b4d698..baf1677492 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -7,6 +7,7 @@ use roc_module::called_via::CalledVia; use roc_module::ident::{Lowercase, TagName}; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; +use roc_parse::ast::NumericBound; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; @@ -4990,15 +4991,32 @@ where I128: Into, { let ii = i.into(); - Int(num_var, precision_var, ii.to_string().into_boxed_str(), ii) + Int( + num_var, + precision_var, + ii.to_string().into_boxed_str(), + ii, + NumericBound::None, + ) } #[inline(always)] fn float(num_var: Variable, precision_var: Variable, f: f64) -> Expr { - Float(num_var, precision_var, f.to_string().into_boxed_str(), f) + Float( + num_var, + precision_var, + f.to_string().into_boxed_str(), + f, + NumericBound::None, + ) } #[inline(always)] fn num(num_var: Variable, i: i64) -> Expr { - Num(num_var, i.to_string().into_boxed_str(), i) + Num( + num_var, + i.to_string().into_boxed_str(), + i, + NumericBound::None, + ) } diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 9343134c60..2e046dbb07 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -840,9 +840,9 @@ fn pattern_to_vars_by_symbol( } } - NumLiteral(_, _, _) - | IntLiteral(_, _, _) - | FloatLiteral(_, _, _) + NumLiteral(..) + | IntLiteral(..) + | FloatLiteral(..) | StrLiteral(_) | Underscore | MalformedPattern(_, _) diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 846a064d5d..0cfb1307de 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -14,7 +14,7 @@ use roc_module::called_via::CalledVia; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; -use roc_parse::ast::{self, EscapedChar, StrLiteral}; +use roc_parse::ast::{self, EscapedChar, FloatWidth, NumWidth, NumericBound, StrLiteral}; use roc_parse::pattern::PatternType::*; use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError}; use roc_region::all::{Loc, Region}; @@ -52,11 +52,11 @@ pub enum Expr { // Num stores the `a` variable in `Num a`. Not the same as the variable // stored in Int and Float below, which is strictly for better error messages - Num(Variable, Box, i64), + Num(Variable, Box, i64, NumericBound), // Int and Float store a variable to generate better error messages - Int(Variable, Variable, Box, i128), - Float(Variable, Variable, Box, f64), + Int(Variable, Variable, Box, i128, NumericBound), + Float(Variable, Variable, Box, f64, NumericBound), Str(Box), List { elem_var: Variable, @@ -208,21 +208,23 @@ pub fn canonicalize_expr<'a>( use Expr::*; let (expr, output) = match expr { - ast::Expr::Num(str) => { + &ast::Expr::Num(str, bound) => { let answer = num_expr_from_result( var_store, - finish_parsing_int(*str).map(|int| (*str, int)), + finish_parsing_int(str).map(|int| (str, int)), region, + bound, env, ); (answer, Output::default()) } - ast::Expr::Float(str) => { + &ast::Expr::Float(str, bound) => { let answer = float_expr_from_result( var_store, - finish_parsing_float(str).map(|f| (*str, f)), + finish_parsing_float(str).map(|f| (str, f)), region, + bound, env, ); @@ -790,21 +792,29 @@ pub fn canonicalize_expr<'a>( (RuntimeError(problem), Output::default()) } - ast::Expr::NonBase10Int { + &ast::Expr::NonBase10Int { string, base, is_negative, + bound, } => { // the minus sign is added before parsing, to get correct overflow/underflow behavior - let answer = match finish_parsing_base(string, *base, *is_negative) { + let answer = match finish_parsing_base(string, base, is_negative) { Ok(int) => { // Done in this kinda round about way with intermediate variables // to keep borrowed values around and make this compile let int_string = int.to_string(); let int_str = int_string.as_str(); - int_expr_from_result(var_store, Ok((int_str, int as i128)), region, *base, env) + int_expr_from_result( + var_store, + Ok((int_str, int as i128)), + region, + base, + bound, + env, + ) } - Err(e) => int_expr_from_result(var_store, Err(e), region, *base, env), + Err(e) => int_expr_from_result(var_store, Err(e), region, base, bound, env), }; (answer, Output::default()) @@ -1226,9 +1236,9 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> match expr { // Num stores the `a` variable in `Num a`. Not the same as the variable // stored in Int and Float below, which is strictly for better error messages - other @ Num(_, _, _) - | other @ Int(_, _, _, _) - | other @ Float(_, _, _, _) + other @ Num(..) + | other @ Int(..) + | other @ Float(..) | other @ Str { .. } | other @ RuntimeError(_) | other @ EmptyRecord diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index e164fb8680..74b7d3d9fa 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -397,9 +397,9 @@ fn fix_values_captured_in_closure_pattern( } } Identifier(_) - | NumLiteral(_, _, _) - | IntLiteral(_, _, _) - | FloatLiteral(_, _, _) + | NumLiteral(..) + | IntLiteral(..) + | FloatLiteral(..) | StrLiteral(_) | Underscore | Shadowed(..) @@ -453,9 +453,9 @@ fn fix_values_captured_in_closure_expr( fix_values_captured_in_closure_expr(&mut loc_body.value, no_capture_symbols); } - Num(_, _, _) - | Int(_, _, _, _) - | Float(_, _, _, _) + Num(..) + | Int(..) + | Float(..) | Str(_) | Var(_) | EmptyRecord diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index aff1231def..16cea034fd 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -1,6 +1,9 @@ use crate::env::Env; use crate::expr::Expr; use roc_parse::ast::Base; +use roc_parse::ast::FloatWidth; +use roc_parse::ast::NumWidth; +use roc_parse::ast::NumericBound; use roc_problem::can::Problem; use roc_problem::can::RuntimeError::*; use roc_problem::can::{FloatErrorKind, IntErrorKind}; @@ -18,10 +21,11 @@ pub fn num_expr_from_result( var_store: &mut VarStore, result: Result<(&str, i64), (&str, IntErrorKind)>, region: Region, + bound: NumericBound, env: &mut Env, ) -> Expr { match result { - Ok((str, num)) => Expr::Num(var_store.fresh(), (*str).into(), num), + Ok((str, num)) => Expr::Num(var_store.fresh(), (*str).into(), num, bound), Err((raw, error)) => { // (Num *) compiles to Int if it doesn't // get specialized to something else first, @@ -41,11 +45,18 @@ pub fn int_expr_from_result( result: Result<(&str, i128), (&str, IntErrorKind)>, region: Region, base: Base, + bound: NumericBound, env: &mut Env, ) -> Expr { // Int stores a variable to generate better error messages match result { - Ok((str, int)) => Expr::Int(var_store.fresh(), var_store.fresh(), (*str).into(), int), + Ok((str, int)) => Expr::Int( + var_store.fresh(), + var_store.fresh(), + (*str).into(), + int, + bound, + ), Err((raw, error)) => { let runtime_error = InvalidInt(error, base, region, raw.into()); @@ -61,11 +72,18 @@ pub fn float_expr_from_result( var_store: &mut VarStore, result: Result<(&str, f64), (&str, FloatErrorKind)>, region: Region, + bound: NumericBound, env: &mut Env, ) -> Expr { // Float stores a variable to generate better error messages match result { - Ok((str, float)) => Expr::Float(var_store.fresh(), var_store.fresh(), (*str).into(), float), + Ok((str, float)) => Expr::Float( + var_store.fresh(), + var_store.fresh(), + (*str).into(), + float, + bound, + ), Err((raw, error)) => { let runtime_error = InvalidFloat(error, region, raw.into()); diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index babfd0622c..04255f9175 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -121,8 +121,8 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> { /// then replace the BinOp nodes with Apply nodes. Also drop SpaceBefore and SpaceAfter nodes. pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc>) -> &'a Loc> { match &loc_expr.value { - Float(_) - | Num(_) + Float(..) + | Num(..) | NonBase10Int { .. } | Str(_) | AccessorFunction(_) diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 6c9392e919..cfde4168ec 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -4,7 +4,7 @@ use crate::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use crate::scope::Scope; use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::symbol::Symbol; -use roc_parse::ast::{self, StrLiteral, StrSegment}; +use roc_parse::ast::{self, FloatWidth, NumWidth, NumericBound, StrLiteral, StrSegment}; use roc_parse::pattern::PatternType; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; use roc_region::all::{Loc, Region}; @@ -25,9 +25,9 @@ pub enum Pattern { ext_var: Variable, destructs: Vec>, }, - IntLiteral(Variable, Box, i64), - NumLiteral(Variable, Box, i64), - FloatLiteral(Variable, Box, f64), + IntLiteral(Variable, Box, i64, NumericBound), + NumLiteral(Variable, Box, i64, NumericBound), + FloatLiteral(Variable, Box, f64, NumericBound), StrLiteral(Box), Underscore, @@ -85,9 +85,9 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec) { } } - NumLiteral(_, _, _) - | IntLiteral(_, _, _) - | FloatLiteral(_, _, _) + NumLiteral(..) + | IntLiteral(..) + | FloatLiteral(..) | StrLiteral(_) | Underscore | MalformedPattern(_, _) @@ -184,13 +184,13 @@ pub fn canonicalize_pattern<'a>( } } - FloatLiteral(str) => match pattern_type { + &FloatLiteral(str, bound) => match pattern_type { WhenBranch => match finish_parsing_float(str) { Err(_error) => { let problem = MalformedPatternProblem::MalformedFloat; malformed_pattern(env, problem, region) } - Ok(float) => Pattern::FloatLiteral(var_store.fresh(), (*str).into(), float), + Ok(float) => Pattern::FloatLiteral(var_store.fresh(), (str).into(), float, bound), }, ptype => unsupported_pattern(env, ptype, region), }, @@ -200,32 +200,33 @@ pub fn canonicalize_pattern<'a>( TopLevelDef | DefExpr => bad_underscore(env, region), }, - NumLiteral(str) => match pattern_type { + &NumLiteral(str, bound) => match pattern_type { WhenBranch => match finish_parsing_int(str) { Err(_error) => { let problem = MalformedPatternProblem::MalformedInt; malformed_pattern(env, problem, region) } - Ok(int) => Pattern::NumLiteral(var_store.fresh(), (*str).into(), int), + Ok(int) => Pattern::NumLiteral(var_store.fresh(), (str).into(), int, bound), }, ptype => unsupported_pattern(env, ptype, region), }, - NonBase10Literal { + &NonBase10Literal { string, base, is_negative, + bound, } => match pattern_type { - WhenBranch => match finish_parsing_base(string, *base, *is_negative) { + WhenBranch => match finish_parsing_base(string, base, is_negative) { Err(_error) => { - let problem = MalformedPatternProblem::MalformedBase(*base); + let problem = MalformedPatternProblem::MalformedBase(base); malformed_pattern(env, problem, region) } Ok(int) => { - let sign_str = if *is_negative { "-" } else { "" }; + let sign_str = if is_negative { "-" } else { "" }; let int_str = format!("{}{}", sign_str, int.to_string()).into_boxed_str(); - let i = if *is_negative { -int } else { int }; - Pattern::IntLiteral(var_store.fresh(), int_str, i) + let i = if is_negative { -int } else { int }; + Pattern::IntLiteral(var_store.fresh(), int_str, i, bound) } }, ptype => unsupported_pattern(env, ptype, region), @@ -473,9 +474,9 @@ fn add_bindings_from_patterns( answer.push((*symbol, *region)); } } - NumLiteral(_, _, _) - | IntLiteral(_, _, _) - | FloatLiteral(_, _, _) + NumLiteral(..) + | IntLiteral(..) + | FloatLiteral(..) | StrLiteral(_) | Underscore | MalformedPattern(_, _) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index c0cfffa07b..3cbdbbbc04 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -96,8 +96,10 @@ pub fn constrain_expr( expected: Expected, ) -> Constraint { match expr { - Int(var, precision, _, _) => int_literal(*var, *precision, expected, region), - Num(var, _, _) => exists( + // TODO constrain with bound + Int(var, precision, _, _, _bound) => int_literal(*var, *precision, expected, region), + // TODO constrain with bound + Num(var, _, _, _bound) => exists( vec![*var], Eq( crate::builtins::num_num(Type::Variable(*var)), @@ -106,7 +108,8 @@ pub fn constrain_expr( region, ), ), - Float(var, precision, _, _) => float_literal(*var, *precision, expected, region), + // TODO constrain with bound + Float(var, precision, _, _, _bound) => float_literal(*var, *precision, expected, region), EmptyRecord => constrain_empty_record(region, expected), Expr::Record { record_var, fields } => { if fields.is_empty() { diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 2a1ce6fa46..894bd07609 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -55,9 +55,9 @@ fn headers_from_annotation_help( Underscore | MalformedPattern(_, _) | UnsupportedPattern(_) - | NumLiteral(_, _, _) - | IntLiteral(_, _, _) - | FloatLiteral(_, _, _) + | NumLiteral(..) + | IntLiteral(..) + | FloatLiteral(..) | StrLiteral(_) => true, RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() { @@ -178,7 +178,8 @@ pub fn constrain_pattern( ); } - NumLiteral(var, _, _) => { + NumLiteral(var, _, _, _bound) => { + // TODO: constrain bound here state.vars.push(*var); state.constraints.push(Constraint::Pattern( @@ -189,7 +190,8 @@ pub fn constrain_pattern( )); } - IntLiteral(precision_var, _, _) => { + IntLiteral(precision_var, _, _, _bound) => { + // TODO: constrain bound here state.constraints.push(Constraint::Pattern( region, PatternCategory::Int, @@ -198,7 +200,8 @@ pub fn constrain_pattern( )); } - FloatLiteral(precision_var, _, _) => { + FloatLiteral(precision_var, _, _, _bound) => { + // TODO: constrain bound here state.constraints.push(Constraint::Pattern( region, PatternCategory::Float, diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index 994d9d9796..3a747a23c1 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -6,7 +6,8 @@ use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT}; use crate::Buf; use roc_module::called_via::{self, BinOp}; use roc_parse::ast::{ - AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces, Pattern, WhenBranch, + AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces, NumericBound, Pattern, + WhenBranch, }; use roc_parse::ast::{StrLiteral, StrSegment}; use roc_region::all::Loc; @@ -27,8 +28,8 @@ impl<'a> Formattable for Expr<'a> { } // These expressions never have newlines - Float(_) - | Num(_) + Float(..) + | Num(..) | NonBase10Int { .. } | Access(_, _) | AccessorFunction(_) @@ -196,7 +197,23 @@ impl<'a> Formattable for Expr<'a> { buf.push(')'); } } - Num(string) | Float(string) | GlobalTag(string) | PrivateTag(string) => { + Num(string, bound) => { + buf.indent(indent); + buf.push_str(string); + + if let &NumericBound::Exact(width) = bound { + buf.push_str(&width.to_string()); + } + } + Float(string, bound) => { + buf.indent(indent); + buf.push_str(string); + + if let &NumericBound::Exact(width) = bound { + buf.push_str(&width.to_string()); + } + } + GlobalTag(string) | PrivateTag(string) => { buf.indent(indent); buf.push_str(string) } @@ -204,6 +221,7 @@ impl<'a> Formattable for Expr<'a> { base, string, is_negative, + bound, } => { buf.indent(indent); if *is_negative { @@ -218,6 +236,10 @@ impl<'a> Formattable for Expr<'a> { } buf.push_str(string); + + if let &NumericBound::Exact(width) = bound { + buf.push_str(&width.to_string()); + } } Record(fields) => { fmt_record(buf, None, *fields, indent); diff --git a/compiler/fmt/src/pattern.rs b/compiler/fmt/src/pattern.rs index 2aac5b3e91..92fc7ba9da 100644 --- a/compiler/fmt/src/pattern.rs +++ b/compiler/fmt/src/pattern.rs @@ -1,7 +1,7 @@ use crate::annotation::{Formattable, Newlines, Parens}; use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt}; use crate::Buf; -use roc_parse::ast::{Base, Pattern}; +use roc_parse::ast::{Base, NumericBound, Pattern}; pub fn fmt_pattern<'a, 'buf>( buf: &mut Buf<'buf>, @@ -31,9 +31,9 @@ impl<'a> Formattable for Pattern<'a> { | Pattern::GlobalTag(_) | Pattern::PrivateTag(_) | Pattern::Apply(_, _) - | Pattern::NumLiteral(_) + | Pattern::NumLiteral(..) | Pattern::NonBase10Literal { .. } - | Pattern::FloatLiteral(_) + | Pattern::FloatLiteral(..) | Pattern::StrLiteral(_) | Pattern::Underscore(_) | Pattern::Malformed(_) @@ -116,14 +116,18 @@ impl<'a> Formattable for Pattern<'a> { loc_pattern.format(buf, indent); } - NumLiteral(string) => { + NumLiteral(string, bound) => { buf.indent(indent); buf.push_str(string); + if let &NumericBound::Exact(width) = bound { + buf.push_str(&width.to_string()); + } } NonBase10Literal { base, string, is_negative, + bound, } => { buf.indent(indent); if *is_negative { @@ -138,10 +142,17 @@ impl<'a> Formattable for Pattern<'a> { } buf.push_str(string); + + if let &NumericBound::Exact(width) = bound { + buf.push_str(&width.to_string()); + } } - FloatLiteral(string) => { + FloatLiteral(string, bound) => { buf.indent(indent); buf.push_str(string); + if let &NumericBound::Exact(width) = bound { + buf.push_str(&width.to_string()); + } } StrLiteral(literal) => { todo!("Format string literal: {:?}", literal); diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index ff51aab7fd..e2ddbd3e1f 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -2020,7 +2020,7 @@ fn pattern_to_when<'a>( (symbol, Loc::at_zero(wrapped_body)) } - IntLiteral(_, _, _) | NumLiteral(_, _, _) | FloatLiteral(_, _, _) | StrLiteral(_) => { + IntLiteral(..) | NumLiteral(..) | FloatLiteral(..) | StrLiteral(_) => { // These patters are refutable, and thus should never occur outside a `when` expression // They should have been replaced with `UnsupportedPattern` during canonicalization unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value) @@ -3009,14 +3009,14 @@ fn try_make_literal<'a>( use roc_can::expr::Expr::*; match can_expr { - Int(_, precision, _, int) => { + Int(_, precision, _, int, _bound) => { match num_argument_to_int_or_float(env.subs, env.target_info, *precision, false) { IntOrFloat::Int(_) => Some(Literal::Int(*int)), _ => unreachable!("unexpected float precision for integer"), } } - Float(_, precision, float_str, float) => { + Float(_, precision, float_str, float, _bound) => { match num_argument_to_int_or_float(env.subs, env.target_info, *precision, true) { IntOrFloat::Float(_) => Some(Literal::Float(*float)), IntOrFloat::DecimalFloatType => { @@ -3036,7 +3036,7 @@ fn try_make_literal<'a>( // TODO investigate lifetime trouble // Str(string) => Some(Literal::Str(env.arena.alloc(string))), - Num(var, num_str, num) => { + Num(var, num_str, num, _bound) => { // first figure out what kind of number this is match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { IntOrFloat::Int(_) => Some(Literal::Int((*num).into())), @@ -3072,7 +3072,7 @@ pub fn with_hole<'a>( let arena = env.arena; match can_expr { - Int(_, precision, _, int) => { + Int(_, precision, _, int, _bound) => { match num_argument_to_int_or_float(env.subs, env.target_info, precision, false) { IntOrFloat::Int(precision) => Stmt::Let( assigned, @@ -3084,7 +3084,7 @@ pub fn with_hole<'a>( } } - Float(_, precision, float_str, float) => { + Float(_, precision, float_str, float, _bound) => { match num_argument_to_int_or_float(env.subs, env.target_info, precision, true) { IntOrFloat::Float(precision) => Stmt::Let( assigned, @@ -3115,7 +3115,7 @@ pub fn with_hole<'a>( hole, ), - Num(var, num_str, num) => { + Num(var, num_str, num, _bound) => { // first figure out what kind of number this is match num_argument_to_int_or_float(env.subs, env.target_info, var, false) { IntOrFloat::Int(precision) => Stmt::Let( @@ -7662,7 +7662,7 @@ fn from_can_pattern_help<'a>( match can_pattern { Underscore => Ok(Pattern::Underscore), Identifier(symbol) => Ok(Pattern::Identifier(*symbol)), - IntLiteral(var, _, int) => { + IntLiteral(var, _, int, _bound) => { match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { IntOrFloat::Int(precision) => Ok(Pattern::IntLiteral(*int as i128, precision)), other => { @@ -7673,7 +7673,7 @@ fn from_can_pattern_help<'a>( } } } - FloatLiteral(var, float_str, float) => { + FloatLiteral(var, float_str, float, _bound) => { // TODO: Can I reuse num_argument_to_int_or_float here if I pass in true? match num_argument_to_int_or_float(env.subs, env.target_info, *var, true) { IntOrFloat::Int(_) => { @@ -7704,7 +7704,7 @@ fn from_can_pattern_help<'a>( // TODO preserve malformed problem information here? Err(RuntimeError::UnsupportedPattern(*region)) } - NumLiteral(var, num_str, num) => { + NumLiteral(var, num_str, num, _bound) => { match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { IntOrFloat::Int(precision) => Ok(Pattern::IntLiteral(*num as i128, precision)), IntOrFloat::Float(precision) => Ok(Pattern::FloatLiteral(*num as u64, precision)), diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index 3fc72c72fd..43e1db0fa8 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -1,4 +1,4 @@ -use std::fmt::Debug; +use std::fmt::{Debug, Display}; use crate::header::{AppHeader, HostedHeader, InterfaceHeader, PlatformHeader}; use crate::ident::Ident; @@ -126,6 +126,70 @@ pub enum StrLiteral<'a> { Block(&'a [&'a [StrSegment<'a>]]), } +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum NumWidth { + U8, + U16, + U32, + U64, + U128, + I8, + I16, + I32, + I64, + I128, + Nat, + Dec, +} + +impl Display for NumWidth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use NumWidth::*; + f.write_str(match self { + U8 => "u8", + U16 => "u16", + U32 => "u32", + U64 => "u64", + U128 => "u128", + I8 => "i8", + I16 => "i16", + I32 => "i32", + I64 => "i64", + I128 => "i128", + Nat => "nat", + Dec => "dec", + }) + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum FloatWidth { + F32, + F64, +} + +impl Display for FloatWidth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use FloatWidth::*; + f.write_str(match self { + F32 => "f32", + F64 => "f64", + }) + } +} + +/// Describes a bound on the width of a numeric literal. +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum NumericBound +where + W: Copy, +{ + /// There is no bound on the width. + None, + /// Must have exactly the width `W`. + Exact(W), +} + /// A parsed expression. This uses lifetimes extensively for two reasons: /// /// 1. It uses Bump::alloc for all allocations, which returns a reference. @@ -138,12 +202,13 @@ pub enum StrLiteral<'a> { #[derive(Clone, Copy, Debug, PartialEq)] pub enum Expr<'a> { // Number Literals - Float(&'a str), - Num(&'a str), + Float(&'a str, NumericBound), + Num(&'a str, NumericBound), NonBase10Int { string: &'a str, base: Base, is_negative: bool, + bound: NumericBound, }, // String Literals @@ -431,13 +496,14 @@ pub enum Pattern<'a> { OptionalField(&'a str, &'a Loc>), // Literal - NumLiteral(&'a str), + NumLiteral(&'a str, NumericBound), NonBase10Literal { string: &'a str, base: Base, is_negative: bool, + bound: NumericBound, }, - FloatLiteral(&'a str), + FloatLiteral(&'a str, NumericBound), StrLiteral(StrLiteral<'a>), Underscore(&'a str), @@ -540,20 +606,27 @@ impl<'a> Pattern<'a> { x == y } // Literal - (NumLiteral(x), NumLiteral(y)) => x == y, + (NumLiteral(x, bound_x), NumLiteral(y, bound_y)) => x == y && bound_x == bound_y, ( NonBase10Literal { string: string_x, base: base_x, is_negative: is_negative_x, + bound: bound_x, }, NonBase10Literal { string: string_y, base: base_y, is_negative: is_negative_y, + bound: bound_y, }, - ) => string_x == string_y && base_x == base_y && is_negative_x == is_negative_y, - (FloatLiteral(x), FloatLiteral(y)) => x == y, + ) => { + string_x == string_y + && base_x == base_y + && is_negative_x == is_negative_y + && bound_x == bound_y + } + (FloatLiteral(x, bound_x), FloatLiteral(y, bound_y)) => x == y && bound_x == bound_y, (StrLiteral(x), StrLiteral(y)) => x == y, (Underscore(x), Underscore(y)) => x == y, diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index c56e6bb5da..58acb0c5a8 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -1,6 +1,6 @@ use crate::ast::{ - AliasHeader, AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, Pattern, - Spaceable, TypeAnnotation, + AliasHeader, AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, + NumericBound, Pattern, Spaceable, TypeAnnotation, }; use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e}; use crate::ident::{lowercase_ident, parse_ident, Ident}; @@ -377,7 +377,7 @@ impl<'a> ExprState<'a> { } else { let region = self.expr.region; - let mut value = Expr::Num(""); + let mut value = Expr::Num("", NumericBound::None); std::mem::swap(&mut self.expr.value, &mut value); self.expr = arena @@ -515,28 +515,30 @@ fn numeric_negate_expression<'a, T>( let region = Region::new(start, expr.region.end()); let new_expr = match &expr.value { - Expr::Num(string) => { + &Expr::Num(string, bound) => { let new_string = unsafe { std::str::from_utf8_unchecked(&state.bytes()[..string.len() + 1]) }; - Expr::Num(new_string) + Expr::Num(new_string, bound) } - Expr::Float(string) => { + &Expr::Float(string, bound) => { let new_string = unsafe { std::str::from_utf8_unchecked(&state.bytes()[..string.len() + 1]) }; - Expr::Float(new_string) + Expr::Float(new_string, bound) } - Expr::NonBase10Int { + &Expr::NonBase10Int { string, base, is_negative, + bound, } => { // don't include the minus sign here; it will not be parsed right Expr::NonBase10Int { is_negative: !is_negative, string, - base: *base, + base, + bound, } } _ => Expr::UnaryOp(arena.alloc(expr), Loc::at(loc_op.region, UnaryOp::Negate)), @@ -1450,16 +1452,18 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result Ok(Pattern::FloatLiteral(string)), - Expr::Num(string) => Ok(Pattern::NumLiteral(string)), + &Expr::Float(string, bound) => Ok(Pattern::FloatLiteral(string, bound)), + &Expr::Num(string, bound) => Ok(Pattern::NumLiteral(string, bound)), Expr::NonBase10Int { string, base, is_negative, + bound, } => Ok(Pattern::NonBase10Literal { string, base: *base, is_negative: *is_negative, + bound: *bound, }), // These would not have parsed as patterns Expr::AccessorFunction(_) @@ -2319,16 +2323,18 @@ fn positive_number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> { use crate::number_literal::NumLiteral::*; match literal { - Num(s) => Expr::Num(s), - Float(s) => Expr::Float(s), + Num(s, bound) => Expr::Num(s, bound), + Float(s, bound) => Expr::Float(s, bound), NonBase10Int { string, base, is_negative, + bound, } => Expr::NonBase10Int { string, base, is_negative, + bound, }, } } @@ -2340,16 +2346,18 @@ fn number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> { use crate::number_literal::NumLiteral::*; match literal { - Num(s) => Expr::Num(s), - Float(s) => Expr::Float(s), + Num(s, bound) => Expr::Num(s, bound), + Float(s, bound) => Expr::Float(s, bound), NonBase10Int { string, base, is_negative, + bound, } => Expr::NonBase10Int { string, base, is_negative, + bound, }, } }) diff --git a/compiler/parse/src/number_literal.rs b/compiler/parse/src/number_literal.rs index a4e8624684..bf6341d76f 100644 --- a/compiler/parse/src/number_literal.rs +++ b/compiler/parse/src/number_literal.rs @@ -1,14 +1,16 @@ -use crate::ast::Base; +use crate::ast::{Base, FloatWidth, NumWidth, NumericBound}; use crate::parser::{ENumber, ParseResult, Parser, Progress}; use crate::state::State; +#[derive(Debug, Copy, Clone)] pub enum NumLiteral<'a> { - Float(&'a str), - Num(&'a str), + Float(&'a str, NumericBound), + Num(&'a str, NumericBound), NonBase10Int { string: &'a str, base: Base, is_negative: bool, + bound: NumericBound, }, } @@ -49,14 +51,104 @@ fn parse_number_base<'a>( bytes: &'a [u8], state: State<'a>, ) -> ParseResult<'a, NumLiteral<'a>, ENumber> { - match bytes.get(0..2) { + let number = match bytes.get(0..2) { Some(b"0b") => chomp_number_base(Base::Binary, is_negated, &bytes[2..], state), Some(b"0o") => chomp_number_base(Base::Octal, is_negated, &bytes[2..], state), Some(b"0x") => chomp_number_base(Base::Hex, is_negated, &bytes[2..], state), _ => chomp_number_dec(is_negated, bytes, state), + }; + number.and_then(|(_, literal, state)| parse_number_suffix(literal, state)) +} + +fn parse_number_suffix<'a>( + literal: NumLiteral<'a>, + state: State<'a>, +) -> ParseResult<'a, NumLiteral<'a>, ENumber> { + match literal { + NumLiteral::Float(s, _) => { + let (bound, state) = match get_float_suffix(state.bytes()) { + Some((bound, n)) => (NumericBound::Exact(bound), state.advance(n)), + None => (NumericBound::None, state), + }; + Ok((Progress::MadeProgress, NumLiteral::Float(s, bound), state)) + } + NumLiteral::Num(s, _) => { + let (bound, state) = match get_int_suffix(state.bytes()) { + Some((bound, n)) => (NumericBound::Exact(bound), state.advance(n)), + None => (NumericBound::None, state), + }; + Ok((Progress::MadeProgress, NumLiteral::Num(s, bound), state)) + } + NumLiteral::NonBase10Int { + string, + base, + is_negative, + bound: _, + } => { + let (bound, state) = match get_int_suffix(state.bytes()) { + Some((bound, n)) => (NumericBound::Exact(bound), state.advance(n)), + None => (NumericBound::None, state), + }; + Ok(( + Progress::MadeProgress, + NumLiteral::NonBase10Int { + string, + base, + is_negative, + bound, + }, + state, + )) + } } } +macro_rules! parse_num_suffix { + ($bytes:expr, $($suffix:expr, $width:expr)*) => { + $( + { + let len = $suffix.len(); + if $bytes.starts_with($suffix) + && { + let next = $bytes[len..].get(0); + match next { Some(c) => !(c.is_ascii_digit() || c.is_ascii_alphabetic()), None => true, } + } + { + return Some(($width, len)) + } + } + )* + } +} + +fn get_int_suffix<'a>(bytes: &'a [u8]) -> Option<(NumWidth, usize)> { + parse_num_suffix! { + bytes, + b"u8", NumWidth::U8 + b"u16", NumWidth::U16 + b"u32", NumWidth::U32 + b"u64", NumWidth::U64 + b"u128", NumWidth::U128 + b"i8", NumWidth::I8 + b"i16", NumWidth::I16 + b"i32", NumWidth::I32 + b"i64", NumWidth::I64 + b"i128", NumWidth::I128 + b"nat", NumWidth::Nat + b"dec", NumWidth::Dec + } + None +} + +fn get_float_suffix<'a>(bytes: &'a [u8]) -> Option<(FloatWidth, usize)> { + parse_num_suffix! { + bytes, + b"f32", FloatWidth::F32 + b"f64", FloatWidth::F64 + } + None +} + fn chomp_number_base<'a>( base: Base, is_negative: bool, @@ -75,6 +167,7 @@ fn chomp_number_base<'a>( is_negative, string, base, + bound: NumericBound::None, }, new, )) @@ -105,9 +198,9 @@ fn chomp_number_dec<'a>( Ok(( Progress::MadeProgress, if is_float { - NumLiteral::Float(string) + NumLiteral::Float(string, NumericBound::None) } else { - NumLiteral::Num(string) + NumLiteral::Num(string, NumericBound::None) }, new, )) @@ -144,8 +237,7 @@ fn chomp_number(mut bytes: &[u8]) -> (bool, usize) { // skip bytes = &bytes[1..]; } - _ if byte.is_ascii_digit() || byte.is_ascii_alphabetic() => { - // valid digits (alphabetic in hex digits, and the `e` in `12e26` scientific notation + _ if byte.is_ascii_digit() => { bytes = &bytes[1..]; } _ => { diff --git a/compiler/parse/src/pattern.rs b/compiler/parse/src/pattern.rs index c76d115fc9..f635cb67e2 100644 --- a/compiler/parse/src/pattern.rs +++ b/compiler/parse/src/pattern.rs @@ -138,16 +138,18 @@ fn number_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> { use crate::number_literal::NumLiteral::*; match literal { - Num(s) => Pattern::NumLiteral(s), - Float(s) => Pattern::FloatLiteral(s), + Num(s, bound) => Pattern::NumLiteral(s, bound), + Float(s, bound) => Pattern::FloatLiteral(s, bound), NonBase10Int { string, base, is_negative, + bound, } => Pattern::NonBase10Literal { string, base, is_negative, + bound, }, } }), diff --git a/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast index cd27503529..727a50e6cc 100644 --- a/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast @@ -10,5 +10,6 @@ BinOps( ], @4-5 Num( "2", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast index 4d75339647..7e4b1cf914 100644 --- a/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast @@ -3,11 +3,13 @@ BinOps( ( @0-1 Num( "1", + None, ), @3-4 Plus, ), ], @7-8 Num( "2", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast b/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast index 3b4a561069..a966f42eb6 100644 --- a/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast @@ -43,6 +43,7 @@ Defs( [], @43-47 Float( "3.14", + None, ), ), ], diff --git a/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast b/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast index 178aca2667..2f8e8f1322 100644 --- a/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast @@ -44,6 +44,7 @@ Defs( [ @44-46 Num( "42", + None, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast index f939664412..bc2ee875cd 100644 --- a/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast @@ -5,9 +5,11 @@ Apply( [ @5-7 Num( "12", + None, ), @8-10 Num( "34", + None, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast index 483292bdcf..6523f3f917 100644 --- a/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast @@ -6,11 +6,13 @@ Apply( @6-8 ParensAround( Num( "12", + None, ), ), @11-13 ParensAround( Num( "34", + None, ), ), ], diff --git a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast index 185e681f5b..c0b0036b9c 100644 --- a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast @@ -5,9 +5,11 @@ Apply( [ @6-8 Num( "12", + None, ), @9-11 Num( "34", + None, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast index 882b37c09d..81609e25ef 100644 --- a/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast @@ -6,9 +6,11 @@ Apply( [ @6-8 Num( "12", + None, ), @10-12 Num( "34", + None, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast index d74301a614..190ea514f8 100644 --- a/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast @@ -9,6 +9,7 @@ Apply( [ @7-9 Num( "12", + None, ), @10-13 Var { module_name: "", diff --git a/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast index 7a50a7af86..9905672a14 100644 --- a/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast @@ -9,6 +9,7 @@ Apply( [ @7-9 Num( "12", + None, ), @10-13 Var { module_name: "", diff --git a/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast index ae21012711..4dcbec2e00 100644 --- a/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast @@ -6,6 +6,7 @@ Apply( [ @5-6 Num( "1", + None, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast index 8f016cf433..d53a867c85 100644 --- a/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast @@ -7,12 +7,14 @@ SpaceBefore( ), @111-112 Num( "5", + None, ), ), ], @114-116 SpaceBefore( Num( "42", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast b/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast index 83a2f401e4..09ae529727 100644 --- a/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast @@ -9,5 +9,6 @@ Closure( ], @13-15 Num( "42", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast index 4930aa1a55..23075fc909 100644 --- a/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast @@ -3,6 +3,7 @@ BinOps( ( @0-2 Num( "12", + None, ), @4-5 Star, ), @@ -10,6 +11,7 @@ BinOps( @15-17 SpaceBefore( Num( "92", + None, ), [ LineComment( diff --git a/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast index 8d2a5f0b63..cf78d29fb2 100644 --- a/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast @@ -4,6 +4,7 @@ BinOps( @0-1 SpaceAfter( Num( "3", + None, ), [ LineComment( @@ -16,5 +17,6 @@ BinOps( ], @13-14 Num( "4", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast index 5bf526ed0e..74b72a2e01 100644 --- a/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast @@ -4,6 +4,7 @@ BinOps( @0-1 SpaceAfter( Num( "3", + None, ), [ LineComment( @@ -16,5 +17,6 @@ BinOps( ], @14-15 Num( "4", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/expect.expr.result-ast b/compiler/parse/tests/snapshots/pass/expect.expr.result-ast index fb56585387..a4b197165d 100644 --- a/compiler/parse/tests/snapshots/pass/expect.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/expect.expr.result-ast @@ -4,17 +4,20 @@ Expect( ( @7-8 Num( "1", + None, ), @9-11 Equals, ), ], @12-13 Num( "1", + None, ), ), @15-16 SpaceBefore( Num( "4", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/float_with_underscores.expr.result-ast b/compiler/parse/tests/snapshots/pass/float_with_underscores.expr.result-ast index fc78c66972..c2dc9ece91 100644 --- a/compiler/parse/tests/snapshots/pass/float_with_underscores.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/float_with_underscores.expr.result-ast @@ -1,3 +1,4 @@ Float( "-1_23_456.0_1_23_456", + None, ) diff --git a/compiler/parse/tests/snapshots/pass/highest_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/highest_float.expr.result-ast index 9b46a3b26d..fca24d4a4d 100644 --- a/compiler/parse/tests/snapshots/pass/highest_float.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/highest_float.expr.result-ast @@ -1,3 +1,4 @@ Float( "179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0", + None, ) diff --git a/compiler/parse/tests/snapshots/pass/highest_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/highest_int.expr.result-ast index aefb235b32..ba6204b17d 100644 --- a/compiler/parse/tests/snapshots/pass/highest_int.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/highest_int.expr.result-ast @@ -1,3 +1,4 @@ Num( "9223372036854775807", + None, ) diff --git a/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast index 08284e3121..f5cc81eb0d 100644 --- a/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast @@ -6,12 +6,14 @@ Defs( ), @5-6 Num( "5", + None, ), ), ], @8-10 SpaceBefore( Num( "42", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/int_with_underscore.expr.result-ast b/compiler/parse/tests/snapshots/pass/int_with_underscore.expr.result-ast index 3912cc7edd..2d0857f573 100644 --- a/compiler/parse/tests/snapshots/pass/int_with_underscore.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/int_with_underscore.expr.result-ast @@ -1,3 +1,4 @@ Num( "1__23", + None, ) diff --git a/compiler/parse/tests/snapshots/pass/lowest_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/lowest_float.expr.result-ast index 6bce8865e0..9967db3c7d 100644 --- a/compiler/parse/tests/snapshots/pass/lowest_float.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/lowest_float.expr.result-ast @@ -1,3 +1,4 @@ Float( "-179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0", + None, ) diff --git a/compiler/parse/tests/snapshots/pass/lowest_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/lowest_int.expr.result-ast index 01111135a9..1ad2735812 100644 --- a/compiler/parse/tests/snapshots/pass/lowest_int.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/lowest_int.expr.result-ast @@ -1,3 +1,4 @@ Num( "-9223372036854775808", + None, ) diff --git a/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast b/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast index b776a10113..1eaa224b6f 100644 --- a/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast @@ -9,5 +9,6 @@ Closure( ], @15-17 Num( "42", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast b/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast index 526bf8a5cd..63413753fa 100644 --- a/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast @@ -17,6 +17,7 @@ When( ], value: @25-26 Num( "1", + None, ), guard: None, }, @@ -33,6 +34,7 @@ When( ], value: @36-37 Num( "4", + None, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast b/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast index fa23b429e9..3ebed0e66c 100644 --- a/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast @@ -17,6 +17,7 @@ When( ], value: @25-26 Num( "1", + None, ), guard: None, }, @@ -33,6 +34,7 @@ When( ], value: @36-37 Num( "4", + None, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast b/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast index 1d8f6c4e68..b914a9d211 100644 --- a/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast @@ -3,11 +3,13 @@ BinOps( ( @0-3 Num( "-12", + None, ), @3-4 Minus, ), ], @4-5 Num( "5", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast b/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast index 190e21ca40..18c0501338 100644 --- a/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast @@ -7,12 +7,14 @@ SpaceBefore( ), @117-118 Num( "5", + None, ), ), ], @120-122 SpaceBefore( Num( "42", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast b/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast index 11a0ded5c2..7c2f20e1d3 100644 --- a/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast +++ b/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast @@ -14,6 +14,7 @@ ), @15-17 Num( "64", + None, ), ), ], diff --git a/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast index 8946c66262..7418143478 100644 --- a/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast @@ -18,6 +18,7 @@ Defs( @12-14 SpaceBefore( Num( "42", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast index 8c30fd95c0..f11b5d9826 100644 --- a/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast @@ -20,6 +20,7 @@ Defs( @21-23 SpaceBefore( Num( "42", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast index c5582c91c6..560733e1a7 100644 --- a/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast @@ -3,17 +3,20 @@ BinOps( ( @0-2 Num( "31", + None, ), @2-3 Star, ), ( @3-5 Num( "42", + None, ), @5-6 Plus, ), ], @6-9 Num( "534", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/negative_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/negative_float.expr.result-ast index b4246aee73..51b0d5e4e7 100644 --- a/compiler/parse/tests/snapshots/pass/negative_float.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/negative_float.expr.result-ast @@ -1,3 +1,4 @@ Float( "-42.9", + None, ) diff --git a/compiler/parse/tests/snapshots/pass/negative_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/negative_int.expr.result-ast index a41780d138..e4c4e79522 100644 --- a/compiler/parse/tests/snapshots/pass/negative_int.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/negative_int.expr.result-ast @@ -1,3 +1,4 @@ Num( "-42", + None, ) diff --git a/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast b/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast index 33722ca531..e6aadc756d 100644 --- a/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast +++ b/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast @@ -72,9 +72,11 @@ [ @112-113 Num( "2", + None, ), @114-115 Num( "3", + None, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast b/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast index c4542c48eb..af9381e997 100644 --- a/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast @@ -9,6 +9,7 @@ If( SpaceAfter( Num( "1", + None, ), [ Newline, @@ -28,6 +29,7 @@ If( SpaceAfter( Num( "2", + None, ), [ Newline, @@ -42,6 +44,7 @@ If( @42-43 SpaceBefore( Num( "3", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast index 3cdf6755b6..e1e32e8dfc 100644 --- a/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast @@ -7,6 +7,7 @@ Defs( @8-9 SpaceBefore( Num( "5", + None, ), [ Newline, @@ -17,6 +18,7 @@ Defs( @11-13 SpaceBefore( Num( "42", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast index ee6690e728..ecd9cc89fa 100644 --- a/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast @@ -3,6 +3,7 @@ BinOps( ( @0-1 Num( "3", + None, ), @3-4 Star, ), @@ -10,6 +11,7 @@ BinOps( @7-8 SpaceBefore( Num( "4", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast index 444199db82..93a50fbd83 100644 --- a/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast @@ -3,6 +3,7 @@ BinOps( ( @0-1 Num( "3", + None, ), @3-4 Minus, ), @@ -10,6 +11,7 @@ BinOps( @7-8 SpaceBefore( Num( "4", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast index cbc30d8ba0..67a12bd88e 100644 --- a/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast @@ -10,6 +10,7 @@ Defs( @4-5 SpaceAfter( Num( "1", + None, ), [ Newline, @@ -20,6 +21,7 @@ Defs( ], @12-13 Num( "2", + None, ), ), ), @@ -27,6 +29,7 @@ Defs( @15-17 SpaceBefore( Num( "42", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast index 57dac2f56f..39ee4c273d 100644 --- a/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast @@ -4,6 +4,7 @@ BinOps( @0-1 SpaceAfter( Num( "3", + None, ), [ Newline, @@ -14,5 +15,6 @@ BinOps( ], @4-5 Num( "4", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast index c01422cca9..116ad5c60b 100644 --- a/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast @@ -4,6 +4,7 @@ BinOps( @0-1 SpaceAfter( Num( "3", + None, ), [ Newline, @@ -14,5 +15,6 @@ BinOps( ], @4-5 Num( "4", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast index 77f172790b..ae84388347 100644 --- a/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast @@ -4,6 +4,7 @@ List( SpaceAfter( Num( "1", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast b/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast index e9a5247b17..0c70ce8605 100644 --- a/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast @@ -7,12 +7,14 @@ SpaceBefore( ), @50-51 Num( "5", + None, ), ), ], @53-55 SpaceBefore( Num( "42", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.result-ast b/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.result-ast new file mode 100644 index 0000000000..60c29d3d23 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.result-ast @@ -0,0 +1,573 @@ +Record( + Collection { + items: [ + @4-15 SpaceBefore( + RequiredValue( + @4-6 "u8", + [], + @10-15 Num( + "123", + Exact( + U8, + ), + ), + ), + [ + Newline, + ], + ), + @19-31 SpaceBefore( + RequiredValue( + @19-22 "u16", + [], + @25-31 Num( + "123", + Exact( + U16, + ), + ), + ), + [ + Newline, + ], + ), + @35-47 SpaceBefore( + RequiredValue( + @35-38 "u32", + [], + @41-47 Num( + "123", + Exact( + U32, + ), + ), + ), + [ + Newline, + ], + ), + @51-63 SpaceBefore( + RequiredValue( + @51-54 "u64", + [], + @57-63 Num( + "123", + Exact( + U64, + ), + ), + ), + [ + Newline, + ], + ), + @67-80 SpaceBefore( + RequiredValue( + @67-71 "u128", + [], + @73-80 Num( + "123", + Exact( + U128, + ), + ), + ), + [ + Newline, + ], + ), + @84-95 SpaceBefore( + RequiredValue( + @84-86 "i8", + [], + @90-95 Num( + "123", + Exact( + I8, + ), + ), + ), + [ + Newline, + ], + ), + @99-111 SpaceBefore( + RequiredValue( + @99-102 "i16", + [], + @105-111 Num( + "123", + Exact( + I16, + ), + ), + ), + [ + Newline, + ], + ), + @115-127 SpaceBefore( + RequiredValue( + @115-118 "i32", + [], + @121-127 Num( + "123", + Exact( + I32, + ), + ), + ), + [ + Newline, + ], + ), + @131-143 SpaceBefore( + RequiredValue( + @131-134 "i64", + [], + @137-143 Num( + "123", + Exact( + I64, + ), + ), + ), + [ + Newline, + ], + ), + @147-160 SpaceBefore( + RequiredValue( + @147-151 "i128", + [], + @153-160 Num( + "123", + Exact( + I128, + ), + ), + ), + [ + Newline, + ], + ), + @164-176 SpaceBefore( + RequiredValue( + @164-167 "nat", + [], + @170-176 Num( + "123", + Exact( + Nat, + ), + ), + ), + [ + Newline, + ], + ), + @180-192 SpaceBefore( + RequiredValue( + @180-183 "dec", + [], + @186-192 Num( + "123", + Exact( + Dec, + ), + ), + ), + [ + Newline, + ], + ), + @196-211 SpaceBefore( + RequiredValue( + @196-201 "u8Neg", + [], + @205-211 Num( + "-123", + Exact( + U8, + ), + ), + ), + [ + Newline, + ], + ), + @215-231 SpaceBefore( + RequiredValue( + @215-221 "u16Neg", + [], + @224-231 Num( + "-123", + Exact( + U16, + ), + ), + ), + [ + Newline, + ], + ), + @235-251 SpaceBefore( + RequiredValue( + @235-241 "u32Neg", + [], + @244-251 Num( + "-123", + Exact( + U32, + ), + ), + ), + [ + Newline, + ], + ), + @255-271 SpaceBefore( + RequiredValue( + @255-261 "u64Neg", + [], + @264-271 Num( + "-123", + Exact( + U64, + ), + ), + ), + [ + Newline, + ], + ), + @275-292 SpaceBefore( + RequiredValue( + @275-282 "u128Neg", + [], + @284-292 Num( + "-123", + Exact( + U128, + ), + ), + ), + [ + Newline, + ], + ), + @296-311 SpaceBefore( + RequiredValue( + @296-301 "i8Neg", + [], + @305-311 Num( + "-123", + Exact( + I8, + ), + ), + ), + [ + Newline, + ], + ), + @315-331 SpaceBefore( + RequiredValue( + @315-321 "i16Neg", + [], + @324-331 Num( + "-123", + Exact( + I16, + ), + ), + ), + [ + Newline, + ], + ), + @335-351 SpaceBefore( + RequiredValue( + @335-341 "i32Neg", + [], + @344-351 Num( + "-123", + Exact( + I32, + ), + ), + ), + [ + Newline, + ], + ), + @355-371 SpaceBefore( + RequiredValue( + @355-361 "i64Neg", + [], + @364-371 Num( + "-123", + Exact( + I64, + ), + ), + ), + [ + Newline, + ], + ), + @375-392 SpaceBefore( + RequiredValue( + @375-382 "i128Neg", + [], + @384-392 Num( + "-123", + Exact( + I128, + ), + ), + ), + [ + Newline, + ], + ), + @396-412 SpaceBefore( + RequiredValue( + @396-402 "natNeg", + [], + @405-412 Num( + "-123", + Exact( + Nat, + ), + ), + ), + [ + Newline, + ], + ), + @416-432 SpaceBefore( + RequiredValue( + @416-422 "decNeg", + [], + @425-432 Num( + "-123", + Exact( + Dec, + ), + ), + ), + [ + Newline, + ], + ), + @436-452 SpaceBefore( + RequiredValue( + @436-441 "u8Bin", + [], + @445-452 NonBase10Int { + string: "101", + base: Binary, + is_negative: false, + bound: Exact( + U8, + ), + }, + ), + [ + Newline, + ], + ), + @456-473 SpaceBefore( + RequiredValue( + @456-462 "u16Bin", + [], + @465-473 NonBase10Int { + string: "101", + base: Binary, + is_negative: false, + bound: Exact( + U16, + ), + }, + ), + [ + Newline, + ], + ), + @477-494 SpaceBefore( + RequiredValue( + @477-483 "u32Bin", + [], + @486-494 NonBase10Int { + string: "101", + base: Binary, + is_negative: false, + bound: Exact( + U32, + ), + }, + ), + [ + Newline, + ], + ), + @498-515 SpaceBefore( + RequiredValue( + @498-504 "u64Bin", + [], + @507-515 NonBase10Int { + string: "101", + base: Binary, + is_negative: false, + bound: Exact( + U64, + ), + }, + ), + [ + Newline, + ], + ), + @519-537 SpaceBefore( + RequiredValue( + @519-526 "u128Bin", + [], + @528-537 NonBase10Int { + string: "101", + base: Binary, + is_negative: false, + bound: Exact( + U128, + ), + }, + ), + [ + Newline, + ], + ), + @541-557 SpaceBefore( + RequiredValue( + @541-546 "i8Bin", + [], + @550-557 NonBase10Int { + string: "101", + base: Binary, + is_negative: false, + bound: Exact( + I8, + ), + }, + ), + [ + Newline, + ], + ), + @561-578 SpaceBefore( + RequiredValue( + @561-567 "i16Bin", + [], + @570-578 NonBase10Int { + string: "101", + base: Binary, + is_negative: false, + bound: Exact( + I16, + ), + }, + ), + [ + Newline, + ], + ), + @582-599 SpaceBefore( + RequiredValue( + @582-588 "i32Bin", + [], + @591-599 NonBase10Int { + string: "101", + base: Binary, + is_negative: false, + bound: Exact( + I32, + ), + }, + ), + [ + Newline, + ], + ), + @603-620 SpaceBefore( + RequiredValue( + @603-609 "i64Bin", + [], + @612-620 NonBase10Int { + string: "101", + base: Binary, + is_negative: false, + bound: Exact( + I64, + ), + }, + ), + [ + Newline, + ], + ), + @624-642 SpaceBefore( + RequiredValue( + @624-631 "i128Bin", + [], + @633-642 NonBase10Int { + string: "101", + base: Binary, + is_negative: false, + bound: Exact( + I128, + ), + }, + ), + [ + Newline, + ], + ), + @646-663 SpaceBefore( + RequiredValue( + @646-652 "natBin", + [], + @655-663 NonBase10Int { + string: "101", + base: Binary, + is_negative: false, + bound: Exact( + Nat, + ), + }, + ), + [ + Newline, + ], + ), + @667-684 SpaceBefore( + RequiredValue( + @667-673 "decBin", + [], + @676-684 NonBase10Int { + string: "101", + base: Binary, + is_negative: false, + bound: Exact( + Dec, + ), + }, + ), + [ + Newline, + ], + ), + ], + final_comments: [ + Newline, + ], + }, +) diff --git a/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.roc b/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.roc new file mode 100644 index 0000000000..9bc838314b --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.roc @@ -0,0 +1,38 @@ +{ + u8: 123u8, + u16: 123u16, + u32: 123u32, + u64: 123u64, + u128: 123u128, + i8: 123i8, + i16: 123i16, + i32: 123i32, + i64: 123i64, + i128: 123i128, + nat: 123nat, + dec: 123dec, + u8Neg: -123u8, + u16Neg: -123u16, + u32Neg: -123u32, + u64Neg: -123u64, + u128Neg: -123u128, + i8Neg: -123i8, + i16Neg: -123i16, + i32Neg: -123i32, + i64Neg: -123i64, + i128Neg: -123i128, + natNeg: -123nat, + decNeg: -123dec, + u8Bin: 0b101u8, + u16Bin: 0b101u16, + u32Bin: 0b101u32, + u64Bin: 0b101u64, + u128Bin: 0b101u128, + i8Bin: 0b101i8, + i16Bin: 0b101i16, + i32Bin: 0b101i32, + i64Bin: 0b101i64, + i128Bin: 0b101i128, + natBin: 0b101nat, + decBin: 0b101dec, +} diff --git a/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast index 7caa61c752..1dbe91e77e 100644 --- a/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast @@ -7,12 +7,14 @@ SpaceBefore( ), @20-21 Num( "5", + None, ), ), ], @23-25 SpaceBefore( Num( "42", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast index 4dc9b56640..4484255d71 100644 --- a/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast @@ -3,11 +3,13 @@ BinOps( ( @0-1 Num( "1", + None, ), @1-2 Minus, ), ], @2-3 Num( "2", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast index e6bcba4260..644ae129f6 100644 --- a/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast @@ -3,11 +3,13 @@ BinOps( ( @0-1 Num( "1", + None, ), @1-2 Plus, ), ], @2-3 Num( "2", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast index a1fab8b814..866cd021a5 100644 --- a/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast @@ -7,12 +7,14 @@ SpaceBefore( ), @22-23 Num( "5", + None, ), ), ], @25-27 SpaceBefore( Num( "42", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast index 28005999cb..bb1f3f7e51 100644 --- a/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast @@ -4,6 +4,7 @@ BinOps( @0-1 SpaceAfter( Num( "3", + None, ), [ Newline, @@ -15,6 +16,7 @@ BinOps( @7-8 SpaceBefore( Num( "4", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast index 859505b0f3..142bbc8ec2 100644 --- a/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast @@ -2,6 +2,7 @@ List( [ @1-2 Num( "1", + None, ), ], ) diff --git a/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast b/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast index c8000965eb..a1e681e48d 100644 --- a/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast @@ -8,6 +8,7 @@ Apply( [ @7-8 Num( "1", + None, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast b/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast index 3deb39c07f..450ae33a78 100644 --- a/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast @@ -29,6 +29,7 @@ Defs( @28-30 SpaceBefore( Num( "42", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast b/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast index f99bd7c8fb..fa4fa90923 100644 --- a/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast @@ -35,6 +35,7 @@ Defs( @35-37 SpaceBefore( Num( "42", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast index d45bbdf656..88cce2b495 100644 --- a/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast @@ -20,6 +20,7 @@ When( ), @21-22 Num( "0", + None, ), ], Space, @@ -63,6 +64,7 @@ When( ), @63-64 Num( "0", + None, ), @65-70 GlobalTag( "False", diff --git a/compiler/parse/tests/snapshots/pass/positive_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/positive_float.expr.result-ast index 366609e578..364fe6092b 100644 --- a/compiler/parse/tests/snapshots/pass/positive_float.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/positive_float.expr.result-ast @@ -1,3 +1,4 @@ Float( "42.9", + None, ) diff --git a/compiler/parse/tests/snapshots/pass/positive_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/positive_int.expr.result-ast index 23ee1510a7..228f2ec6a9 100644 --- a/compiler/parse/tests/snapshots/pass/positive_int.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/positive_int.expr.result-ast @@ -1,3 +1,4 @@ Num( "42", + None, ) diff --git a/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast index 498e786bb0..dfd198000b 100644 --- a/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast @@ -14,6 +14,7 @@ SpaceBefore( ), @29-30 Num( "5", + None, ), ), @31-36 SpaceBefore( @@ -23,6 +24,7 @@ SpaceBefore( ), @35-36 Num( "6", + None, ), ), [ @@ -33,6 +35,7 @@ SpaceBefore( @38-40 SpaceBefore( Num( "42", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast index 6ae895c6e0..351ab5e4ec 100644 --- a/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast @@ -103,6 +103,7 @@ Defs( @124-126 SpaceBefore( Num( "42", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast index 8666e0151f..a05a639f1a 100644 --- a/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast @@ -9,6 +9,7 @@ RecordUpdate { [], @19-20 Num( "5", + None, ), ), @22-26 RequiredValue( @@ -16,6 +17,7 @@ RecordUpdate { [], @25-26 Num( "0", + None, ), ), ], diff --git a/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast index 1776ed2ff8..aad598b259 100644 --- a/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast @@ -11,11 +11,13 @@ Record( ), @18-19 Num( "1", + None, ), ), ], @25-26 Num( "2", + None, ), ), ), @@ -24,6 +26,7 @@ Record( [], @31-32 Num( "3", + None, ), ), ], diff --git a/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast index 1c5fbbaa84..66c27b88b0 100644 --- a/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast @@ -6,5 +6,6 @@ Closure( ], @6-8 Num( "42", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast index d04c130ed2..90c3a81dac 100644 --- a/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast @@ -6,5 +6,6 @@ Closure( ], @6-8 Num( "42", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast index 231c1c41b1..a437d0a55a 100644 --- a/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast @@ -2,6 +2,7 @@ List( [ @2-3 Num( "1", + None, ), ], ) diff --git a/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast b/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast index e03334f801..d545c1e5df 100644 --- a/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast +++ b/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast @@ -6,6 +6,7 @@ ), @18-19 Num( "1", + None, ), ), [ diff --git a/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast index 2e4b2c87d2..e12971a1e5 100644 --- a/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast @@ -10,5 +10,6 @@ BinOps( ], @4-5 Num( "2", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast index 849e5609fd..2179daf40b 100644 --- a/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast @@ -3,11 +3,13 @@ BinOps( ( @0-1 Num( "1", + None, ), @3-4 Minus, ), ], @7-8 Num( "2", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast b/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast index 93bc481021..0d15dfdac0 100644 --- a/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast @@ -6,5 +6,6 @@ Closure( ], @10-12 Num( "42", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast b/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast index 6b4d587eb8..7ab54b96e1 100644 --- a/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast @@ -3,11 +3,13 @@ BinOps( ( @0-2 Num( "10", + None, ), @2-3 Star, ), ], @3-5 Num( "11", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast index 72f726931a..6f238c731d 100644 --- a/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast @@ -12,5 +12,6 @@ Closure( ], @12-14 Num( "42", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast index 84d9721826..908239feeb 100644 --- a/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast @@ -9,5 +9,6 @@ Closure( ], @9-11 Num( "42", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast index 7cab905c51..0c7a32957d 100644 --- a/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast @@ -19,6 +19,7 @@ When( ], value: @17-18 Num( "1", + None, ), guard: None, }, @@ -37,6 +38,7 @@ When( ], value: @30-31 Num( "2", + None, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast index 7ae7c0abd2..34047512e4 100644 --- a/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast @@ -7,6 +7,7 @@ SpaceBefore( ), @22-23 Num( "5", + None, ), ), @24-29 SpaceBefore( @@ -16,6 +17,7 @@ SpaceBefore( ), @28-29 Num( "6", + None, ), ), [ @@ -26,6 +28,7 @@ SpaceBefore( @31-33 SpaceBefore( Num( "42", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast b/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast index ca8a45f681..26f71e1d6e 100644 --- a/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast @@ -30,6 +30,7 @@ Defs( @31-33 SpaceBefore( Num( "42", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast index f42c07d6fd..81cd356a99 100644 --- a/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast @@ -6,6 +6,7 @@ Apply( [ @6-8 Num( "12", + None, ), @9-13 UnaryOp( @10-13 Var { diff --git a/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast index acb507d2b2..43ba59029f 100644 --- a/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast @@ -8,6 +8,7 @@ UnaryOp( [ @8-10 Num( "12", + None, ), @11-14 Var { module_name: "", diff --git a/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast index b1f93ec376..11f0f6dbcb 100644 --- a/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast @@ -8,6 +8,7 @@ UnaryOp( [ @8-10 Num( "12", + None, ), @11-14 Var { module_name: "", diff --git a/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast b/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast index 744f023e4f..c92bf33640 100644 --- a/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast @@ -21,6 +21,7 @@ SpaceBefore( @34-35 SpaceBefore( Num( "4", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast b/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast index 499046f993..446cee92e7 100644 --- a/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast @@ -10,5 +10,6 @@ BinOps( ], @2-3 Num( "2", + None, ), ) diff --git a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast index e0f8d41ece..1794c04a01 100644 --- a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast @@ -18,6 +18,7 @@ When( value: @27-28 SpaceBefore( Num( "1", + None, ), [ Newline, @@ -40,6 +41,7 @@ When( value: @47-48 SpaceBefore( Num( "2", + None, ), [ Newline, @@ -62,6 +64,7 @@ When( value: @68-69 SpaceBefore( Num( "3", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast index 9192d1f1b6..b97dbf4f5f 100644 --- a/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast @@ -19,6 +19,7 @@ ParensAround( value: @29-30 SpaceBefore( Num( "3", + None, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast index 6f74fb5e4d..d35b055fbd 100644 --- a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast @@ -19,6 +19,7 @@ ParensAround( ], value: @21-22 Num( "3", + None, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast index 62dd86481e..0a103eb427 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast @@ -24,6 +24,7 @@ When( ], value: @30-31 Num( "1", + None, ), guard: None, }, @@ -52,6 +53,7 @@ When( ], value: @52-53 Num( "2", + None, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast index 1822dfa962..0a69a10f87 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast @@ -9,6 +9,7 @@ When( @14-15 SpaceBefore( NumLiteral( "1", + None, ), [ Newline, @@ -24,6 +25,7 @@ When( @32-33 SpaceBefore( Num( "2", + None, ), [ Newline, @@ -47,6 +49,7 @@ When( ], value: @43-44 Num( "4", + None, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast index 8c6a5e4e61..ecdf37e82a 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast @@ -9,6 +9,7 @@ When( @11-12 SpaceBefore( NumLiteral( "1", + None, ), [ Newline, @@ -17,6 +18,7 @@ When( ], value: @16-17 Num( "2", + None, ), guard: None, }, @@ -25,6 +27,7 @@ When( @19-21 SpaceBefore( NumLiteral( "-3", + None, ), [ Newline, @@ -33,6 +36,7 @@ When( ], value: @25-26 Num( "4", + None, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast index 8866aef903..19bc713c53 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast @@ -9,6 +9,7 @@ When( @11-12 SpaceBefore( NumLiteral( "1", + None, ), [ Newline, @@ -17,6 +18,7 @@ When( ], value: @16-17 Num( "2", + None, ), guard: None, }, @@ -25,6 +27,7 @@ When( @19-20 SpaceBefore( NumLiteral( "3", + None, ), [ Newline, @@ -33,6 +36,7 @@ When( ], value: @24-25 Num( "4", + None, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast index cfb5251e17..fda2147f10 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast @@ -21,6 +21,7 @@ When( ], value: @20-21 Num( "2", + None, ), guard: None, }, @@ -44,6 +45,7 @@ When( ], value: @35-36 Num( "4", + None, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/zero_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/zero_float.expr.result-ast index b164e7a7fe..1278731f67 100644 --- a/compiler/parse/tests/snapshots/pass/zero_float.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/zero_float.expr.result-ast @@ -1,3 +1,4 @@ Float( "0.0", + None, ) diff --git a/compiler/parse/tests/snapshots/pass/zero_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/zero_int.expr.result-ast index 0a7ff7c8ce..08ddfc51a8 100644 --- a/compiler/parse/tests/snapshots/pass/zero_int.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/zero_int.expr.result-ast @@ -1,3 +1,4 @@ Num( "0", + None, ) diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 60c92b5b07..13cee9d372 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -16,6 +16,7 @@ mod test_parse { use bumpalo::collections::vec::Vec; use bumpalo::{self, Bump}; use roc_parse::ast::Expr::{self, *}; + use roc_parse::ast::NumericBound; use roc_parse::ast::StrLiteral::*; use roc_parse::ast::StrSegment::*; use roc_parse::ast::{self, EscapedChar}; @@ -182,6 +183,7 @@ mod test_parse { pass/newline_singleton_list.expr, pass/nonempty_platform_header.header, pass/not_docs.expr, + pass/number_literal_suffixes.expr, pass/one_backpassing.expr, pass/one_char_string.expr, pass/one_def.expr, @@ -494,20 +496,23 @@ mod test_parse { #[quickcheck] fn all_i64_values_parse(num: i64) { - assert_parses_to(num.to_string().as_str(), Num(num.to_string().as_str())); + assert_parses_to( + num.to_string().as_str(), + Num(num.to_string().as_str(), NumericBound::None), + ); } #[quickcheck] fn all_f64_values_parse(num: f64) { let string = num.to_string(); if string.contains('.') { - assert_parses_to(&string, Float(&string)); + assert_parses_to(&string, Float(&string, NumericBound::None)); } else if num.is_nan() { assert_parses_to(&string, Expr::GlobalTag(&string)); } else if num.is_finite() { // These are whole numbers. Add the `.0` back to make float. let float_string = format!("{}.0", string); - assert_parses_to(&float_string, Float(&float_string)); + assert_parses_to(&float_string, Float(&float_string, NumericBound::None)); } } diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index d24823b26c..7c0c39ee2e 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -11,7 +11,7 @@ use roc_mono::ir::ProcLayout; use roc_mono::layout::{ union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant, WrappedVariant, }; -use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral}; +use roc_parse::ast::{AssignedField, Collection, Expr, NumericBound, StrLiteral}; use roc_region::all::{Loc, Region}; use roc_target::TargetInfo; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; @@ -1041,7 +1041,10 @@ fn byte_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: u8, content: &Cont // If this tag union represents a number, skip right to // returning it as an Expr::Num if let TagName::Private(Symbol::NUM_AT_NUM) = &tag_name { - return Expr::Num(env.arena.alloc_str(&value.to_string())); + return Expr::Num( + env.arena.alloc_str(&value.to_string()), + NumericBound::None, + ); } let loc_tag_expr = { @@ -1195,7 +1198,7 @@ fn num_to_ast<'a, M: AppMemory>( /// This is centralized in case we want to format it differently later, /// e.g. adding underscores for large numbers fn number_literal_to_ast(arena: &Bump, num: T) -> Expr<'_> { - Expr::Num(arena.alloc(format!("{}", num))) + Expr::Num(arena.alloc(format!("{}", num)), NumericBound::None) } #[cfg(target_endian = "little")] From e03592930f9b1921105e1f240e9fe79b03ccc5d5 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 31 Jan 2022 17:58:59 -0500 Subject: [PATCH 425/541] Typecheck numeric literals with suffixes in expressions Part of #2350 --- compiler/can/src/builtins.rs | 415 ++++++++++++++++++++++----- compiler/can/src/expr.rs | 23 +- compiler/can/src/num.rs | 46 ++- compiler/can/src/operator.rs | 1 + compiler/can/src/pattern.rs | 49 +++- compiler/constrain/src/builtins.rs | 209 ++++++++++++-- compiler/constrain/src/expr.rs | 23 +- compiler/module/src/lib.rs | 1 + compiler/module/src/numeric.rs | 84 ++++++ compiler/parse/src/ast.rs | 76 +---- compiler/parse/src/expr.rs | 10 +- compiler/parse/src/number_literal.rs | 229 +++++++++------ compiler/parse/src/pattern.rs | 1 + compiler/solve/tests/solve_expr.rs | 48 ++++ compiler/types/src/pretty_print.rs | 109 ++++++- compiler/types/src/subs.rs | 19 ++ compiler/types/src/types.rs | 1 + reporting/src/error/parse.rs | 31 +- reporting/src/error/type.rs | 17 ++ reporting/tests/test_reporting.rs | 140 ++++----- roc-for-elm-programmers.md | 2 +- 21 files changed, 1165 insertions(+), 369 deletions(-) create mode 100644 compiler/module/src/numeric.rs diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index baf1677492..8ac27c49f6 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1,13 +1,13 @@ use crate::def::Def; -use crate::expr::{self, ClosureData, Expr::*}; +use crate::expr::{self, ClosureData, Expr::*, NumericBound}; use crate::expr::{Expr, Field, Recursive}; use crate::pattern::Pattern; use roc_collections::all::SendMap; use roc_module::called_via::CalledVia; use roc_module::ident::{Lowercase, TagName}; use roc_module::low_level::LowLevel; +use roc_module::numeric::{FloatWidth, IntWidth, NumWidth}; use roc_module::symbol::Symbol; -use roc_parse::ast::NumericBound; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; @@ -794,7 +794,10 @@ fn num_is_zero(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::Eq, args: vec![ (arg_var, Var(Symbol::ARG_1)), - (arg_var, num(unbound_zero_var, 0)), + ( + arg_var, + num(unbound_zero_var, 0, num_no_bound(var_store.fresh())), + ), ], ret_var: bool_var, }; @@ -817,7 +820,10 @@ fn num_is_negative(symbol: Symbol, var_store: &mut VarStore) -> Def { let body = RunLowLevel { op: LowLevel::NumGt, args: vec![ - (arg_var, num(unbound_zero_var, 0)), + ( + arg_var, + num(unbound_zero_var, 0, num_no_bound(var_store.fresh())), + ), (arg_var, Var(Symbol::ARG_1)), ], ret_var: bool_var, @@ -842,7 +848,10 @@ fn num_is_positive(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::NumGt, args: vec![ (arg_var, Var(Symbol::ARG_1)), - (arg_var, num(unbound_zero_var, 0)), + ( + arg_var, + num(unbound_zero_var, 0, num_no_bound(var_store.fresh())), + ), ], ret_var: bool_var, }; @@ -867,7 +876,12 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def { args: vec![ ( arg_var, - int::(var_store.fresh(), var_store.fresh(), 1), + int::( + var_store.fresh(), + var_store.fresh(), + 1, + num_no_bound(var_store.fresh()), + ), ), ( arg_var, @@ -875,7 +889,10 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::NumRemUnchecked, args: vec![ (arg_var, Var(Symbol::ARG_1)), - (arg_var, num(unbound_two_var, 2)), + ( + arg_var, + num(unbound_two_var, 2, num_no_bound(var_store.fresh())), + ), ], ret_var: arg_var, }, @@ -902,14 +919,20 @@ fn num_is_even(symbol: Symbol, var_store: &mut VarStore) -> Def { let body = RunLowLevel { op: LowLevel::Eq, args: vec![ - (arg_var, num(arg_num_var, 0)), + ( + arg_var, + num(arg_num_var, 0, num_no_bound(var_store.fresh())), + ), ( arg_var, RunLowLevel { op: LowLevel::NumRemUnchecked, args: vec![ (arg_var, Var(Symbol::ARG_1)), - (arg_var, num(arg_num_var, 2)), + ( + arg_var, + num(arg_num_var, 2, num_no_bound(var_store.fresh())), + ), ], ret_var: arg_var, }, @@ -963,7 +986,15 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::NumGte, args: vec![ (float_var, Var(Symbol::ARG_1)), - (float_var, float(unbound_zero_var, precision_var, 0.0)), + ( + float_var, + float( + unbound_zero_var, + precision_var, + 0.0, + num_no_bound(var_store.fresh()), + ), + ), ], ret_var: bool_var, }), @@ -1009,7 +1040,15 @@ fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::NumGt, args: vec![ (float_var, Var(Symbol::ARG_1)), - (float_var, float(unbound_zero_var, precision_var, 0.0)), + ( + float_var, + float( + unbound_zero_var, + precision_var, + 0.0, + num_no_bound(var_store.fresh()), + ), + ), ], ret_var: bool_var, }), @@ -1247,92 +1286,182 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def { /// Num.minI8: I8 fn num_min_i8(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i8::MIN) + int_min_or_max::( + symbol, + var_store, + i8::MIN, + NumericBound::Exact(IntWidth::I8), + ) } /// Num.maxI8: I8 fn num_max_i8(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i8::MAX) + int_min_or_max::( + symbol, + var_store, + i8::MAX, + NumericBound::Exact(IntWidth::I8), + ) } /// Num.minU8: U8 fn num_min_u8(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u8::MIN) + int_min_or_max::( + symbol, + var_store, + u8::MIN, + NumericBound::Exact(IntWidth::U8), + ) } /// Num.maxU8: U8 fn num_max_u8(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u8::MAX) + int_min_or_max::( + symbol, + var_store, + u8::MAX, + NumericBound::Exact(IntWidth::U8), + ) } /// Num.minI16: I16 fn num_min_i16(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i16::MIN) + int_min_or_max::( + symbol, + var_store, + i16::MIN, + NumericBound::Exact(IntWidth::I16), + ) } /// Num.maxI16: I16 fn num_max_i16(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i16::MAX) + int_min_or_max::( + symbol, + var_store, + i16::MAX, + NumericBound::Exact(IntWidth::I16), + ) } /// Num.minU16: U16 fn num_min_u16(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u16::MIN) + int_min_or_max::( + symbol, + var_store, + u16::MIN, + NumericBound::Exact(IntWidth::U16), + ) } /// Num.maxU16: U16 fn num_max_u16(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u16::MAX) + int_min_or_max::( + symbol, + var_store, + u16::MAX, + NumericBound::Exact(IntWidth::U16), + ) } /// Num.minI32: I32 fn num_min_i32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i32::MIN) + int_min_or_max::( + symbol, + var_store, + i32::MIN, + NumericBound::Exact(IntWidth::I32), + ) } /// Num.maxI32: I32 fn num_max_i32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i32::MAX) + int_min_or_max::( + symbol, + var_store, + i32::MAX, + NumericBound::Exact(IntWidth::I32), + ) } /// Num.minU32: U32 fn num_min_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u32::MIN) + int_min_or_max::( + symbol, + var_store, + u32::MIN, + NumericBound::Exact(IntWidth::U32), + ) } /// Num.maxU32: U32 fn num_max_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u32::MAX) + int_min_or_max::( + symbol, + var_store, + u32::MAX, + NumericBound::Exact(IntWidth::U32), + ) } /// Num.minI64: I64 fn num_min_i64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i64::MIN) + int_min_or_max::( + symbol, + var_store, + i64::MIN, + NumericBound::Exact(IntWidth::I64), + ) } /// Num.maxI64: I64 fn num_max_i64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i64::MAX) + int_min_or_max::( + symbol, + var_store, + i64::MAX, + NumericBound::Exact(IntWidth::I64), + ) } /// Num.minU64: U64 fn num_min_u64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u64::MIN) + int_min_or_max::( + symbol, + var_store, + u64::MIN, + NumericBound::Exact(IntWidth::U64), + ) } /// Num.maxU64: U64 fn num_max_u64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u64::MAX) + int_min_or_max::( + symbol, + var_store, + u64::MAX, + NumericBound::Exact(IntWidth::U64), + ) } /// Num.minI128: I128 fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i128::MIN) + int_min_or_max::( + symbol, + var_store, + i128::MIN, + NumericBound::Exact(IntWidth::I128), + ) } /// Num.maxI128: I128 fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i128::MAX) + int_min_or_max::( + symbol, + var_store, + i128::MAX, + NumericBound::Exact(IntWidth::I128), + ) } /// List.isEmpty : List * -> Bool @@ -1345,7 +1474,10 @@ fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def { let body = RunLowLevel { op: LowLevel::Eq, args: vec![ - (len_var, num(unbound_zero_var, 0)), + ( + len_var, + num(unbound_zero_var, 0, num_no_bound(var_store.fresh())), + ), ( len_var, RunLowLevel { @@ -1459,7 +1591,12 @@ fn str_to_num(symbol: Symbol, var_store: &mut VarStore) -> Def { ), ( errorcode_var, - int::(errorcode_var, Variable::UNSIGNED8, 0), + int::( + errorcode_var, + Variable::UNSIGNED8, + 0, + NumericBound::Exact(IntWidth::U8), + ), ), ], ret_var: bool_var, @@ -2202,7 +2339,12 @@ fn list_swap(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_take_first(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); let len_var = var_store.fresh(); - let zero = int::(len_var, Variable::NATURAL, 0); + let zero = int::( + len_var, + Variable::NATURAL, + 0, + NumericBound::Exact(IntWidth::Nat), + ); let body = RunLowLevel { op: LowLevel::ListSublist, @@ -2228,7 +2370,12 @@ fn list_take_last(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); let len_var = var_store.fresh(); - let zero = int::(len_var, Variable::NATURAL, 0); + let zero = int::( + len_var, + Variable::NATURAL, + 0, + NumericBound::Exact(IntWidth::Nat), + ); let bool_var = var_store.fresh(); let get_list_len = RunLowLevel { @@ -2338,7 +2485,12 @@ fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def { let clos_elem_sym = Symbol::ARG_4; let int_var = var_store.fresh(); - let zero = int::(int_var, Variable::NATURAL, 0); + let zero = int::( + int_var, + Variable::NATURAL, + 0, + NumericBound::Exact(IntWidth::Nat), + ); // \acc, elem -> acc |> List.append sep |> List.append elem let clos = Closure(ClosureData { @@ -2418,7 +2570,12 @@ fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def { let clos_ret_var = var_store.fresh(); let ret_var = var_store.fresh(); - let zero = int::(index_var, Variable::NATURAL, 0); + let zero = int::( + index_var, + Variable::NATURAL, + 0, + NumericBound::Exact(IntWidth::Nat), + ); let clos = Closure(ClosureData { function_type: clos_fun_var, @@ -2580,7 +2737,15 @@ fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::ListDropAt, args: vec![ (list_var, Var(Symbol::ARG_1)), - (index_var, int::(num_var, num_precision_var, 0)), + ( + index_var, + int::( + num_var, + num_precision_var, + 0, + num_no_bound(var_store.fresh()), + ), + ), ], ret_var: list_var, }; @@ -2677,7 +2842,15 @@ fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def { ret_var: len_var, }, ), - (arg_var, int::(num_var, num_precision_var, 1)), + ( + arg_var, + int::( + num_var, + num_precision_var, + 1, + num_no_bound(var_store.fresh()), + ), + ), ], ret_var: len_var, }, @@ -2875,7 +3048,15 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def { RunLowLevel { op: LowLevel::NotEq, args: vec![ - (len_var, int::(num_var, num_precision_var, 0)), + ( + len_var, + int::( + num_var, + num_precision_var, + 0, + num_no_bound(var_store.fresh()), + ), + ), ( len_var, RunLowLevel { @@ -2906,7 +3087,15 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::ListGetUnsafe, args: vec![ (list_var, Var(Symbol::ARG_1)), - (arg_var, int::(num_var, num_precision_var, 0)), + ( + arg_var, + int::( + num_var, + num_precision_var, + 0, + num_no_bound(var_store.fresh()), + ), + ), ], ret_var: list_elem_var, }, @@ -3005,7 +3194,15 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def { RunLowLevel { op: LowLevel::NotEq, args: vec![ - (len_var, int::(num_var, num_precision_var, 0)), + ( + len_var, + int::( + num_var, + num_precision_var, + 0, + num_no_bound(var_store.fresh()), + ), + ), ( len_var, RunLowLevel { @@ -3036,7 +3233,15 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::ListGetUnsafe, args: vec![ (list_var, Var(Symbol::ARG_1)), - (arg_var, int::(num_var, num_precision_var, 0)), + ( + arg_var, + int::( + num_var, + num_precision_var, + 0, + num_no_bound(var_store.fresh()), + ), + ), ], ret_var: list_elem_var, }, @@ -3130,7 +3335,10 @@ fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def { Box::new(function), vec![ (list_var, Loc::at_zero(Var(Symbol::ARG_1))), - (num_var, Loc::at_zero(num(var_store.fresh(), 0))), + ( + num_var, + Loc::at_zero(num(var_store.fresh(), 0, num_no_bound(var_store.fresh()))), + ), (closure_var, Loc::at_zero(Var(Symbol::NUM_ADD))), ], CalledVia::Space, @@ -3162,7 +3370,10 @@ fn list_product(symbol: Symbol, var_store: &mut VarStore) -> Def { Box::new(function), vec![ (list_var, Loc::at_zero(Var(Symbol::ARG_1))), - (num_var, Loc::at_zero(num(var_store.fresh(), 1))), + ( + num_var, + Loc::at_zero(num(var_store.fresh(), 1, num_no_bound(var_store.fresh()))), + ), (closure_var, Loc::at_zero(Var(Symbol::NUM_MUL))), ], CalledVia::Space, @@ -3816,7 +4027,10 @@ fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::NotEq, args: vec![ (num_var, Var(Symbol::ARG_2)), - (num_var, num(unbound_zero_var, 0)), + ( + num_var, + num(unbound_zero_var, 0, num_no_bound(var_store.fresh())), + ), ], ret_var: bool_var, }, @@ -3919,7 +4133,15 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::NotEq, args: vec![ (num_var, Var(Symbol::ARG_2)), - (num_var, float(unbound_zero_var, precision_var, 0.0)), + ( + num_var, + float( + unbound_zero_var, + precision_var, + 0.0, + num_no_bound(var_store.fresh()), + ), + ), ], ret_var: bool_var, }, @@ -3984,7 +4206,12 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def { (num_var, Var(Symbol::ARG_2)), ( num_var, - int::(unbound_zero_var, unbound_zero_precision_var, 0), + int::( + unbound_zero_var, + unbound_zero_precision_var, + 0, + num_no_bound(var_store.fresh()), + ), ), ], ret_var: bool_var, @@ -4050,7 +4277,12 @@ fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def { (num_var, Var(Symbol::ARG_2)), ( num_var, - int::(unbound_zero_var, unbound_zero_precision_var, 0), + int::( + unbound_zero_var, + unbound_zero_precision_var, + 0, + num_no_bound(var_store.fresh()), + ), ), ], ret_var: bool_var, @@ -4120,7 +4352,15 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def { RunLowLevel { op: LowLevel::NotEq, args: vec![ - (len_var, int::(zero_var, zero_precision_var, 0)), + ( + len_var, + int::( + zero_var, + zero_precision_var, + 0, + num_no_bound(var_store.fresh()), + ), + ), ( len_var, RunLowLevel { @@ -4144,7 +4384,15 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::ListGetUnsafe, args: vec![ (list_var, Var(Symbol::ARG_1)), - (len_var, int::(zero_var, zero_precision_var, 0)), + ( + len_var, + int::( + zero_var, + zero_precision_var, + 0, + num_no_bound(var_store.fresh()), + ), + ), ], ret_var: list_elem_var, }, @@ -4201,7 +4449,15 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def { RunLowLevel { op: LowLevel::NotEq, args: vec![ - (len_var, int::(num_var, num_precision_var, 0)), + ( + len_var, + int::( + num_var, + num_precision_var, + 0, + num_no_bound(var_store.fresh()), + ), + ), ( len_var, RunLowLevel { @@ -4240,7 +4496,15 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def { ret_var: len_var, }, ), - (arg_var, int::(num_var, num_precision_var, 1)), + ( + arg_var, + int::( + num_var, + num_precision_var, + 1, + num_no_bound(var_store.fresh()), + ), + ), ], ret_var: len_var, }, @@ -4868,7 +5132,14 @@ fn num_bytes_to(symbol: Symbol, var_store: &mut VarStore, offset: i64, low_level add_var, RunLowLevel { ret_var: cast_var, - args: vec![(cast_var, num(var_store.fresh(), offset))], + args: vec![( + cast_var, + num( + var_store.fresh(), + offset, + num_no_bound(var_store.fresh()), + ), + )], op: LowLevel::NumIntCast, }, ), @@ -4956,13 +5227,18 @@ fn defn_help( } #[inline(always)] -fn int_min_or_max(symbol: Symbol, var_store: &mut VarStore, i: I128) -> Def +fn int_min_or_max( + symbol: Symbol, + var_store: &mut VarStore, + i: I128, + bound: NumericBound, +) -> Def where I128: Into, { let int_var = var_store.fresh(); let int_precision_var = var_store.fresh(); - let body = int::(int_var, int_precision_var, i); + let body = int::(int_var, int_precision_var, i, bound); let std = roc_builtins::std::types(); let solved = std.get(&symbol).unwrap(); @@ -4985,8 +5261,17 @@ where } } +fn num_no_bound(width_variable: Variable) -> NumericBound { + NumericBound::None { width_variable } +} + #[inline(always)] -fn int(num_var: Variable, precision_var: Variable, i: I128) -> Expr +fn int( + num_var: Variable, + precision_var: Variable, + i: I128, + bound: NumericBound, +) -> Expr where I128: Into, { @@ -4996,27 +5281,27 @@ where precision_var, ii.to_string().into_boxed_str(), ii, - NumericBound::None, + bound, ) } #[inline(always)] -fn float(num_var: Variable, precision_var: Variable, f: f64) -> Expr { +fn float( + num_var: Variable, + precision_var: Variable, + f: f64, + bound: NumericBound, +) -> Expr { Float( num_var, precision_var, f.to_string().into_boxed_str(), f, - NumericBound::None, + bound, ) } #[inline(always)] -fn num(num_var: Variable, i: i64) -> Expr { - Num( - num_var, - i.to_string().into_boxed_str(), - i, - NumericBound::None, - ) +fn num(num_var: Variable, i: i64, bound: NumericBound) -> Expr { + Num(num_var, i.to_string().into_boxed_str(), i, bound) } diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 0cfb1307de..86cfdf25cc 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -3,8 +3,8 @@ use crate::builtins::builtin_defs_map; use crate::def::{can_defs_with_return, Def}; use crate::env::Env; use crate::num::{ - finish_parsing_base, finish_parsing_float, finish_parsing_int, float_expr_from_result, - int_expr_from_result, num_expr_from_result, + finish_parsing_base, finish_parsing_float, finish_parsing_int, finish_parsing_int128, + float_expr_from_result, int_expr_from_result, num_expr_from_result, }; use crate::pattern::{canonicalize_pattern, Pattern}; use crate::procedure::References; @@ -13,8 +13,9 @@ use roc_collections::all::{ImSet, MutMap, MutSet, SendMap}; use roc_module::called_via::CalledVia; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; +use roc_module::numeric::{FloatWidth, IntWidth, NumWidth}; use roc_module::symbol::Symbol; -use roc_parse::ast::{self, EscapedChar, FloatWidth, NumWidth, NumericBound, StrLiteral}; +use roc_parse::ast::{self, Base, EscapedChar, StrLiteral}; use roc_parse::pattern::PatternType::*; use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError}; use roc_region::all::{Loc, Region}; @@ -46,6 +47,8 @@ impl Output { } } +pub type NumericBound = roc_module::numeric::NumericBound; + #[derive(Clone, Debug, PartialEq)] pub enum Expr { // Literals @@ -55,7 +58,7 @@ pub enum Expr { Num(Variable, Box, i64, NumericBound), // Int and Float store a variable to generate better error messages - Int(Variable, Variable, Box, i128, NumericBound), + Int(Variable, Variable, Box, i128, NumericBound), Float(Variable, Variable, Box, f64, NumericBound), Str(Box), List { @@ -819,6 +822,18 @@ pub fn canonicalize_expr<'a>( (answer, Output::default()) } + &ast::Expr::Int(str, bound) => { + let answer = int_expr_from_result( + var_store, + finish_parsing_int128(str).map(|f| (str, f)), + region, + Base::Decimal, + bound, + env, + ); + + (answer, Output::default()) + } // Below this point, we shouln't see any of these nodes anymore because // operator desugaring should have removed them! bad_expr @ ast::Expr::ParensAround(_) => { diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index 16cea034fd..ed0f0ab783 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -1,16 +1,26 @@ use crate::env::Env; use crate::expr::Expr; +use roc_module::numeric::{FloatWidth, IntWidth, NumWidth, NumericBound}; use roc_parse::ast::Base; -use roc_parse::ast::FloatWidth; -use roc_parse::ast::NumWidth; -use roc_parse::ast::NumericBound; use roc_problem::can::Problem; use roc_problem::can::RuntimeError::*; use roc_problem::can::{FloatErrorKind, IntErrorKind}; use roc_region::all::Region; -use roc_types::subs::VarStore; +use roc_types::subs::{VarStore, Variable}; use std::i64; +pub fn reify_numeric_bound( + var_store: &mut VarStore, + bound: NumericBound, +) -> NumericBound { + match bound { + NumericBound::None { width_variable: () } => NumericBound::None { + width_variable: var_store.fresh(), + }, + NumericBound::Exact(width) => NumericBound::Exact(width), + } +} + // TODO use rust's integer parsing again // // We're waiting for libcore here, see https://github.com/rust-lang/rust/issues/22639 @@ -21,11 +31,16 @@ pub fn num_expr_from_result( var_store: &mut VarStore, result: Result<(&str, i64), (&str, IntErrorKind)>, region: Region, - bound: NumericBound, + bound: NumericBound, env: &mut Env, ) -> Expr { match result { - Ok((str, num)) => Expr::Num(var_store.fresh(), (*str).into(), num, bound), + Ok((str, num)) => Expr::Num( + var_store.fresh(), + (*str).into(), + num, + reify_numeric_bound(var_store, bound), + ), Err((raw, error)) => { // (Num *) compiles to Int if it doesn't // get specialized to something else first, @@ -45,7 +60,7 @@ pub fn int_expr_from_result( result: Result<(&str, i128), (&str, IntErrorKind)>, region: Region, base: Base, - bound: NumericBound, + bound: NumericBound, env: &mut Env, ) -> Expr { // Int stores a variable to generate better error messages @@ -55,7 +70,7 @@ pub fn int_expr_from_result( var_store.fresh(), (*str).into(), int, - bound, + reify_numeric_bound(var_store, bound), ), Err((raw, error)) => { let runtime_error = InvalidInt(error, base, region, raw.into()); @@ -72,7 +87,7 @@ pub fn float_expr_from_result( var_store: &mut VarStore, result: Result<(&str, f64), (&str, FloatErrorKind)>, region: Region, - bound: NumericBound, + bound: NumericBound, env: &mut Env, ) -> Expr { // Float stores a variable to generate better error messages @@ -82,7 +97,7 @@ pub fn float_expr_from_result( var_store.fresh(), (*str).into(), float, - bound, + reify_numeric_bound(var_store, bound), ), Err((raw, error)) => { let runtime_error = InvalidFloat(error, region, raw.into()); @@ -101,6 +116,13 @@ pub fn finish_parsing_int(raw: &str) -> Result { from_str_radix::(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e.kind)) } +#[inline(always)] +pub fn finish_parsing_int128(raw: &str) -> Result { + // Ignore underscores. + let radix = 10; + from_str_radix::(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e.kind)) +} + #[inline(always)] pub fn finish_parsing_base( raw: &str, @@ -178,9 +200,9 @@ macro_rules! doit { } })*) } -// We only need the i64 implementation, but libcore defines +// We only need the i64 and i128 implementations, but libcore defines // doit! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize } -doit! { i64 } +doit! { i64 i128 } fn from_str_radix(src: &str, radix: u32) -> Result { use self::IntErrorKind::*; diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index 04255f9175..5107adff0c 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -122,6 +122,7 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> { pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc>) -> &'a Loc> { match &loc_expr.value { Float(..) + | Int(..) | Num(..) | NonBase10Int { .. } | Str(_) diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index cfde4168ec..7765deb5a1 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -1,14 +1,18 @@ use crate::env::Env; -use crate::expr::{canonicalize_expr, unescape_char, Expr, Output}; -use crate::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; +use crate::expr::{canonicalize_expr, unescape_char, Expr, NumericBound, Output}; +use crate::num::{ + finish_parsing_base, finish_parsing_float, finish_parsing_int, reify_numeric_bound, +}; use crate::scope::Scope; use roc_module::ident::{Ident, Lowercase, TagName}; +use roc_module::numeric::{FloatWidth, IntWidth, NumWidth}; use roc_module::symbol::Symbol; -use roc_parse::ast::{self, FloatWidth, NumWidth, NumericBound, StrLiteral, StrSegment}; +use roc_parse::ast::{self, StrLiteral, StrSegment}; use roc_parse::pattern::PatternType; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; + /// A pattern, including possible problems (e.g. shadowing) so that /// codegen can generate a runtime error if this pattern is reached. #[derive(Clone, Debug, PartialEq)] @@ -25,7 +29,7 @@ pub enum Pattern { ext_var: Variable, destructs: Vec>, }, - IntLiteral(Variable, Box, i64, NumericBound), + IntLiteral(Variable, Box, i64, NumericBound), NumLiteral(Variable, Box, i64, NumericBound), FloatLiteral(Variable, Box, f64, NumericBound), StrLiteral(Box), @@ -190,7 +194,12 @@ pub fn canonicalize_pattern<'a>( let problem = MalformedPatternProblem::MalformedFloat; malformed_pattern(env, problem, region) } - Ok(float) => Pattern::FloatLiteral(var_store.fresh(), (str).into(), float, bound), + Ok(float) => Pattern::FloatLiteral( + var_store.fresh(), + (str).into(), + float, + reify_numeric_bound(var_store, bound), + ), }, ptype => unsupported_pattern(env, ptype, region), }, @@ -206,7 +215,12 @@ pub fn canonicalize_pattern<'a>( let problem = MalformedPatternProblem::MalformedInt; malformed_pattern(env, problem, region) } - Ok(int) => Pattern::NumLiteral(var_store.fresh(), (str).into(), int, bound), + Ok(int) => Pattern::NumLiteral( + var_store.fresh(), + (str).into(), + int, + reify_numeric_bound(var_store, bound), + ), }, ptype => unsupported_pattern(env, ptype, region), }, @@ -226,12 +240,33 @@ pub fn canonicalize_pattern<'a>( let sign_str = if is_negative { "-" } else { "" }; let int_str = format!("{}{}", sign_str, int.to_string()).into_boxed_str(); let i = if is_negative { -int } else { int }; - Pattern::IntLiteral(var_store.fresh(), int_str, i, bound) + Pattern::IntLiteral( + var_store.fresh(), + int_str, + i, + reify_numeric_bound(var_store, bound), + ) } }, ptype => unsupported_pattern(env, ptype, region), }, + &IntLiteral(str, bound) => match pattern_type { + WhenBranch => match finish_parsing_int(str) { + Err(_error) => { + let problem = MalformedPatternProblem::MalformedInt; + malformed_pattern(env, problem, region) + } + Ok(int) => Pattern::IntLiteral( + var_store.fresh(), + str.into(), + int, + reify_numeric_bound(var_store, bound), + ), + }, + ptype => unsupported_pattern(env, ptype, region), + }, + StrLiteral(literal) => match pattern_type { WhenBranch => flatten_str_literal(literal), ptype => unsupported_pattern(env, ptype, region), diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 8fe6bfeece..20b368139c 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -1,8 +1,10 @@ use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::LetConstraint; use roc_can::expected::Expected::{self, *}; +use roc_can::expr::NumericBound; use roc_collections::all::SendMap; use roc_module::ident::{Lowercase, TagName}; +use roc_module::numeric::{FloatWidth, IntWidth, NumWidth}; use roc_module::symbol::Symbol; use roc_region::all::Region; use roc_types::subs::Variable; @@ -10,28 +12,49 @@ use roc_types::types::Category; use roc_types::types::Reason; use roc_types::types::Type::{self, *}; +fn add_numeric_bound_constr( + constrs: &mut Vec, + num_type: Type, + bound: impl TypedNumericBound, + region: Region, + category: Category, +) { + if bound.is_concrete() { + constrs.push(Eq( + num_type, + Expected::ForReason(Reason::NumericLiteralSuffix, bound.num_type(), region), + category, + region, + )); + } +} + #[inline(always)] pub fn int_literal( num_var: Variable, precision_var: Variable, expected: Expected, region: Region, + bound: NumericBound, ) -> Constraint { let num_type = Variable(num_var); let reason = Reason::IntLiteral; - exists( - vec![num_var], - And(vec![ - Eq( - num_type.clone(), - ForReason(reason, num_int(Type::Variable(precision_var)), region), - Category::Int, - region, - ), - Eq(num_type, expected, Category::Int, region), - ]), - ) + let mut constrs = Vec::with_capacity(3); + // Always add the bound first; this improves the resolved type quality in case it's an alias + // like "U8". + add_numeric_bound_constr(&mut constrs, num_type.clone(), bound, region, Category::Num); + constrs.extend(vec![ + Eq( + num_type.clone(), + ForReason(reason, num_int(Type::Variable(precision_var)), region), + Category::Int, + region, + ), + Eq(num_type, expected, Category::Int, region), + ]); + + exists(vec![num_var], And(constrs)) } #[inline(always)] @@ -40,22 +63,46 @@ pub fn float_literal( precision_var: Variable, expected: Expected, region: Region, + bound: NumericBound, ) -> Constraint { let num_type = Variable(num_var); let reason = Reason::FloatLiteral; - exists( - vec![num_var, precision_var], - And(vec![ - Eq( - num_type.clone(), - ForReason(reason, num_float(Type::Variable(precision_var)), region), - Category::Float, - region, - ), - Eq(num_type, expected, Category::Float, region), - ]), - ) + let mut constrs = Vec::with_capacity(3); + add_numeric_bound_constr( + &mut constrs, + num_type.clone(), + bound, + region, + Category::Float, + ); + constrs.extend(vec![ + Eq( + num_type.clone(), + ForReason(reason, num_float(Type::Variable(precision_var)), region), + Category::Float, + region, + ), + Eq(num_type, expected, Category::Float, region), + ]); + + exists(vec![num_var, precision_var], And(constrs)) +} + +#[inline(always)] +pub fn num_literal( + num_var: Variable, + expected: Expected, + region: Region, + bound: NumericBound, +) -> Constraint { + let num_type = crate::builtins::num_num(Type::Variable(num_var)); + + let mut constrs = Vec::with_capacity(3); + add_numeric_bound_constr(&mut constrs, num_type.clone(), bound, region, Category::Num); + constrs.extend(vec![Eq(num_type, expected, Category::Num, region)]); + + exists(vec![num_var], And(constrs)) } #[inline(always)] @@ -148,6 +195,57 @@ pub fn num_int(range: Type) -> Type { ) } +macro_rules! num_types { + // Represent + // num_u8 ~ U8 : Num Integer Unsigned8 = @Num (@Integer (@Unsigned8)) + // int_u8 ~ Integer Unsigned8 = @Integer (@Unsigned8) + // + // num_f32 ~ F32 : Num FloaingPoint Binary32 = @Num (@FloaingPoint (@Binary32)) + // float_f32 ~ FloatingPoint Binary32 = @FloatingPoint (@Binary32) + // and so on, for all numeric types. + ($($num_fn:ident, $sub_fn:ident, $num_type:ident, $alias:path, $inner_alias:path, $inner_private_tag:path)*) => { + $( + #[inline(always)] + fn $sub_fn() -> Type { + builtin_alias( + $inner_alias, + vec![], + Box::new(Type::TagUnion( + vec![(TagName::Private($inner_private_tag), vec![])], + Box::new(Type::EmptyTagUnion) + )), + ) + } + + #[inline(always)] + fn $num_fn() -> Type { + builtin_alias( + $alias, + vec![], + Box::new($num_type($sub_fn())) + ) + } + )* + } +} + +num_types! { + num_u8, int_u8, num_int, Symbol::NUM_U8, Symbol::NUM_UNSIGNED8, Symbol::NUM_AT_UNSIGNED8 + num_u16, int_u16, num_int, Symbol::NUM_U16, Symbol::NUM_UNSIGNED16, Symbol::NUM_AT_UNSIGNED16 + num_u32, int_u32, num_int, Symbol::NUM_U32, Symbol::NUM_UNSIGNED32, Symbol::NUM_AT_UNSIGNED32 + num_u64, int_u64, num_int, Symbol::NUM_U64, Symbol::NUM_UNSIGNED64, Symbol::NUM_AT_UNSIGNED64 + num_u128, int_u128, num_int, Symbol::NUM_U128, Symbol::NUM_UNSIGNED128, Symbol::NUM_AT_UNSIGNED128 + num_i8, int_i8, num_int, Symbol::NUM_I8, Symbol::NUM_SIGNED8, Symbol::NUM_AT_SIGNED8 + num_i16, int_i16, num_int, Symbol::NUM_I16, Symbol::NUM_SIGNED16, Symbol::NUM_AT_SIGNED16 + num_i32, int_i32, num_int, Symbol::NUM_I32, Symbol::NUM_SIGNED32, Symbol::NUM_AT_SIGNED32 + num_i64, int_i64, num_int, Symbol::NUM_I64, Symbol::NUM_SIGNED64, Symbol::NUM_AT_SIGNED64 + num_i128, int_i128, num_int, Symbol::NUM_I128, Symbol::NUM_SIGNED128, Symbol::NUM_AT_SIGNED128 + num_nat, int_nat, num_int, Symbol::NUM_NAT, Symbol::NUM_NATURAL, Symbol::NUM_AT_NATURAL + num_dec, float_dec, num_float, Symbol::NUM_DEC, Symbol::NUM_DECIMAL, Symbol::NUM_AT_DECIMAL + num_f32, float_f32, num_float, Symbol::NUM_F32, Symbol::NUM_BINARY32, Symbol::NUM_AT_BINARY32 + num_f64, float_f64, num_float, Symbol::NUM_F64, Symbol::NUM_BINARY64, Symbol::NUM_AT_BINARY64 +} + #[inline(always)] pub fn num_signed64() -> Type { let alias_content = Type::TagUnion( @@ -188,3 +286,66 @@ pub fn num_num(typ: Type) -> Type { Box::new(alias_content), ) } + +pub trait TypedNumericBound { + fn num_type(&self) -> Type; + + /// Whether the bound has a concrete range, and could not be filled by an arbitrary type. + fn is_concrete(&self) -> bool; +} + +impl TypedNumericBound for NumericBound { + fn num_type(&self) -> Type { + match self { + &NumericBound::None { width_variable } => num_num(Type::Variable(width_variable)), + NumericBound::Exact(w) => match w { + IntWidth::U8 => num_u8(), + IntWidth::U16 => num_u16(), + IntWidth::U32 => num_u32(), + IntWidth::U64 => num_u64(), + IntWidth::U128 => num_u128(), + IntWidth::I8 => num_i8(), + IntWidth::I16 => num_i16(), + IntWidth::I32 => num_i32(), + IntWidth::I64 => num_i64(), + IntWidth::I128 => num_i128(), + IntWidth::Nat => num_nat(), + }, + } + } + + fn is_concrete(&self) -> bool { + !matches!(self, NumericBound::None { .. }) + } +} + +impl TypedNumericBound for NumericBound { + fn num_type(&self) -> Type { + match self { + &NumericBound::None { width_variable } => num_num(Type::Variable(width_variable)), + NumericBound::Exact(w) => match w { + FloatWidth::Dec => num_dec(), + FloatWidth::F32 => num_f32(), + FloatWidth::F64 => num_f64(), + }, + } + } + + fn is_concrete(&self) -> bool { + !matches!(self, NumericBound::None { .. }) + } +} + +impl TypedNumericBound for NumericBound { + fn num_type(&self) -> Type { + match self { + &NumericBound::None { width_variable } => num_num(Type::Variable(width_variable)), + &NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(iw).num_type(), + &NumericBound::Exact(NumWidth::Float(fw)) => NumericBound::Exact(fw).num_type(), + } + } + + fn is_concrete(&self) -> bool { + !matches!(self, NumericBound::None { .. }) + } +} diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 3cbdbbbc04..5d7ec4e22d 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1,4 +1,6 @@ -use crate::builtins::{empty_list_type, float_literal, int_literal, list_type, str_type}; +use crate::builtins::{ + empty_list_type, float_literal, int_literal, list_type, num_literal, str_type, +}; use crate::pattern::{constrain_pattern, PatternState}; use roc_can::annotation::IntroducedVariables; use roc_can::constraint::Constraint::{self, *}; @@ -96,20 +98,11 @@ pub fn constrain_expr( expected: Expected, ) -> Constraint { match expr { - // TODO constrain with bound - Int(var, precision, _, _, _bound) => int_literal(*var, *precision, expected, region), - // TODO constrain with bound - Num(var, _, _, _bound) => exists( - vec![*var], - Eq( - crate::builtins::num_num(Type::Variable(*var)), - expected, - Category::Num, - region, - ), - ), - // TODO constrain with bound - Float(var, precision, _, _, _bound) => float_literal(*var, *precision, expected, region), + &Int(var, precision, _, _, bound) => int_literal(var, precision, expected, region, bound), + &Num(var, _, _, bound) => num_literal(var, expected, region, bound), + &Float(var, precision, _, _, bound) => { + float_literal(var, precision, expected, region, bound) + } EmptyRecord => constrain_empty_record(region, expected), Expr::Record { record_var, fields } => { if fields.is_empty() { diff --git a/compiler/module/src/lib.rs b/compiler/module/src/lib.rs index 044f697a07..f9f10fd02f 100644 --- a/compiler/module/src/lib.rs +++ b/compiler/module/src/lib.rs @@ -6,6 +6,7 @@ pub mod called_via; pub mod ident; pub mod low_level; pub mod module_err; +pub mod numeric; pub mod symbol; #[macro_use] diff --git a/compiler/module/src/numeric.rs b/compiler/module/src/numeric.rs new file mode 100644 index 0000000000..84b5a5a9b5 --- /dev/null +++ b/compiler/module/src/numeric.rs @@ -0,0 +1,84 @@ +//! Module `numeric` has utilities for numeric values in the Roc surface syntax. + +use std::fmt::Display; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum IntWidth { + U8, + U16, + U32, + U64, + U128, + I8, + I16, + I32, + I64, + I128, + Nat, +} + +impl Display for IntWidth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use IntWidth::*; + f.write_str(match self { + U8 => "u8", + U16 => "u16", + U32 => "u32", + U64 => "u64", + U128 => "u128", + I8 => "i8", + I16 => "i16", + I32 => "i32", + I64 => "i64", + I128 => "i128", + Nat => "nat", + }) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum FloatWidth { + Dec, + F32, + F64, +} + +impl Display for FloatWidth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use FloatWidth::*; + f.write_str(match self { + Dec => "dec", + F32 => "f32", + F64 => "f64", + }) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum NumWidth { + Int(IntWidth), + Float(FloatWidth), +} + +impl Display for NumWidth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use NumWidth::*; + match self { + Int(iw) => iw.fmt(f), + Float(fw) => fw.fmt(f), + } + } +} + +/// Describes a bound on the width of a numeric literal. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum NumericBound +where + W: Copy, + V: Copy, +{ + /// There is no bound on the width. + None { width_variable: V }, + /// Must have exactly the width `W`. + Exact(W), +} diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index 43e1db0fa8..6ffd6febc5 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -1,10 +1,11 @@ -use std::fmt::{Debug, Display}; +use std::fmt::Debug; use crate::header::{AppHeader, HostedHeader, InterfaceHeader, PlatformHeader}; use crate::ident::Ident; use bumpalo::collections::{String, Vec}; use bumpalo::Bump; use roc_module::called_via::{BinOp, CalledVia, UnaryOp}; +use roc_module::numeric::{FloatWidth, IntWidth, NumWidth}; use roc_region::all::{Loc, Position, Region}; #[derive(Debug)] @@ -126,70 +127,6 @@ pub enum StrLiteral<'a> { Block(&'a [&'a [StrSegment<'a>]]), } -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum NumWidth { - U8, - U16, - U32, - U64, - U128, - I8, - I16, - I32, - I64, - I128, - Nat, - Dec, -} - -impl Display for NumWidth { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use NumWidth::*; - f.write_str(match self { - U8 => "u8", - U16 => "u16", - U32 => "u32", - U64 => "u64", - U128 => "u128", - I8 => "i8", - I16 => "i16", - I32 => "i32", - I64 => "i64", - I128 => "i128", - Nat => "nat", - Dec => "dec", - }) - } -} - -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum FloatWidth { - F32, - F64, -} - -impl Display for FloatWidth { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use FloatWidth::*; - f.write_str(match self { - F32 => "f32", - F64 => "f64", - }) - } -} - -/// Describes a bound on the width of a numeric literal. -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum NumericBound -where - W: Copy, -{ - /// There is no bound on the width. - None, - /// Must have exactly the width `W`. - Exact(W), -} - /// A parsed expression. This uses lifetimes extensively for two reasons: /// /// 1. It uses Bump::alloc for all allocations, which returns a reference. @@ -204,11 +141,12 @@ pub enum Expr<'a> { // Number Literals Float(&'a str, NumericBound), Num(&'a str, NumericBound), + Int(&'a str, NumericBound), NonBase10Int { string: &'a str, base: Base, is_negative: bool, - bound: NumericBound, + bound: NumericBound, }, // String Literals @@ -473,6 +411,9 @@ impl<'a> CommentOrNewline<'a> { } } +/// A `NumericBound` with the unit type as a placeholder width variable. +pub type NumericBound = roc_module::numeric::NumericBound; + #[derive(Clone, Copy, Debug, PartialEq)] pub enum Pattern<'a> { // Identifier @@ -501,9 +442,10 @@ pub enum Pattern<'a> { string: &'a str, base: Base, is_negative: bool, - bound: NumericBound, + bound: NumericBound, }, FloatLiteral(&'a str, NumericBound), + IntLiteral(&'a str, NumericBound), StrLiteral(StrLiteral<'a>), Underscore(&'a str), diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 58acb0c5a8..7ef2ee4a6c 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -1,6 +1,6 @@ use crate::ast::{ - AliasHeader, AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, - NumericBound, Pattern, Spaceable, TypeAnnotation, + AliasHeader, AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, Pattern, + Spaceable, TypeAnnotation, }; use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e}; use crate::ident::{lowercase_ident, parse_ident, Ident}; @@ -16,6 +16,7 @@ use crate::type_annotation; use bumpalo::collections::Vec; use bumpalo::Bump; use roc_module::called_via::{BinOp, CalledVia, UnaryOp}; +use roc_module::numeric::NumericBound; use roc_region::all::{Loc, Position, Region}; use crate::parser::Progress::{self, *}; @@ -377,7 +378,7 @@ impl<'a> ExprState<'a> { } else { let region = self.expr.region; - let mut value = Expr::Num("", NumericBound::None); + let mut value = Expr::Num("", NumericBound::None { width_variable: () }); std::mem::swap(&mut self.expr.value, &mut value); self.expr = arena @@ -1454,6 +1455,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result Ok(Pattern::FloatLiteral(string, bound)), &Expr::Num(string, bound) => Ok(Pattern::NumLiteral(string, bound)), + &Expr::Int(string, bound) => Ok(Pattern::IntLiteral(string, bound)), Expr::NonBase10Int { string, base, @@ -2325,6 +2327,7 @@ fn positive_number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> { match literal { Num(s, bound) => Expr::Num(s, bound), Float(s, bound) => Expr::Float(s, bound), + Int(s, bound) => Expr::Int(s, bound), NonBase10Int { string, base, @@ -2348,6 +2351,7 @@ fn number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> { match literal { Num(s, bound) => Expr::Num(s, bound), Float(s, bound) => Expr::Float(s, bound), + Int(s, bound) => Expr::Int(s, bound), NonBase10Int { string, base, diff --git a/compiler/parse/src/number_literal.rs b/compiler/parse/src/number_literal.rs index bf6341d76f..47c9ae9410 100644 --- a/compiler/parse/src/number_literal.rs +++ b/compiler/parse/src/number_literal.rs @@ -1,16 +1,18 @@ -use crate::ast::{Base, FloatWidth, NumWidth, NumericBound}; +use crate::ast::{Base, NumericBound}; use crate::parser::{ENumber, ParseResult, Parser, Progress}; use crate::state::State; +use roc_module::numeric::{FloatWidth, IntWidth, NumWidth}; #[derive(Debug, Copy, Clone)] pub enum NumLiteral<'a> { Float(&'a str, NumericBound), + Int(&'a str, NumericBound), Num(&'a str, NumericBound), NonBase10Int { string: &'a str, base: Base, is_negative: bool, - bound: NumericBound, + bound: NumericBound, }, } @@ -51,55 +53,11 @@ fn parse_number_base<'a>( bytes: &'a [u8], state: State<'a>, ) -> ParseResult<'a, NumLiteral<'a>, ENumber> { - let number = match bytes.get(0..2) { + match bytes.get(0..2) { Some(b"0b") => chomp_number_base(Base::Binary, is_negated, &bytes[2..], state), Some(b"0o") => chomp_number_base(Base::Octal, is_negated, &bytes[2..], state), Some(b"0x") => chomp_number_base(Base::Hex, is_negated, &bytes[2..], state), _ => chomp_number_dec(is_negated, bytes, state), - }; - number.and_then(|(_, literal, state)| parse_number_suffix(literal, state)) -} - -fn parse_number_suffix<'a>( - literal: NumLiteral<'a>, - state: State<'a>, -) -> ParseResult<'a, NumLiteral<'a>, ENumber> { - match literal { - NumLiteral::Float(s, _) => { - let (bound, state) = match get_float_suffix(state.bytes()) { - Some((bound, n)) => (NumericBound::Exact(bound), state.advance(n)), - None => (NumericBound::None, state), - }; - Ok((Progress::MadeProgress, NumLiteral::Float(s, bound), state)) - } - NumLiteral::Num(s, _) => { - let (bound, state) = match get_int_suffix(state.bytes()) { - Some((bound, n)) => (NumericBound::Exact(bound), state.advance(n)), - None => (NumericBound::None, state), - }; - Ok((Progress::MadeProgress, NumLiteral::Num(s, bound), state)) - } - NumLiteral::NonBase10Int { - string, - base, - is_negative, - bound: _, - } => { - let (bound, state) = match get_int_suffix(state.bytes()) { - Some((bound, n)) => (NumericBound::Exact(bound), state.advance(n)), - None => (NumericBound::None, state), - }; - Ok(( - Progress::MadeProgress, - NumLiteral::NonBase10Int { - string, - base, - is_negative, - bound, - }, - state, - )) - } } } @@ -121,21 +79,20 @@ macro_rules! parse_num_suffix { } } -fn get_int_suffix<'a>(bytes: &'a [u8]) -> Option<(NumWidth, usize)> { +fn get_int_suffix<'a>(bytes: &'a [u8]) -> Option<(IntWidth, usize)> { parse_num_suffix! { bytes, - b"u8", NumWidth::U8 - b"u16", NumWidth::U16 - b"u32", NumWidth::U32 - b"u64", NumWidth::U64 - b"u128", NumWidth::U128 - b"i8", NumWidth::I8 - b"i16", NumWidth::I16 - b"i32", NumWidth::I32 - b"i64", NumWidth::I64 - b"i128", NumWidth::I128 - b"nat", NumWidth::Nat - b"dec", NumWidth::Dec + b"u8", IntWidth::U8 + b"u16", IntWidth::U16 + b"u32", IntWidth::U32 + b"u64", IntWidth::U64 + b"u128", IntWidth::U128 + b"i8", IntWidth::I8 + b"i16", IntWidth::I16 + b"i32", IntWidth::I32 + b"i64", IntWidth::I64 + b"i128", IntWidth::I128 + b"nat", IntWidth::Nat } None } @@ -143,34 +100,60 @@ fn get_int_suffix<'a>(bytes: &'a [u8]) -> Option<(NumWidth, usize)> { fn get_float_suffix<'a>(bytes: &'a [u8]) -> Option<(FloatWidth, usize)> { parse_num_suffix! { bytes, + b"dec", FloatWidth::Dec b"f32", FloatWidth::F32 b"f64", FloatWidth::F64 } None } +fn get_num_suffix<'a>(bytes: &'a [u8]) -> Option<(NumWidth, usize)> { + (get_int_suffix(bytes).map(|(iw, l)| (NumWidth::Int(iw), l))) + .or_else(|| get_float_suffix(bytes).map(|(fw, l)| (NumWidth::Float(fw), l))) +} + fn chomp_number_base<'a>( base: Base, is_negative: bool, bytes: &'a [u8], state: State<'a>, ) -> ParseResult<'a, NumLiteral<'a>, ENumber> { - let (_is_float, chomped) = chomp_number(bytes); + let (_, (_is_float, bound, chomped), state) = + chomp_number(bytes, state, is_negative, base == Base::Hex)?; - let string = unsafe { std::str::from_utf8_unchecked(&bytes[..chomped]) }; + let (bound, chomped_number) = if let Some((bound, chomped_before_suffix)) = bound { + (Some(bound), chomped_before_suffix) + } else { + (None, chomped) + }; + + let string = unsafe { std::str::from_utf8_unchecked(&bytes[..chomped_number]) }; let new = state.advance(chomped + 2 + is_negative as usize); - Ok(( - Progress::MadeProgress, - NumLiteral::NonBase10Int { - is_negative, - string, - base, - bound: NumericBound::None, - }, - new, - )) + match bound { + None => Ok(( + Progress::MadeProgress, + NumLiteral::NonBase10Int { + is_negative, + string, + base, + bound: NumericBound::None { width_variable: () }, + }, + new, + )), + Some(NumWidth::Int(iw)) => Ok(( + Progress::MadeProgress, + NumLiteral::NonBase10Int { + is_negative, + string, + base, + bound: NumericBound::Exact(iw), + }, + new, + )), + Some(NumWidth::Float(_)) => Err((Progress::MadeProgress, ENumber::End, state)), + } } fn chomp_number_dec<'a>( @@ -178,37 +161,59 @@ fn chomp_number_dec<'a>( bytes: &'a [u8], state: State<'a>, ) -> ParseResult<'a, NumLiteral<'a>, ENumber> { - let (is_float, chomped) = chomp_number(bytes); - - if is_negative && chomped == 0 { - // we're probably actually looking at unary negation here - return Err((Progress::NoProgress, ENumber::End, state)); - } + let (_, (is_float, bound, chomped), state) = chomp_number(bytes, state, is_negative, false)?; if !bytes.get(0).copied().unwrap_or_default().is_ascii_digit() { // we're probably actually looking at unary negation here return Err((Progress::NoProgress, ENumber::End, state)); } - let string = - unsafe { std::str::from_utf8_unchecked(&state.bytes()[0..chomped + is_negative as usize]) }; + let (bound, chomped_number) = if let Some((bound, chomped_before_suffix)) = bound { + (Some(bound), chomped_before_suffix) + } else { + (None, chomped) + }; + + let string = unsafe { + std::str::from_utf8_unchecked(&state.bytes()[0..chomped_number + is_negative as usize]) + }; let new = state.advance(chomped + is_negative as usize); - Ok(( - Progress::MadeProgress, - if is_float { - NumLiteral::Float(string, NumericBound::None) - } else { - NumLiteral::Num(string, NumericBound::None) - }, - new, - )) + match (is_float, bound) { + (true, None) => Ok(( + Progress::MadeProgress, + NumLiteral::Float(string, NumericBound::None { width_variable: () }), + new, + )), + (false, None) => Ok(( + Progress::MadeProgress, + NumLiteral::Num(string, NumericBound::None { width_variable: () }), + new, + )), + (_, Some(NumWidth::Float(fw))) => Ok(( + Progress::MadeProgress, + NumLiteral::Float(string, NumericBound::Exact(fw)), + new, + )), + (false, Some(NumWidth::Int(iw))) => Ok(( + Progress::MadeProgress, + NumLiteral::Int(string, NumericBound::Exact(iw)), + new, + )), + (true, Some(NumWidth::Int(_))) => Err((Progress::MadeProgress, ENumber::End, state)), + } } -fn chomp_number(mut bytes: &[u8]) -> (bool, usize) { +fn chomp_number<'a>( + mut bytes: &'a [u8], + state: State<'a>, + is_negative: bool, + hex: bool, +) -> ParseResult<'a, (bool, Option<(NumWidth, usize)>, usize), ENumber> { let start_bytes_len = bytes.len(); let mut is_float = false; + let mut suffix_and_chomped_before = None; while let Some(byte) = bytes.get(0) { match byte { @@ -240,14 +245,52 @@ fn chomp_number(mut bytes: &[u8]) -> (bool, usize) { _ if byte.is_ascii_digit() => { bytes = &bytes[1..]; } - _ => { + _ if byte.is_ascii_hexdigit() && hex => { + bytes = &bytes[1..]; + } + _ if byte.is_ascii_whitespace() || byte.is_ascii_punctuation() => { // not a valid digit; we're done - return (is_float, start_bytes_len - bytes.len()); + return Ok(( + Progress::MadeProgress, + ( + is_float, + suffix_and_chomped_before, + start_bytes_len - bytes.len(), + ), + state, + )); + } + _ => { + // This might be a suffix; try that first. + let parsed_suffix = if suffix_and_chomped_before.is_none() { + get_num_suffix(bytes) + } else { + None + }; + + if let Some((bound, advanced_by)) = parsed_suffix { + suffix_and_chomped_before = Some((bound, start_bytes_len - bytes.len())); + bytes = &bytes[advanced_by..]; + continue; + } + + // Okay, this number is invalid. + + if start_bytes_len - bytes.len() == 0 && is_negative { + // We're probably actually looking at unary negation here. Reset the progress. + return Err((Progress::NoProgress, ENumber::End, state)); + } + + return Err((Progress::MadeProgress, ENumber::End, state)); } } } // if the above loop exits, we must be dealing with an empty slice // therefore we parsed all of the bytes in the input - (is_float, start_bytes_len) + Ok(( + Progress::MadeProgress, + (is_float, suffix_and_chomped_before, start_bytes_len), + state, + )) } diff --git a/compiler/parse/src/pattern.rs b/compiler/parse/src/pattern.rs index f635cb67e2..2acf0a784f 100644 --- a/compiler/parse/src/pattern.rs +++ b/compiler/parse/src/pattern.rs @@ -140,6 +140,7 @@ fn number_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> { match literal { Num(s, bound) => Pattern::NumLiteral(s, bound), Float(s, bound) => Pattern::FloatLiteral(s, bound), + Int(s, bound) => Pattern::IntLiteral(s, bound), NonBase10Int { string, base, diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index b86caf3c81..ba2c9410c8 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -5044,4 +5044,52 @@ mod solve_expr { "[ Email Str ] -> Bool", ) } + + #[test] + fn numeric_literal_suffixes() { + infer_eq_without_problem( + indoc!( + r#" + { + u8: 123u8, + u16: 123u16, + u32: 123u32, + u64: 123u64, + u128: 123u128, + + i8: 123i8, + i16: 123i16, + i32: 123i32, + i64: 123i64, + i128: 123i128, + + nat: 123nat, + + bu8: 0b11u8, + bu16: 0b11u16, + bu32: 0b11u32, + bu64: 0b11u64, + bu128: 0b11u128, + + bi8: 0b11i8, + bi16: 0b11i16, + bi32: 0b11i32, + bi64: 0b11i64, + bi128: 0b11i128, + + bnat: 0b11nat, + + dec: 123.0dec, + f32: 123.0f32, + f64: 123.0f64, + + fdec: 123dec, + ff32: 123f32, + ff64: 123f64, + } + "# + ), + r#"{ bi128 : I128, bi16 : I16, bi32 : I32, bi64 : I64, bi8 : I8, bnat : Nat, bu128 : U128, bu16 : U16, bu32 : U32, bu64 : U64, bu8 : U8, dec : Dec, f32 : F32, f64 : F64, fdec : Dec, ff32 : F32, ff64 : F64, i128 : I128, i16 : I16, i32 : I32, i64 : I64, i8 : I8, nat : Nat, u128 : U128, u16 : U16, u32 : U32, u64 : U64, u8 : U8 }"#, + ) + } } diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index a861242bd5..a40bfbe410 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -1,4 +1,4 @@ -use crate::subs::{Content, FlatType, GetSubsSlice, Subs, UnionTags, Variable}; +use crate::subs::{AliasVariables, Content, FlatType, GetSubsSlice, Subs, UnionTags, Variable}; use crate::types::{name_type_var, RecordField}; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Lowercase, TagName}; @@ -289,6 +289,17 @@ pub fn content_to_string( buf } +pub fn get_single_arg<'a>(subs: &'a Subs, args: &'a AliasVariables) -> &'a Content { + debug_assert_eq!(args.len(), 1); + + let arg_var_index = args + .into_iter() + .next() + .expect("Num was not applied to a type argument!"); + let arg_var = subs[arg_var_index]; + subs.get_content_without_compacting(arg_var) +} + fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, parens: Parens) { use crate::subs::Content::*; @@ -306,18 +317,19 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa match *symbol { Symbol::NUM_NUM => { - debug_assert_eq!(args.len(), 1); - - let arg_var_index = args - .into_iter() - .next() - .expect("Num was not applied to a type argument!"); - let arg_var = subs[arg_var_index]; - let content = subs.get_content_without_compacting(arg_var); - - match &content { - Alias(nested, _, _) => match *nested { - Symbol::NUM_INTEGER => buf.push_str("I64"), + let content = get_single_arg(subs, args); + match content { + &Alias(nested, args, _actual) => match nested { + Symbol::NUM_INTEGER => { + write_integer( + env, + get_single_arg(subs, &args), + subs, + buf, + parens, + false, + ); + } Symbol::NUM_FLOATINGPOINT => buf.push_str("F64"), _ => write_parens!(write_parens, buf, { @@ -333,6 +345,32 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa } } + Symbol::NUM_INT => { + let content = get_single_arg(subs, args); + + write_integer(env, content, subs, buf, parens, write_parens) + } + + Symbol::NUM_FLOAT => { + debug_assert_eq!(args.len(), 1); + + let arg_var_index = args + .into_iter() + .next() + .expect("Num was not applied to a type argument!"); + let arg_var = subs[arg_var_index]; + let content = subs.get_content_without_compacting(arg_var); + + match content { + &Alias(Symbol::NUM_BINARY32, _, _) => buf.push_str("F32"), + &Alias(Symbol::NUM_BINARY64, _, _) => buf.push_str("F64"), + _ => write_parens!(write_parens, buf, { + buf.push_str("Float "); + write_content(env, content, subs, buf, parens); + }), + } + } + _ => write_parens!(write_parens, buf, { write_symbol(env, *symbol, buf); @@ -362,6 +400,51 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa } } +fn write_integer( + env: &Env, + content: &Content, + subs: &Subs, + buf: &mut String, + parens: Parens, + write_parens: bool, +) { + use crate::subs::Content::*; + + macro_rules! derive_num_writes { + ($($lit:expr, $tag:path, $private_tag:path)*) => { + write_parens!( + write_parens, + buf, + match content { + $( + &Alias($tag, _, _) => { + buf.push_str($lit) + }, + )* + actual => { + buf.push_str("Int "); + write_content(env, actual, subs, buf, parens); + } + } + ) + } + } + + derive_num_writes! { + "U8", Symbol::NUM_UNSIGNED8, Symbol::NUM_AT_UNSIGNED8 + "U16", Symbol::NUM_UNSIGNED16, Symbol::NUM_AT_UNSIGNED16 + "U32", Symbol::NUM_UNSIGNED32, Symbol::NUM_AT_UNSIGNED32 + "U64", Symbol::NUM_UNSIGNED64, Symbol::NUM_AT_UNSIGNED64 + "U128", Symbol::NUM_UNSIGNED128, Symbol::NUM_AT_UNSIGNED128 + "I8", Symbol::NUM_SIGNED8, Symbol::NUM_AT_SIGNED8 + "I16", Symbol::NUM_SIGNED16, Symbol::NUM_AT_SIGNED16 + "I32", Symbol::NUM_SIGNED32, Symbol::NUM_AT_SIGNED32 + "I64", Symbol::NUM_SIGNED64, Symbol::NUM_AT_SIGNED64 + "I128", Symbol::NUM_SIGNED128, Symbol::NUM_AT_SIGNED128 + "Nat", Symbol::NUM_NATURAL, Symbol::NUM_AT_NATURAL + } +} + enum ExtContent<'a> { Empty, Content(Variable, &'a Content), diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 2f27493b8e..26e84320cd 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -1626,6 +1626,25 @@ pub enum FlatType { EmptyTagUnion, } +impl FlatType { + pub fn get_singleton_tag_union<'a>(&'a self, subs: &'a Subs) -> Option<&'a TagName> { + match self { + Self::TagUnion(tags, ext) => { + let tags = tags.unsorted_tags_and_ext(subs, *ext).0.tags; + if tags.len() != 1 { + return None; + } + let (tag_name, vars) = tags[0]; + if !vars.is_empty() { + return None; + } + Some(tag_name) + } + _ => None, + } + } +} + #[derive(PartialEq, Eq, Debug, Clone, Copy)] pub enum Builtin { Str, diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 648f1089f6..f8e1296bdf 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -1186,6 +1186,7 @@ pub enum Reason { RecordUpdateValue(Lowercase), RecordUpdateKeys(Symbol, SendMap), RecordDefaultField(Lowercase), + NumericLiteralSuffix, } #[derive(PartialEq, Debug, Clone)] diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index cb1660597f..f5f33e25fa 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -1,4 +1,4 @@ -use roc_parse::parser::{FileError, SyntaxError}; +use roc_parse::parser::{ENumber, FileError, SyntaxError}; use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Position, Region}; use std::path::PathBuf; @@ -519,6 +519,10 @@ fn to_expr_report<'a>( EExpr::Space(error, pos) => to_space_report(alloc, lines, filename, error, *pos), + &EExpr::Number(ENumber::End, pos) => { + to_malformed_number_literal_report(alloc, lines, filename, pos) + } + _ => todo!("unhandled parse error: {:?}", parse_problem), } } @@ -1554,6 +1558,9 @@ fn to_pattern_report<'a>( EPattern::PInParens(inparens, pos) => { to_pattern_in_parens_report(alloc, lines, filename, inparens, *pos) } + &EPattern::NumLiteral(ENumber::End, pos) => { + to_malformed_number_literal_report(alloc, lines, filename, pos) + } _ => todo!("unhandled parse error: {:?}", parse_problem), } } @@ -1947,6 +1954,28 @@ fn to_pattern_in_parens_report<'a>( } } +fn to_malformed_number_literal_report<'a>( + alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, + filename: PathBuf, + start: Position, +) -> Report<'a> { + let surroundings = Region::new(start, start); + let region = LineColumnRegion::from_pos(lines.convert_pos(start)); + + let doc = alloc.stack(vec![ + alloc.reflow(r"This number literal is malformed:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + ]); + + Report { + filename, + doc, + title: "INVALID NUMBER LITERAL".to_string(), + severity: Severity::RuntimeError, + } +} + fn to_type_report<'a>( alloc: &'a RocDocAllocator<'a>, lines: &LineInfo, diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index 3bcde68b07..fbad75fc6a 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -929,6 +929,22 @@ fn to_expr_report<'b>( None, ) } + + Reason::NumericLiteralSuffix => report_mismatch( + alloc, + lines, + filename, + &category, + found, + expected_type, + region, + Some(expr_region), + alloc.text("This numeric literal is being used improperly:"), + alloc.text("Here's it's been used as"), + alloc.text("But its suffix says it's a:"), + None, + ), + Reason::LowLevelOpArg { op, arg_index } => { panic!( "Compiler bug: argument #{} to low-level operation {:?} was the wrong type!", @@ -946,6 +962,7 @@ fn to_expr_report<'b>( foreign_symbol ); } + Reason::FloatLiteral | Reason::IntLiteral | Reason::NumLiteral => { unreachable!("I don't think these can be reached") } diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index f736655cea..ad5bab8d4c 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -1730,14 +1730,12 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── INVALID NUMBER LITERAL ────────────────────────────────────────────────────── - This integer pattern is malformed: + This number literal is malformed: 2│ 100A -> 3 - ^^^^ - - Tip: Learn more about number literals at TODO + ^ "# ), ) @@ -1755,14 +1753,12 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── INVALID NUMBER LITERAL ────────────────────────────────────────────────────── - This float pattern is malformed: + This number literal is malformed: 2│ 2.X -> 3 - ^^^ - - Tip: Learn more about number literals at TODO + ^ "# ), ) @@ -1780,14 +1776,12 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── INVALID NUMBER LITERAL ────────────────────────────────────────────────────── - This hex integer pattern is malformed: + This number literal is malformed: 2│ 0xZ -> 3 - ^^^ - - Tip: Learn more about number literals at TODO + ^ "# ), ) @@ -3539,50 +3533,12 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── INVALID NUMBER LITERAL ────────────────────────────────────────────────────── - This integer literal contains an invalid digit: + This number literal is malformed: 1│ dec = 100A - ^^^^ - - Integer literals can only contain the digits 0-9. - - Tip: Learn more about number literals at TODO - - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── - - This hex integer literal contains an invalid digit: - - 3│ hex = 0xZZZ - ^^^^^ - - Hexadecimal (base-16) integer literals can only contain the digits - 0-9, a-f and A-F. - - Tip: Learn more about number literals at TODO - - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── - - This octal integer literal contains an invalid digit: - - 5│ oct = 0o9 - ^^^ - - Octal (base-8) integer literals can only contain the digits 0-7. - - Tip: Learn more about number literals at TODO - - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── - - This binary integer literal contains an invalid digit: - - 7│ bin = 0b2 - ^^^ - - Binary (base-2) integer literals can only contain the digits 0 and 1. - - Tip: Learn more about number literals at TODO + ^ "# ), ) @@ -3658,17 +3614,12 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── INVALID NUMBER LITERAL ────────────────────────────────────────────────────── - This float literal contains an invalid digit: + This number literal is malformed: 1│ x = 3.0A - ^^^^ - - Floating point literals can only contain the digits 0-9, or use - scientific notation 10e4 - - Tip: Learn more about number literals at TODO + ^ "# ), ) @@ -7304,4 +7255,65 @@ I need all branches in an `if` to have the same type! ), ) } + + macro_rules! mismatched_suffix_tests { + ($($number:expr, $suffix:expr, $name:ident)*) => {$( + #[test] + fn $name() { + let number = $number.to_string(); + let mut typ = $suffix.to_string(); + typ.get_mut(0..1).unwrap().make_ascii_uppercase(); + let bad_type = if $suffix == "u8" { "I8" } else { "U8" }; + let carets = "^".repeat(number.len() + $suffix.len()); + let kind = match $suffix { + "dec"|"f32"|"f64" => "a float", + _ => "an integer", + }; + + report_problem_as( + &format!(indoc!( + r#" + use : {} -> U8 + use {}{} + "# + ), bad_type, number, $suffix), + &format!(indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 1st argument to `use` is not what I expect: + + 2│ use {}{} + {} + + This argument is {} of type: + + {} + + But `use` needs the 1st argument to be: + + {} + "# + ), number, $suffix, carets, kind, typ, bad_type), + ) + } + )*} + } + + mismatched_suffix_tests! { + 1, "u8", mismatched_suffix_u8 + 1, "u16", mismatched_suffix_u16 + 1, "u32", mismatched_suffix_u32 + 1, "u64", mismatched_suffix_u64 + 1, "u128", mismatched_suffix_u128 + 1, "i8", mismatched_suffix_i8 + 1, "i16", mismatched_suffix_i16 + 1, "i32", mismatched_suffix_i32 + 1, "i64", mismatched_suffix_i64 + 1, "i128", mismatched_suffix_i128 + 1, "nat", mismatched_suffix_nat + 1, "dec", mismatched_suffix_dec + 1, "f32", mismatched_suffix_f32 + 1, "f64", mismatched_suffix_f64 + } } diff --git a/roc-for-elm-programmers.md b/roc-for-elm-programmers.md index 5d4ddb6d62..aa90d30258 100644 --- a/roc-for-elm-programmers.md +++ b/roc-for-elm-programmers.md @@ -1267,7 +1267,7 @@ So Roc does not use `number`, but rather uses `Num` - which works more like `Lis Either way, you get `+` being able to work on both integers and floats! Separately, there's also `Int a`, which is a type alias for `Num (Integer a)`, -and `Float a`, which is a type alias for `Num (Float a)`. These allow functions +and `Float a`, which is a type alias for `Num (FloatingPoint a)`. These allow functions that can work on any integer or any float. For example, `Num.bitwiseAnd : Int a, Int a -> Int a`. From f7a055fc78bb9c64584487c9999d1e05057d6143 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 31 Jan 2022 18:06:09 -0500 Subject: [PATCH 426/541] Fix parser tests --- .../pass/add_var_with_spaces.expr.result-ast | 4 +- .../pass/add_with_spaces.expr.result-ast | 8 +- ...notated_record_destructure.expr.result-ast | 4 +- .../annotated_tag_destructure.expr.result-ast | 4 +- .../pass/apply_global_tag.expr.result-ast | 8 +- ...enthetical_global_tag_args.expr.result-ast | 8 +- .../pass/apply_private_tag.expr.result-ast | 8 +- .../pass/apply_two_args.expr.result-ast | 8 +- .../pass/apply_unary_negation.expr.result-ast | 4 +- .../pass/apply_unary_not.expr.result-ast | 4 +- .../pass/basic_apply.expr.result-ast | 4 +- .../snapshots/pass/basic_docs.expr.result-ast | 8 +- .../closure_with_underscores.expr.result-ast | 4 +- .../pass/comment_after_op.expr.result-ast | 8 +- .../pass/comment_before_op.expr.result-ast | 8 +- .../comment_with_non_ascii.expr.result-ast | 8 +- .../snapshots/pass/expect.expr.result-ast | 12 +- .../float_with_underscores.expr.result-ast | 4 +- .../pass/highest_float.expr.result-ast | 4 +- .../pass/highest_int.expr.result-ast | 4 +- .../snapshots/pass/if_def.expr.result-ast | 8 +- .../pass/int_with_underscore.expr.result-ast | 4 +- .../pass/lowest_float.expr.result-ast | 4 +- .../snapshots/pass/lowest_int.expr.result-ast | 4 +- ...ed_ident_due_to_underscore.expr.result-ast | 4 +- ...ormed_pattern_field_access.expr.result-ast | 8 +- ...formed_pattern_module_name.expr.result-ast | 8 +- .../minus_twelve_minus_five.expr.result-ast | 8 +- .../snapshots/pass/mixed_docs.expr.result-ast | 8 +- .../pass/module_def_newline.module.result-ast | 4 +- .../multiline_type_signature.expr.result-ast | 4 +- ...ype_signature_with_comment.expr.result-ast | 4 +- .../pass/multiple_operators.expr.result-ast | 12 +- .../pass/negative_float.expr.result-ast | 4 +- .../pass/negative_int.expr.result-ast | 4 +- .../nested_def_annotation.module.result-ast | 8 +- .../snapshots/pass/nested_if.expr.result-ast | 12 +- .../pass/newline_after_equals.expr.result-ast | 8 +- .../pass/newline_after_mul.expr.result-ast | 8 +- .../pass/newline_after_sub.expr.result-ast | 8 +- ...nd_spaces_before_less_than.expr.result-ast | 12 +- .../pass/newline_before_add.expr.result-ast | 8 +- .../pass/newline_before_sub.expr.result-ast | 8 +- .../newline_singleton_list.expr.result-ast | 4 +- .../snapshots/pass/not_docs.expr.result-ast | 8 +- .../number_literal_suffixes.expr.result-ast | 164 ++++++++++-------- .../pass/number_literal_suffixes.expr.roc | 1 - .../snapshots/pass/one_def.expr.result-ast | 8 +- .../pass/one_minus_two.expr.result-ast | 8 +- .../pass/one_plus_two.expr.result-ast | 8 +- .../pass/one_spaced_def.expr.result-ast | 8 +- .../pass/ops_with_newlines.expr.result-ast | 8 +- .../packed_singleton_list.expr.result-ast | 4 +- .../pass/parenthetical_apply.expr.result-ast | 4 +- .../pass/parse_alias.expr.result-ast | 4 +- .../pass/parse_as_ann.expr.result-ast | 4 +- ...ttern_with_space_in_parens.expr.result-ast | 8 +- .../pass/positive_float.expr.result-ast | 4 +- .../pass/positive_int.expr.result-ast | 4 +- .../record_destructure_def.expr.result-ast | 12 +- .../record_func_type_decl.expr.result-ast | 4 +- .../pass/record_update.expr.result-ast | 8 +- .../pass/record_with_if.expr.result-ast | 12 +- .../pass/single_arg_closure.expr.result-ast | 4 +- .../single_underscore_closure.expr.result-ast | 4 +- .../spaced_singleton_list.expr.result-ast | 4 +- .../standalone_module_defs.module.result-ast | 4 +- .../pass/sub_var_with_spaces.expr.result-ast | 4 +- .../pass/sub_with_spaces.expr.result-ast | 8 +- .../pass/tag_pattern.expr.result-ast | 4 +- .../pass/ten_times_eleven.expr.result-ast | 8 +- .../pass/three_arg_closure.expr.result-ast | 4 +- .../pass/two_arg_closure.expr.result-ast | 4 +- .../pass/two_branch_when.expr.result-ast | 8 +- .../pass/two_spaced_def.expr.result-ast | 12 +- .../type_decl_with_underscore.expr.result-ast | 4 +- .../pass/unary_negation_arg.expr.result-ast | 4 +- ...unary_negation_with_parens.expr.result-ast | 4 +- .../unary_not_with_parens.expr.result-ast | 4 +- .../underscore_backpassing.expr.result-ast | 4 +- .../pass/var_minus_two.expr.result-ast | 4 +- .../pass/when_if_guard.expr.result-ast | 12 +- .../pass/when_in_parens.expr.result-ast | 4 +- .../when_in_parens_indented.expr.result-ast | 4 +- ..._with_alternative_patterns.expr.result-ast | 8 +- ..._with_function_application.expr.result-ast | 12 +- ...when_with_negative_numbers.expr.result-ast | 16 +- .../pass/when_with_numbers.expr.result-ast | 16 +- .../pass/when_with_records.expr.result-ast | 8 +- .../snapshots/pass/zero_float.expr.result-ast | 4 +- .../snapshots/pass/zero_int.expr.result-ast | 4 +- compiler/parse/tests/test_parse.rs | 15 +- 92 files changed, 540 insertions(+), 224 deletions(-) diff --git a/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast index 727a50e6cc..046ec82ba8 100644 --- a/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast @@ -10,6 +10,8 @@ BinOps( ], @4-5 Num( "2", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast index 7e4b1cf914..a377ea6225 100644 --- a/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast @@ -3,13 +3,17 @@ BinOps( ( @0-1 Num( "1", - None, + None { + width_variable: (), + }, ), @3-4 Plus, ), ], @7-8 Num( "2", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast b/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast index a966f42eb6..b48a145bd1 100644 --- a/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast @@ -43,7 +43,9 @@ Defs( [], @43-47 Float( "3.14", - None, + None { + width_variable: (), + }, ), ), ], diff --git a/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast b/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast index 2f8e8f1322..c208af4942 100644 --- a/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast @@ -44,7 +44,9 @@ Defs( [ @44-46 Num( "42", - None, + None { + width_variable: (), + }, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast index bc2ee875cd..7be36650fc 100644 --- a/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast @@ -5,11 +5,15 @@ Apply( [ @5-7 Num( "12", - None, + None { + width_variable: (), + }, ), @8-10 Num( "34", - None, + None { + width_variable: (), + }, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast index 6523f3f917..b5a6c707d4 100644 --- a/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast @@ -6,13 +6,17 @@ Apply( @6-8 ParensAround( Num( "12", - None, + None { + width_variable: (), + }, ), ), @11-13 ParensAround( Num( "34", - None, + None { + width_variable: (), + }, ), ), ], diff --git a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast index c0b0036b9c..0e5d83b9b1 100644 --- a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast @@ -5,11 +5,15 @@ Apply( [ @6-8 Num( "12", - None, + None { + width_variable: (), + }, ), @9-11 Num( "34", - None, + None { + width_variable: (), + }, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast index 81609e25ef..b5a7731d53 100644 --- a/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast @@ -6,11 +6,15 @@ Apply( [ @6-8 Num( "12", - None, + None { + width_variable: (), + }, ), @10-12 Num( "34", - None, + None { + width_variable: (), + }, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast index 190ea514f8..a2e6e37452 100644 --- a/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast @@ -9,7 +9,9 @@ Apply( [ @7-9 Num( "12", - None, + None { + width_variable: (), + }, ), @10-13 Var { module_name: "", diff --git a/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast index 9905672a14..536e747856 100644 --- a/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast @@ -9,7 +9,9 @@ Apply( [ @7-9 Num( "12", - None, + None { + width_variable: (), + }, ), @10-13 Var { module_name: "", diff --git a/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast index 4dcbec2e00..6de714179b 100644 --- a/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast @@ -6,7 +6,9 @@ Apply( [ @5-6 Num( "1", - None, + None { + width_variable: (), + }, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast index d53a867c85..bb175f392b 100644 --- a/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast @@ -7,14 +7,18 @@ SpaceBefore( ), @111-112 Num( "5", - None, + None { + width_variable: (), + }, ), ), ], @114-116 SpaceBefore( Num( "42", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast b/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast index 09ae529727..1be7970b0e 100644 --- a/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast @@ -9,6 +9,8 @@ Closure( ], @13-15 Num( "42", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast index 23075fc909..37df2fdf5a 100644 --- a/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast @@ -3,7 +3,9 @@ BinOps( ( @0-2 Num( "12", - None, + None { + width_variable: (), + }, ), @4-5 Star, ), @@ -11,7 +13,9 @@ BinOps( @15-17 SpaceBefore( Num( "92", - None, + None { + width_variable: (), + }, ), [ LineComment( diff --git a/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast index cf78d29fb2..921a937a21 100644 --- a/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast @@ -4,7 +4,9 @@ BinOps( @0-1 SpaceAfter( Num( "3", - None, + None { + width_variable: (), + }, ), [ LineComment( @@ -17,6 +19,8 @@ BinOps( ], @13-14 Num( "4", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast index 74b72a2e01..f200472637 100644 --- a/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast @@ -4,7 +4,9 @@ BinOps( @0-1 SpaceAfter( Num( "3", - None, + None { + width_variable: (), + }, ), [ LineComment( @@ -17,6 +19,8 @@ BinOps( ], @14-15 Num( "4", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/expect.expr.result-ast b/compiler/parse/tests/snapshots/pass/expect.expr.result-ast index a4b197165d..a8e2ca5fd2 100644 --- a/compiler/parse/tests/snapshots/pass/expect.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/expect.expr.result-ast @@ -4,20 +4,26 @@ Expect( ( @7-8 Num( "1", - None, + None { + width_variable: (), + }, ), @9-11 Equals, ), ], @12-13 Num( "1", - None, + None { + width_variable: (), + }, ), ), @15-16 SpaceBefore( Num( "4", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/float_with_underscores.expr.result-ast b/compiler/parse/tests/snapshots/pass/float_with_underscores.expr.result-ast index c2dc9ece91..ec9a9890e5 100644 --- a/compiler/parse/tests/snapshots/pass/float_with_underscores.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/float_with_underscores.expr.result-ast @@ -1,4 +1,6 @@ Float( "-1_23_456.0_1_23_456", - None, + None { + width_variable: (), + }, ) diff --git a/compiler/parse/tests/snapshots/pass/highest_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/highest_float.expr.result-ast index fca24d4a4d..6a75e62e9a 100644 --- a/compiler/parse/tests/snapshots/pass/highest_float.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/highest_float.expr.result-ast @@ -1,4 +1,6 @@ Float( "179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0", - None, + None { + width_variable: (), + }, ) diff --git a/compiler/parse/tests/snapshots/pass/highest_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/highest_int.expr.result-ast index ba6204b17d..bab2d14d15 100644 --- a/compiler/parse/tests/snapshots/pass/highest_int.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/highest_int.expr.result-ast @@ -1,4 +1,6 @@ Num( "9223372036854775807", - None, + None { + width_variable: (), + }, ) diff --git a/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast index f5cc81eb0d..67dff3b996 100644 --- a/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast @@ -6,14 +6,18 @@ Defs( ), @5-6 Num( "5", - None, + None { + width_variable: (), + }, ), ), ], @8-10 SpaceBefore( Num( "42", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/int_with_underscore.expr.result-ast b/compiler/parse/tests/snapshots/pass/int_with_underscore.expr.result-ast index 2d0857f573..08878ed7a7 100644 --- a/compiler/parse/tests/snapshots/pass/int_with_underscore.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/int_with_underscore.expr.result-ast @@ -1,4 +1,6 @@ Num( "1__23", - None, + None { + width_variable: (), + }, ) diff --git a/compiler/parse/tests/snapshots/pass/lowest_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/lowest_float.expr.result-ast index 9967db3c7d..16b05135a8 100644 --- a/compiler/parse/tests/snapshots/pass/lowest_float.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/lowest_float.expr.result-ast @@ -1,4 +1,6 @@ Float( "-179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0", - None, + None { + width_variable: (), + }, ) diff --git a/compiler/parse/tests/snapshots/pass/lowest_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/lowest_int.expr.result-ast index 1ad2735812..cd4b5cfd0f 100644 --- a/compiler/parse/tests/snapshots/pass/lowest_int.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/lowest_int.expr.result-ast @@ -1,4 +1,6 @@ Num( "-9223372036854775808", - None, + None { + width_variable: (), + }, ) diff --git a/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast b/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast index 1eaa224b6f..2ab6663d4a 100644 --- a/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast @@ -9,6 +9,8 @@ Closure( ], @15-17 Num( "42", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast b/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast index 63413753fa..cb09158833 100644 --- a/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast @@ -17,7 +17,9 @@ When( ], value: @25-26 Num( "1", - None, + None { + width_variable: (), + }, ), guard: None, }, @@ -34,7 +36,9 @@ When( ], value: @36-37 Num( "4", - None, + None { + width_variable: (), + }, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast b/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast index 3ebed0e66c..1958e1b5d9 100644 --- a/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast @@ -17,7 +17,9 @@ When( ], value: @25-26 Num( "1", - None, + None { + width_variable: (), + }, ), guard: None, }, @@ -34,7 +36,9 @@ When( ], value: @36-37 Num( "4", - None, + None { + width_variable: (), + }, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast b/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast index b914a9d211..31bad5fe49 100644 --- a/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast @@ -3,13 +3,17 @@ BinOps( ( @0-3 Num( "-12", - None, + None { + width_variable: (), + }, ), @3-4 Minus, ), ], @4-5 Num( "5", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast b/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast index 18c0501338..b12e1f61ec 100644 --- a/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast @@ -7,14 +7,18 @@ SpaceBefore( ), @117-118 Num( "5", - None, + None { + width_variable: (), + }, ), ), ], @120-122 SpaceBefore( Num( "42", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast b/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast index 7c2f20e1d3..205d39bb75 100644 --- a/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast +++ b/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast @@ -14,7 +14,9 @@ ), @15-17 Num( "64", - None, + None { + width_variable: (), + }, ), ), ], diff --git a/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast index 7418143478..b2632555fa 100644 --- a/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast @@ -18,7 +18,9 @@ Defs( @12-14 SpaceBefore( Num( "42", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast index f11b5d9826..886f4e3805 100644 --- a/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast @@ -20,7 +20,9 @@ Defs( @21-23 SpaceBefore( Num( "42", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast index 560733e1a7..58ed26fe20 100644 --- a/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast @@ -3,20 +3,26 @@ BinOps( ( @0-2 Num( "31", - None, + None { + width_variable: (), + }, ), @2-3 Star, ), ( @3-5 Num( "42", - None, + None { + width_variable: (), + }, ), @5-6 Plus, ), ], @6-9 Num( "534", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/negative_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/negative_float.expr.result-ast index 51b0d5e4e7..3ab10fc141 100644 --- a/compiler/parse/tests/snapshots/pass/negative_float.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/negative_float.expr.result-ast @@ -1,4 +1,6 @@ Float( "-42.9", - None, + None { + width_variable: (), + }, ) diff --git a/compiler/parse/tests/snapshots/pass/negative_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/negative_int.expr.result-ast index e4c4e79522..a3d4d12fa6 100644 --- a/compiler/parse/tests/snapshots/pass/negative_int.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/negative_int.expr.result-ast @@ -1,4 +1,6 @@ Num( "-42", - None, + None { + width_variable: (), + }, ) diff --git a/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast b/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast index e6aadc756d..af2521c8ec 100644 --- a/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast +++ b/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast @@ -72,11 +72,15 @@ [ @112-113 Num( "2", - None, + None { + width_variable: (), + }, ), @114-115 Num( "3", - None, + None { + width_variable: (), + }, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast b/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast index af9381e997..0a15c30247 100644 --- a/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast @@ -9,7 +9,9 @@ If( SpaceAfter( Num( "1", - None, + None { + width_variable: (), + }, ), [ Newline, @@ -29,7 +31,9 @@ If( SpaceAfter( Num( "2", - None, + None { + width_variable: (), + }, ), [ Newline, @@ -44,7 +48,9 @@ If( @42-43 SpaceBefore( Num( "3", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast index e1e32e8dfc..e8c57c4405 100644 --- a/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast @@ -7,7 +7,9 @@ Defs( @8-9 SpaceBefore( Num( "5", - None, + None { + width_variable: (), + }, ), [ Newline, @@ -18,7 +20,9 @@ Defs( @11-13 SpaceBefore( Num( "42", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast index ecd9cc89fa..1750d787a1 100644 --- a/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast @@ -3,7 +3,9 @@ BinOps( ( @0-1 Num( "3", - None, + None { + width_variable: (), + }, ), @3-4 Star, ), @@ -11,7 +13,9 @@ BinOps( @7-8 SpaceBefore( Num( "4", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast index 93a50fbd83..0d7b951f92 100644 --- a/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast @@ -3,7 +3,9 @@ BinOps( ( @0-1 Num( "3", - None, + None { + width_variable: (), + }, ), @3-4 Minus, ), @@ -11,7 +13,9 @@ BinOps( @7-8 SpaceBefore( Num( "4", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast index 67a12bd88e..ae9a0ad8d8 100644 --- a/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast @@ -10,7 +10,9 @@ Defs( @4-5 SpaceAfter( Num( "1", - None, + None { + width_variable: (), + }, ), [ Newline, @@ -21,7 +23,9 @@ Defs( ], @12-13 Num( "2", - None, + None { + width_variable: (), + }, ), ), ), @@ -29,7 +33,9 @@ Defs( @15-17 SpaceBefore( Num( "42", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast index 39ee4c273d..9e54f093a5 100644 --- a/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast @@ -4,7 +4,9 @@ BinOps( @0-1 SpaceAfter( Num( "3", - None, + None { + width_variable: (), + }, ), [ Newline, @@ -15,6 +17,8 @@ BinOps( ], @4-5 Num( "4", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast index 116ad5c60b..8fb37f8909 100644 --- a/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast @@ -4,7 +4,9 @@ BinOps( @0-1 SpaceAfter( Num( "3", - None, + None { + width_variable: (), + }, ), [ Newline, @@ -15,6 +17,8 @@ BinOps( ], @4-5 Num( "4", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast index ae84388347..f3531ef2a9 100644 --- a/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast @@ -4,7 +4,9 @@ List( SpaceAfter( Num( "1", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast b/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast index 0c70ce8605..d06272a518 100644 --- a/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast @@ -7,14 +7,18 @@ SpaceBefore( ), @50-51 Num( "5", - None, + None { + width_variable: (), + }, ), ), ], @53-55 SpaceBefore( Num( "42", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.result-ast b/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.result-ast index 60c29d3d23..b4c930d4a0 100644 --- a/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.result-ast @@ -5,7 +5,7 @@ Record( RequiredValue( @4-6 "u8", [], - @10-15 Num( + @10-15 Int( "123", Exact( U8, @@ -20,7 +20,7 @@ Record( RequiredValue( @19-22 "u16", [], - @25-31 Num( + @25-31 Int( "123", Exact( U16, @@ -35,7 +35,7 @@ Record( RequiredValue( @35-38 "u32", [], - @41-47 Num( + @41-47 Int( "123", Exact( U32, @@ -50,7 +50,7 @@ Record( RequiredValue( @51-54 "u64", [], - @57-63 Num( + @57-63 Int( "123", Exact( U64, @@ -65,7 +65,7 @@ Record( RequiredValue( @67-71 "u128", [], - @73-80 Num( + @73-80 Int( "123", Exact( U128, @@ -80,7 +80,7 @@ Record( RequiredValue( @84-86 "i8", [], - @90-95 Num( + @90-95 Int( "123", Exact( I8, @@ -95,7 +95,7 @@ Record( RequiredValue( @99-102 "i16", [], - @105-111 Num( + @105-111 Int( "123", Exact( I16, @@ -110,7 +110,7 @@ Record( RequiredValue( @115-118 "i32", [], - @121-127 Num( + @121-127 Int( "123", Exact( I32, @@ -125,7 +125,7 @@ Record( RequiredValue( @131-134 "i64", [], - @137-143 Num( + @137-143 Int( "123", Exact( I64, @@ -140,7 +140,7 @@ Record( RequiredValue( @147-151 "i128", [], - @153-160 Num( + @153-160 Int( "123", Exact( I128, @@ -155,7 +155,7 @@ Record( RequiredValue( @164-167 "nat", [], - @170-176 Num( + @170-176 Int( "123", Exact( Nat, @@ -170,7 +170,7 @@ Record( RequiredValue( @180-183 "dec", [], - @186-192 Num( + @186-192 Float( "123", Exact( Dec, @@ -185,11 +185,14 @@ Record( RequiredValue( @196-201 "u8Neg", [], - @205-211 Num( - "-123", - Exact( - U8, + @205-211 UnaryOp( + @206-211 Int( + "123", + Exact( + U8, + ), ), + @205-206 Negate, ), ), [ @@ -200,11 +203,14 @@ Record( RequiredValue( @215-221 "u16Neg", [], - @224-231 Num( - "-123", - Exact( - U16, + @224-231 UnaryOp( + @225-231 Int( + "123", + Exact( + U16, + ), ), + @224-225 Negate, ), ), [ @@ -215,11 +221,14 @@ Record( RequiredValue( @235-241 "u32Neg", [], - @244-251 Num( - "-123", - Exact( - U32, + @244-251 UnaryOp( + @245-251 Int( + "123", + Exact( + U32, + ), ), + @244-245 Negate, ), ), [ @@ -230,11 +239,14 @@ Record( RequiredValue( @255-261 "u64Neg", [], - @264-271 Num( - "-123", - Exact( - U64, + @264-271 UnaryOp( + @265-271 Int( + "123", + Exact( + U64, + ), ), + @264-265 Negate, ), ), [ @@ -245,11 +257,14 @@ Record( RequiredValue( @275-282 "u128Neg", [], - @284-292 Num( - "-123", - Exact( - U128, + @284-292 UnaryOp( + @285-292 Int( + "123", + Exact( + U128, + ), ), + @284-285 Negate, ), ), [ @@ -260,11 +275,14 @@ Record( RequiredValue( @296-301 "i8Neg", [], - @305-311 Num( - "-123", - Exact( - I8, + @305-311 UnaryOp( + @306-311 Int( + "123", + Exact( + I8, + ), ), + @305-306 Negate, ), ), [ @@ -275,11 +293,14 @@ Record( RequiredValue( @315-321 "i16Neg", [], - @324-331 Num( - "-123", - Exact( - I16, + @324-331 UnaryOp( + @325-331 Int( + "123", + Exact( + I16, + ), ), + @324-325 Negate, ), ), [ @@ -290,11 +311,14 @@ Record( RequiredValue( @335-341 "i32Neg", [], - @344-351 Num( - "-123", - Exact( - I32, + @344-351 UnaryOp( + @345-351 Int( + "123", + Exact( + I32, + ), ), + @344-345 Negate, ), ), [ @@ -305,11 +329,14 @@ Record( RequiredValue( @355-361 "i64Neg", [], - @364-371 Num( - "-123", - Exact( - I64, + @364-371 UnaryOp( + @365-371 Int( + "123", + Exact( + I64, + ), ), + @364-365 Negate, ), ), [ @@ -320,11 +347,14 @@ Record( RequiredValue( @375-382 "i128Neg", [], - @384-392 Num( - "-123", - Exact( - I128, + @384-392 UnaryOp( + @385-392 Int( + "123", + Exact( + I128, + ), ), + @384-385 Negate, ), ), [ @@ -335,11 +365,14 @@ Record( RequiredValue( @396-402 "natNeg", [], - @405-412 Num( - "-123", - Exact( - Nat, + @405-412 UnaryOp( + @406-412 Int( + "123", + Exact( + Nat, + ), ), + @405-406 Negate, ), ), [ @@ -350,7 +383,7 @@ Record( RequiredValue( @416-422 "decNeg", [], - @425-432 Num( + @425-432 Float( "-123", Exact( Dec, @@ -548,23 +581,6 @@ Record( Newline, ], ), - @667-684 SpaceBefore( - RequiredValue( - @667-673 "decBin", - [], - @676-684 NonBase10Int { - string: "101", - base: Binary, - is_negative: false, - bound: Exact( - Dec, - ), - }, - ), - [ - Newline, - ], - ), ], final_comments: [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.roc b/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.roc index 9bc838314b..e76387bf3c 100644 --- a/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.roc +++ b/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.roc @@ -34,5 +34,4 @@ i64Bin: 0b101i64, i128Bin: 0b101i128, natBin: 0b101nat, - decBin: 0b101dec, } diff --git a/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast index 1dbe91e77e..52eaa9c501 100644 --- a/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast @@ -7,14 +7,18 @@ SpaceBefore( ), @20-21 Num( "5", - None, + None { + width_variable: (), + }, ), ), ], @23-25 SpaceBefore( Num( "42", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast index 4484255d71..34053464c9 100644 --- a/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast @@ -3,13 +3,17 @@ BinOps( ( @0-1 Num( "1", - None, + None { + width_variable: (), + }, ), @1-2 Minus, ), ], @2-3 Num( "2", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast index 644ae129f6..bcd254914a 100644 --- a/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast @@ -3,13 +3,17 @@ BinOps( ( @0-1 Num( "1", - None, + None { + width_variable: (), + }, ), @1-2 Plus, ), ], @2-3 Num( "2", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast index 866cd021a5..5fad56c4fb 100644 --- a/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast @@ -7,14 +7,18 @@ SpaceBefore( ), @22-23 Num( "5", - None, + None { + width_variable: (), + }, ), ), ], @25-27 SpaceBefore( Num( "42", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast index bb1f3f7e51..224073d034 100644 --- a/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast @@ -4,7 +4,9 @@ BinOps( @0-1 SpaceAfter( Num( "3", - None, + None { + width_variable: (), + }, ), [ Newline, @@ -16,7 +18,9 @@ BinOps( @7-8 SpaceBefore( Num( "4", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast index 142bbc8ec2..9a688604a1 100644 --- a/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast @@ -2,7 +2,9 @@ List( [ @1-2 Num( "1", - None, + None { + width_variable: (), + }, ), ], ) diff --git a/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast b/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast index a1e681e48d..b7e49e27aa 100644 --- a/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast @@ -8,7 +8,9 @@ Apply( [ @7-8 Num( "1", - None, + None { + width_variable: (), + }, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast b/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast index 450ae33a78..46314703d7 100644 --- a/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast @@ -29,7 +29,9 @@ Defs( @28-30 SpaceBefore( Num( "42", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast b/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast index fa4fa90923..319d317a93 100644 --- a/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast @@ -35,7 +35,9 @@ Defs( @35-37 SpaceBefore( Num( "42", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast index 88cce2b495..766d0e8b93 100644 --- a/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast @@ -20,7 +20,9 @@ When( ), @21-22 Num( "0", - None, + None { + width_variable: (), + }, ), ], Space, @@ -64,7 +66,9 @@ When( ), @63-64 Num( "0", - None, + None { + width_variable: (), + }, ), @65-70 GlobalTag( "False", diff --git a/compiler/parse/tests/snapshots/pass/positive_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/positive_float.expr.result-ast index 364fe6092b..52e82b2d9c 100644 --- a/compiler/parse/tests/snapshots/pass/positive_float.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/positive_float.expr.result-ast @@ -1,4 +1,6 @@ Float( "42.9", - None, + None { + width_variable: (), + }, ) diff --git a/compiler/parse/tests/snapshots/pass/positive_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/positive_int.expr.result-ast index 228f2ec6a9..0768f4614f 100644 --- a/compiler/parse/tests/snapshots/pass/positive_int.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/positive_int.expr.result-ast @@ -1,4 +1,6 @@ Num( "42", - None, + None { + width_variable: (), + }, ) diff --git a/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast index dfd198000b..12639adfbd 100644 --- a/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast @@ -14,7 +14,9 @@ SpaceBefore( ), @29-30 Num( "5", - None, + None { + width_variable: (), + }, ), ), @31-36 SpaceBefore( @@ -24,7 +26,9 @@ SpaceBefore( ), @35-36 Num( "6", - None, + None { + width_variable: (), + }, ), ), [ @@ -35,7 +39,9 @@ SpaceBefore( @38-40 SpaceBefore( Num( "42", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast index 351ab5e4ec..537ef938ff 100644 --- a/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast @@ -103,7 +103,9 @@ Defs( @124-126 SpaceBefore( Num( "42", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast index a05a639f1a..9eef119aa3 100644 --- a/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast @@ -9,7 +9,9 @@ RecordUpdate { [], @19-20 Num( "5", - None, + None { + width_variable: (), + }, ), ), @22-26 RequiredValue( @@ -17,7 +19,9 @@ RecordUpdate { [], @25-26 Num( "0", - None, + None { + width_variable: (), + }, ), ), ], diff --git a/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast index aad598b259..1f86fb19fe 100644 --- a/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast @@ -11,13 +11,17 @@ Record( ), @18-19 Num( "1", - None, + None { + width_variable: (), + }, ), ), ], @25-26 Num( "2", - None, + None { + width_variable: (), + }, ), ), ), @@ -26,7 +30,9 @@ Record( [], @31-32 Num( "3", - None, + None { + width_variable: (), + }, ), ), ], diff --git a/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast index 66c27b88b0..c04f801fd2 100644 --- a/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast @@ -6,6 +6,8 @@ Closure( ], @6-8 Num( "42", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast index 90c3a81dac..0e172bc62e 100644 --- a/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast @@ -6,6 +6,8 @@ Closure( ], @6-8 Num( "42", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast index a437d0a55a..40503abf8a 100644 --- a/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast @@ -2,7 +2,9 @@ List( [ @2-3 Num( "1", - None, + None { + width_variable: (), + }, ), ], ) diff --git a/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast b/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast index d545c1e5df..e538188db8 100644 --- a/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast +++ b/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast @@ -6,7 +6,9 @@ ), @18-19 Num( "1", - None, + None { + width_variable: (), + }, ), ), [ diff --git a/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast index e12971a1e5..d86ff0acd9 100644 --- a/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast @@ -10,6 +10,8 @@ BinOps( ], @4-5 Num( "2", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast index 2179daf40b..5e69e1c81b 100644 --- a/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast @@ -3,13 +3,17 @@ BinOps( ( @0-1 Num( "1", - None, + None { + width_variable: (), + }, ), @3-4 Minus, ), ], @7-8 Num( "2", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast b/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast index 0d15dfdac0..6d04ca0ef9 100644 --- a/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast @@ -6,6 +6,8 @@ Closure( ], @10-12 Num( "42", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast b/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast index 7ab54b96e1..16aa825ad8 100644 --- a/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast @@ -3,13 +3,17 @@ BinOps( ( @0-2 Num( "10", - None, + None { + width_variable: (), + }, ), @2-3 Star, ), ], @3-5 Num( "11", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast index 6f238c731d..1fcee5345c 100644 --- a/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast @@ -12,6 +12,8 @@ Closure( ], @12-14 Num( "42", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast index 908239feeb..1d6af8d3fb 100644 --- a/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast @@ -9,6 +9,8 @@ Closure( ], @9-11 Num( "42", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast index 0c7a32957d..f6812214e6 100644 --- a/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast @@ -19,7 +19,9 @@ When( ], value: @17-18 Num( "1", - None, + None { + width_variable: (), + }, ), guard: None, }, @@ -38,7 +40,9 @@ When( ], value: @30-31 Num( "2", - None, + None { + width_variable: (), + }, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast index 34047512e4..2cdab1165d 100644 --- a/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast @@ -7,7 +7,9 @@ SpaceBefore( ), @22-23 Num( "5", - None, + None { + width_variable: (), + }, ), ), @24-29 SpaceBefore( @@ -17,7 +19,9 @@ SpaceBefore( ), @28-29 Num( "6", - None, + None { + width_variable: (), + }, ), ), [ @@ -28,7 +32,9 @@ SpaceBefore( @31-33 SpaceBefore( Num( "42", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast b/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast index 26f71e1d6e..e1081f2e75 100644 --- a/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast @@ -30,7 +30,9 @@ Defs( @31-33 SpaceBefore( Num( "42", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast index 81cd356a99..e4654ad067 100644 --- a/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast @@ -6,7 +6,9 @@ Apply( [ @6-8 Num( "12", - None, + None { + width_variable: (), + }, ), @9-13 UnaryOp( @10-13 Var { diff --git a/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast index 43ba59029f..d5ed1524ef 100644 --- a/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast @@ -8,7 +8,9 @@ UnaryOp( [ @8-10 Num( "12", - None, + None { + width_variable: (), + }, ), @11-14 Var { module_name: "", diff --git a/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast index 11f0f6dbcb..a84f789127 100644 --- a/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast @@ -8,7 +8,9 @@ UnaryOp( [ @8-10 Num( "12", - None, + None { + width_variable: (), + }, ), @11-14 Var { module_name: "", diff --git a/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast b/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast index c92bf33640..59058d3b61 100644 --- a/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast @@ -21,7 +21,9 @@ SpaceBefore( @34-35 SpaceBefore( Num( "4", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast b/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast index 446cee92e7..5ad87ac1e0 100644 --- a/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast @@ -10,6 +10,8 @@ BinOps( ], @2-3 Num( "2", - None, + None { + width_variable: (), + }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast index 1794c04a01..44c0b33509 100644 --- a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast @@ -18,7 +18,9 @@ When( value: @27-28 SpaceBefore( Num( "1", - None, + None { + width_variable: (), + }, ), [ Newline, @@ -41,7 +43,9 @@ When( value: @47-48 SpaceBefore( Num( "2", - None, + None { + width_variable: (), + }, ), [ Newline, @@ -64,7 +68,9 @@ When( value: @68-69 SpaceBefore( Num( "3", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast index b97dbf4f5f..b2e54cb8d0 100644 --- a/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast @@ -19,7 +19,9 @@ ParensAround( value: @29-30 SpaceBefore( Num( "3", - None, + None { + width_variable: (), + }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast index d35b055fbd..8123439254 100644 --- a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast @@ -19,7 +19,9 @@ ParensAround( ], value: @21-22 Num( "3", - None, + None { + width_variable: (), + }, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast index 0a103eb427..f3f0ae04bf 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast @@ -24,7 +24,9 @@ When( ], value: @30-31 Num( "1", - None, + None { + width_variable: (), + }, ), guard: None, }, @@ -53,7 +55,9 @@ When( ], value: @52-53 Num( "2", - None, + None { + width_variable: (), + }, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast index 0a69a10f87..1e076d0e84 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast @@ -9,7 +9,9 @@ When( @14-15 SpaceBefore( NumLiteral( "1", - None, + None { + width_variable: (), + }, ), [ Newline, @@ -25,7 +27,9 @@ When( @32-33 SpaceBefore( Num( "2", - None, + None { + width_variable: (), + }, ), [ Newline, @@ -49,7 +53,9 @@ When( ], value: @43-44 Num( "4", - None, + None { + width_variable: (), + }, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast index ecdf37e82a..311eccafbd 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast @@ -9,7 +9,9 @@ When( @11-12 SpaceBefore( NumLiteral( "1", - None, + None { + width_variable: (), + }, ), [ Newline, @@ -18,7 +20,9 @@ When( ], value: @16-17 Num( "2", - None, + None { + width_variable: (), + }, ), guard: None, }, @@ -27,7 +31,9 @@ When( @19-21 SpaceBefore( NumLiteral( "-3", - None, + None { + width_variable: (), + }, ), [ Newline, @@ -36,7 +42,9 @@ When( ], value: @25-26 Num( "4", - None, + None { + width_variable: (), + }, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast index 19bc713c53..ed291e47fc 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast @@ -9,7 +9,9 @@ When( @11-12 SpaceBefore( NumLiteral( "1", - None, + None { + width_variable: (), + }, ), [ Newline, @@ -18,7 +20,9 @@ When( ], value: @16-17 Num( "2", - None, + None { + width_variable: (), + }, ), guard: None, }, @@ -27,7 +31,9 @@ When( @19-20 SpaceBefore( NumLiteral( "3", - None, + None { + width_variable: (), + }, ), [ Newline, @@ -36,7 +42,9 @@ When( ], value: @24-25 Num( "4", - None, + None { + width_variable: (), + }, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast index fda2147f10..6135ae7e36 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast @@ -21,7 +21,9 @@ When( ], value: @20-21 Num( "2", - None, + None { + width_variable: (), + }, ), guard: None, }, @@ -45,7 +47,9 @@ When( ], value: @35-36 Num( "4", - None, + None { + width_variable: (), + }, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/zero_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/zero_float.expr.result-ast index 1278731f67..22b1820f75 100644 --- a/compiler/parse/tests/snapshots/pass/zero_float.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/zero_float.expr.result-ast @@ -1,4 +1,6 @@ Float( "0.0", - None, + None { + width_variable: (), + }, ) diff --git a/compiler/parse/tests/snapshots/pass/zero_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/zero_int.expr.result-ast index 08ddfc51a8..385709ac81 100644 --- a/compiler/parse/tests/snapshots/pass/zero_int.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/zero_int.expr.result-ast @@ -1,4 +1,6 @@ Num( "0", - None, + None { + width_variable: (), + }, ) diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 13cee9d372..eb10375ab9 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -498,7 +498,10 @@ mod test_parse { fn all_i64_values_parse(num: i64) { assert_parses_to( num.to_string().as_str(), - Num(num.to_string().as_str(), NumericBound::None), + Num( + num.to_string().as_str(), + NumericBound::None { width_variable: () }, + ), ); } @@ -506,13 +509,19 @@ mod test_parse { fn all_f64_values_parse(num: f64) { let string = num.to_string(); if string.contains('.') { - assert_parses_to(&string, Float(&string, NumericBound::None)); + assert_parses_to( + &string, + Float(&string, NumericBound::None { width_variable: () }), + ); } else if num.is_nan() { assert_parses_to(&string, Expr::GlobalTag(&string)); } else if num.is_finite() { // These are whole numbers. Add the `.0` back to make float. let float_string = format!("{}.0", string); - assert_parses_to(&float_string, Float(&float_string, NumericBound::None)); + assert_parses_to( + &float_string, + Float(&float_string, NumericBound::None { width_variable: () }), + ); } } From 9f72b2710f129083193cb8be1effec0964305516 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 31 Jan 2022 18:18:14 -0500 Subject: [PATCH 427/541] Run linter --- ast/src/lang/core/pattern.rs | 11 +++++++++++ cli/src/format.rs | 2 ++ compiler/constrain/src/builtins.rs | 8 ++++---- compiler/fmt/src/expr.rs | 23 ++++++++++++++++------- compiler/fmt/src/pattern.rs | 22 +++++++++++++++------- compiler/parse/src/expr.rs | 8 ++++---- compiler/parse/src/number_literal.rs | 7 ++++--- compiler/types/src/pretty_print.rs | 10 +++++----- repl_eval/src/eval.rs | 7 +++++-- 9 files changed, 66 insertions(+), 32 deletions(-) diff --git a/ast/src/lang/core/pattern.rs b/ast/src/lang/core/pattern.rs index ee0c8ca72a..57844b901c 100644 --- a/ast/src/lang/core/pattern.rs +++ b/ast/src/lang/core/pattern.rs @@ -188,6 +188,17 @@ pub fn to_pattern2<'a>( ptype => unsupported_pattern(env, ptype, region), }, + IntLiteral(string, _bound) => match pattern_type { + WhenBranch => match finish_parsing_int(string) { + Err(_error) => { + let problem = MalformedPatternProblem::MalformedInt; + malformed_pattern(env, problem, region) + } + Ok(int) => Pattern2::NumLiteral(env.var_store.fresh(), int), + }, + ptype => unsupported_pattern(env, ptype, region), + }, + NumLiteral(string, _bound) => match pattern_type { WhenBranch => match finish_parsing_int(string) { Err(_error) => { diff --git a/cli/src/format.rs b/cli/src/format.rs index 9c62b228a1..1b61cab1f2 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -486,6 +486,7 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> { match *self { Expr::Float(a, bound) => Expr::Float(a, bound), Expr::Num(a, bound) => Expr::Num(a, bound), + Expr::Int(a, bound) => Expr::Int(a, bound), Expr::NonBase10Int { string, base, @@ -584,6 +585,7 @@ impl<'a> RemoveSpaces<'a> for Pattern<'a> { bound, }, Pattern::FloatLiteral(a, bound) => Pattern::FloatLiteral(a, bound), + Pattern::IntLiteral(a, bound) => Pattern::IntLiteral(a, bound), Pattern::StrLiteral(a) => Pattern::StrLiteral(a), Pattern::Underscore(a) => Pattern::Underscore(a), Pattern::Malformed(a) => Pattern::Malformed(a), diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 20b368139c..df1cbdc268 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -338,10 +338,10 @@ impl TypedNumericBound for NumericBound { impl TypedNumericBound for NumericBound { fn num_type(&self) -> Type { - match self { - &NumericBound::None { width_variable } => num_num(Type::Variable(width_variable)), - &NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(iw).num_type(), - &NumericBound::Exact(NumWidth::Float(fw)) => NumericBound::Exact(fw).num_type(), + match *self { + NumericBound::None { width_variable } => num_num(Type::Variable(width_variable)), + NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(iw).num_type(), + NumericBound::Exact(NumWidth::Float(fw)) => NumericBound::Exact(fw).num_type(), } } diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index 3a747a23c1..1f557bef48 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -30,6 +30,7 @@ impl<'a> Formattable for Expr<'a> { // These expressions never have newlines Float(..) | Num(..) + | Int(..) | NonBase10Int { .. } | Access(_, _) | AccessorFunction(_) @@ -197,19 +198,27 @@ impl<'a> Formattable for Expr<'a> { buf.push(')'); } } - Num(string, bound) => { + &Num(string, bound) => { buf.indent(indent); buf.push_str(string); - if let &NumericBound::Exact(width) = bound { + if let NumericBound::Exact(width) = bound { buf.push_str(&width.to_string()); } } - Float(string, bound) => { + &Float(string, bound) => { buf.indent(indent); buf.push_str(string); - if let &NumericBound::Exact(width) = bound { + if let NumericBound::Exact(width) = bound { + buf.push_str(&width.to_string()); + } + } + &Int(string, bound) => { + buf.indent(indent); + buf.push_str(string); + + if let NumericBound::Exact(width) = bound { buf.push_str(&width.to_string()); } } @@ -217,14 +226,14 @@ impl<'a> Formattable for Expr<'a> { buf.indent(indent); buf.push_str(string) } - NonBase10Int { + &NonBase10Int { base, string, is_negative, bound, } => { buf.indent(indent); - if *is_negative { + if is_negative { buf.push('-'); } @@ -237,7 +246,7 @@ impl<'a> Formattable for Expr<'a> { buf.push_str(string); - if let &NumericBound::Exact(width) = bound { + if let NumericBound::Exact(width) = bound { buf.push_str(&width.to_string()); } } diff --git a/compiler/fmt/src/pattern.rs b/compiler/fmt/src/pattern.rs index 92fc7ba9da..04b97ea13d 100644 --- a/compiler/fmt/src/pattern.rs +++ b/compiler/fmt/src/pattern.rs @@ -32,6 +32,7 @@ impl<'a> Formattable for Pattern<'a> { | Pattern::PrivateTag(_) | Pattern::Apply(_, _) | Pattern::NumLiteral(..) + | Pattern::IntLiteral(..) | Pattern::NonBase10Literal { .. } | Pattern::FloatLiteral(..) | Pattern::StrLiteral(_) @@ -116,21 +117,28 @@ impl<'a> Formattable for Pattern<'a> { loc_pattern.format(buf, indent); } - NumLiteral(string, bound) => { + &NumLiteral(string, bound) => { buf.indent(indent); buf.push_str(string); - if let &NumericBound::Exact(width) = bound { + if let NumericBound::Exact(width) = bound { buf.push_str(&width.to_string()); } } - NonBase10Literal { + &IntLiteral(string, bound) => { + buf.indent(indent); + buf.push_str(string); + if let NumericBound::Exact(width) = bound { + buf.push_str(&width.to_string()); + } + } + &NonBase10Literal { base, string, is_negative, bound, } => { buf.indent(indent); - if *is_negative { + if is_negative { buf.push('-'); } @@ -143,14 +151,14 @@ impl<'a> Formattable for Pattern<'a> { buf.push_str(string); - if let &NumericBound::Exact(width) = bound { + if let NumericBound::Exact(width) = bound { buf.push_str(&width.to_string()); } } - FloatLiteral(string, bound) => { + &FloatLiteral(string, bound) => { buf.indent(indent); buf.push_str(string); - if let &NumericBound::Exact(width) = bound { + if let NumericBound::Exact(width) = bound { buf.push_str(&width.to_string()); } } diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 7ef2ee4a6c..6cdbed3fb6 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -515,20 +515,20 @@ fn numeric_negate_expression<'a, T>( let start = state.pos(); let region = Region::new(start, expr.region.end()); - let new_expr = match &expr.value { - &Expr::Num(string, bound) => { + let new_expr = match expr.value { + Expr::Num(string, bound) => { let new_string = unsafe { std::str::from_utf8_unchecked(&state.bytes()[..string.len() + 1]) }; Expr::Num(new_string, bound) } - &Expr::Float(string, bound) => { + Expr::Float(string, bound) => { let new_string = unsafe { std::str::from_utf8_unchecked(&state.bytes()[..string.len() + 1]) }; Expr::Float(new_string, bound) } - &Expr::NonBase10Int { + Expr::NonBase10Int { string, base, is_negative, diff --git a/compiler/parse/src/number_literal.rs b/compiler/parse/src/number_literal.rs index 47c9ae9410..8319cb6f76 100644 --- a/compiler/parse/src/number_literal.rs +++ b/compiler/parse/src/number_literal.rs @@ -79,7 +79,7 @@ macro_rules! parse_num_suffix { } } -fn get_int_suffix<'a>(bytes: &'a [u8]) -> Option<(IntWidth, usize)> { +fn get_int_suffix(bytes: &[u8]) -> Option<(IntWidth, usize)> { parse_num_suffix! { bytes, b"u8", IntWidth::U8 @@ -97,7 +97,7 @@ fn get_int_suffix<'a>(bytes: &'a [u8]) -> Option<(IntWidth, usize)> { None } -fn get_float_suffix<'a>(bytes: &'a [u8]) -> Option<(FloatWidth, usize)> { +fn get_float_suffix(bytes: &[u8]) -> Option<(FloatWidth, usize)> { parse_num_suffix! { bytes, b"dec", FloatWidth::Dec @@ -107,7 +107,7 @@ fn get_float_suffix<'a>(bytes: &'a [u8]) -> Option<(FloatWidth, usize)> { None } -fn get_num_suffix<'a>(bytes: &'a [u8]) -> Option<(NumWidth, usize)> { +fn get_num_suffix(bytes: &[u8]) -> Option<(NumWidth, usize)> { (get_int_suffix(bytes).map(|(iw, l)| (NumWidth::Int(iw), l))) .or_else(|| get_float_suffix(bytes).map(|(fw, l)| (NumWidth::Float(fw), l))) } @@ -205,6 +205,7 @@ fn chomp_number_dec<'a>( } } +#[allow(clippy::type_complexity)] fn chomp_number<'a>( mut bytes: &'a [u8], state: State<'a>, diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index a40bfbe410..186680debc 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -318,8 +318,8 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa match *symbol { Symbol::NUM_NUM => { let content = get_single_arg(subs, args); - match content { - &Alias(nested, args, _actual) => match nested { + match *content { + Alias(nested, args, _actual) => match nested { Symbol::NUM_INTEGER => { write_integer( env, @@ -361,9 +361,9 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa let arg_var = subs[arg_var_index]; let content = subs.get_content_without_compacting(arg_var); - match content { - &Alias(Symbol::NUM_BINARY32, _, _) => buf.push_str("F32"), - &Alias(Symbol::NUM_BINARY64, _, _) => buf.push_str("F64"), + match *content { + Alias(Symbol::NUM_BINARY32, _, _) => buf.push_str("F32"), + Alias(Symbol::NUM_BINARY64, _, _) => buf.push_str("F64"), _ => write_parens!(write_parens, buf, { buf.push_str("Float "); write_content(env, content, subs, buf, parens); diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index 7c0c39ee2e..0d66f56efa 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -1043,7 +1043,7 @@ fn byte_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: u8, content: &Cont if let TagName::Private(Symbol::NUM_AT_NUM) = &tag_name { return Expr::Num( env.arena.alloc_str(&value.to_string()), - NumericBound::None, + NumericBound::None { width_variable: () }, ); } @@ -1198,7 +1198,10 @@ fn num_to_ast<'a, M: AppMemory>( /// This is centralized in case we want to format it differently later, /// e.g. adding underscores for large numbers fn number_literal_to_ast(arena: &Bump, num: T) -> Expr<'_> { - Expr::Num(arena.alloc(format!("{}", num)), NumericBound::None) + Expr::Num( + arena.alloc(format!("{}", num)), + NumericBound::None { width_variable: () }, + ) } #[cfg(target_endian = "little")] From f55e76703582f444f9a304d2a09b7c61605d359a Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Mon, 31 Jan 2022 22:45:26 -0500 Subject: [PATCH 428/541] Fix test_can tests --- compiler/can/tests/test_can.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index 196be508c0..e526334175 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -32,7 +32,7 @@ mod test_can { let actual_out = can_expr_with(&arena, test_home(), input); match actual_out.loc_expr.value { - Expr::Float(_, _, _, actual) => { + Expr::Float(_, _, _, actual, _) => { assert_eq!(expected, actual); } actual => { @@ -46,7 +46,7 @@ mod test_can { let actual_out = can_expr_with(&arena, test_home(), input); match actual_out.loc_expr.value { - Expr::Int(_, _, _, actual) => { + Expr::Int(_, _, _, actual, _) => { assert_eq!(expected, actual); } actual => { @@ -60,7 +60,7 @@ mod test_can { let actual_out = can_expr_with(&arena, test_home(), input); match actual_out.loc_expr.value { - Expr::Num(_, _, actual) => { + Expr::Num(_, _, actual, _) => { assert_eq!(expected, actual); } actual => { From 17c5fe0bff96fa061584bd0c54b24f6818307c86 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Tue, 1 Feb 2022 00:18:42 -0500 Subject: [PATCH 429/541] Add granular errors regarding numeric literal parsing --- compiler/parse/src/number_literal.rs | 22 ++++- compiler/parse/src/parser.rs | 3 + reporting/src/error/parse.rs | 69 ++++++++++++++++ reporting/tests/test_reporting.rs | 119 +++++++++++++++++++++++---- 4 files changed, 197 insertions(+), 16 deletions(-) diff --git a/compiler/parse/src/number_literal.rs b/compiler/parse/src/number_literal.rs index 8319cb6f76..c03266664f 100644 --- a/compiler/parse/src/number_literal.rs +++ b/compiler/parse/src/number_literal.rs @@ -152,7 +152,9 @@ fn chomp_number_base<'a>( }, new, )), - Some(NumWidth::Float(_)) => Err((Progress::MadeProgress, ENumber::End, state)), + Some(NumWidth::Float(_)) => { + Err((Progress::MadeProgress, ENumber::IntHasFloatSuffix, state)) + } } } @@ -201,7 +203,9 @@ fn chomp_number_dec<'a>( NumLiteral::Int(string, NumericBound::Exact(iw)), new, )), - (true, Some(NumWidth::Int(_))) => Err((Progress::MadeProgress, ENumber::End, state)), + (true, Some(NumWidth::Int(_))) => { + Err((Progress::MadeProgress, ENumber::FloatHasIntSuffix, state)) + } } } @@ -282,6 +286,20 @@ fn chomp_number<'a>( return Err((Progress::NoProgress, ENumber::End, state)); } + if bytes + .get(0) + .copied() + .unwrap_or_default() + .is_ascii_alphabetic() + { + // The user likely mistyped a literal suffix type here. + return Err(( + Progress::MadeProgress, + ENumber::LiteralSuffix, + state.advance(start_bytes_len - bytes.len()), + )); + } + return Err((Progress::MadeProgress, ENumber::End, state)); } } diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index 6542f83003..cd2a2ea3f0 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -338,6 +338,9 @@ pub enum EExpr<'a> { #[derive(Debug, Clone, PartialEq, Eq)] pub enum ENumber { End, + LiteralSuffix, + IntHasFloatSuffix, + FloatHasIntSuffix, } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index f5f33e25fa..23b01009bc 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -522,6 +522,15 @@ fn to_expr_report<'a>( &EExpr::Number(ENumber::End, pos) => { to_malformed_number_literal_report(alloc, lines, filename, pos) } + &EExpr::Number(ENumber::LiteralSuffix, pos) => { + to_number_literal_with_bad_suffix_report(alloc, lines, filename, pos) + } + &EExpr::Number(ENumber::IntHasFloatSuffix, pos) => { + to_number_literal_mismatch_suffix(alloc, lines, filename, pos, "an integer", "a float") + } + &EExpr::Number(ENumber::FloatHasIntSuffix, pos) => { + to_number_literal_mismatch_suffix(alloc, lines, filename, pos, "a float", "an integer") + } _ => todo!("unhandled parse error: {:?}", parse_problem), } @@ -1561,6 +1570,15 @@ fn to_pattern_report<'a>( &EPattern::NumLiteral(ENumber::End, pos) => { to_malformed_number_literal_report(alloc, lines, filename, pos) } + &EPattern::NumLiteral(ENumber::LiteralSuffix, pos) => { + to_number_literal_with_bad_suffix_report(alloc, lines, filename, pos) + } + &EPattern::NumLiteral(ENumber::IntHasFloatSuffix, pos) => { + to_number_literal_mismatch_suffix(alloc, lines, filename, pos, "an integer", "a float") + } + &EPattern::NumLiteral(ENumber::FloatHasIntSuffix, pos) => { + to_number_literal_mismatch_suffix(alloc, lines, filename, pos, "a float", "an integer") + } _ => todo!("unhandled parse error: {:?}", parse_problem), } } @@ -1976,6 +1994,57 @@ fn to_malformed_number_literal_report<'a>( } } +fn to_number_literal_with_bad_suffix_report<'a>( + alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, + filename: PathBuf, + start: Position, +) -> Report<'a> { + let surroundings = Region::new(start, start); + let region = LineColumnRegion::from_pos(lines.convert_pos(start)); + + let doc = alloc.stack(vec![ + alloc.reflow(r"It looks like you are trying to use type suffix on this number literal, but it's not one that I recognize:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + ]); + + Report { + filename, + doc, + title: "INVALID NUMBER LITERAL SUFFIX".to_string(), + severity: Severity::RuntimeError, + } +} + +fn to_number_literal_mismatch_suffix<'a>( + alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, + filename: PathBuf, + start: Position, + literal_kind: &'static str, + suffix_kind: &'static str, +) -> Report<'a> { + let surroundings = Region::new(start, start); + let region = LineColumnRegion::from_pos(lines.convert_pos(start)); + + let doc = alloc.stack(vec![ + alloc.concat(vec![ + alloc.text(r"This number literal is "), + alloc.text(literal_kind), + alloc.text(", but its suffix says it's "), + alloc.text(suffix_kind), + ]), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + ]); + + Report { + filename, + doc, + title: "NUMBER LITERAL CONFLICTS WITH SUFFIX".to_string(), + severity: Severity::RuntimeError, + } +} + fn to_type_report<'a>( alloc: &'a RocDocAllocator<'a>, lines: &LineInfo, diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index ad5bab8d4c..f980dfd674 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -1730,12 +1730,13 @@ mod test_reporting { ), indoc!( r#" - ── INVALID NUMBER LITERAL ────────────────────────────────────────────────────── + ── INVALID NUMBER LITERAL SUFFIX ─────────────────────────────────────────────── - This number literal is malformed: + It looks like you are trying to use type suffix on this number + literal, but it's not one that I recognize: 2│ 100A -> 3 - ^ + ^ "# ), ) @@ -1753,12 +1754,13 @@ mod test_reporting { ), indoc!( r#" - ── INVALID NUMBER LITERAL ────────────────────────────────────────────────────── + ── INVALID NUMBER LITERAL SUFFIX ─────────────────────────────────────────────── - This number literal is malformed: + It looks like you are trying to use type suffix on this number + literal, but it's not one that I recognize: 2│ 2.X -> 3 - ^ + ^ "# ), ) @@ -1776,9 +1778,10 @@ mod test_reporting { ), indoc!( r#" - ── INVALID NUMBER LITERAL ────────────────────────────────────────────────────── + ── INVALID NUMBER LITERAL SUFFIX ─────────────────────────────────────────────── - This number literal is malformed: + It looks like you are trying to use type suffix on this number + literal, but it's not one that I recognize: 2│ 0xZ -> 3 ^ @@ -3533,12 +3536,13 @@ mod test_reporting { ), indoc!( r#" - ── INVALID NUMBER LITERAL ────────────────────────────────────────────────────── + ── INVALID NUMBER LITERAL SUFFIX ─────────────────────────────────────────────── - This number literal is malformed: + It looks like you are trying to use type suffix on this number + literal, but it's not one that I recognize: 1│ dec = 100A - ^ + ^ "# ), ) @@ -3614,12 +3618,13 @@ mod test_reporting { ), indoc!( r#" - ── INVALID NUMBER LITERAL ────────────────────────────────────────────────────── + ── INVALID NUMBER LITERAL SUFFIX ─────────────────────────────────────────────── - This number literal is malformed: + It looks like you are trying to use type suffix on this number + literal, but it's not one that I recognize: 1│ x = 3.0A - ^ + ^ "# ), ) @@ -7316,4 +7321,90 @@ I need all branches in an `if` to have the same type! 1, "f32", mismatched_suffix_f32 1, "f64", mismatched_suffix_f64 } + + #[test] + fn bad_numeric_literal_suffix() { + report_problem_as( + indoc!( + r#" + 1u256 + "# + ), + indoc!( + r#" + ── INVALID NUMBER LITERAL SUFFIX ─────────────────────────────────────────────── + + It looks like you are trying to use type suffix on this number + literal, but it's not one that I recognize: + + 1│ 1u256 + ^ + "# + ), + ) + } + + #[test] + fn numer_literal_multi_suffix() { + report_problem_as( + indoc!( + r#" + 1u8u8 + "# + ), + indoc!( + r#" + ── INVALID NUMBER LITERAL SUFFIX ─────────────────────────────────────────────── + + It looks like you are trying to use type suffix on this number + literal, but it's not one that I recognize: + + 1│ 1u8u8 + ^ + "# + ), + ) + } + + #[test] + fn int_literal_has_float_suffix() { + report_problem_as( + indoc!( + r#" + 0b1f32 + "# + ), + indoc!( + r#" + ── NUMBER LITERAL CONFLICTS WITH SUFFIX ──────────────────────────────────────── + + This number literal is an integer, but its suffix says it's a float + + 1│ 0b1f32 + ^ + "# + ), + ) + } + + #[test] + fn float_literal_has_int_suffix() { + report_problem_as( + indoc!( + r#" + 1.0u8 + "# + ), + indoc!( + r#" + ── NUMBER LITERAL CONFLICTS WITH SUFFIX ──────────────────────────────────────── + + This number literal is a float, but its suffix says it's an integer + + 1│ 1.0u8 + ^ + "# + ), + ) + } } From a6f7579c07bd797662f23a9358f4159be981d21b Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Tue, 1 Feb 2022 22:48:29 -0500 Subject: [PATCH 430/541] Parse and expand numeric bounds in canonicalization pass --- ast/src/lang/core/expr/expr_to_expr2.rs | 26 +- ast/src/lang/core/pattern.rs | 32 +-- cli/src/format.rs | 14 +- compiler/can/src/builtins.rs | 145 +++------- compiler/can/src/expr.rs | 40 +-- compiler/can/src/num.rs | 145 +++++++--- compiler/can/src/operator.rs | 1 - compiler/can/src/pattern.rs | 64 ++--- compiler/constrain/src/builtins.rs | 50 ++-- compiler/fmt/src/expr.rs | 29 +- compiler/fmt/src/pattern.rs | 25 +- compiler/module/src/numeric.rs | 5 +- compiler/parse/src/ast.rs | 29 +- compiler/parse/src/expr.rs | 34 +-- compiler/parse/src/number_literal.rs | 228 +++------------- compiler/parse/src/parser.rs | 3 - compiler/parse/src/pattern.rs | 7 +- .../pass/add_var_with_spaces.expr.result-ast | 3 - .../pass/add_with_spaces.expr.result-ast | 6 - ...notated_record_destructure.expr.result-ast | 3 - .../annotated_tag_destructure.expr.result-ast | 3 - .../pass/apply_global_tag.expr.result-ast | 6 - ...enthetical_global_tag_args.expr.result-ast | 6 - .../pass/apply_private_tag.expr.result-ast | 6 - .../pass/apply_two_args.expr.result-ast | 6 - .../pass/apply_unary_negation.expr.result-ast | 3 - .../pass/apply_unary_not.expr.result-ast | 3 - .../pass/basic_apply.expr.result-ast | 3 - .../snapshots/pass/basic_docs.expr.result-ast | 6 - .../closure_with_underscores.expr.result-ast | 3 - .../pass/comment_after_op.expr.result-ast | 6 - .../pass/comment_before_op.expr.result-ast | 6 - .../comment_with_non_ascii.expr.result-ast | 6 - .../snapshots/pass/expect.expr.result-ast | 9 - .../float_with_underscores.expr.result-ast | 3 - .../pass/highest_float.expr.result-ast | 3 - .../pass/highest_int.expr.result-ast | 3 - .../snapshots/pass/if_def.expr.result-ast | 6 - .../pass/int_with_underscore.expr.result-ast | 3 - .../pass/lowest_float.expr.result-ast | 3 - .../snapshots/pass/lowest_int.expr.result-ast | 3 - ...ed_ident_due_to_underscore.expr.result-ast | 3 - ...ormed_pattern_field_access.expr.result-ast | 6 - ...formed_pattern_module_name.expr.result-ast | 6 - .../minus_twelve_minus_five.expr.result-ast | 6 - .../snapshots/pass/mixed_docs.expr.result-ast | 6 - .../pass/module_def_newline.module.result-ast | 3 - .../multiline_type_signature.expr.result-ast | 3 - ...ype_signature_with_comment.expr.result-ast | 3 - .../pass/multiple_operators.expr.result-ast | 9 - .../pass/negative_float.expr.result-ast | 3 - .../pass/negative_int.expr.result-ast | 3 - .../nested_def_annotation.module.result-ast | 6 - .../snapshots/pass/nested_if.expr.result-ast | 9 - .../pass/newline_after_equals.expr.result-ast | 6 - .../pass/newline_after_mul.expr.result-ast | 6 - .../pass/newline_after_sub.expr.result-ast | 6 - ...nd_spaces_before_less_than.expr.result-ast | 9 - .../pass/newline_before_add.expr.result-ast | 6 - .../pass/newline_before_sub.expr.result-ast | 6 - .../newline_singleton_list.expr.result-ast | 3 - .../snapshots/pass/not_docs.expr.result-ast | 6 - .../number_literal_suffixes.expr.result-ast | 256 ++++-------------- .../snapshots/pass/one_def.expr.result-ast | 6 - .../pass/one_minus_two.expr.result-ast | 6 - .../pass/one_plus_two.expr.result-ast | 6 - .../pass/one_spaced_def.expr.result-ast | 6 - .../pass/ops_with_newlines.expr.result-ast | 6 - .../packed_singleton_list.expr.result-ast | 3 - .../pass/parenthetical_apply.expr.result-ast | 3 - .../pass/parse_alias.expr.result-ast | 3 - .../pass/parse_as_ann.expr.result-ast | 3 - ...ttern_with_space_in_parens.expr.result-ast | 6 - .../pass/positive_float.expr.result-ast | 3 - .../pass/positive_int.expr.result-ast | 3 - .../record_destructure_def.expr.result-ast | 9 - .../record_func_type_decl.expr.result-ast | 3 - .../pass/record_update.expr.result-ast | 6 - .../pass/record_with_if.expr.result-ast | 9 - .../pass/single_arg_closure.expr.result-ast | 3 - .../single_underscore_closure.expr.result-ast | 3 - .../spaced_singleton_list.expr.result-ast | 3 - .../standalone_module_defs.module.result-ast | 3 - .../pass/sub_var_with_spaces.expr.result-ast | 3 - .../pass/sub_with_spaces.expr.result-ast | 6 - .../pass/tag_pattern.expr.result-ast | 3 - .../pass/ten_times_eleven.expr.result-ast | 6 - .../pass/three_arg_closure.expr.result-ast | 3 - .../pass/two_arg_closure.expr.result-ast | 3 - .../pass/two_branch_when.expr.result-ast | 6 - .../pass/two_spaced_def.expr.result-ast | 9 - .../type_decl_with_underscore.expr.result-ast | 3 - .../pass/unary_negation_arg.expr.result-ast | 3 - ...unary_negation_with_parens.expr.result-ast | 3 - .../unary_not_with_parens.expr.result-ast | 3 - .../underscore_backpassing.expr.result-ast | 3 - .../pass/var_minus_two.expr.result-ast | 3 - .../pass/when_if_guard.expr.result-ast | 9 - .../pass/when_in_parens.expr.result-ast | 3 - .../when_in_parens_indented.expr.result-ast | 3 - ..._with_alternative_patterns.expr.result-ast | 6 - ..._with_function_application.expr.result-ast | 9 - ...when_with_negative_numbers.expr.result-ast | 12 - .../pass/when_with_numbers.expr.result-ast | 12 - .../pass/when_with_records.expr.result-ast | 6 - .../snapshots/pass/zero_float.expr.result-ast | 3 - .../snapshots/pass/zero_int.expr.result-ast | 3 - compiler/parse/tests/test_parse.rs | 19 +- compiler/problem/src/can.rs | 4 + repl_eval/src/eval.rs | 12 +- reporting/src/error/canonicalize.rs | 25 +- reporting/src/error/parse.rs | 69 ----- reporting/tests/test_reporting.rs | 133 ++++++--- 113 files changed, 472 insertions(+), 1361 deletions(-) diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index 8b35a7158f..1d0553797d 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -1,6 +1,8 @@ use bumpalo::Bump; use roc_can::expr::Recursive; -use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; +use roc_can::num::{ + finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult, +}; use roc_can::operator::desugar_expr; use roc_collections::all::MutSet; use roc_module::symbol::Symbol; @@ -50,9 +52,9 @@ pub fn expr_to_expr2<'a>( use roc_parse::ast::Expr::*; match parse_expr { - Float(string, _bound) => { + Float(string) => { match finish_parsing_float(string) { - Ok(float) => { + Ok((float, _bound)) => { let expr = Expr2::Float { number: FloatVal::F64(float), var: env.var_store.fresh(), @@ -72,9 +74,9 @@ pub fn expr_to_expr2<'a>( } } } - Num(string, _bound) => { - match finish_parsing_int(string) { - Ok(int) => { + Num(string) => { + match finish_parsing_num(string) { + Ok(ParsedNumResult::UnknownNum(int) | ParsedNumResult::Int(int, _)) => { let expr = Expr2::SmallInt { number: IntVal::I64(int), var: env.var_store.fresh(), @@ -85,6 +87,15 @@ pub fn expr_to_expr2<'a>( (expr, Output::default()) } + Ok(ParsedNumResult::Float(float, _)) => { + let expr = Expr2::Float { + number: FloatVal::F64(float), + var: env.var_store.fresh(), + text: PoolStr::new(string, env.pool), + }; + + (expr, Output::default()) + } Err((raw, error)) => { // emit runtime error let runtime_error = RuntimeError::InvalidInt( @@ -105,10 +116,9 @@ pub fn expr_to_expr2<'a>( string, base, is_negative, - bound: _, } => { match finish_parsing_base(string, *base, *is_negative) { - Ok(int) => { + Ok((int, _bound)) => { let expr = Expr2::SmallInt { number: IntVal::I64(int), var: env.var_store.fresh(), diff --git a/ast/src/lang/core/pattern.rs b/ast/src/lang/core/pattern.rs index 57844b901c..4dcf759964 100644 --- a/ast/src/lang/core/pattern.rs +++ b/ast/src/lang/core/pattern.rs @@ -4,7 +4,9 @@ use bumpalo::collections::Vec as BumpVec; use roc_can::expr::unescape_char; -use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; +use roc_can::num::{ + finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult, +}; use roc_collections::all::BumpMap; use roc_module::symbol::{Interns, Symbol}; use roc_parse::ast::{StrLiteral, StrSegment}; @@ -177,35 +179,30 @@ pub fn to_pattern2<'a>( TopLevelDef | DefExpr => underscore_in_def(env, region), }, - FloatLiteral(ref string, _bound) => match pattern_type { + FloatLiteral(ref string) => match pattern_type { WhenBranch => match finish_parsing_float(string) { Err(_error) => { let problem = MalformedPatternProblem::MalformedFloat; malformed_pattern(env, problem, region) } - Ok(float) => Pattern2::FloatLiteral(FloatVal::F64(float)), + Ok((float, _bound)) => Pattern2::FloatLiteral(FloatVal::F64(float)), }, ptype => unsupported_pattern(env, ptype, region), }, - IntLiteral(string, _bound) => match pattern_type { - WhenBranch => match finish_parsing_int(string) { + NumLiteral(string) => match pattern_type { + WhenBranch => match finish_parsing_num(string) { Err(_error) => { let problem = MalformedPatternProblem::MalformedInt; malformed_pattern(env, problem, region) } - Ok(int) => Pattern2::NumLiteral(env.var_store.fresh(), int), - }, - ptype => unsupported_pattern(env, ptype, region), - }, - - NumLiteral(string, _bound) => match pattern_type { - WhenBranch => match finish_parsing_int(string) { - Err(_error) => { - let problem = MalformedPatternProblem::MalformedInt; - malformed_pattern(env, problem, region) + Ok(ParsedNumResult::UnknownNum(int)) => { + Pattern2::NumLiteral(env.var_store.fresh(), int) + } + Ok(ParsedNumResult::Int(int, _bound)) => Pattern2::IntLiteral(IntVal::I64(int)), + Ok(ParsedNumResult::Float(int, _bound)) => { + Pattern2::FloatLiteral(FloatVal::F64(int)) } - Ok(int) => Pattern2::NumLiteral(env.var_store.fresh(), int), }, ptype => unsupported_pattern(env, ptype, region), }, @@ -214,14 +211,13 @@ pub fn to_pattern2<'a>( string, base, is_negative, - bound: _, } => match pattern_type { WhenBranch => match finish_parsing_base(string, *base, *is_negative) { Err(_error) => { let problem = MalformedPatternProblem::MalformedBase(*base); malformed_pattern(env, problem, region) } - Ok(int) => { + Ok((int, _bound)) => { if *is_negative { Pattern2::IntLiteral(IntVal::I64(-int)) } else { diff --git a/cli/src/format.rs b/cli/src/format.rs index 1b61cab1f2..365bb0034e 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -484,19 +484,16 @@ impl<'a> RemoveSpaces<'a> for StrSegment<'a> { impl<'a> RemoveSpaces<'a> for Expr<'a> { fn remove_spaces(&self, arena: &'a Bump) -> Self { match *self { - Expr::Float(a, bound) => Expr::Float(a, bound), - Expr::Num(a, bound) => Expr::Num(a, bound), - Expr::Int(a, bound) => Expr::Int(a, bound), + Expr::Float(a) => Expr::Float(a), + Expr::Num(a) => Expr::Num(a), Expr::NonBase10Int { string, base, is_negative, - bound, } => Expr::NonBase10Int { string, base, is_negative, - bound, }, Expr::Str(a) => Expr::Str(a.remove_spaces(arena)), Expr::Access(a, b) => Expr::Access(arena.alloc(a.remove_spaces(arena)), b), @@ -572,20 +569,17 @@ impl<'a> RemoveSpaces<'a> for Pattern<'a> { Pattern::OptionalField(a, b) => { Pattern::OptionalField(a, arena.alloc(b.remove_spaces(arena))) } - Pattern::NumLiteral(a, bound) => Pattern::NumLiteral(a, bound), + Pattern::NumLiteral(a) => Pattern::NumLiteral(a), Pattern::NonBase10Literal { string, base, is_negative, - bound, } => Pattern::NonBase10Literal { string, base, is_negative, - bound, }, - Pattern::FloatLiteral(a, bound) => Pattern::FloatLiteral(a, bound), - Pattern::IntLiteral(a, bound) => Pattern::IntLiteral(a, bound), + Pattern::FloatLiteral(a) => Pattern::FloatLiteral(a), Pattern::StrLiteral(a) => Pattern::StrLiteral(a), Pattern::Underscore(a) => Pattern::Underscore(a), Pattern::Malformed(a) => Pattern::Malformed(a), diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 8ac27c49f6..0088fe6de7 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1,12 +1,12 @@ use crate::def::Def; -use crate::expr::{self, ClosureData, Expr::*, NumericBound}; +use crate::expr::{self, ClosureData, Expr::*}; use crate::expr::{Expr, Field, Recursive}; use crate::pattern::Pattern; use roc_collections::all::SendMap; use roc_module::called_via::CalledVia; use roc_module::ident::{Lowercase, TagName}; use roc_module::low_level::LowLevel; -use roc_module::numeric::{FloatWidth, IntWidth, NumWidth}; +use roc_module::numeric::{FloatWidth, IntWidth, NumWidth, NumericBound}; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; @@ -794,10 +794,7 @@ fn num_is_zero(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::Eq, args: vec![ (arg_var, Var(Symbol::ARG_1)), - ( - arg_var, - num(unbound_zero_var, 0, num_no_bound(var_store.fresh())), - ), + (arg_var, num(unbound_zero_var, 0, num_no_bound())), ], ret_var: bool_var, }; @@ -820,10 +817,7 @@ fn num_is_negative(symbol: Symbol, var_store: &mut VarStore) -> Def { let body = RunLowLevel { op: LowLevel::NumGt, args: vec![ - ( - arg_var, - num(unbound_zero_var, 0, num_no_bound(var_store.fresh())), - ), + (arg_var, num(unbound_zero_var, 0, num_no_bound())), (arg_var, Var(Symbol::ARG_1)), ], ret_var: bool_var, @@ -848,10 +842,7 @@ fn num_is_positive(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::NumGt, args: vec![ (arg_var, Var(Symbol::ARG_1)), - ( - arg_var, - num(unbound_zero_var, 0, num_no_bound(var_store.fresh())), - ), + (arg_var, num(unbound_zero_var, 0, num_no_bound())), ], ret_var: bool_var, }; @@ -876,12 +867,7 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def { args: vec![ ( arg_var, - int::( - var_store.fresh(), - var_store.fresh(), - 1, - num_no_bound(var_store.fresh()), - ), + int::(var_store.fresh(), var_store.fresh(), 1, num_no_bound()), ), ( arg_var, @@ -889,10 +875,7 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::NumRemUnchecked, args: vec![ (arg_var, Var(Symbol::ARG_1)), - ( - arg_var, - num(unbound_two_var, 2, num_no_bound(var_store.fresh())), - ), + (arg_var, num(unbound_two_var, 2, num_no_bound())), ], ret_var: arg_var, }, @@ -919,20 +902,14 @@ fn num_is_even(symbol: Symbol, var_store: &mut VarStore) -> Def { let body = RunLowLevel { op: LowLevel::Eq, args: vec![ - ( - arg_var, - num(arg_num_var, 0, num_no_bound(var_store.fresh())), - ), + (arg_var, num(arg_num_var, 0, num_no_bound())), ( arg_var, RunLowLevel { op: LowLevel::NumRemUnchecked, args: vec![ (arg_var, Var(Symbol::ARG_1)), - ( - arg_var, - num(arg_num_var, 2, num_no_bound(var_store.fresh())), - ), + (arg_var, num(arg_num_var, 2, num_no_bound())), ], ret_var: arg_var, }, @@ -988,12 +965,7 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def { (float_var, Var(Symbol::ARG_1)), ( float_var, - float( - unbound_zero_var, - precision_var, - 0.0, - num_no_bound(var_store.fresh()), - ), + float(unbound_zero_var, precision_var, 0.0, num_no_bound()), ), ], ret_var: bool_var, @@ -1042,12 +1014,7 @@ fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def { (float_var, Var(Symbol::ARG_1)), ( float_var, - float( - unbound_zero_var, - precision_var, - 0.0, - num_no_bound(var_store.fresh()), - ), + float(unbound_zero_var, precision_var, 0.0, num_no_bound()), ), ], ret_var: bool_var, @@ -1474,10 +1441,7 @@ fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def { let body = RunLowLevel { op: LowLevel::Eq, args: vec![ - ( - len_var, - num(unbound_zero_var, 0, num_no_bound(var_store.fresh())), - ), + (len_var, num(unbound_zero_var, 0, num_no_bound())), ( len_var, RunLowLevel { @@ -2739,12 +2703,7 @@ fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def { (list_var, Var(Symbol::ARG_1)), ( index_var, - int::( - num_var, - num_precision_var, - 0, - num_no_bound(var_store.fresh()), - ), + int::(num_var, num_precision_var, 0, num_no_bound()), ), ], ret_var: list_var, @@ -2844,12 +2803,7 @@ fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def { ), ( arg_var, - int::( - num_var, - num_precision_var, - 1, - num_no_bound(var_store.fresh()), - ), + int::(num_var, num_precision_var, 1, num_no_bound()), ), ], ret_var: len_var, @@ -3050,12 +3004,7 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def { args: vec![ ( len_var, - int::( - num_var, - num_precision_var, - 0, - num_no_bound(var_store.fresh()), - ), + int::(num_var, num_precision_var, 0, num_no_bound()), ), ( len_var, @@ -3093,7 +3042,7 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def { num_var, num_precision_var, 0, - num_no_bound(var_store.fresh()), + num_no_bound(), ), ), ], @@ -3196,12 +3145,7 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def { args: vec![ ( len_var, - int::( - num_var, - num_precision_var, - 0, - num_no_bound(var_store.fresh()), - ), + int::(num_var, num_precision_var, 0, num_no_bound()), ), ( len_var, @@ -3239,7 +3183,7 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def { num_var, num_precision_var, 0, - num_no_bound(var_store.fresh()), + num_no_bound(), ), ), ], @@ -3337,7 +3281,7 @@ fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def { (list_var, Loc::at_zero(Var(Symbol::ARG_1))), ( num_var, - Loc::at_zero(num(var_store.fresh(), 0, num_no_bound(var_store.fresh()))), + Loc::at_zero(num(var_store.fresh(), 0, num_no_bound())), ), (closure_var, Loc::at_zero(Var(Symbol::NUM_ADD))), ], @@ -3372,7 +3316,7 @@ fn list_product(symbol: Symbol, var_store: &mut VarStore) -> Def { (list_var, Loc::at_zero(Var(Symbol::ARG_1))), ( num_var, - Loc::at_zero(num(var_store.fresh(), 1, num_no_bound(var_store.fresh()))), + Loc::at_zero(num(var_store.fresh(), 1, num_no_bound())), ), (closure_var, Loc::at_zero(Var(Symbol::NUM_MUL))), ], @@ -4027,10 +3971,7 @@ fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::NotEq, args: vec![ (num_var, Var(Symbol::ARG_2)), - ( - num_var, - num(unbound_zero_var, 0, num_no_bound(var_store.fresh())), - ), + (num_var, num(unbound_zero_var, 0, num_no_bound())), ], ret_var: bool_var, }, @@ -4135,12 +4076,7 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def { (num_var, Var(Symbol::ARG_2)), ( num_var, - float( - unbound_zero_var, - precision_var, - 0.0, - num_no_bound(var_store.fresh()), - ), + float(unbound_zero_var, precision_var, 0.0, num_no_bound()), ), ], ret_var: bool_var, @@ -4210,7 +4146,7 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def { unbound_zero_var, unbound_zero_precision_var, 0, - num_no_bound(var_store.fresh()), + num_no_bound(), ), ), ], @@ -4281,7 +4217,7 @@ fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def { unbound_zero_var, unbound_zero_precision_var, 0, - num_no_bound(var_store.fresh()), + num_no_bound(), ), ), ], @@ -4354,12 +4290,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def { args: vec![ ( len_var, - int::( - zero_var, - zero_precision_var, - 0, - num_no_bound(var_store.fresh()), - ), + int::(zero_var, zero_precision_var, 0, num_no_bound()), ), ( len_var, @@ -4386,12 +4317,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def { (list_var, Var(Symbol::ARG_1)), ( len_var, - int::( - zero_var, - zero_precision_var, - 0, - num_no_bound(var_store.fresh()), - ), + int::(zero_var, zero_precision_var, 0, num_no_bound()), ), ], ret_var: list_elem_var, @@ -4451,12 +4377,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def { args: vec![ ( len_var, - int::( - num_var, - num_precision_var, - 0, - num_no_bound(var_store.fresh()), - ), + int::(num_var, num_precision_var, 0, num_no_bound()), ), ( len_var, @@ -4502,7 +4423,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def { num_var, num_precision_var, 1, - num_no_bound(var_store.fresh()), + num_no_bound(), ), ), ], @@ -5134,11 +5055,7 @@ fn num_bytes_to(symbol: Symbol, var_store: &mut VarStore, offset: i64, low_level ret_var: cast_var, args: vec![( cast_var, - num( - var_store.fresh(), - offset, - num_no_bound(var_store.fresh()), - ), + num(var_store.fresh(), offset, num_no_bound()), )], op: LowLevel::NumIntCast, }, @@ -5261,8 +5178,8 @@ where } } -fn num_no_bound(width_variable: Variable) -> NumericBound { - NumericBound::None { width_variable } +fn num_no_bound() -> NumericBound { + NumericBound::None } #[inline(always)] diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 86cfdf25cc..60be157fe1 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -3,8 +3,8 @@ use crate::builtins::builtin_defs_map; use crate::def::{can_defs_with_return, Def}; use crate::env::Env; use crate::num::{ - finish_parsing_base, finish_parsing_float, finish_parsing_int, finish_parsing_int128, - float_expr_from_result, int_expr_from_result, num_expr_from_result, + finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result, + int_expr_from_result, num_expr_from_result, }; use crate::pattern::{canonicalize_pattern, Pattern}; use crate::procedure::References; @@ -13,9 +13,9 @@ use roc_collections::all::{ImSet, MutMap, MutSet, SendMap}; use roc_module::called_via::CalledVia; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; -use roc_module::numeric::{FloatWidth, IntWidth, NumWidth}; +use roc_module::numeric::{FloatWidth, IntWidth, NumWidth, NumericBound}; use roc_module::symbol::Symbol; -use roc_parse::ast::{self, Base, EscapedChar, StrLiteral}; +use roc_parse::ast::{self, EscapedChar, StrLiteral}; use roc_parse::pattern::PatternType::*; use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError}; use roc_region::all::{Loc, Region}; @@ -47,8 +47,6 @@ impl Output { } } -pub type NumericBound = roc_module::numeric::NumericBound; - #[derive(Clone, Debug, PartialEq)] pub enum Expr { // Literals @@ -211,23 +209,21 @@ pub fn canonicalize_expr<'a>( use Expr::*; let (expr, output) = match expr { - &ast::Expr::Num(str, bound) => { + &ast::Expr::Num(str) => { let answer = num_expr_from_result( var_store, - finish_parsing_int(str).map(|int| (str, int)), + finish_parsing_num(str).map(|result| (str, result)), region, - bound, env, ); (answer, Output::default()) } - &ast::Expr::Float(str, bound) => { + &ast::Expr::Float(str) => { let answer = float_expr_from_result( var_store, - finish_parsing_float(str).map(|f| (str, f)), + finish_parsing_float(str).map(|(f, bound)| (str, f, bound)), region, - bound, env, ); @@ -799,41 +795,27 @@ pub fn canonicalize_expr<'a>( string, base, is_negative, - bound, } => { // the minus sign is added before parsing, to get correct overflow/underflow behavior let answer = match finish_parsing_base(string, base, is_negative) { - Ok(int) => { + Ok((int, bound)) => { // Done in this kinda round about way with intermediate variables // to keep borrowed values around and make this compile let int_string = int.to_string(); let int_str = int_string.as_str(); int_expr_from_result( var_store, - Ok((int_str, int as i128)), + Ok((int_str, int as i128, bound)), region, base, - bound, env, ) } - Err(e) => int_expr_from_result(var_store, Err(e), region, base, bound, env), + Err(e) => int_expr_from_result(var_store, Err(e), region, base, env), }; (answer, Output::default()) } - &ast::Expr::Int(str, bound) => { - let answer = int_expr_from_result( - var_store, - finish_parsing_int128(str).map(|f| (str, f)), - region, - Base::Decimal, - bound, - env, - ); - - (answer, Output::default()) - } // Below this point, we shouln't see any of these nodes anymore because // operator desugaring should have removed them! bad_expr @ ast::Expr::ParensAround(_) => { diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index ed0f0ab783..e134cace5a 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -6,20 +6,9 @@ use roc_problem::can::Problem; use roc_problem::can::RuntimeError::*; use roc_problem::can::{FloatErrorKind, IntErrorKind}; use roc_region::all::Region; -use roc_types::subs::{VarStore, Variable}; +use roc_types::subs::VarStore; use std::i64; - -pub fn reify_numeric_bound( - var_store: &mut VarStore, - bound: NumericBound, -) -> NumericBound { - match bound { - NumericBound::None { width_variable: () } => NumericBound::None { - width_variable: var_store.fresh(), - }, - NumericBound::Exact(width) => NumericBound::Exact(width), - } -} +use std::str; // TODO use rust's integer parsing again // @@ -29,17 +18,27 @@ pub fn reify_numeric_bound( #[inline(always)] pub fn num_expr_from_result( var_store: &mut VarStore, - result: Result<(&str, i64), (&str, IntErrorKind)>, + result: Result<(&str, ParsedNumResult), (&str, IntErrorKind)>, region: Region, - bound: NumericBound, env: &mut Env, ) -> Expr { match result { - Ok((str, num)) => Expr::Num( + Ok((str, ParsedNumResult::UnknownNum(num))) => { + Expr::Num(var_store.fresh(), (*str).into(), num, NumericBound::None) + } + Ok((str, ParsedNumResult::Int(num, bound))) => Expr::Int( + var_store.fresh(), + var_store.fresh(), + (*str).into(), + num.into(), + bound, + ), + Ok((str, ParsedNumResult::Float(num, bound))) => Expr::Float( + var_store.fresh(), var_store.fresh(), (*str).into(), num, - reify_numeric_bound(var_store, bound), + bound, ), Err((raw, error)) => { // (Num *) compiles to Int if it doesn't @@ -57,20 +56,19 @@ pub fn num_expr_from_result( #[inline(always)] pub fn int_expr_from_result( var_store: &mut VarStore, - result: Result<(&str, i128), (&str, IntErrorKind)>, + result: Result<(&str, i128, NumericBound), (&str, IntErrorKind)>, region: Region, base: Base, - bound: NumericBound, env: &mut Env, ) -> Expr { // Int stores a variable to generate better error messages match result { - Ok((str, int)) => Expr::Int( + Ok((str, int, bound)) => Expr::Int( var_store.fresh(), var_store.fresh(), (*str).into(), int, - reify_numeric_bound(var_store, bound), + bound, ), Err((raw, error)) => { let runtime_error = InvalidInt(error, base, region, raw.into()); @@ -85,19 +83,18 @@ pub fn int_expr_from_result( #[inline(always)] pub fn float_expr_from_result( var_store: &mut VarStore, - result: Result<(&str, f64), (&str, FloatErrorKind)>, + result: Result<(&str, f64, NumericBound), (&str, FloatErrorKind)>, region: Region, - bound: NumericBound, env: &mut Env, ) -> Expr { // Float stores a variable to generate better error messages match result { - Ok((str, float)) => Expr::Float( + Ok((str, float, bound)) => Expr::Float( var_store.fresh(), var_store.fresh(), (*str).into(), float, - reify_numeric_bound(var_store, bound), + bound, ), Err((raw, error)) => { let runtime_error = InvalidFloat(error, region, raw.into()); @@ -109,18 +106,28 @@ pub fn float_expr_from_result( } } -#[inline(always)] -pub fn finish_parsing_int(raw: &str) -> Result { - // Ignore underscores. - let radix = 10; - from_str_radix::(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e.kind)) +pub enum ParsedNumResult { + Int(i64, NumericBound), + Float(f64, NumericBound), + UnknownNum(i64), } #[inline(always)] -pub fn finish_parsing_int128(raw: &str) -> Result { +pub fn finish_parsing_num(raw: &str) -> Result { // Ignore underscores. let radix = 10; - from_str_radix::(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e.kind)) + let (num, bound) = + from_str_radix::(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e.kind))?; + // Let's try to specialize the number + Ok(match bound { + NumericBound::None => ParsedNumResult::UnknownNum(num), + NumericBound::Exact(NumWidth::Int(iw)) => { + ParsedNumResult::Int(num, NumericBound::Exact(iw)) + } + NumericBound::Exact(NumWidth::Float(fw)) => { + ParsedNumResult::Float(num as f64, NumericBound::Exact(fw)) + } + }) } #[inline(always)] @@ -128,7 +135,7 @@ pub fn finish_parsing_base( raw: &str, base: Base, is_negative: bool, -) -> Result { +) -> Result<(i64, NumericBound), (&str, IntErrorKind)> { let radix = match base { Base::Hex => 16, Base::Decimal => 10, @@ -142,14 +149,36 @@ pub fn finish_parsing_base( } else { from_str_radix::(raw.replace("_", "").as_str(), radix) }) - .map_err(|e| (raw, e.kind)) + .map_err(|e| e.kind) + .and_then(|(n, bound)| { + let bound = match bound { + NumericBound::None => NumericBound::None, + NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(iw), + NumericBound::Exact(NumWidth::Float(_)) => return Err(IntErrorKind::FloatSuffix), + }; + Ok((n, bound)) + }) + .map_err(|e| (raw, e)) } #[inline(always)] -pub fn finish_parsing_float(raw: &str) -> Result { +pub fn finish_parsing_float( + raw: &str, +) -> Result<(f64, NumericBound), (&str, FloatErrorKind)> { + let (bound, raw_without_suffix) = parse_literal_suffix(raw.as_bytes()); + // Safety: `raw` is valid UTF8, and `parse_literal_suffix` will only chop UTF8 + // characters off the end, if it chops off anything at all. + let raw_without_suffix = unsafe { str::from_utf8_unchecked(raw_without_suffix) }; + + let bound = match bound { + NumericBound::None => NumericBound::None, + NumericBound::Exact(NumWidth::Float(fw)) => NumericBound::Exact(fw), + NumericBound::Exact(NumWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)), + }; + // Ignore underscores. - match raw.replace("_", "").parse::() { - Ok(float) if float.is_finite() => Ok(float), + match raw_without_suffix.replace("_", "").parse::() { + Ok(float) if float.is_finite() => Ok((float, bound)), Ok(float) => { if float.is_sign_positive() { Err((raw, FloatErrorKind::PositiveInfinity)) @@ -161,6 +190,35 @@ pub fn finish_parsing_float(raw: &str) -> Result { } } +fn parse_literal_suffix(num_str: &[u8]) -> (NumericBound, &[u8]) { + macro_rules! parse_num_suffix { + ($($suffix:expr, $width:expr)*) => {$( + if num_str.ends_with($suffix) { + return (NumericBound::Exact($width), num_str.get(0..num_str.len() - $suffix.len()).unwrap()); + } + )*} + } + + parse_num_suffix! { + b"u8", NumWidth::Int(IntWidth::U8) + b"u16", NumWidth::Int(IntWidth::U16) + b"u32", NumWidth::Int(IntWidth::U32) + b"u64", NumWidth::Int(IntWidth::U64) + b"u128", NumWidth::Int(IntWidth::U128) + b"i8", NumWidth::Int(IntWidth::I8) + b"i16", NumWidth::Int(IntWidth::I16) + b"i32", NumWidth::Int(IntWidth::I32) + b"i64", NumWidth::Int(IntWidth::I64) + b"i128", NumWidth::Int(IntWidth::I128) + b"nat", NumWidth::Int(IntWidth::Nat) + b"dec", NumWidth::Float(FloatWidth::Dec) + b"f32", NumWidth::Float(FloatWidth::F32) + b"f64", NumWidth::Float(FloatWidth::F64) + } + + (NumericBound::None, num_str) +} + /// Integer parsing code taken from the rust libcore, /// pulled in so we can give custom error messages /// @@ -200,11 +258,14 @@ macro_rules! doit { } })*) } -// We only need the i64 and i128 implementations, but libcore defines +// We only need the i64 implementation, but libcore defines // doit! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize } -doit! { i64 i128 } +doit! { i64 } -fn from_str_radix(src: &str, radix: u32) -> Result { +fn from_str_radix( + src: &str, + radix: u32, +) -> Result<(T, NumericBound), ParseIntError> { use self::IntErrorKind::*; use self::ParseIntError as PIE; @@ -232,6 +293,8 @@ fn from_str_radix(src: &str, radix: u32) -> Result (true, src), }; + let (bound, digits) = parse_literal_suffix(digits); + if digits.is_empty() { return Err(PIE { kind: Empty }); } @@ -270,7 +333,7 @@ fn from_str_radix(src: &str, radix: u32) -> Result(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> { pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc>) -> &'a Loc> { match &loc_expr.value { Float(..) - | Int(..) | Num(..) | NonBase10Int { .. } | Str(_) diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 7765deb5a1..4b43915701 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -1,11 +1,9 @@ use crate::env::Env; -use crate::expr::{canonicalize_expr, unescape_char, Expr, NumericBound, Output}; -use crate::num::{ - finish_parsing_base, finish_parsing_float, finish_parsing_int, reify_numeric_bound, -}; +use crate::expr::{canonicalize_expr, unescape_char, Expr, Output}; +use crate::num::{finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult}; use crate::scope::Scope; use roc_module::ident::{Ident, Lowercase, TagName}; -use roc_module::numeric::{FloatWidth, IntWidth, NumWidth}; +use roc_module::numeric::{FloatWidth, IntWidth, NumWidth, NumericBound}; use roc_module::symbol::Symbol; use roc_parse::ast::{self, StrLiteral, StrSegment}; use roc_parse::pattern::PatternType; @@ -188,18 +186,15 @@ pub fn canonicalize_pattern<'a>( } } - &FloatLiteral(str, bound) => match pattern_type { + &FloatLiteral(str) => match pattern_type { WhenBranch => match finish_parsing_float(str) { Err(_error) => { let problem = MalformedPatternProblem::MalformedFloat; malformed_pattern(env, problem, region) } - Ok(float) => Pattern::FloatLiteral( - var_store.fresh(), - (str).into(), - float, - reify_numeric_bound(var_store, bound), - ), + Ok((float, bound)) => { + Pattern::FloatLiteral(var_store.fresh(), (str).into(), float, bound) + } }, ptype => unsupported_pattern(env, ptype, region), }, @@ -209,18 +204,21 @@ pub fn canonicalize_pattern<'a>( TopLevelDef | DefExpr => bad_underscore(env, region), }, - &NumLiteral(str, bound) => match pattern_type { - WhenBranch => match finish_parsing_int(str) { + &NumLiteral(str) => match pattern_type { + WhenBranch => match finish_parsing_num(str) { Err(_error) => { let problem = MalformedPatternProblem::MalformedInt; malformed_pattern(env, problem, region) } - Ok(int) => Pattern::NumLiteral( - var_store.fresh(), - (str).into(), - int, - reify_numeric_bound(var_store, bound), - ), + Ok(ParsedNumResult::UnknownNum(int)) => { + Pattern::NumLiteral(var_store.fresh(), (str).into(), int, NumericBound::None) + } + Ok(ParsedNumResult::Int(int, bound)) => { + Pattern::IntLiteral(var_store.fresh(), (str).into(), int, bound) + } + Ok(ParsedNumResult::Float(float, bound)) => { + Pattern::FloatLiteral(var_store.fresh(), (str).into(), float, bound) + } }, ptype => unsupported_pattern(env, ptype, region), }, @@ -229,44 +227,22 @@ pub fn canonicalize_pattern<'a>( string, base, is_negative, - bound, } => match pattern_type { WhenBranch => match finish_parsing_base(string, base, is_negative) { Err(_error) => { let problem = MalformedPatternProblem::MalformedBase(base); malformed_pattern(env, problem, region) } - Ok(int) => { + Ok((int, bound)) => { let sign_str = if is_negative { "-" } else { "" }; let int_str = format!("{}{}", sign_str, int.to_string()).into_boxed_str(); let i = if is_negative { -int } else { int }; - Pattern::IntLiteral( - var_store.fresh(), - int_str, - i, - reify_numeric_bound(var_store, bound), - ) + Pattern::IntLiteral(var_store.fresh(), int_str, i, bound) } }, ptype => unsupported_pattern(env, ptype, region), }, - &IntLiteral(str, bound) => match pattern_type { - WhenBranch => match finish_parsing_int(str) { - Err(_error) => { - let problem = MalformedPatternProblem::MalformedInt; - malformed_pattern(env, problem, region) - } - Ok(int) => Pattern::IntLiteral( - var_store.fresh(), - str.into(), - int, - reify_numeric_bound(var_store, bound), - ), - }, - ptype => unsupported_pattern(env, ptype, region), - }, - StrLiteral(literal) => match pattern_type { WhenBranch => flatten_str_literal(literal), ptype => unsupported_pattern(env, ptype, region), diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index df1cbdc268..62b4b07710 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -1,10 +1,9 @@ use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::LetConstraint; use roc_can::expected::Expected::{self, *}; -use roc_can::expr::NumericBound; use roc_collections::all::SendMap; use roc_module::ident::{Lowercase, TagName}; -use roc_module::numeric::{FloatWidth, IntWidth, NumWidth}; +use roc_module::numeric::{FloatWidth, IntWidth, NumWidth, NumericBound}; use roc_module::symbol::Symbol; use roc_region::all::Region; use roc_types::subs::Variable; @@ -19,10 +18,10 @@ fn add_numeric_bound_constr( region: Region, category: Category, ) { - if bound.is_concrete() { + if let Some(typ) = bound.concrete_num_type() { constrs.push(Eq( num_type, - Expected::ForReason(Reason::NumericLiteralSuffix, bound.num_type(), region), + Expected::ForReason(Reason::NumericLiteralSuffix, typ, region), category, region, )); @@ -288,17 +287,16 @@ pub fn num_num(typ: Type) -> Type { } pub trait TypedNumericBound { - fn num_type(&self) -> Type; - - /// Whether the bound has a concrete range, and could not be filled by an arbitrary type. - fn is_concrete(&self) -> bool; + /// Get a concrete type for this number, if one exists. + /// Returns `None` e.g. if the bound is open, like `Int *`. + fn concrete_num_type(&self) -> Option; } impl TypedNumericBound for NumericBound { - fn num_type(&self) -> Type { + fn concrete_num_type(&self) -> Option { match self { - &NumericBound::None { width_variable } => num_num(Type::Variable(width_variable)), - NumericBound::Exact(w) => match w { + NumericBound::None => None, + NumericBound::Exact(w) => Some(match w { IntWidth::U8 => num_u8(), IntWidth::U16 => num_u16(), IntWidth::U32 => num_u32(), @@ -310,42 +308,30 @@ impl TypedNumericBound for NumericBound { IntWidth::I64 => num_i64(), IntWidth::I128 => num_i128(), IntWidth::Nat => num_nat(), - }, + }), } } - - fn is_concrete(&self) -> bool { - !matches!(self, NumericBound::None { .. }) - } } impl TypedNumericBound for NumericBound { - fn num_type(&self) -> Type { + fn concrete_num_type(&self) -> Option { match self { - &NumericBound::None { width_variable } => num_num(Type::Variable(width_variable)), - NumericBound::Exact(w) => match w { + NumericBound::None => None, + NumericBound::Exact(w) => Some(match w { FloatWidth::Dec => num_dec(), FloatWidth::F32 => num_f32(), FloatWidth::F64 => num_f64(), - }, + }), } } - - fn is_concrete(&self) -> bool { - !matches!(self, NumericBound::None { .. }) - } } impl TypedNumericBound for NumericBound { - fn num_type(&self) -> Type { + fn concrete_num_type(&self) -> Option { match *self { - NumericBound::None { width_variable } => num_num(Type::Variable(width_variable)), - NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(iw).num_type(), - NumericBound::Exact(NumWidth::Float(fw)) => NumericBound::Exact(fw).num_type(), + NumericBound::None => None, + NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(iw).concrete_num_type(), + NumericBound::Exact(NumWidth::Float(fw)) => NumericBound::Exact(fw).concrete_num_type(), } } - - fn is_concrete(&self) -> bool { - !matches!(self, NumericBound::None { .. }) - } } diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index 1f557bef48..8487c19761 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -6,8 +6,7 @@ use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT}; use crate::Buf; use roc_module::called_via::{self, BinOp}; use roc_parse::ast::{ - AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces, NumericBound, Pattern, - WhenBranch, + AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces, Pattern, WhenBranch, }; use roc_parse::ast::{StrLiteral, StrSegment}; use roc_region::all::Loc; @@ -30,7 +29,6 @@ impl<'a> Formattable for Expr<'a> { // These expressions never have newlines Float(..) | Num(..) - | Int(..) | NonBase10Int { .. } | Access(_, _) | AccessorFunction(_) @@ -198,29 +196,13 @@ impl<'a> Formattable for Expr<'a> { buf.push(')'); } } - &Num(string, bound) => { + &Num(string) => { buf.indent(indent); buf.push_str(string); - - if let NumericBound::Exact(width) = bound { - buf.push_str(&width.to_string()); - } } - &Float(string, bound) => { + &Float(string) => { buf.indent(indent); buf.push_str(string); - - if let NumericBound::Exact(width) = bound { - buf.push_str(&width.to_string()); - } - } - &Int(string, bound) => { - buf.indent(indent); - buf.push_str(string); - - if let NumericBound::Exact(width) = bound { - buf.push_str(&width.to_string()); - } } GlobalTag(string) | PrivateTag(string) => { buf.indent(indent); @@ -230,7 +212,6 @@ impl<'a> Formattable for Expr<'a> { base, string, is_negative, - bound, } => { buf.indent(indent); if is_negative { @@ -245,10 +226,6 @@ impl<'a> Formattable for Expr<'a> { } buf.push_str(string); - - if let NumericBound::Exact(width) = bound { - buf.push_str(&width.to_string()); - } } Record(fields) => { fmt_record(buf, None, *fields, indent); diff --git a/compiler/fmt/src/pattern.rs b/compiler/fmt/src/pattern.rs index 04b97ea13d..e09f9e19d7 100644 --- a/compiler/fmt/src/pattern.rs +++ b/compiler/fmt/src/pattern.rs @@ -1,7 +1,7 @@ use crate::annotation::{Formattable, Newlines, Parens}; use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt}; use crate::Buf; -use roc_parse::ast::{Base, NumericBound, Pattern}; +use roc_parse::ast::{Base, Pattern}; pub fn fmt_pattern<'a, 'buf>( buf: &mut Buf<'buf>, @@ -32,7 +32,6 @@ impl<'a> Formattable for Pattern<'a> { | Pattern::PrivateTag(_) | Pattern::Apply(_, _) | Pattern::NumLiteral(..) - | Pattern::IntLiteral(..) | Pattern::NonBase10Literal { .. } | Pattern::FloatLiteral(..) | Pattern::StrLiteral(_) @@ -117,25 +116,14 @@ impl<'a> Formattable for Pattern<'a> { loc_pattern.format(buf, indent); } - &NumLiteral(string, bound) => { + &NumLiteral(string) => { buf.indent(indent); buf.push_str(string); - if let NumericBound::Exact(width) = bound { - buf.push_str(&width.to_string()); - } - } - &IntLiteral(string, bound) => { - buf.indent(indent); - buf.push_str(string); - if let NumericBound::Exact(width) = bound { - buf.push_str(&width.to_string()); - } } &NonBase10Literal { base, string, is_negative, - bound, } => { buf.indent(indent); if is_negative { @@ -150,17 +138,10 @@ impl<'a> Formattable for Pattern<'a> { } buf.push_str(string); - - if let NumericBound::Exact(width) = bound { - buf.push_str(&width.to_string()); - } } - &FloatLiteral(string, bound) => { + &FloatLiteral(string) => { buf.indent(indent); buf.push_str(string); - if let NumericBound::Exact(width) = bound { - buf.push_str(&width.to_string()); - } } StrLiteral(literal) => { todo!("Format string literal: {:?}", literal); diff --git a/compiler/module/src/numeric.rs b/compiler/module/src/numeric.rs index 84b5a5a9b5..0db0c45fc5 100644 --- a/compiler/module/src/numeric.rs +++ b/compiler/module/src/numeric.rs @@ -72,13 +72,12 @@ impl Display for NumWidth { /// Describes a bound on the width of a numeric literal. #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum NumericBound +pub enum NumericBound where W: Copy, - V: Copy, { /// There is no bound on the width. - None { width_variable: V }, + None, /// Must have exactly the width `W`. Exact(W), } diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index 6ffd6febc5..3fc72c72fd 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -5,7 +5,6 @@ use crate::ident::Ident; use bumpalo::collections::{String, Vec}; use bumpalo::Bump; use roc_module::called_via::{BinOp, CalledVia, UnaryOp}; -use roc_module::numeric::{FloatWidth, IntWidth, NumWidth}; use roc_region::all::{Loc, Position, Region}; #[derive(Debug)] @@ -139,14 +138,12 @@ pub enum StrLiteral<'a> { #[derive(Clone, Copy, Debug, PartialEq)] pub enum Expr<'a> { // Number Literals - Float(&'a str, NumericBound), - Num(&'a str, NumericBound), - Int(&'a str, NumericBound), + Float(&'a str), + Num(&'a str), NonBase10Int { string: &'a str, base: Base, is_negative: bool, - bound: NumericBound, }, // String Literals @@ -411,9 +408,6 @@ impl<'a> CommentOrNewline<'a> { } } -/// A `NumericBound` with the unit type as a placeholder width variable. -pub type NumericBound = roc_module::numeric::NumericBound; - #[derive(Clone, Copy, Debug, PartialEq)] pub enum Pattern<'a> { // Identifier @@ -437,15 +431,13 @@ pub enum Pattern<'a> { OptionalField(&'a str, &'a Loc>), // Literal - NumLiteral(&'a str, NumericBound), + NumLiteral(&'a str), NonBase10Literal { string: &'a str, base: Base, is_negative: bool, - bound: NumericBound, }, - FloatLiteral(&'a str, NumericBound), - IntLiteral(&'a str, NumericBound), + FloatLiteral(&'a str), StrLiteral(StrLiteral<'a>), Underscore(&'a str), @@ -548,27 +540,20 @@ impl<'a> Pattern<'a> { x == y } // Literal - (NumLiteral(x, bound_x), NumLiteral(y, bound_y)) => x == y && bound_x == bound_y, + (NumLiteral(x), NumLiteral(y)) => x == y, ( NonBase10Literal { string: string_x, base: base_x, is_negative: is_negative_x, - bound: bound_x, }, NonBase10Literal { string: string_y, base: base_y, is_negative: is_negative_y, - bound: bound_y, }, - ) => { - string_x == string_y - && base_x == base_y - && is_negative_x == is_negative_y - && bound_x == bound_y - } - (FloatLiteral(x, bound_x), FloatLiteral(y, bound_y)) => x == y && bound_x == bound_y, + ) => string_x == string_y && base_x == base_y && is_negative_x == is_negative_y, + (FloatLiteral(x), FloatLiteral(y)) => x == y, (StrLiteral(x), StrLiteral(y)) => x == y, (Underscore(x), Underscore(y)) => x == y, diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 6cdbed3fb6..f45cbe5c9a 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -16,7 +16,6 @@ use crate::type_annotation; use bumpalo::collections::Vec; use bumpalo::Bump; use roc_module::called_via::{BinOp, CalledVia, UnaryOp}; -use roc_module::numeric::NumericBound; use roc_region::all::{Loc, Position, Region}; use crate::parser::Progress::{self, *}; @@ -378,7 +377,7 @@ impl<'a> ExprState<'a> { } else { let region = self.expr.region; - let mut value = Expr::Num("", NumericBound::None { width_variable: () }); + let mut value = Expr::Num(""); std::mem::swap(&mut self.expr.value, &mut value); self.expr = arena @@ -516,30 +515,28 @@ fn numeric_negate_expression<'a, T>( let region = Region::new(start, expr.region.end()); let new_expr = match expr.value { - Expr::Num(string, bound) => { + Expr::Num(string) => { let new_string = unsafe { std::str::from_utf8_unchecked(&state.bytes()[..string.len() + 1]) }; - Expr::Num(new_string, bound) + Expr::Num(new_string) } - Expr::Float(string, bound) => { + Expr::Float(string) => { let new_string = unsafe { std::str::from_utf8_unchecked(&state.bytes()[..string.len() + 1]) }; - Expr::Float(new_string, bound) + Expr::Float(new_string) } Expr::NonBase10Int { string, base, is_negative, - bound, } => { // don't include the minus sign here; it will not be parsed right Expr::NonBase10Int { is_negative: !is_negative, string, base, - bound, } } _ => Expr::UnaryOp(arena.alloc(expr), Loc::at(loc_op.region, UnaryOp::Negate)), @@ -1453,19 +1450,16 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result Ok(Pattern::FloatLiteral(string, bound)), - &Expr::Num(string, bound) => Ok(Pattern::NumLiteral(string, bound)), - &Expr::Int(string, bound) => Ok(Pattern::IntLiteral(string, bound)), + &Expr::Float(string) => Ok(Pattern::FloatLiteral(string)), + &Expr::Num(string) => Ok(Pattern::NumLiteral(string)), Expr::NonBase10Int { string, base, is_negative, - bound, } => Ok(Pattern::NonBase10Literal { string, base: *base, is_negative: *is_negative, - bound: *bound, }), // These would not have parsed as patterns Expr::AccessorFunction(_) @@ -2325,19 +2319,16 @@ fn positive_number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> { use crate::number_literal::NumLiteral::*; match literal { - Num(s, bound) => Expr::Num(s, bound), - Float(s, bound) => Expr::Float(s, bound), - Int(s, bound) => Expr::Int(s, bound), + Num(s) => Expr::Num(s), + Float(s) => Expr::Float(s), NonBase10Int { string, base, is_negative, - bound, } => Expr::NonBase10Int { string, base, is_negative, - bound, }, } } @@ -2349,19 +2340,16 @@ fn number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> { use crate::number_literal::NumLiteral::*; match literal { - Num(s, bound) => Expr::Num(s, bound), - Float(s, bound) => Expr::Float(s, bound), - Int(s, bound) => Expr::Int(s, bound), + Num(s) => Expr::Num(s), + Float(s) => Expr::Float(s), NonBase10Int { string, base, is_negative, - bound, } => Expr::NonBase10Int { string, base, is_negative, - bound, }, } }) diff --git a/compiler/parse/src/number_literal.rs b/compiler/parse/src/number_literal.rs index c03266664f..a4e8624684 100644 --- a/compiler/parse/src/number_literal.rs +++ b/compiler/parse/src/number_literal.rs @@ -1,18 +1,14 @@ -use crate::ast::{Base, NumericBound}; +use crate::ast::Base; use crate::parser::{ENumber, ParseResult, Parser, Progress}; use crate::state::State; -use roc_module::numeric::{FloatWidth, IntWidth, NumWidth}; -#[derive(Debug, Copy, Clone)] pub enum NumLiteral<'a> { - Float(&'a str, NumericBound), - Int(&'a str, NumericBound), - Num(&'a str, NumericBound), + Float(&'a str), + Num(&'a str), NonBase10Int { string: &'a str, base: Base, is_negative: bool, - bound: NumericBound, }, } @@ -61,101 +57,27 @@ fn parse_number_base<'a>( } } -macro_rules! parse_num_suffix { - ($bytes:expr, $($suffix:expr, $width:expr)*) => { - $( - { - let len = $suffix.len(); - if $bytes.starts_with($suffix) - && { - let next = $bytes[len..].get(0); - match next { Some(c) => !(c.is_ascii_digit() || c.is_ascii_alphabetic()), None => true, } - } - { - return Some(($width, len)) - } - } - )* - } -} - -fn get_int_suffix(bytes: &[u8]) -> Option<(IntWidth, usize)> { - parse_num_suffix! { - bytes, - b"u8", IntWidth::U8 - b"u16", IntWidth::U16 - b"u32", IntWidth::U32 - b"u64", IntWidth::U64 - b"u128", IntWidth::U128 - b"i8", IntWidth::I8 - b"i16", IntWidth::I16 - b"i32", IntWidth::I32 - b"i64", IntWidth::I64 - b"i128", IntWidth::I128 - b"nat", IntWidth::Nat - } - None -} - -fn get_float_suffix(bytes: &[u8]) -> Option<(FloatWidth, usize)> { - parse_num_suffix! { - bytes, - b"dec", FloatWidth::Dec - b"f32", FloatWidth::F32 - b"f64", FloatWidth::F64 - } - None -} - -fn get_num_suffix(bytes: &[u8]) -> Option<(NumWidth, usize)> { - (get_int_suffix(bytes).map(|(iw, l)| (NumWidth::Int(iw), l))) - .or_else(|| get_float_suffix(bytes).map(|(fw, l)| (NumWidth::Float(fw), l))) -} - fn chomp_number_base<'a>( base: Base, is_negative: bool, bytes: &'a [u8], state: State<'a>, ) -> ParseResult<'a, NumLiteral<'a>, ENumber> { - let (_, (_is_float, bound, chomped), state) = - chomp_number(bytes, state, is_negative, base == Base::Hex)?; + let (_is_float, chomped) = chomp_number(bytes); - let (bound, chomped_number) = if let Some((bound, chomped_before_suffix)) = bound { - (Some(bound), chomped_before_suffix) - } else { - (None, chomped) - }; - - let string = unsafe { std::str::from_utf8_unchecked(&bytes[..chomped_number]) }; + let string = unsafe { std::str::from_utf8_unchecked(&bytes[..chomped]) }; let new = state.advance(chomped + 2 + is_negative as usize); - match bound { - None => Ok(( - Progress::MadeProgress, - NumLiteral::NonBase10Int { - is_negative, - string, - base, - bound: NumericBound::None { width_variable: () }, - }, - new, - )), - Some(NumWidth::Int(iw)) => Ok(( - Progress::MadeProgress, - NumLiteral::NonBase10Int { - is_negative, - string, - base, - bound: NumericBound::Exact(iw), - }, - new, - )), - Some(NumWidth::Float(_)) => { - Err((Progress::MadeProgress, ENumber::IntHasFloatSuffix, state)) - } - } + Ok(( + Progress::MadeProgress, + NumLiteral::NonBase10Int { + is_negative, + string, + base, + }, + new, + )) } fn chomp_number_dec<'a>( @@ -163,62 +85,37 @@ fn chomp_number_dec<'a>( bytes: &'a [u8], state: State<'a>, ) -> ParseResult<'a, NumLiteral<'a>, ENumber> { - let (_, (is_float, bound, chomped), state) = chomp_number(bytes, state, is_negative, false)?; + let (is_float, chomped) = chomp_number(bytes); + + if is_negative && chomped == 0 { + // we're probably actually looking at unary negation here + return Err((Progress::NoProgress, ENumber::End, state)); + } if !bytes.get(0).copied().unwrap_or_default().is_ascii_digit() { // we're probably actually looking at unary negation here return Err((Progress::NoProgress, ENumber::End, state)); } - let (bound, chomped_number) = if let Some((bound, chomped_before_suffix)) = bound { - (Some(bound), chomped_before_suffix) - } else { - (None, chomped) - }; - - let string = unsafe { - std::str::from_utf8_unchecked(&state.bytes()[0..chomped_number + is_negative as usize]) - }; + let string = + unsafe { std::str::from_utf8_unchecked(&state.bytes()[0..chomped + is_negative as usize]) }; let new = state.advance(chomped + is_negative as usize); - match (is_float, bound) { - (true, None) => Ok(( - Progress::MadeProgress, - NumLiteral::Float(string, NumericBound::None { width_variable: () }), - new, - )), - (false, None) => Ok(( - Progress::MadeProgress, - NumLiteral::Num(string, NumericBound::None { width_variable: () }), - new, - )), - (_, Some(NumWidth::Float(fw))) => Ok(( - Progress::MadeProgress, - NumLiteral::Float(string, NumericBound::Exact(fw)), - new, - )), - (false, Some(NumWidth::Int(iw))) => Ok(( - Progress::MadeProgress, - NumLiteral::Int(string, NumericBound::Exact(iw)), - new, - )), - (true, Some(NumWidth::Int(_))) => { - Err((Progress::MadeProgress, ENumber::FloatHasIntSuffix, state)) - } - } + Ok(( + Progress::MadeProgress, + if is_float { + NumLiteral::Float(string) + } else { + NumLiteral::Num(string) + }, + new, + )) } -#[allow(clippy::type_complexity)] -fn chomp_number<'a>( - mut bytes: &'a [u8], - state: State<'a>, - is_negative: bool, - hex: bool, -) -> ParseResult<'a, (bool, Option<(NumWidth, usize)>, usize), ENumber> { +fn chomp_number(mut bytes: &[u8]) -> (bool, usize) { let start_bytes_len = bytes.len(); let mut is_float = false; - let mut suffix_and_chomped_before = None; while let Some(byte) = bytes.get(0) { match byte { @@ -247,69 +144,18 @@ fn chomp_number<'a>( // skip bytes = &bytes[1..]; } - _ if byte.is_ascii_digit() => { + _ if byte.is_ascii_digit() || byte.is_ascii_alphabetic() => { + // valid digits (alphabetic in hex digits, and the `e` in `12e26` scientific notation bytes = &bytes[1..]; } - _ if byte.is_ascii_hexdigit() && hex => { - bytes = &bytes[1..]; - } - _ if byte.is_ascii_whitespace() || byte.is_ascii_punctuation() => { - // not a valid digit; we're done - return Ok(( - Progress::MadeProgress, - ( - is_float, - suffix_and_chomped_before, - start_bytes_len - bytes.len(), - ), - state, - )); - } _ => { - // This might be a suffix; try that first. - let parsed_suffix = if suffix_and_chomped_before.is_none() { - get_num_suffix(bytes) - } else { - None - }; - - if let Some((bound, advanced_by)) = parsed_suffix { - suffix_and_chomped_before = Some((bound, start_bytes_len - bytes.len())); - bytes = &bytes[advanced_by..]; - continue; - } - - // Okay, this number is invalid. - - if start_bytes_len - bytes.len() == 0 && is_negative { - // We're probably actually looking at unary negation here. Reset the progress. - return Err((Progress::NoProgress, ENumber::End, state)); - } - - if bytes - .get(0) - .copied() - .unwrap_or_default() - .is_ascii_alphabetic() - { - // The user likely mistyped a literal suffix type here. - return Err(( - Progress::MadeProgress, - ENumber::LiteralSuffix, - state.advance(start_bytes_len - bytes.len()), - )); - } - - return Err((Progress::MadeProgress, ENumber::End, state)); + // not a valid digit; we're done + return (is_float, start_bytes_len - bytes.len()); } } } // if the above loop exits, we must be dealing with an empty slice // therefore we parsed all of the bytes in the input - Ok(( - Progress::MadeProgress, - (is_float, suffix_and_chomped_before, start_bytes_len), - state, - )) + (is_float, start_bytes_len) } diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index cd2a2ea3f0..6542f83003 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -338,9 +338,6 @@ pub enum EExpr<'a> { #[derive(Debug, Clone, PartialEq, Eq)] pub enum ENumber { End, - LiteralSuffix, - IntHasFloatSuffix, - FloatHasIntSuffix, } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/compiler/parse/src/pattern.rs b/compiler/parse/src/pattern.rs index 2acf0a784f..c76d115fc9 100644 --- a/compiler/parse/src/pattern.rs +++ b/compiler/parse/src/pattern.rs @@ -138,19 +138,16 @@ fn number_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> { use crate::number_literal::NumLiteral::*; match literal { - Num(s, bound) => Pattern::NumLiteral(s, bound), - Float(s, bound) => Pattern::FloatLiteral(s, bound), - Int(s, bound) => Pattern::IntLiteral(s, bound), + Num(s) => Pattern::NumLiteral(s), + Float(s) => Pattern::FloatLiteral(s), NonBase10Int { string, base, is_negative, - bound, } => Pattern::NonBase10Literal { string, base, is_negative, - bound, }, } }), diff --git a/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast index 046ec82ba8..cd27503529 100644 --- a/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast @@ -10,8 +10,5 @@ BinOps( ], @4-5 Num( "2", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast index a377ea6225..4d75339647 100644 --- a/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast @@ -3,17 +3,11 @@ BinOps( ( @0-1 Num( "1", - None { - width_variable: (), - }, ), @3-4 Plus, ), ], @7-8 Num( "2", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast b/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast index b48a145bd1..3b4a561069 100644 --- a/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast @@ -43,9 +43,6 @@ Defs( [], @43-47 Float( "3.14", - None { - width_variable: (), - }, ), ), ], diff --git a/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast b/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast index c208af4942..178aca2667 100644 --- a/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast @@ -44,9 +44,6 @@ Defs( [ @44-46 Num( "42", - None { - width_variable: (), - }, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast index 7be36650fc..f939664412 100644 --- a/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast @@ -5,15 +5,9 @@ Apply( [ @5-7 Num( "12", - None { - width_variable: (), - }, ), @8-10 Num( "34", - None { - width_variable: (), - }, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast index b5a6c707d4..483292bdcf 100644 --- a/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast @@ -6,17 +6,11 @@ Apply( @6-8 ParensAround( Num( "12", - None { - width_variable: (), - }, ), ), @11-13 ParensAround( Num( "34", - None { - width_variable: (), - }, ), ), ], diff --git a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast index 0e5d83b9b1..185e681f5b 100644 --- a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast @@ -5,15 +5,9 @@ Apply( [ @6-8 Num( "12", - None { - width_variable: (), - }, ), @9-11 Num( "34", - None { - width_variable: (), - }, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast index b5a7731d53..882b37c09d 100644 --- a/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast @@ -6,15 +6,9 @@ Apply( [ @6-8 Num( "12", - None { - width_variable: (), - }, ), @10-12 Num( "34", - None { - width_variable: (), - }, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast index a2e6e37452..d74301a614 100644 --- a/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast @@ -9,9 +9,6 @@ Apply( [ @7-9 Num( "12", - None { - width_variable: (), - }, ), @10-13 Var { module_name: "", diff --git a/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast index 536e747856..7a50a7af86 100644 --- a/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast @@ -9,9 +9,6 @@ Apply( [ @7-9 Num( "12", - None { - width_variable: (), - }, ), @10-13 Var { module_name: "", diff --git a/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast index 6de714179b..ae21012711 100644 --- a/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast @@ -6,9 +6,6 @@ Apply( [ @5-6 Num( "1", - None { - width_variable: (), - }, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast index bb175f392b..8f016cf433 100644 --- a/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast @@ -7,18 +7,12 @@ SpaceBefore( ), @111-112 Num( "5", - None { - width_variable: (), - }, ), ), ], @114-116 SpaceBefore( Num( "42", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast b/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast index 1be7970b0e..83a2f401e4 100644 --- a/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast @@ -9,8 +9,5 @@ Closure( ], @13-15 Num( "42", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast index 37df2fdf5a..4930aa1a55 100644 --- a/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast @@ -3,9 +3,6 @@ BinOps( ( @0-2 Num( "12", - None { - width_variable: (), - }, ), @4-5 Star, ), @@ -13,9 +10,6 @@ BinOps( @15-17 SpaceBefore( Num( "92", - None { - width_variable: (), - }, ), [ LineComment( diff --git a/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast index 921a937a21..8d2a5f0b63 100644 --- a/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast @@ -4,9 +4,6 @@ BinOps( @0-1 SpaceAfter( Num( "3", - None { - width_variable: (), - }, ), [ LineComment( @@ -19,8 +16,5 @@ BinOps( ], @13-14 Num( "4", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast index f200472637..5bf526ed0e 100644 --- a/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast @@ -4,9 +4,6 @@ BinOps( @0-1 SpaceAfter( Num( "3", - None { - width_variable: (), - }, ), [ LineComment( @@ -19,8 +16,5 @@ BinOps( ], @14-15 Num( "4", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/expect.expr.result-ast b/compiler/parse/tests/snapshots/pass/expect.expr.result-ast index a8e2ca5fd2..fb56585387 100644 --- a/compiler/parse/tests/snapshots/pass/expect.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/expect.expr.result-ast @@ -4,26 +4,17 @@ Expect( ( @7-8 Num( "1", - None { - width_variable: (), - }, ), @9-11 Equals, ), ], @12-13 Num( "1", - None { - width_variable: (), - }, ), ), @15-16 SpaceBefore( Num( "4", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/float_with_underscores.expr.result-ast b/compiler/parse/tests/snapshots/pass/float_with_underscores.expr.result-ast index ec9a9890e5..fc78c66972 100644 --- a/compiler/parse/tests/snapshots/pass/float_with_underscores.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/float_with_underscores.expr.result-ast @@ -1,6 +1,3 @@ Float( "-1_23_456.0_1_23_456", - None { - width_variable: (), - }, ) diff --git a/compiler/parse/tests/snapshots/pass/highest_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/highest_float.expr.result-ast index 6a75e62e9a..9b46a3b26d 100644 --- a/compiler/parse/tests/snapshots/pass/highest_float.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/highest_float.expr.result-ast @@ -1,6 +1,3 @@ Float( "179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0", - None { - width_variable: (), - }, ) diff --git a/compiler/parse/tests/snapshots/pass/highest_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/highest_int.expr.result-ast index bab2d14d15..aefb235b32 100644 --- a/compiler/parse/tests/snapshots/pass/highest_int.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/highest_int.expr.result-ast @@ -1,6 +1,3 @@ Num( "9223372036854775807", - None { - width_variable: (), - }, ) diff --git a/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast index 67dff3b996..08284e3121 100644 --- a/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast @@ -6,18 +6,12 @@ Defs( ), @5-6 Num( "5", - None { - width_variable: (), - }, ), ), ], @8-10 SpaceBefore( Num( "42", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/int_with_underscore.expr.result-ast b/compiler/parse/tests/snapshots/pass/int_with_underscore.expr.result-ast index 08878ed7a7..3912cc7edd 100644 --- a/compiler/parse/tests/snapshots/pass/int_with_underscore.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/int_with_underscore.expr.result-ast @@ -1,6 +1,3 @@ Num( "1__23", - None { - width_variable: (), - }, ) diff --git a/compiler/parse/tests/snapshots/pass/lowest_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/lowest_float.expr.result-ast index 16b05135a8..6bce8865e0 100644 --- a/compiler/parse/tests/snapshots/pass/lowest_float.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/lowest_float.expr.result-ast @@ -1,6 +1,3 @@ Float( "-179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0", - None { - width_variable: (), - }, ) diff --git a/compiler/parse/tests/snapshots/pass/lowest_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/lowest_int.expr.result-ast index cd4b5cfd0f..01111135a9 100644 --- a/compiler/parse/tests/snapshots/pass/lowest_int.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/lowest_int.expr.result-ast @@ -1,6 +1,3 @@ Num( "-9223372036854775808", - None { - width_variable: (), - }, ) diff --git a/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast b/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast index 2ab6663d4a..b776a10113 100644 --- a/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast @@ -9,8 +9,5 @@ Closure( ], @15-17 Num( "42", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast b/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast index cb09158833..526bf8a5cd 100644 --- a/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast @@ -17,9 +17,6 @@ When( ], value: @25-26 Num( "1", - None { - width_variable: (), - }, ), guard: None, }, @@ -36,9 +33,6 @@ When( ], value: @36-37 Num( "4", - None { - width_variable: (), - }, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast b/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast index 1958e1b5d9..fa23b429e9 100644 --- a/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast @@ -17,9 +17,6 @@ When( ], value: @25-26 Num( "1", - None { - width_variable: (), - }, ), guard: None, }, @@ -36,9 +33,6 @@ When( ], value: @36-37 Num( "4", - None { - width_variable: (), - }, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast b/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast index 31bad5fe49..1d8f6c4e68 100644 --- a/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast @@ -3,17 +3,11 @@ BinOps( ( @0-3 Num( "-12", - None { - width_variable: (), - }, ), @3-4 Minus, ), ], @4-5 Num( "5", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast b/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast index b12e1f61ec..190e21ca40 100644 --- a/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast @@ -7,18 +7,12 @@ SpaceBefore( ), @117-118 Num( "5", - None { - width_variable: (), - }, ), ), ], @120-122 SpaceBefore( Num( "42", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast b/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast index 205d39bb75..11a0ded5c2 100644 --- a/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast +++ b/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast @@ -14,9 +14,6 @@ ), @15-17 Num( "64", - None { - width_variable: (), - }, ), ), ], diff --git a/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast index b2632555fa..8946c66262 100644 --- a/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast @@ -18,9 +18,6 @@ Defs( @12-14 SpaceBefore( Num( "42", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast index 886f4e3805..8c30fd95c0 100644 --- a/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast @@ -20,9 +20,6 @@ Defs( @21-23 SpaceBefore( Num( "42", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast index 58ed26fe20..c5582c91c6 100644 --- a/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast @@ -3,26 +3,17 @@ BinOps( ( @0-2 Num( "31", - None { - width_variable: (), - }, ), @2-3 Star, ), ( @3-5 Num( "42", - None { - width_variable: (), - }, ), @5-6 Plus, ), ], @6-9 Num( "534", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/negative_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/negative_float.expr.result-ast index 3ab10fc141..b4246aee73 100644 --- a/compiler/parse/tests/snapshots/pass/negative_float.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/negative_float.expr.result-ast @@ -1,6 +1,3 @@ Float( "-42.9", - None { - width_variable: (), - }, ) diff --git a/compiler/parse/tests/snapshots/pass/negative_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/negative_int.expr.result-ast index a3d4d12fa6..a41780d138 100644 --- a/compiler/parse/tests/snapshots/pass/negative_int.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/negative_int.expr.result-ast @@ -1,6 +1,3 @@ Num( "-42", - None { - width_variable: (), - }, ) diff --git a/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast b/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast index af2521c8ec..33722ca531 100644 --- a/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast +++ b/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast @@ -72,15 +72,9 @@ [ @112-113 Num( "2", - None { - width_variable: (), - }, ), @114-115 Num( "3", - None { - width_variable: (), - }, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast b/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast index 0a15c30247..c4542c48eb 100644 --- a/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast @@ -9,9 +9,6 @@ If( SpaceAfter( Num( "1", - None { - width_variable: (), - }, ), [ Newline, @@ -31,9 +28,6 @@ If( SpaceAfter( Num( "2", - None { - width_variable: (), - }, ), [ Newline, @@ -48,9 +42,6 @@ If( @42-43 SpaceBefore( Num( "3", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast index e8c57c4405..3cdf6755b6 100644 --- a/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast @@ -7,9 +7,6 @@ Defs( @8-9 SpaceBefore( Num( "5", - None { - width_variable: (), - }, ), [ Newline, @@ -20,9 +17,6 @@ Defs( @11-13 SpaceBefore( Num( "42", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast index 1750d787a1..ee6690e728 100644 --- a/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast @@ -3,9 +3,6 @@ BinOps( ( @0-1 Num( "3", - None { - width_variable: (), - }, ), @3-4 Star, ), @@ -13,9 +10,6 @@ BinOps( @7-8 SpaceBefore( Num( "4", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast index 0d7b951f92..444199db82 100644 --- a/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast @@ -3,9 +3,6 @@ BinOps( ( @0-1 Num( "3", - None { - width_variable: (), - }, ), @3-4 Minus, ), @@ -13,9 +10,6 @@ BinOps( @7-8 SpaceBefore( Num( "4", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast index ae9a0ad8d8..cbc30d8ba0 100644 --- a/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast @@ -10,9 +10,6 @@ Defs( @4-5 SpaceAfter( Num( "1", - None { - width_variable: (), - }, ), [ Newline, @@ -23,9 +20,6 @@ Defs( ], @12-13 Num( "2", - None { - width_variable: (), - }, ), ), ), @@ -33,9 +27,6 @@ Defs( @15-17 SpaceBefore( Num( "42", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast index 9e54f093a5..57dac2f56f 100644 --- a/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast @@ -4,9 +4,6 @@ BinOps( @0-1 SpaceAfter( Num( "3", - None { - width_variable: (), - }, ), [ Newline, @@ -17,8 +14,5 @@ BinOps( ], @4-5 Num( "4", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast index 8fb37f8909..c01422cca9 100644 --- a/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast @@ -4,9 +4,6 @@ BinOps( @0-1 SpaceAfter( Num( "3", - None { - width_variable: (), - }, ), [ Newline, @@ -17,8 +14,5 @@ BinOps( ], @4-5 Num( "4", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast index f3531ef2a9..77f172790b 100644 --- a/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast @@ -4,9 +4,6 @@ List( SpaceAfter( Num( "1", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast b/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast index d06272a518..e9a5247b17 100644 --- a/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast @@ -7,18 +7,12 @@ SpaceBefore( ), @50-51 Num( "5", - None { - width_variable: (), - }, ), ), ], @53-55 SpaceBefore( Num( "42", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.result-ast b/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.result-ast index b4c930d4a0..8ee611e8f7 100644 --- a/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.result-ast @@ -5,11 +5,8 @@ Record( RequiredValue( @4-6 "u8", [], - @10-15 Int( - "123", - Exact( - U8, - ), + @10-15 Num( + "123u8", ), ), [ @@ -20,11 +17,8 @@ Record( RequiredValue( @19-22 "u16", [], - @25-31 Int( - "123", - Exact( - U16, - ), + @25-31 Num( + "123u16", ), ), [ @@ -35,11 +29,8 @@ Record( RequiredValue( @35-38 "u32", [], - @41-47 Int( - "123", - Exact( - U32, - ), + @41-47 Num( + "123u32", ), ), [ @@ -50,11 +41,8 @@ Record( RequiredValue( @51-54 "u64", [], - @57-63 Int( - "123", - Exact( - U64, - ), + @57-63 Num( + "123u64", ), ), [ @@ -65,11 +53,8 @@ Record( RequiredValue( @67-71 "u128", [], - @73-80 Int( - "123", - Exact( - U128, - ), + @73-80 Num( + "123u128", ), ), [ @@ -80,11 +65,8 @@ Record( RequiredValue( @84-86 "i8", [], - @90-95 Int( - "123", - Exact( - I8, - ), + @90-95 Num( + "123i8", ), ), [ @@ -95,11 +77,8 @@ Record( RequiredValue( @99-102 "i16", [], - @105-111 Int( - "123", - Exact( - I16, - ), + @105-111 Num( + "123i16", ), ), [ @@ -110,11 +89,8 @@ Record( RequiredValue( @115-118 "i32", [], - @121-127 Int( - "123", - Exact( - I32, - ), + @121-127 Num( + "123i32", ), ), [ @@ -125,11 +101,8 @@ Record( RequiredValue( @131-134 "i64", [], - @137-143 Int( - "123", - Exact( - I64, - ), + @137-143 Num( + "123i64", ), ), [ @@ -140,11 +113,8 @@ Record( RequiredValue( @147-151 "i128", [], - @153-160 Int( - "123", - Exact( - I128, - ), + @153-160 Num( + "123i128", ), ), [ @@ -155,11 +125,8 @@ Record( RequiredValue( @164-167 "nat", [], - @170-176 Int( - "123", - Exact( - Nat, - ), + @170-176 Num( + "123nat", ), ), [ @@ -170,11 +137,8 @@ Record( RequiredValue( @180-183 "dec", [], - @186-192 Float( - "123", - Exact( - Dec, - ), + @186-192 Num( + "123dec", ), ), [ @@ -185,14 +149,8 @@ Record( RequiredValue( @196-201 "u8Neg", [], - @205-211 UnaryOp( - @206-211 Int( - "123", - Exact( - U8, - ), - ), - @205-206 Negate, + @205-211 Num( + "-123u8", ), ), [ @@ -203,14 +161,8 @@ Record( RequiredValue( @215-221 "u16Neg", [], - @224-231 UnaryOp( - @225-231 Int( - "123", - Exact( - U16, - ), - ), - @224-225 Negate, + @224-231 Num( + "-123u16", ), ), [ @@ -221,14 +173,8 @@ Record( RequiredValue( @235-241 "u32Neg", [], - @244-251 UnaryOp( - @245-251 Int( - "123", - Exact( - U32, - ), - ), - @244-245 Negate, + @244-251 Num( + "-123u32", ), ), [ @@ -239,14 +185,8 @@ Record( RequiredValue( @255-261 "u64Neg", [], - @264-271 UnaryOp( - @265-271 Int( - "123", - Exact( - U64, - ), - ), - @264-265 Negate, + @264-271 Num( + "-123u64", ), ), [ @@ -257,14 +197,8 @@ Record( RequiredValue( @275-282 "u128Neg", [], - @284-292 UnaryOp( - @285-292 Int( - "123", - Exact( - U128, - ), - ), - @284-285 Negate, + @284-292 Num( + "-123u128", ), ), [ @@ -275,14 +209,8 @@ Record( RequiredValue( @296-301 "i8Neg", [], - @305-311 UnaryOp( - @306-311 Int( - "123", - Exact( - I8, - ), - ), - @305-306 Negate, + @305-311 Num( + "-123i8", ), ), [ @@ -293,14 +221,8 @@ Record( RequiredValue( @315-321 "i16Neg", [], - @324-331 UnaryOp( - @325-331 Int( - "123", - Exact( - I16, - ), - ), - @324-325 Negate, + @324-331 Num( + "-123i16", ), ), [ @@ -311,14 +233,8 @@ Record( RequiredValue( @335-341 "i32Neg", [], - @344-351 UnaryOp( - @345-351 Int( - "123", - Exact( - I32, - ), - ), - @344-345 Negate, + @344-351 Num( + "-123i32", ), ), [ @@ -329,14 +245,8 @@ Record( RequiredValue( @355-361 "i64Neg", [], - @364-371 UnaryOp( - @365-371 Int( - "123", - Exact( - I64, - ), - ), - @364-365 Negate, + @364-371 Num( + "-123i64", ), ), [ @@ -347,14 +257,8 @@ Record( RequiredValue( @375-382 "i128Neg", [], - @384-392 UnaryOp( - @385-392 Int( - "123", - Exact( - I128, - ), - ), - @384-385 Negate, + @384-392 Num( + "-123i128", ), ), [ @@ -365,14 +269,8 @@ Record( RequiredValue( @396-402 "natNeg", [], - @405-412 UnaryOp( - @406-412 Int( - "123", - Exact( - Nat, - ), - ), - @405-406 Negate, + @405-412 Num( + "-123nat", ), ), [ @@ -383,11 +281,8 @@ Record( RequiredValue( @416-422 "decNeg", [], - @425-432 Float( - "-123", - Exact( - Dec, - ), + @425-432 Num( + "-123dec", ), ), [ @@ -399,12 +294,9 @@ Record( @436-441 "u8Bin", [], @445-452 NonBase10Int { - string: "101", + string: "101u8", base: Binary, is_negative: false, - bound: Exact( - U8, - ), }, ), [ @@ -416,12 +308,9 @@ Record( @456-462 "u16Bin", [], @465-473 NonBase10Int { - string: "101", + string: "101u16", base: Binary, is_negative: false, - bound: Exact( - U16, - ), }, ), [ @@ -433,12 +322,9 @@ Record( @477-483 "u32Bin", [], @486-494 NonBase10Int { - string: "101", + string: "101u32", base: Binary, is_negative: false, - bound: Exact( - U32, - ), }, ), [ @@ -450,12 +336,9 @@ Record( @498-504 "u64Bin", [], @507-515 NonBase10Int { - string: "101", + string: "101u64", base: Binary, is_negative: false, - bound: Exact( - U64, - ), }, ), [ @@ -467,12 +350,9 @@ Record( @519-526 "u128Bin", [], @528-537 NonBase10Int { - string: "101", + string: "101u128", base: Binary, is_negative: false, - bound: Exact( - U128, - ), }, ), [ @@ -484,12 +364,9 @@ Record( @541-546 "i8Bin", [], @550-557 NonBase10Int { - string: "101", + string: "101i8", base: Binary, is_negative: false, - bound: Exact( - I8, - ), }, ), [ @@ -501,12 +378,9 @@ Record( @561-567 "i16Bin", [], @570-578 NonBase10Int { - string: "101", + string: "101i16", base: Binary, is_negative: false, - bound: Exact( - I16, - ), }, ), [ @@ -518,12 +392,9 @@ Record( @582-588 "i32Bin", [], @591-599 NonBase10Int { - string: "101", + string: "101i32", base: Binary, is_negative: false, - bound: Exact( - I32, - ), }, ), [ @@ -535,12 +406,9 @@ Record( @603-609 "i64Bin", [], @612-620 NonBase10Int { - string: "101", + string: "101i64", base: Binary, is_negative: false, - bound: Exact( - I64, - ), }, ), [ @@ -552,12 +420,9 @@ Record( @624-631 "i128Bin", [], @633-642 NonBase10Int { - string: "101", + string: "101i128", base: Binary, is_negative: false, - bound: Exact( - I128, - ), }, ), [ @@ -569,12 +434,9 @@ Record( @646-652 "natBin", [], @655-663 NonBase10Int { - string: "101", + string: "101nat", base: Binary, is_negative: false, - bound: Exact( - Nat, - ), }, ), [ diff --git a/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast index 52eaa9c501..7caa61c752 100644 --- a/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast @@ -7,18 +7,12 @@ SpaceBefore( ), @20-21 Num( "5", - None { - width_variable: (), - }, ), ), ], @23-25 SpaceBefore( Num( "42", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast index 34053464c9..4dc9b56640 100644 --- a/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast @@ -3,17 +3,11 @@ BinOps( ( @0-1 Num( "1", - None { - width_variable: (), - }, ), @1-2 Minus, ), ], @2-3 Num( "2", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast index bcd254914a..e6bcba4260 100644 --- a/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast @@ -3,17 +3,11 @@ BinOps( ( @0-1 Num( "1", - None { - width_variable: (), - }, ), @1-2 Plus, ), ], @2-3 Num( "2", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast index 5fad56c4fb..a1fab8b814 100644 --- a/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast @@ -7,18 +7,12 @@ SpaceBefore( ), @22-23 Num( "5", - None { - width_variable: (), - }, ), ), ], @25-27 SpaceBefore( Num( "42", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast index 224073d034..28005999cb 100644 --- a/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast @@ -4,9 +4,6 @@ BinOps( @0-1 SpaceAfter( Num( "3", - None { - width_variable: (), - }, ), [ Newline, @@ -18,9 +15,6 @@ BinOps( @7-8 SpaceBefore( Num( "4", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast index 9a688604a1..859505b0f3 100644 --- a/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast @@ -2,9 +2,6 @@ List( [ @1-2 Num( "1", - None { - width_variable: (), - }, ), ], ) diff --git a/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast b/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast index b7e49e27aa..c8000965eb 100644 --- a/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast @@ -8,9 +8,6 @@ Apply( [ @7-8 Num( "1", - None { - width_variable: (), - }, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast b/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast index 46314703d7..3deb39c07f 100644 --- a/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast @@ -29,9 +29,6 @@ Defs( @28-30 SpaceBefore( Num( "42", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast b/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast index 319d317a93..f99bd7c8fb 100644 --- a/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast @@ -35,9 +35,6 @@ Defs( @35-37 SpaceBefore( Num( "42", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast index 766d0e8b93..d45bbdf656 100644 --- a/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast @@ -20,9 +20,6 @@ When( ), @21-22 Num( "0", - None { - width_variable: (), - }, ), ], Space, @@ -66,9 +63,6 @@ When( ), @63-64 Num( "0", - None { - width_variable: (), - }, ), @65-70 GlobalTag( "False", diff --git a/compiler/parse/tests/snapshots/pass/positive_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/positive_float.expr.result-ast index 52e82b2d9c..366609e578 100644 --- a/compiler/parse/tests/snapshots/pass/positive_float.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/positive_float.expr.result-ast @@ -1,6 +1,3 @@ Float( "42.9", - None { - width_variable: (), - }, ) diff --git a/compiler/parse/tests/snapshots/pass/positive_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/positive_int.expr.result-ast index 0768f4614f..23ee1510a7 100644 --- a/compiler/parse/tests/snapshots/pass/positive_int.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/positive_int.expr.result-ast @@ -1,6 +1,3 @@ Num( "42", - None { - width_variable: (), - }, ) diff --git a/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast index 12639adfbd..498e786bb0 100644 --- a/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast @@ -14,9 +14,6 @@ SpaceBefore( ), @29-30 Num( "5", - None { - width_variable: (), - }, ), ), @31-36 SpaceBefore( @@ -26,9 +23,6 @@ SpaceBefore( ), @35-36 Num( "6", - None { - width_variable: (), - }, ), ), [ @@ -39,9 +33,6 @@ SpaceBefore( @38-40 SpaceBefore( Num( "42", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast index 537ef938ff..6ae895c6e0 100644 --- a/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast @@ -103,9 +103,6 @@ Defs( @124-126 SpaceBefore( Num( "42", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast index 9eef119aa3..8666e0151f 100644 --- a/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast @@ -9,9 +9,6 @@ RecordUpdate { [], @19-20 Num( "5", - None { - width_variable: (), - }, ), ), @22-26 RequiredValue( @@ -19,9 +16,6 @@ RecordUpdate { [], @25-26 Num( "0", - None { - width_variable: (), - }, ), ), ], diff --git a/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast index 1f86fb19fe..1776ed2ff8 100644 --- a/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast @@ -11,17 +11,11 @@ Record( ), @18-19 Num( "1", - None { - width_variable: (), - }, ), ), ], @25-26 Num( "2", - None { - width_variable: (), - }, ), ), ), @@ -30,9 +24,6 @@ Record( [], @31-32 Num( "3", - None { - width_variable: (), - }, ), ), ], diff --git a/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast index c04f801fd2..1c5fbbaa84 100644 --- a/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast @@ -6,8 +6,5 @@ Closure( ], @6-8 Num( "42", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast index 0e172bc62e..d04c130ed2 100644 --- a/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast @@ -6,8 +6,5 @@ Closure( ], @6-8 Num( "42", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast index 40503abf8a..231c1c41b1 100644 --- a/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast @@ -2,9 +2,6 @@ List( [ @2-3 Num( "1", - None { - width_variable: (), - }, ), ], ) diff --git a/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast b/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast index e538188db8..e03334f801 100644 --- a/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast +++ b/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast @@ -6,9 +6,6 @@ ), @18-19 Num( "1", - None { - width_variable: (), - }, ), ), [ diff --git a/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast index d86ff0acd9..2e4b2c87d2 100644 --- a/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast @@ -10,8 +10,5 @@ BinOps( ], @4-5 Num( "2", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast index 5e69e1c81b..849e5609fd 100644 --- a/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast @@ -3,17 +3,11 @@ BinOps( ( @0-1 Num( "1", - None { - width_variable: (), - }, ), @3-4 Minus, ), ], @7-8 Num( "2", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast b/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast index 6d04ca0ef9..93bc481021 100644 --- a/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast @@ -6,8 +6,5 @@ Closure( ], @10-12 Num( "42", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast b/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast index 16aa825ad8..6b4d587eb8 100644 --- a/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast @@ -3,17 +3,11 @@ BinOps( ( @0-2 Num( "10", - None { - width_variable: (), - }, ), @2-3 Star, ), ], @3-5 Num( "11", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast index 1fcee5345c..72f726931a 100644 --- a/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast @@ -12,8 +12,5 @@ Closure( ], @12-14 Num( "42", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast index 1d6af8d3fb..84d9721826 100644 --- a/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast @@ -9,8 +9,5 @@ Closure( ], @9-11 Num( "42", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast index f6812214e6..7cab905c51 100644 --- a/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast @@ -19,9 +19,6 @@ When( ], value: @17-18 Num( "1", - None { - width_variable: (), - }, ), guard: None, }, @@ -40,9 +37,6 @@ When( ], value: @30-31 Num( "2", - None { - width_variable: (), - }, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast index 2cdab1165d..7ae7c0abd2 100644 --- a/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast @@ -7,9 +7,6 @@ SpaceBefore( ), @22-23 Num( "5", - None { - width_variable: (), - }, ), ), @24-29 SpaceBefore( @@ -19,9 +16,6 @@ SpaceBefore( ), @28-29 Num( "6", - None { - width_variable: (), - }, ), ), [ @@ -32,9 +26,6 @@ SpaceBefore( @31-33 SpaceBefore( Num( "42", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast b/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast index e1081f2e75..ca8a45f681 100644 --- a/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast @@ -30,9 +30,6 @@ Defs( @31-33 SpaceBefore( Num( "42", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast index e4654ad067..f42c07d6fd 100644 --- a/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast @@ -6,9 +6,6 @@ Apply( [ @6-8 Num( "12", - None { - width_variable: (), - }, ), @9-13 UnaryOp( @10-13 Var { diff --git a/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast index d5ed1524ef..acb507d2b2 100644 --- a/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast @@ -8,9 +8,6 @@ UnaryOp( [ @8-10 Num( "12", - None { - width_variable: (), - }, ), @11-14 Var { module_name: "", diff --git a/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast index a84f789127..b1f93ec376 100644 --- a/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast @@ -8,9 +8,6 @@ UnaryOp( [ @8-10 Num( "12", - None { - width_variable: (), - }, ), @11-14 Var { module_name: "", diff --git a/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast b/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast index 59058d3b61..744f023e4f 100644 --- a/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast @@ -21,9 +21,6 @@ SpaceBefore( @34-35 SpaceBefore( Num( "4", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast b/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast index 5ad87ac1e0..499046f993 100644 --- a/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast @@ -10,8 +10,5 @@ BinOps( ], @2-3 Num( "2", - None { - width_variable: (), - }, ), ) diff --git a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast index 44c0b33509..e0f8d41ece 100644 --- a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast @@ -18,9 +18,6 @@ When( value: @27-28 SpaceBefore( Num( "1", - None { - width_variable: (), - }, ), [ Newline, @@ -43,9 +40,6 @@ When( value: @47-48 SpaceBefore( Num( "2", - None { - width_variable: (), - }, ), [ Newline, @@ -68,9 +62,6 @@ When( value: @68-69 SpaceBefore( Num( "3", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast index b2e54cb8d0..9192d1f1b6 100644 --- a/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast @@ -19,9 +19,6 @@ ParensAround( value: @29-30 SpaceBefore( Num( "3", - None { - width_variable: (), - }, ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast index 8123439254..6f74fb5e4d 100644 --- a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast @@ -19,9 +19,6 @@ ParensAround( ], value: @21-22 Num( "3", - None { - width_variable: (), - }, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast index f3f0ae04bf..62dd86481e 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast @@ -24,9 +24,6 @@ When( ], value: @30-31 Num( "1", - None { - width_variable: (), - }, ), guard: None, }, @@ -55,9 +52,6 @@ When( ], value: @52-53 Num( "2", - None { - width_variable: (), - }, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast index 1e076d0e84..1822dfa962 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast @@ -9,9 +9,6 @@ When( @14-15 SpaceBefore( NumLiteral( "1", - None { - width_variable: (), - }, ), [ Newline, @@ -27,9 +24,6 @@ When( @32-33 SpaceBefore( Num( "2", - None { - width_variable: (), - }, ), [ Newline, @@ -53,9 +47,6 @@ When( ], value: @43-44 Num( "4", - None { - width_variable: (), - }, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast index 311eccafbd..8c6a5e4e61 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast @@ -9,9 +9,6 @@ When( @11-12 SpaceBefore( NumLiteral( "1", - None { - width_variable: (), - }, ), [ Newline, @@ -20,9 +17,6 @@ When( ], value: @16-17 Num( "2", - None { - width_variable: (), - }, ), guard: None, }, @@ -31,9 +25,6 @@ When( @19-21 SpaceBefore( NumLiteral( "-3", - None { - width_variable: (), - }, ), [ Newline, @@ -42,9 +33,6 @@ When( ], value: @25-26 Num( "4", - None { - width_variable: (), - }, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast index ed291e47fc..8866aef903 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast @@ -9,9 +9,6 @@ When( @11-12 SpaceBefore( NumLiteral( "1", - None { - width_variable: (), - }, ), [ Newline, @@ -20,9 +17,6 @@ When( ], value: @16-17 Num( "2", - None { - width_variable: (), - }, ), guard: None, }, @@ -31,9 +25,6 @@ When( @19-20 SpaceBefore( NumLiteral( "3", - None { - width_variable: (), - }, ), [ Newline, @@ -42,9 +33,6 @@ When( ], value: @24-25 Num( "4", - None { - width_variable: (), - }, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast index 6135ae7e36..cfb5251e17 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast @@ -21,9 +21,6 @@ When( ], value: @20-21 Num( "2", - None { - width_variable: (), - }, ), guard: None, }, @@ -47,9 +44,6 @@ When( ], value: @35-36 Num( "4", - None { - width_variable: (), - }, ), guard: None, }, diff --git a/compiler/parse/tests/snapshots/pass/zero_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/zero_float.expr.result-ast index 22b1820f75..b164e7a7fe 100644 --- a/compiler/parse/tests/snapshots/pass/zero_float.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/zero_float.expr.result-ast @@ -1,6 +1,3 @@ Float( "0.0", - None { - width_variable: (), - }, ) diff --git a/compiler/parse/tests/snapshots/pass/zero_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/zero_int.expr.result-ast index 385709ac81..0a7ff7c8ce 100644 --- a/compiler/parse/tests/snapshots/pass/zero_int.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/zero_int.expr.result-ast @@ -1,6 +1,3 @@ Num( "0", - None { - width_variable: (), - }, ) diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index eb10375ab9..b42d3edca6 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -16,7 +16,6 @@ mod test_parse { use bumpalo::collections::vec::Vec; use bumpalo::{self, Bump}; use roc_parse::ast::Expr::{self, *}; - use roc_parse::ast::NumericBound; use roc_parse::ast::StrLiteral::*; use roc_parse::ast::StrSegment::*; use roc_parse::ast::{self, EscapedChar}; @@ -496,32 +495,20 @@ mod test_parse { #[quickcheck] fn all_i64_values_parse(num: i64) { - assert_parses_to( - num.to_string().as_str(), - Num( - num.to_string().as_str(), - NumericBound::None { width_variable: () }, - ), - ); + assert_parses_to(num.to_string().as_str(), Num(num.to_string().as_str())); } #[quickcheck] fn all_f64_values_parse(num: f64) { let string = num.to_string(); if string.contains('.') { - assert_parses_to( - &string, - Float(&string, NumericBound::None { width_variable: () }), - ); + assert_parses_to(&string, Float(&string)); } else if num.is_nan() { assert_parses_to(&string, Expr::GlobalTag(&string)); } else if num.is_finite() { // These are whole numbers. Add the `.0` back to make float. let float_string = format!("{}.0", string); - assert_parses_to( - &float_string, - Float(&float_string, NumericBound::None { width_variable: () }), - ); + assert_parses_to(&float_string, Float(&float_string)); } } diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index e2dce07a9a..f7011d6b1d 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -107,6 +107,8 @@ pub enum IntErrorKind { Overflow, /// Integer is too small to store in target integer type. Underflow, + /// This is an integer, but it has a float numeric suffix. + FloatSuffix, } /// Enum to store the various types of errors that can cause parsing a float to fail. @@ -118,6 +120,8 @@ pub enum FloatErrorKind { NegativeInfinity, /// the literal is too large for f64 PositiveInfinity, + /// This is a float, but it has an integer numeric suffix. + IntSuffix, } #[derive(Clone, Debug, PartialEq)] diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index 0d66f56efa..d24823b26c 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -11,7 +11,7 @@ use roc_mono::ir::ProcLayout; use roc_mono::layout::{ union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant, WrappedVariant, }; -use roc_parse::ast::{AssignedField, Collection, Expr, NumericBound, StrLiteral}; +use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral}; use roc_region::all::{Loc, Region}; use roc_target::TargetInfo; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; @@ -1041,10 +1041,7 @@ fn byte_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: u8, content: &Cont // If this tag union represents a number, skip right to // returning it as an Expr::Num if let TagName::Private(Symbol::NUM_AT_NUM) = &tag_name { - return Expr::Num( - env.arena.alloc_str(&value.to_string()), - NumericBound::None { width_variable: () }, - ); + return Expr::Num(env.arena.alloc_str(&value.to_string())); } let loc_tag_expr = { @@ -1198,10 +1195,7 @@ fn num_to_ast<'a, M: AppMemory>( /// This is centralized in case we want to format it differently later, /// e.g. adding underscores for large numbers fn number_literal_to_ast(arena: &Bump, num: T) -> Expr<'_> { - Expr::Num( - arena.alloc(format!("{}", num)), - NumericBound::None { width_variable: () }, - ) + Expr::Num(arena.alloc(format!("{}", num))) } #[cfg(target_endian = "little")] diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index ed0c9d97e1..fd34e795c3 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -25,6 +25,7 @@ const DUPLICATE_NAME: &str = "DUPLICATE NAME"; const VALUE_NOT_EXPOSED: &str = "NOT EXPOSED"; const MODULE_NOT_IMPORTED: &str = "MODULE NOT IMPORTED"; const NESTED_DATATYPE: &str = "NESTED DATATYPE"; +const CONFLICTING_NUMBER_SUFFIX: &str = "CONFLICTING NUMBER SUFFIX"; pub fn can_problem<'b>( alloc: &'b RocDocAllocator<'b>, @@ -1055,13 +1056,23 @@ fn pretty_runtime_error<'b>( ]), alloc.region(lines.convert_region(region)), alloc.concat(vec![ - alloc.reflow("Floating point literals can only contain the digits 0-9, or use scientific notation 10e4"), + alloc.reflow("Floating point literals can only contain the digits 0-9, or use scientific notation 10e4, or have a float suffix."), ]), tip, ]); title = SYNTAX_PROBLEM; } + RuntimeError::InvalidFloat(FloatErrorKind::IntSuffix, region, _raw_str) => { + doc = alloc.stack(vec![ + alloc.concat(vec![alloc.reflow( + "This number literal is a float, but it has an integer suffix:", + )]), + alloc.region(lines.convert_region(region)), + ]); + + title = CONFLICTING_NUMBER_SUFFIX; + } RuntimeError::InvalidInt(error @ IntErrorKind::InvalidDigit, base, region, _raw_str) | RuntimeError::InvalidInt(error @ IntErrorKind::Empty, base, region, _raw_str) => { use roc_parse::ast::Base::*; @@ -1116,7 +1127,7 @@ fn pretty_runtime_error<'b>( alloc.text(plurals), contains, alloc.text(charset), - alloc.text("."), + alloc.text(", or have an integer suffix."), ]), tip, ]); @@ -1148,6 +1159,16 @@ fn pretty_runtime_error<'b>( title = SYNTAX_PROBLEM; } + RuntimeError::InvalidInt(IntErrorKind::FloatSuffix, _base, region, _raw_str) => { + doc = alloc.stack(vec![ + alloc.concat(vec![alloc.reflow( + "This number literal is an integer, but it has a float suffix:", + )]), + alloc.region(lines.convert_region(region)), + ]); + + title = CONFLICTING_NUMBER_SUFFIX; + } RuntimeError::InvalidOptionalValue { field_name, field_region, diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index 23b01009bc..f5f33e25fa 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -522,15 +522,6 @@ fn to_expr_report<'a>( &EExpr::Number(ENumber::End, pos) => { to_malformed_number_literal_report(alloc, lines, filename, pos) } - &EExpr::Number(ENumber::LiteralSuffix, pos) => { - to_number_literal_with_bad_suffix_report(alloc, lines, filename, pos) - } - &EExpr::Number(ENumber::IntHasFloatSuffix, pos) => { - to_number_literal_mismatch_suffix(alloc, lines, filename, pos, "an integer", "a float") - } - &EExpr::Number(ENumber::FloatHasIntSuffix, pos) => { - to_number_literal_mismatch_suffix(alloc, lines, filename, pos, "a float", "an integer") - } _ => todo!("unhandled parse error: {:?}", parse_problem), } @@ -1570,15 +1561,6 @@ fn to_pattern_report<'a>( &EPattern::NumLiteral(ENumber::End, pos) => { to_malformed_number_literal_report(alloc, lines, filename, pos) } - &EPattern::NumLiteral(ENumber::LiteralSuffix, pos) => { - to_number_literal_with_bad_suffix_report(alloc, lines, filename, pos) - } - &EPattern::NumLiteral(ENumber::IntHasFloatSuffix, pos) => { - to_number_literal_mismatch_suffix(alloc, lines, filename, pos, "an integer", "a float") - } - &EPattern::NumLiteral(ENumber::FloatHasIntSuffix, pos) => { - to_number_literal_mismatch_suffix(alloc, lines, filename, pos, "a float", "an integer") - } _ => todo!("unhandled parse error: {:?}", parse_problem), } } @@ -1994,57 +1976,6 @@ fn to_malformed_number_literal_report<'a>( } } -fn to_number_literal_with_bad_suffix_report<'a>( - alloc: &'a RocDocAllocator<'a>, - lines: &LineInfo, - filename: PathBuf, - start: Position, -) -> Report<'a> { - let surroundings = Region::new(start, start); - let region = LineColumnRegion::from_pos(lines.convert_pos(start)); - - let doc = alloc.stack(vec![ - alloc.reflow(r"It looks like you are trying to use type suffix on this number literal, but it's not one that I recognize:"), - alloc.region_with_subregion(lines.convert_region(surroundings), region), - ]); - - Report { - filename, - doc, - title: "INVALID NUMBER LITERAL SUFFIX".to_string(), - severity: Severity::RuntimeError, - } -} - -fn to_number_literal_mismatch_suffix<'a>( - alloc: &'a RocDocAllocator<'a>, - lines: &LineInfo, - filename: PathBuf, - start: Position, - literal_kind: &'static str, - suffix_kind: &'static str, -) -> Report<'a> { - let surroundings = Region::new(start, start); - let region = LineColumnRegion::from_pos(lines.convert_pos(start)); - - let doc = alloc.stack(vec![ - alloc.concat(vec![ - alloc.text(r"This number literal is "), - alloc.text(literal_kind), - alloc.text(", but its suffix says it's "), - alloc.text(suffix_kind), - ]), - alloc.region_with_subregion(lines.convert_region(surroundings), region), - ]); - - Report { - filename, - doc, - title: "NUMBER LITERAL CONFLICTS WITH SUFFIX".to_string(), - severity: Severity::RuntimeError, - } -} - fn to_type_report<'a>( alloc: &'a RocDocAllocator<'a>, lines: &LineInfo, diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index f980dfd674..c30e177ffb 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -1730,13 +1730,14 @@ mod test_reporting { ), indoc!( r#" - ── INVALID NUMBER LITERAL SUFFIX ─────────────────────────────────────────────── + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── - It looks like you are trying to use type suffix on this number - literal, but it's not one that I recognize: + This integer pattern is malformed: 2│ 100A -> 3 - ^ + ^^^^ + + Tip: Learn more about number literals at TODO "# ), ) @@ -1754,13 +1755,14 @@ mod test_reporting { ), indoc!( r#" - ── INVALID NUMBER LITERAL SUFFIX ─────────────────────────────────────────────── + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── - It looks like you are trying to use type suffix on this number - literal, but it's not one that I recognize: + This float pattern is malformed: 2│ 2.X -> 3 - ^ + ^^^ + + Tip: Learn more about number literals at TODO "# ), ) @@ -1778,13 +1780,14 @@ mod test_reporting { ), indoc!( r#" - ── INVALID NUMBER LITERAL SUFFIX ─────────────────────────────────────────────── + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── - It looks like you are trying to use type suffix on this number - literal, but it's not one that I recognize: + This hex integer pattern is malformed: 2│ 0xZ -> 3 - ^ + ^^^ + + Tip: Learn more about number literals at TODO "# ), ) @@ -3536,13 +3539,53 @@ mod test_reporting { ), indoc!( r#" - ── INVALID NUMBER LITERAL SUFFIX ─────────────────────────────────────────────── + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── - It looks like you are trying to use type suffix on this number - literal, but it's not one that I recognize: + This integer literal contains an invalid digit: 1│ dec = 100A - ^ + ^^^^ + + Integer literals can only contain the digits + 0-9, or have an integer suffix. + + Tip: Learn more about number literals at TODO + + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + + This hex integer literal contains an invalid digit: + + 3│ hex = 0xZZZ + ^^^^^ + + Hexadecimal (base-16) integer literals can only contain the digits + 0-9, a-f and A-F, or have an integer suffix. + + Tip: Learn more about number literals at TODO + + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + + This octal integer literal contains an invalid digit: + + 5│ oct = 0o9 + ^^^ + + Octal (base-8) integer literals can only contain the digits + 0-7, or have an integer suffix. + + Tip: Learn more about number literals at TODO + + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + + This binary integer literal contains an invalid digit: + + 7│ bin = 0b2 + ^^^ + + Binary (base-2) integer literals can only contain the digits + 0 and 1, or have an integer suffix. + + Tip: Learn more about number literals at TODO "# ), ) @@ -3574,7 +3617,7 @@ mod test_reporting { ^^ Hexadecimal (base-16) integer literals must contain at least one of - the digits 0-9, a-f and A-F. + the digits 0-9, a-f and A-F, or have an integer suffix. Tip: Learn more about number literals at TODO @@ -3586,7 +3629,7 @@ mod test_reporting { ^^ Octal (base-8) integer literals must contain at least one of the - digits 0-7. + digits 0-7, or have an integer suffix. Tip: Learn more about number literals at TODO @@ -3598,7 +3641,7 @@ mod test_reporting { ^^ Binary (base-2) integer literals must contain at least one of the - digits 0 and 1. + digits 0 and 1, or have an integer suffix. Tip: Learn more about number literals at TODO "# @@ -3618,13 +3661,17 @@ mod test_reporting { ), indoc!( r#" - ── INVALID NUMBER LITERAL SUFFIX ─────────────────────────────────────────────── + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── - It looks like you are trying to use type suffix on this number - literal, but it's not one that I recognize: + This float literal contains an invalid digit: 1│ x = 3.0A - ^ + ^^^^ + + Floating point literals can only contain the digits 0-9, or use + scientific notation 10e4, or have a float suffix. + + Tip: Learn more about number literals at TODO "# ), ) @@ -5456,7 +5503,7 @@ mod test_reporting { ^^^^^ Floating point literals can only contain the digits 0-9, or use - scientific notation 10e4 + scientific notation 10e4, or have a float suffix. Tip: Learn more about number literals at TODO "# @@ -7330,15 +7377,20 @@ I need all branches in an `if` to have the same type! 1u256 "# ), + // TODO: link to number suffixes indoc!( r#" - ── INVALID NUMBER LITERAL SUFFIX ─────────────────────────────────────────────── + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── - It looks like you are trying to use type suffix on this number - literal, but it's not one that I recognize: + This integer literal contains an invalid digit: 1│ 1u256 - ^ + ^^^^^ + + Integer literals can only contain the digits + 0-9, or have an integer suffix. + + Tip: Learn more about number literals at TODO "# ), ) @@ -7352,15 +7404,20 @@ I need all branches in an `if` to have the same type! 1u8u8 "# ), + // TODO: link to number suffixes indoc!( r#" - ── INVALID NUMBER LITERAL SUFFIX ─────────────────────────────────────────────── + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── - It looks like you are trying to use type suffix on this number - literal, but it's not one that I recognize: + This integer literal contains an invalid digit: 1│ 1u8u8 - ^ + ^^^^^ + + Integer literals can only contain the digits + 0-9, or have an integer suffix. + + Tip: Learn more about number literals at TODO "# ), ) @@ -7376,12 +7433,12 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NUMBER LITERAL CONFLICTS WITH SUFFIX ──────────────────────────────────────── + ── CONFLICTING NUMBER SUFFIX ─────────────────────────────────────────────────── - This number literal is an integer, but its suffix says it's a float + This number literal is an integer, but it has a float suffix: 1│ 0b1f32 - ^ + ^^^^^^ "# ), ) @@ -7397,12 +7454,12 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NUMBER LITERAL CONFLICTS WITH SUFFIX ──────────────────────────────────────── + ── CONFLICTING NUMBER SUFFIX ─────────────────────────────────────────────────── - This number literal is a float, but its suffix says it's an integer + This number literal is a float, but it has an integer suffix: 1│ 1.0u8 - ^ + ^^^^^ "# ), ) From df8113ce3216efe4ff690410402fe864c72cb6f7 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Tue, 1 Feb 2022 23:35:14 -0500 Subject: [PATCH 431/541] Typecheck numeric suffixes in patterns --- compiler/can/src/pattern.rs | 36 ++++++---- compiler/constrain/src/builtins.rs | 2 +- compiler/constrain/src/pattern.rs | 69 ++++++++++++++++--- compiler/mono/src/ir.rs | 10 +-- compiler/solve/tests/solve_expr.rs | 104 +++++++++++++++++++++++++++++ compiler/types/src/pretty_print.rs | 25 +++---- reporting/src/error/type.rs | 2 +- reporting/tests/test_reporting.rs | 63 +++++++++++++++++ 8 files changed, 270 insertions(+), 41 deletions(-) diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 4b43915701..797dbc961a 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -27,9 +27,9 @@ pub enum Pattern { ext_var: Variable, destructs: Vec>, }, - IntLiteral(Variable, Box, i64, NumericBound), NumLiteral(Variable, Box, i64, NumericBound), - FloatLiteral(Variable, Box, f64, NumericBound), + IntLiteral(Variable, Variable, Box, i64, NumericBound), + FloatLiteral(Variable, Variable, Box, f64, NumericBound), StrLiteral(Box), Underscore, @@ -192,9 +192,13 @@ pub fn canonicalize_pattern<'a>( let problem = MalformedPatternProblem::MalformedFloat; malformed_pattern(env, problem, region) } - Ok((float, bound)) => { - Pattern::FloatLiteral(var_store.fresh(), (str).into(), float, bound) - } + Ok((float, bound)) => Pattern::FloatLiteral( + var_store.fresh(), + var_store.fresh(), + (str).into(), + float, + bound, + ), }, ptype => unsupported_pattern(env, ptype, region), }, @@ -213,12 +217,20 @@ pub fn canonicalize_pattern<'a>( Ok(ParsedNumResult::UnknownNum(int)) => { Pattern::NumLiteral(var_store.fresh(), (str).into(), int, NumericBound::None) } - Ok(ParsedNumResult::Int(int, bound)) => { - Pattern::IntLiteral(var_store.fresh(), (str).into(), int, bound) - } - Ok(ParsedNumResult::Float(float, bound)) => { - Pattern::FloatLiteral(var_store.fresh(), (str).into(), float, bound) - } + Ok(ParsedNumResult::Int(int, bound)) => Pattern::IntLiteral( + var_store.fresh(), + var_store.fresh(), + (str).into(), + int, + bound, + ), + Ok(ParsedNumResult::Float(float, bound)) => Pattern::FloatLiteral( + var_store.fresh(), + var_store.fresh(), + (str).into(), + float, + bound, + ), }, ptype => unsupported_pattern(env, ptype, region), }, @@ -237,7 +249,7 @@ pub fn canonicalize_pattern<'a>( let sign_str = if is_negative { "-" } else { "" }; let int_str = format!("{}{}", sign_str, int.to_string()).into_boxed_str(); let i = if is_negative { -int } else { int }; - Pattern::IntLiteral(var_store.fresh(), int_str, i, bound) + Pattern::IntLiteral(var_store.fresh(), var_store.fresh(), int_str, i, bound) } }, ptype => unsupported_pattern(env, ptype, region), diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 62b4b07710..c11f8b5223 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -11,7 +11,7 @@ use roc_types::types::Category; use roc_types::types::Reason; use roc_types::types::Type::{self, *}; -fn add_numeric_bound_constr( +pub fn add_numeric_bound_constr( constrs: &mut Vec, num_type: Type, bound: impl TypedNumericBound, diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 894bd07609..242ff11335 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -178,34 +178,83 @@ pub fn constrain_pattern( ); } - NumLiteral(var, _, _, _bound) => { - // TODO: constrain bound here - state.vars.push(*var); + &NumLiteral(var, _, _, bound) => { + state.vars.push(var); + + let num_type = builtins::num_num(Type::Variable(var)); + + builtins::add_numeric_bound_constr( + &mut state.constraints, + num_type.clone(), + bound, + region, + Category::Num, + ); state.constraints.push(Constraint::Pattern( region, PatternCategory::Num, - builtins::num_num(Type::Variable(*var)), + num_type, expected, )); } - IntLiteral(precision_var, _, _, _bound) => { - // TODO: constrain bound here + &IntLiteral(num_var, precision_var, _, _, bound) => { + // First constraint on the free num var; this improves the resolved type quality in + // case the bound is an alias. + builtins::add_numeric_bound_constr( + &mut state.constraints, + Type::Variable(num_var), + bound, + region, + Category::Int, + ); + + // Link the free num var with the int var and our expectation. + let int_type = builtins::num_int(Type::Variable(precision_var)); + + state.constraints.push(Constraint::Eq( + Type::Variable(num_var), + Expected::NoExpectation(int_type), + Category::Int, + region, + )); + + // Also constrain the pattern against the num var, again to reuse aliases if they're present. state.constraints.push(Constraint::Pattern( region, PatternCategory::Int, - builtins::num_int(Type::Variable(*precision_var)), + Type::Variable(num_var), expected, )); } - FloatLiteral(precision_var, _, _, _bound) => { - // TODO: constrain bound here + &FloatLiteral(num_var, precision_var, _, _, bound) => { + // First constraint on the free num var; this improves the resolved type quality in + // case the bound is an alias. + builtins::add_numeric_bound_constr( + &mut state.constraints, + Type::Variable(num_var), + bound, + region, + Category::Float, + ); + + // Link the free num var with the float var and our expectation. + let float_type = builtins::num_float(Type::Variable(precision_var)); + + state.constraints.push(Constraint::Eq( + Type::Variable(num_var), + Expected::NoExpectation(float_type), + Category::Float, + region, + )); + + // Also constrain the pattern against the num var, again to reuse aliases if they're present. state.constraints.push(Constraint::Pattern( region, PatternCategory::Float, - builtins::num_float(Type::Variable(*precision_var)), + Type::Variable(num_var), expected, )); } diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index e2ddbd3e1f..2296d5b55d 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -7662,8 +7662,8 @@ fn from_can_pattern_help<'a>( match can_pattern { Underscore => Ok(Pattern::Underscore), Identifier(symbol) => Ok(Pattern::Identifier(*symbol)), - IntLiteral(var, _, int, _bound) => { - match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { + IntLiteral(_, precision_var, _, int, _bound) => { + match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) { IntOrFloat::Int(precision) => Ok(Pattern::IntLiteral(*int as i128, precision)), other => { panic!( @@ -7673,11 +7673,11 @@ fn from_can_pattern_help<'a>( } } } - FloatLiteral(var, float_str, float, _bound) => { + FloatLiteral(_, precision_var, float_str, float, _bound) => { // TODO: Can I reuse num_argument_to_int_or_float here if I pass in true? - match num_argument_to_int_or_float(env.subs, env.target_info, *var, true) { + match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, true) { IntOrFloat::Int(_) => { - panic!("Invalid precision for float pattern {:?}", var) + panic!("Invalid precision for float pattern {:?}", precision_var) } IntOrFloat::Float(precision) => { Ok(Pattern::FloatLiteral(f64::to_bits(*float), precision)) diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index ba2c9410c8..d8c5a38d6e 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -5092,4 +5092,108 @@ mod solve_expr { r#"{ bi128 : I128, bi16 : I16, bi32 : I32, bi64 : I64, bi8 : I8, bnat : Nat, bu128 : U128, bu16 : U16, bu32 : U32, bu64 : U64, bu8 : U8, dec : Dec, f32 : F32, f64 : F64, fdec : Dec, ff32 : F32, ff64 : F64, i128 : I128, i16 : I16, i32 : I32, i64 : I64, i8 : I8, nat : Nat, u128 : U128, u16 : U16, u32 : U32, u64 : U64, u8 : U8 }"#, ) } + + #[test] + fn numeric_literal_suffixes_in_pattern() { + infer_eq_without_problem( + indoc!( + r#" + { + u8: (\n -> + when n is + 123u8 -> n), + u16: (\n -> + when n is + 123u16 -> n), + u32: (\n -> + when n is + 123u32 -> n), + u64: (\n -> + when n is + 123u64 -> n), + u128: (\n -> + when n is + 123u128 -> n), + + i8: (\n -> + when n is + 123i8 -> n), + i16: (\n -> + when n is + 123i16 -> n), + i32: (\n -> + when n is + 123i32 -> n), + i64: (\n -> + when n is + 123i64 -> n), + i128: (\n -> + when n is + 123i128 -> n), + + nat: (\n -> + when n is + 123nat -> n), + + bu8: (\n -> + when n is + 0b11u8 -> n), + bu16: (\n -> + when n is + 0b11u16 -> n), + bu32: (\n -> + when n is + 0b11u32 -> n), + bu64: (\n -> + when n is + 0b11u64 -> n), + bu128: (\n -> + when n is + 0b11u128 -> n), + + bi8: (\n -> + when n is + 0b11i8 -> n), + bi16: (\n -> + when n is + 0b11i16 -> n), + bi32: (\n -> + when n is + 0b11i32 -> n), + bi64: (\n -> + when n is + 0b11i64 -> n), + bi128: (\n -> + when n is + 0b11i128 -> n), + + bnat: (\n -> + when n is + 0b11nat -> n), + + dec: (\n -> + when n is + 123.0dec -> n), + f32: (\n -> + when n is + 123.0f32 -> n), + f64: (\n -> + when n is + 123.0f64 -> n), + + fdec: (\n -> + when n is + 123dec -> n), + ff32: (\n -> + when n is + 123f32 -> n), + ff64: (\n -> + when n is + 123f64 -> n), + } + "# + ), + r#"{ bi128 : I128 -> I128, bi16 : I16 -> I16, bi32 : I32 -> I32, bi64 : I64 -> I64, bi8 : I8 -> I8, bnat : Nat -> Nat, bu128 : U128 -> U128, bu16 : U16 -> U16, bu32 : U32 -> U32, bu64 : U64 -> U64, bu8 : U8 -> U8, dec : Dec -> Dec, f32 : F32 -> F32, f64 : F64 -> F64, fdec : Dec -> Dec, ff32 : F32 -> F32, ff64 : F64 -> F64, i128 : I128 -> I128, i16 : I16 -> I16, i32 : I32 -> I32, i64 : I64 -> I64, i8 : I8 -> I8, nat : Nat -> Nat, u128 : U128 -> U128, u16 : U16 -> U16, u32 : U32 -> U32, u64 : U64 -> U64, u8 : U8 -> U8 }"#, + ) + } } diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index 186680debc..e8c8c3b4f3 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -364,6 +364,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa match *content { Alias(Symbol::NUM_BINARY32, _, _) => buf.push_str("F32"), Alias(Symbol::NUM_BINARY64, _, _) => buf.push_str("F64"), + Alias(Symbol::NUM_DECIMAL, _, _) => buf.push_str("Dec"), _ => write_parens!(write_parens, buf, { buf.push_str("Float "); write_content(env, content, subs, buf, parens); @@ -411,7 +412,7 @@ fn write_integer( use crate::subs::Content::*; macro_rules! derive_num_writes { - ($($lit:expr, $tag:path, $private_tag:path)*) => { + ($($lit:expr, $tag:path)*) => { write_parens!( write_parens, buf, @@ -431,17 +432,17 @@ fn write_integer( } derive_num_writes! { - "U8", Symbol::NUM_UNSIGNED8, Symbol::NUM_AT_UNSIGNED8 - "U16", Symbol::NUM_UNSIGNED16, Symbol::NUM_AT_UNSIGNED16 - "U32", Symbol::NUM_UNSIGNED32, Symbol::NUM_AT_UNSIGNED32 - "U64", Symbol::NUM_UNSIGNED64, Symbol::NUM_AT_UNSIGNED64 - "U128", Symbol::NUM_UNSIGNED128, Symbol::NUM_AT_UNSIGNED128 - "I8", Symbol::NUM_SIGNED8, Symbol::NUM_AT_SIGNED8 - "I16", Symbol::NUM_SIGNED16, Symbol::NUM_AT_SIGNED16 - "I32", Symbol::NUM_SIGNED32, Symbol::NUM_AT_SIGNED32 - "I64", Symbol::NUM_SIGNED64, Symbol::NUM_AT_SIGNED64 - "I128", Symbol::NUM_SIGNED128, Symbol::NUM_AT_SIGNED128 - "Nat", Symbol::NUM_NATURAL, Symbol::NUM_AT_NATURAL + "U8", Symbol::NUM_UNSIGNED8 + "U16", Symbol::NUM_UNSIGNED16 + "U32", Symbol::NUM_UNSIGNED32 + "U64", Symbol::NUM_UNSIGNED64 + "U128", Symbol::NUM_UNSIGNED128 + "I8", Symbol::NUM_SIGNED8 + "I16", Symbol::NUM_SIGNED16 + "I32", Symbol::NUM_SIGNED32 + "I64", Symbol::NUM_SIGNED64 + "I128", Symbol::NUM_SIGNED128 + "Nat", Symbol::NUM_NATURAL } } diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index fbad75fc6a..64963f262d 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -1468,7 +1468,7 @@ fn add_pattern_category<'b>( Str => alloc.reflow(" strings:"), Num => alloc.reflow(" numbers:"), Int => alloc.reflow(" integers:"), - Float => alloc.reflow(" floats"), + Float => alloc.reflow(" floats:"), }; alloc.concat(vec![i_am_trying_to_match, rest]) diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index c30e177ffb..e81c0919a5 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -7369,6 +7369,69 @@ I need all branches in an `if` to have the same type! 1, "f64", mismatched_suffix_f64 } + macro_rules! mismatched_suffix_tests_in_pattern { + ($($number:expr, $suffix:expr, $name:ident)*) => {$( + #[test] + fn $name() { + let number = $number.to_string(); + let mut typ = $suffix.to_string(); + typ.get_mut(0..1).unwrap().make_ascii_uppercase(); + let bad_suffix = if $suffix == "u8" { "i8" } else { "u8" }; + let bad_type = if $suffix == "u8" { "I8" } else { "U8" }; + let carets = "^".repeat(number.len() + $suffix.len()); + let kind = match $suffix { + "dec"|"f32"|"f64" => "floats", + _ => "integers", + }; + + report_problem_as( + &format!(indoc!( + r#" + when {}{} is + {}{} -> 1 + _ -> 1 + "# + ), number, bad_suffix, number, $suffix), + &format!(indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 1st pattern in this `when` is causing a mismatch: + + 2│ {}{} -> 1 + {} + + The first pattern is trying to match {}: + + {} + + But the expression between `when` and `is` has the type: + + {} + "# + ), number, $suffix, carets, kind, typ, bad_type), + ) + } + )*} + } + + mismatched_suffix_tests_in_pattern! { + 1, "u8", mismatched_suffix_u8_pattern + 1, "u16", mismatched_suffix_u16_pattern + 1, "u32", mismatched_suffix_u32_pattern + 1, "u64", mismatched_suffix_u64_pattern + 1, "u128", mismatched_suffix_u128_pattern + 1, "i8", mismatched_suffix_i8_pattern + 1, "i16", mismatched_suffix_i16_pattern + 1, "i32", mismatched_suffix_i32_pattern + 1, "i64", mismatched_suffix_i64_pattern + 1, "i128", mismatched_suffix_i128_pattern + 1, "nat", mismatched_suffix_nat_pattern + 1, "dec", mismatched_suffix_dec_pattern + 1, "f32", mismatched_suffix_f32_pattern + 1, "f64", mismatched_suffix_f64_pattern + } + #[test] fn bad_numeric_literal_suffix() { report_problem_as( From ae5766fdf5a13e622591fe9eee83b1dad73d9b26 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Tue, 1 Feb 2022 23:42:28 -0500 Subject: [PATCH 432/541] Folkert's suggestions --- compiler/constrain/src/builtins.rs | 6 +++--- compiler/types/src/pretty_print.rs | 2 +- reporting/src/error/type.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index c11f8b5223..2b769eae8a 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -328,10 +328,10 @@ impl TypedNumericBound for NumericBound { impl TypedNumericBound for NumericBound { fn concrete_num_type(&self) -> Option { - match *self { + match self { NumericBound::None => None, - NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(iw).concrete_num_type(), - NumericBound::Exact(NumWidth::Float(fw)) => NumericBound::Exact(fw).concrete_num_type(), + NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(*iw).concrete_num_type(), + NumericBound::Exact(NumWidth::Float(fw)) => NumericBound::Exact(*fw).concrete_num_type(), } } } diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index e8c8c3b4f3..ead9c32afd 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -361,7 +361,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa let arg_var = subs[arg_var_index]; let content = subs.get_content_without_compacting(arg_var); - match *content { + match content { Alias(Symbol::NUM_BINARY32, _, _) => buf.push_str("F32"), Alias(Symbol::NUM_BINARY64, _, _) => buf.push_str("F64"), Alias(Symbol::NUM_DECIMAL, _, _) => buf.push_str("Dec"), diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index 64963f262d..1b147ebb84 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -940,7 +940,7 @@ fn to_expr_report<'b>( region, Some(expr_region), alloc.text("This numeric literal is being used improperly:"), - alloc.text("Here's it's been used as"), + alloc.text("Here the value is used as a:"), alloc.text("But its suffix says it's a:"), None, ), From e7dcc2daa51e805cf2ddcea3b92fd96a9614ffae Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Wed, 2 Feb 2022 00:23:43 -0500 Subject: [PATCH 433/541] Move NumWidth to roc_can --- compiler/can/src/builtins.rs | 2 +- compiler/can/src/expr.rs | 3 +- compiler/can/src/num.rs | 41 ++++++++++++++- compiler/can/src/pattern.rs | 6 ++- compiler/constrain/src/builtins.rs | 6 ++- compiler/module/src/lib.rs | 1 - compiler/module/src/numeric.rs | 83 ------------------------------ 7 files changed, 50 insertions(+), 92 deletions(-) delete mode 100644 compiler/module/src/numeric.rs diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 0088fe6de7..42e27f49ed 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1,12 +1,12 @@ use crate::def::Def; use crate::expr::{self, ClosureData, Expr::*}; use crate::expr::{Expr, Field, Recursive}; +use crate::num::{FloatWidth, IntWidth, NumWidth, NumericBound}; use crate::pattern::Pattern; use roc_collections::all::SendMap; use roc_module::called_via::CalledVia; use roc_module::ident::{Lowercase, TagName}; use roc_module::low_level::LowLevel; -use roc_module::numeric::{FloatWidth, IntWidth, NumWidth, NumericBound}; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 60be157fe1..1f12b281ce 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -4,7 +4,7 @@ use crate::def::{can_defs_with_return, Def}; use crate::env::Env; use crate::num::{ finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result, - int_expr_from_result, num_expr_from_result, + int_expr_from_result, num_expr_from_result, FloatWidth, IntWidth, NumWidth, NumericBound, }; use crate::pattern::{canonicalize_pattern, Pattern}; use crate::procedure::References; @@ -13,7 +13,6 @@ use roc_collections::all::{ImSet, MutMap, MutSet, SendMap}; use roc_module::called_via::CalledVia; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; -use roc_module::numeric::{FloatWidth, IntWidth, NumWidth, NumericBound}; use roc_module::symbol::Symbol; use roc_parse::ast::{self, EscapedChar, StrLiteral}; use roc_parse::pattern::PatternType::*; diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index e134cace5a..cffbec95db 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -1,6 +1,5 @@ use crate::env::Env; use crate::expr::Expr; -use roc_module::numeric::{FloatWidth, IntWidth, NumWidth, NumericBound}; use roc_parse::ast::Base; use roc_problem::can::Problem; use roc_problem::can::RuntimeError::*; @@ -336,6 +335,46 @@ fn from_str_radix( Ok((result, bound)) } +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum IntWidth { + U8, + U16, + U32, + U64, + U128, + I8, + I16, + I32, + I64, + I128, + Nat, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum FloatWidth { + Dec, + F32, + F64, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum NumWidth { + Int(IntWidth), + Float(FloatWidth), +} + +/// Describes a bound on the width of a numeric literal. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum NumericBound +where + W: Copy, +{ + /// There is no bound on the width. + None, + /// Must have exactly the width `W`. + Exact(W), +} + /// An error which can be returned when parsing an integer. /// /// This error is used as the error type for the `from_str_radix()` functions diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 797dbc961a..eeb8c4b7ce 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -1,9 +1,11 @@ use crate::env::Env; use crate::expr::{canonicalize_expr, unescape_char, Expr, Output}; -use crate::num::{finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult}; +use crate::num::{ + finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatWidth, IntWidth, NumWidth, + NumericBound, ParsedNumResult, +}; use crate::scope::Scope; use roc_module::ident::{Ident, Lowercase, TagName}; -use roc_module::numeric::{FloatWidth, IntWidth, NumWidth, NumericBound}; use roc_module::symbol::Symbol; use roc_parse::ast::{self, StrLiteral, StrSegment}; use roc_parse::pattern::PatternType; diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 2b769eae8a..020d5b4564 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -1,9 +1,9 @@ use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::LetConstraint; use roc_can::expected::Expected::{self, *}; +use roc_can::num::{FloatWidth, IntWidth, NumWidth, NumericBound}; use roc_collections::all::SendMap; use roc_module::ident::{Lowercase, TagName}; -use roc_module::numeric::{FloatWidth, IntWidth, NumWidth, NumericBound}; use roc_module::symbol::Symbol; use roc_region::all::Region; use roc_types::subs::Variable; @@ -331,7 +331,9 @@ impl TypedNumericBound for NumericBound { match self { NumericBound::None => None, NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(*iw).concrete_num_type(), - NumericBound::Exact(NumWidth::Float(fw)) => NumericBound::Exact(*fw).concrete_num_type(), + NumericBound::Exact(NumWidth::Float(fw)) => { + NumericBound::Exact(*fw).concrete_num_type() + } } } } diff --git a/compiler/module/src/lib.rs b/compiler/module/src/lib.rs index f9f10fd02f..044f697a07 100644 --- a/compiler/module/src/lib.rs +++ b/compiler/module/src/lib.rs @@ -6,7 +6,6 @@ pub mod called_via; pub mod ident; pub mod low_level; pub mod module_err; -pub mod numeric; pub mod symbol; #[macro_use] diff --git a/compiler/module/src/numeric.rs b/compiler/module/src/numeric.rs deleted file mode 100644 index 0db0c45fc5..0000000000 --- a/compiler/module/src/numeric.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! Module `numeric` has utilities for numeric values in the Roc surface syntax. - -use std::fmt::Display; - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum IntWidth { - U8, - U16, - U32, - U64, - U128, - I8, - I16, - I32, - I64, - I128, - Nat, -} - -impl Display for IntWidth { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use IntWidth::*; - f.write_str(match self { - U8 => "u8", - U16 => "u16", - U32 => "u32", - U64 => "u64", - U128 => "u128", - I8 => "i8", - I16 => "i16", - I32 => "i32", - I64 => "i64", - I128 => "i128", - Nat => "nat", - }) - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum FloatWidth { - Dec, - F32, - F64, -} - -impl Display for FloatWidth { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use FloatWidth::*; - f.write_str(match self { - Dec => "dec", - F32 => "f32", - F64 => "f64", - }) - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum NumWidth { - Int(IntWidth), - Float(FloatWidth), -} - -impl Display for NumWidth { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use NumWidth::*; - match self { - Int(iw) => iw.fmt(f), - Float(fw) => fw.fmt(f), - } - } -} - -/// Describes a bound on the width of a numeric literal. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum NumericBound -where - W: Copy, -{ - /// There is no bound on the width. - None, - /// Must have exactly the width `W`. - Exact(W), -} From eb7ceddee40e3c3db7cc581d128a5953a81fad40 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Wed, 2 Feb 2022 01:57:37 -0500 Subject: [PATCH 434/541] Fix repl tests --- repl_cli/src/tests.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repl_cli/src/tests.rs b/repl_cli/src/tests.rs index aa0022384f..b3a2dc8690 100644 --- a/repl_cli/src/tests.rs +++ b/repl_cli/src/tests.rs @@ -192,7 +192,7 @@ fn num_addition() { #[test] fn int_addition() { - expect_success("0x1 + 2", "3 : I64"); + expect_success("0x1 + 2", "3 : Int *"); } #[test] @@ -425,7 +425,7 @@ fn nested_num_list() { fn nested_int_list() { expect_success( r#"[ [ [ 4, 3, 2 ], [ 1, 0x0 ] ], [ [] ], [] ]"#, - r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List I64))"#, + r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List Int *))"#, ); } @@ -461,7 +461,7 @@ fn num_bitwise_xor() { fn num_add_wrap() { expect_success( "Num.addWrap Num.maxI64 1", - "-9223372036854775808 : Int Signed64", + "-9223372036854775808 : I64", ); } @@ -469,13 +469,13 @@ fn num_add_wrap() { fn num_sub_wrap() { expect_success( "Num.subWrap Num.minI64 1", - "9223372036854775807 : Int Signed64", + "9223372036854775807 : I64", ); } #[test] fn num_mul_wrap() { - expect_success("Num.mulWrap Num.maxI64 2", "-2 : Int Signed64"); + expect_success("Num.mulWrap Num.maxI64 2", "-2 : I64"); } #[test] From 30be700f01edc2dea337833bf33409ba4e2b0c80 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Wed, 2 Feb 2022 08:24:03 -0500 Subject: [PATCH 435/541] Format --- repl_cli/src/tests.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/repl_cli/src/tests.rs b/repl_cli/src/tests.rs index b3a2dc8690..b95a50fef7 100644 --- a/repl_cli/src/tests.rs +++ b/repl_cli/src/tests.rs @@ -459,18 +459,12 @@ fn num_bitwise_xor() { #[test] fn num_add_wrap() { - expect_success( - "Num.addWrap Num.maxI64 1", - "-9223372036854775808 : I64", - ); + expect_success("Num.addWrap Num.maxI64 1", "-9223372036854775808 : I64"); } #[test] fn num_sub_wrap() { - expect_success( - "Num.subWrap Num.minI64 1", - "9223372036854775807 : I64", - ); + expect_success("Num.subWrap Num.minI64 1", "9223372036854775807 : I64"); } #[test] From 1fb746757c01373fde88fe70c7621b01204a3312 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Feb 2022 16:01:31 +0100 Subject: [PATCH 436/541] pipe module name into canonicalization of modules --- compiler/can/src/module.rs | 1 + compiler/load/src/file.rs | 18 ++++++------------ compiler/parse/src/header.rs | 9 +++++++++ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index e164fb8680..df35130db7 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -44,6 +44,7 @@ pub struct ModuleOutput { pub fn canonicalize_module_defs<'a, F>( arena: &Bump, loc_defs: &'a [Loc>], + module_name: &roc_parse::header::ModuleNameEnum, home: ModuleId, module_ids: &ModuleIds, exposed_ident_ids: IdentIds, diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index b73aaa4211..9069000931 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -24,8 +24,8 @@ use roc_mono::ir::{ }; use roc_mono::layout::{Layout, LayoutCache, LayoutProblem}; use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation}; -use roc_parse::header::PackageName; use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent}; +use roc_parse::header::{ModuleNameEnum, PackageName}; use roc_parse::ident::UppercaseIdent; use roc_parse::module::module_defs; use roc_parse::parser::{FileError, Parser, SyntaxError}; @@ -2760,15 +2760,6 @@ fn load_from_str<'a>( ) } -#[derive(Debug)] -enum ModuleNameEnum<'a> { - /// A filename - App(StrLiteral<'a>), - Interface(roc_parse::header::ModuleName<'a>), - Hosted(roc_parse::header::ModuleName<'a>), - PkgConfig, -} - #[derive(Debug)] struct HeaderInfo<'a> { loc_name: Loc>, @@ -3680,6 +3671,7 @@ where let canonicalized = canonicalize_module_defs( arena, parsed_defs, + &module_name, module_id, module_ids, exposed_ident_ids, @@ -3702,12 +3694,14 @@ where ModuleNameEnum::PkgConfig => None, ModuleNameEnum::App(_) => None, ModuleNameEnum::Interface(name) | ModuleNameEnum::Hosted(name) => { - Some(crate::docs::generate_module_docs( + let docs = crate::docs::generate_module_docs( module_output.scope, name.as_str().into(), &module_output.ident_ids, parsed_defs, - )) + ); + + Some(docs) } }; diff --git a/compiler/parse/src/header.rs b/compiler/parse/src/header.rs index 17aa05950d..cb2c4b7677 100644 --- a/compiler/parse/src/header.rs +++ b/compiler/parse/src/header.rs @@ -47,6 +47,15 @@ impl<'a> ModuleName<'a> { } } +#[derive(Debug)] +pub enum ModuleNameEnum<'a> { + /// A filename + App(StrLiteral<'a>), + Interface(ModuleName<'a>), + Hosted(ModuleName<'a>), + PkgConfig, +} + #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct ExposedName<'a>(&'a str); From 4994b0ef1b1a041aa8e2fbe7e4fd40a2b09640ea Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 2 Feb 2022 14:02:25 +0000 Subject: [PATCH 437/541] repl: replace LLVM jit macros with functions --- compiler/gen_llvm/src/lib.rs | 2 - compiler/gen_llvm/src/run_roc.rs | 121 ------------------------- repl_eval/src/eval.rs | 147 ++++++++++++++++++++++++------- 3 files changed, 115 insertions(+), 155 deletions(-) delete mode 100644 compiler/gen_llvm/src/run_roc.rs diff --git a/compiler/gen_llvm/src/lib.rs b/compiler/gen_llvm/src/lib.rs index bef97f894c..afe4ba885a 100644 --- a/compiler/gen_llvm/src/lib.rs +++ b/compiler/gen_llvm/src/lib.rs @@ -5,5 +5,3 @@ #![allow(clippy::float_cmp)] pub mod llvm; - -pub mod run_roc; diff --git a/compiler/gen_llvm/src/run_roc.rs b/compiler/gen_llvm/src/run_roc.rs deleted file mode 100644 index d6237ce629..0000000000 --- a/compiler/gen_llvm/src/run_roc.rs +++ /dev/null @@ -1,121 +0,0 @@ -use std::ffi::CString; -use std::mem::MaybeUninit; -use std::os::raw::c_char; - -/// This must have the same size as the repr() of RocCallResult! -pub const ROC_CALL_RESULT_DISCRIMINANT_SIZE: usize = std::mem::size_of::(); - -#[repr(C)] -pub struct RocCallResult { - tag: u64, - error_msg: *mut c_char, - value: MaybeUninit, -} - -impl From> for Result { - fn from(call_result: RocCallResult) -> Self { - match call_result.tag { - 0 => Ok(unsafe { call_result.value.assume_init() }), - _ => Err({ - let raw = unsafe { CString::from_raw(call_result.error_msg) }; - - let result = format!("{:?}", raw); - - // make sure rust does not try to free the Roc string - std::mem::forget(raw); - - result - }), - } - } -} - -#[macro_export] -macro_rules! run_jit_function { - ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{ - let v: String = String::new(); - run_jit_function!($lib, $main_fn_name, $ty, $transform, v) - }}; - - ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr) => {{ - use inkwell::context::Context; - use roc_gen_llvm::run_roc::RocCallResult; - use std::mem::MaybeUninit; - - unsafe { - let main: libloading::Symbol) -> ()> = - $lib.get($main_fn_name.as_bytes()) - .ok() - .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) - .expect("errored"); - - let mut result = MaybeUninit::uninit(); - - main(result.as_mut_ptr()); - - match result.assume_init().into() { - Ok(success) => { - // only if there are no exceptions thrown, check for errors - assert!($errors.is_empty(), "Encountered errors:\n{}", $errors); - - $transform(success) - } - Err(error_msg) => panic!("Roc failed with message: {}", error_msg), - } - } - }}; -} - -/// In the repl, we don't know the type that is returned; it it's large enough to not fit in 2 -/// registers (i.e. size bigger than 16 bytes on 64-bit systems), then we use this macro. -/// It explicitly allocates a buffer that the roc main function can write its result into. -#[macro_export] -macro_rules! run_jit_function_dynamic_type { - ($lib: expr, $main_fn_name: expr, $bytes:expr, $transform:expr) => {{ - let v: String = String::new(); - run_jit_function_dynamic_type!($lib, $main_fn_name, $bytes, $transform, v) - }}; - - ($lib: expr, $main_fn_name: expr, $bytes:expr, $transform:expr, $errors:expr) => {{ - use inkwell::context::Context; - use roc_gen_llvm::run_roc::RocCallResult; - - unsafe { - let main: libloading::Symbol = $lib - .get($main_fn_name.as_bytes()) - .ok() - .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) - .expect("errored"); - - let size = std::mem::size_of::>() + $bytes; - let layout = std::alloc::Layout::array::(size).unwrap(); - let result = std::alloc::alloc(layout); - main(result); - - let flag = *result; - - if flag == 0 { - $transform(result.add(std::mem::size_of::>()) as usize) - } else { - use std::ffi::CString; - use std::os::raw::c_char; - - // first field is a char pointer (to the error message) - // read value, and transmute to a pointer - let ptr_as_int = *(result as *const u64).offset(1); - let ptr = std::mem::transmute::(ptr_as_int); - - // make CString (null-terminated) - let raw = CString::from_raw(ptr); - - let result = format!("{:?}", raw); - - // make sure rust doesn't try to free the Roc constant string - std::mem::forget(raw); - - eprintln!("{}", result); - panic!("Roc hit an error"); - } - } - }}; -} diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index d24823b26c..300ddad081 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -1,6 +1,10 @@ use bumpalo::collections::Vec; use bumpalo::Bump; +use libloading::Library; use std::cmp::{max_by_key, min_by_key}; +use std::ffi::CString; +use std::mem::MaybeUninit; +use std::os::raw::c_char; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::MutMap; @@ -16,10 +20,6 @@ use roc_region::all::{Loc, Region}; use roc_target::TargetInfo; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; -#[cfg(feature = "llvm")] -use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type}; - -#[cfg(feature = "llvm")] type AppExecutable = libloading::Library; use super::app_memory::AppMemory; @@ -37,6 +37,99 @@ pub enum ToAstProblem { FunctionLayout, } +#[repr(C)] +pub struct RocCallResult { + tag: u64, + error_msg: *mut c_char, + value: MaybeUninit, +} + +impl From> for Result { + fn from(call_result: RocCallResult) -> Self { + match call_result.tag { + 0 => Ok(unsafe { call_result.value.assume_init() }), + _ => Err({ + let raw = unsafe { CString::from_raw(call_result.error_msg) }; + + let result = format!("{:?}", raw); + + // make sure rust does not try to free the Roc string + std::mem::forget(raw); + + result + }), + } + } +} + +pub fn run_jit_function<'a, T: Sized, F: Fn(T) -> Expr<'a>>( + lib: Library, + main_fn_name: &str, + transform: F, +) -> Expr<'a> { + unsafe { + let main: libloading::Symbol) -> ()> = lib + .get(main_fn_name.as_bytes()) + .ok() + .ok_or(format!("Unable to JIT compile `{}`", main_fn_name)) + .expect("errored"); + + let mut result = MaybeUninit::uninit(); + + main(result.as_mut_ptr()); + + match result.assume_init().into() { + Ok(success) => transform(success), + Err(error_msg) => panic!("Roc failed with message: {}", error_msg), + } + } +} + +/// In the repl, we don't know the type that is returned; if it's large enough to not fit in 2 +/// registers (i.e. size bigger than 16 bytes on 64-bit systems), then we use this function. +/// It explicitly allocates a buffer that the roc main function can write its result into. +pub fn run_jit_function_dynamic_type<'a, T: Sized, F: Fn(usize) -> T>( + lib: Library, + main_fn_name: &str, + bytes: usize, + transform: F, +) -> T { + unsafe { + let main: libloading::Symbol = lib + .get(main_fn_name.as_bytes()) + .ok() + .ok_or(format!("Unable to JIT compile `{}`", main_fn_name)) + .expect("errored"); + + let size = std::mem::size_of::>() + bytes; + let layout = std::alloc::Layout::array::(size).unwrap(); + let result = std::alloc::alloc(layout); + main(result); + + let flag = *result; + + if flag == 0 { + transform(result.add(std::mem::size_of::>()) as usize) + } else { + // first field is a char pointer (to the error message) + // read value, and transmute to a pointer + let ptr_as_int = *(result as *const u64).offset(1); + let ptr = std::mem::transmute::(ptr_as_int); + + // make CString (null-terminated) + let raw = CString::from_raw(ptr); + + let result = format!("{:?}", raw); + + // make sure rust doesn't try to free the Roc constant string + std::mem::forget(raw); + + eprintln!("{}", result); + panic!("Roc hit an error"); + } + } +} + /// JIT execute the given main function, and then wrap its results in an Expr /// so we can display them to the user using the formatter. /// @@ -274,7 +367,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( let (newtype_containers, content) = unroll_newtypes(env, content); let content = unroll_aliases(env, content); let result = match layout { - Layout::Builtin(Builtin::Bool) => Ok(run_jit_function!(app, main_fn_name, bool, |num| { + Layout::Builtin(Builtin::Bool) => Ok(run_jit_function(app, main_fn_name, |num: bool| { bool_to_ast(env, num, content) })), Layout::Builtin(Builtin::Int(int_width)) => { @@ -282,18 +375,16 @@ fn jit_to_ast_help<'a, M: AppMemory>( macro_rules! helper { ($ty:ty) => { - run_jit_function!(app, main_fn_name, $ty, |num| num_to_ast( - env, - number_literal_to_ast(env.arena, num), - content - )) + run_jit_function(app, main_fn_name, |num: $ty| { + num_to_ast(env, number_literal_to_ast(env.arena, num), content) + }) }; } let result = match int_width { U8 | I8 => { // NOTE: this is does not handle 8-bit numbers yet - run_jit_function!(app, main_fn_name, u8, |num| byte_to_ast(env, num, content)) + run_jit_function(app, main_fn_name, |num: u8| byte_to_ast(env, num, content)) } U16 => helper!(u16), U32 => helper!(u32), @@ -312,11 +403,9 @@ fn jit_to_ast_help<'a, M: AppMemory>( macro_rules! helper { ($ty:ty) => { - run_jit_function!(app, main_fn_name, $ty, |num| num_to_ast( - env, - number_literal_to_ast(env.arena, num), - content - )) + run_jit_function(app, main_fn_name, |num: $ty| { + num_to_ast(env, number_literal_to_ast(env.arena, num), content) + }) }; } @@ -328,17 +417,15 @@ fn jit_to_ast_help<'a, M: AppMemory>( Ok(result) } - Layout::Builtin(Builtin::Str) => Ok(run_jit_function!( + Layout::Builtin(Builtin::Str) => Ok(run_jit_function( app, main_fn_name, - &'static str, - |string: &'static str| { str_to_ast(env.arena, env.arena.alloc(string)) } + |string: &'static str| str_to_ast(env.arena, env.arena.alloc(string)), )), - Layout::Builtin(Builtin::List(elem_layout)) => Ok(run_jit_function!( + Layout::Builtin(Builtin::List(elem_layout)) => Ok(run_jit_function( app, main_fn_name, - (usize, usize), - |(addr, len): (usize, usize)| { list_to_ast(env, addr, len, elem_layout, content) } + |(addr, len): (usize, usize)| list_to_ast(env, addr, len, elem_layout, content), )), Layout::Builtin(other) => { todo!("add support for rendering builtin {:?} to the REPL", other) @@ -395,22 +482,20 @@ fn jit_to_ast_help<'a, M: AppMemory>( let result_stack_size = layout.stack_size(env.target_info); - run_jit_function_dynamic_type!( + run_jit_function_dynamic_type( app, main_fn_name, result_stack_size as usize, - |bytes_addr: usize| { struct_addr_to_ast(bytes_addr as usize) } + |bytes_addr: usize| struct_addr_to_ast(bytes_addr), ) } Layout::Union(UnionLayout::NonRecursive(_)) => { let size = layout.stack_size(env.target_info); - Ok(run_jit_function_dynamic_type!( + Ok(run_jit_function_dynamic_type( app, main_fn_name, size as usize, - |addr: usize| { - addr_to_ast(env, addr, layout, WhenRecursive::Unreachable, content) - } + |addr: usize| addr_to_ast(env, addr, layout, WhenRecursive::Unreachable, content), )) } Layout::Union(UnionLayout::Recursive(_)) @@ -418,13 +503,11 @@ fn jit_to_ast_help<'a, M: AppMemory>( | Layout::Union(UnionLayout::NullableUnwrapped { .. }) | Layout::Union(UnionLayout::NullableWrapped { .. }) => { let size = layout.stack_size(env.target_info); - Ok(run_jit_function_dynamic_type!( + Ok(run_jit_function_dynamic_type( app, main_fn_name, size as usize, - |addr: usize| { - addr_to_ast(env, addr, layout, WhenRecursive::Loop(*layout), content) - } + |addr: usize| addr_to_ast(env, addr, layout, WhenRecursive::Loop(*layout), content), )) } Layout::RecursivePointer => { From 87886e2e6b9a3639cc0bdb8292053584be35f1c5 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 2 Feb 2022 15:20:38 +0000 Subject: [PATCH 438/541] repl: implement string evaluation for 32-bit platforms --- repl_eval/src/eval.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index 300ddad081..7e5dfedb60 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -1282,15 +1282,15 @@ fn number_literal_to_ast(arena: &Bump, num: T) -> Expr<'_> } #[cfg(target_endian = "little")] -#[cfg(target_pointer_width = "64")] -/// TODO implement this for 32-bit and big-endian targets. NOTE: As of this writing, -/// we don't have big-endian small strings implemented yet! +/// NOTE: As of this writing, we don't have big-endian small strings implemented yet! fn str_to_ast<'a>(arena: &'a Bump, string: &'a str) -> Expr<'a> { - let bytes: [u8; 16] = unsafe { std::mem::transmute::<&'a str, [u8; 16]>(string) }; - let is_small = (bytes[15] & 0b1000_0000) != 0; + const STR_SIZE: usize = 2 * std::mem::size_of::(); + + let bytes: [u8; STR_SIZE] = unsafe { std::mem::transmute(string) }; + let is_small = (bytes[STR_SIZE - 1] & 0b1000_0000) != 0; if is_small { - let len = (bytes[15] & 0b0111_1111) as usize; + let len = (bytes[STR_SIZE - 1] & 0b0111_1111) as usize; let mut string = bumpalo::collections::String::with_capacity_in(len, arena); for byte in bytes.iter().take(len) { From c335a8b4064e6d28e0b16c831ca02f2e7b8c045d Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Feb 2022 16:04:00 +0100 Subject: [PATCH 439/541] move effect_module.rs --- compiler/{load => can}/src/effect_module.rs | 30 ++++++++++----------- compiler/can/src/lib.rs | 1 + 2 files changed, 16 insertions(+), 15 deletions(-) rename compiler/{load => can}/src/effect_module.rs (98%) diff --git a/compiler/load/src/effect_module.rs b/compiler/can/src/effect_module.rs similarity index 98% rename from compiler/load/src/effect_module.rs rename to compiler/can/src/effect_module.rs index 28bc2f22f4..0d4a276f01 100644 --- a/compiler/load/src/effect_module.rs +++ b/compiler/can/src/effect_module.rs @@ -1,9 +1,9 @@ -use roc_can::annotation::IntroducedVariables; -use roc_can::def::{Declaration, Def}; -use roc_can::env::Env; -use roc_can::expr::{ClosureData, Expr, Recursive}; -use roc_can::pattern::Pattern; -use roc_can::scope::Scope; +use crate::annotation::IntroducedVariables; +use crate::def::{Declaration, Def}; +use crate::env::Env; +use crate::expr::{ClosureData, Expr, Recursive}; +use crate::pattern::Pattern; +use crate::scope::Scope; use roc_collections::all::{MutSet, SendMap}; use roc_module::called_via::CalledVia; use roc_module::ident::TagName; @@ -220,7 +220,7 @@ fn build_effect_always( ) }; - let def_annotation = roc_can::def::Annotation { + let def_annotation = crate::def::Annotation { signature, introduced_variables, aliases: SendMap::default(), @@ -432,7 +432,7 @@ fn build_effect_map( ) }; - let def_annotation = roc_can::def::Annotation { + let def_annotation = crate::def::Annotation { signature, introduced_variables, aliases: SendMap::default(), @@ -599,7 +599,7 @@ fn build_effect_after( ) }; - let def_annotation = roc_can::def::Annotation { + let def_annotation = crate::def::Annotation { signature, introduced_variables, aliases: SendMap::default(), @@ -852,7 +852,7 @@ fn build_effect_forever( ) }; - let def_annotation = roc_can::def::Annotation { + let def_annotation = crate::def::Annotation { signature, introduced_variables, aliases: SendMap::default(), @@ -1150,7 +1150,7 @@ fn build_effect_loop( ) }; - let def_annotation = roc_can::def::Annotation { + let def_annotation = crate::def::Annotation { signature, introduced_variables, aliases: SendMap::default(), @@ -1334,7 +1334,7 @@ fn build_effect_loop_inner_body( let step_pattern = applied_tag_pattern(step_tag_name, &[new_state_symbol], var_store); - roc_can::expr::WhenBranch { + crate::expr::WhenBranch { patterns: vec![Loc::at_zero(step_pattern)], value: Loc::at_zero(force_thunk2), guard: None, @@ -1345,7 +1345,7 @@ fn build_effect_loop_inner_body( let done_tag_name = TagName::Global("Done".into()); let done_pattern = applied_tag_pattern(done_tag_name, &[done_symbol], var_store); - roc_can::expr::WhenBranch { + crate::expr::WhenBranch { patterns: vec![Loc::at_zero(done_pattern)], value: Loc::at_zero(Expr::Var(done_symbol)), guard: None, @@ -1376,7 +1376,7 @@ pub fn build_host_exposed_def( ident: &str, effect_tag_name: TagName, var_store: &mut VarStore, - annotation: roc_can::annotation::Annotation, + annotation: crate::annotation::Annotation, ) -> Def { let expr_var = var_store.fresh(); let pattern = Pattern::Identifier(symbol); @@ -1520,7 +1520,7 @@ pub fn build_host_exposed_def( } }; - let def_annotation = roc_can::def::Annotation { + let def_annotation = crate::def::Annotation { signature: annotation.typ, introduced_variables: annotation.introduced_variables, aliases: annotation.aliases, diff --git a/compiler/can/src/lib.rs b/compiler/can/src/lib.rs index 7230ab1a36..fd813a423f 100644 --- a/compiler/can/src/lib.rs +++ b/compiler/can/src/lib.rs @@ -5,6 +5,7 @@ pub mod annotation; pub mod builtins; pub mod constraint; pub mod def; +pub mod effect_module; pub mod env; pub mod expected; pub mod expr; From 14c0caa275139aeca0e744e03dda2c2620bda8d2 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Feb 2022 16:49:56 +0100 Subject: [PATCH 440/541] generate the Effect type and after/map/etc methods in a hosted module --- compiler/can/src/module.rs | 80 ++++++++++++++++++++++++++++++++++++-- compiler/load/src/file.rs | 8 ++-- compiler/load/src/lib.rs | 1 - 3 files changed, 80 insertions(+), 9 deletions(-) diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index df35130db7..ff5faba9c8 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -6,15 +6,16 @@ use crate::pattern::Pattern; use crate::scope::Scope; use bumpalo::Bump; use roc_collections::all::{MutMap, MutSet, SendMap}; -use roc_module::ident::Ident; use roc_module::ident::Lowercase; +use roc_module::ident::{Ident, TagName}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_parse::ast; +use roc_parse::header::ModuleNameEnum; use roc_parse::pattern::PatternType; use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; -use roc_types::types::Alias; +use roc_types::types::{Alias, Type}; #[derive(Debug)] pub struct Module { @@ -60,12 +61,48 @@ where { let mut can_exposed_imports = MutMap::default(); let mut scope = Scope::new(home, var_store); + let mut env = Env::new(home, dep_idents, module_ids, exposed_ident_ids); let num_deps = dep_idents.len(); for (name, alias) in aliases.into_iter() { scope.add_alias(name, alias.region, alias.type_variables, alias.typ); } + let effect_symbol = if let ModuleNameEnum::Hosted(_) = module_name { + let name = "Effect"; + let effect_symbol = scope + .introduce( + name.into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap(); + + let effect_tag_name = TagName::Private(effect_symbol); + + { + let a_var = var_store.fresh(); + + let actual = crate::effect_module::build_effect_actual( + effect_tag_name, + Type::Variable(a_var), + var_store, + ); + + scope.add_alias( + effect_symbol, + Region::zero(), + vec![Loc::at_zero(("a".into(), a_var))], + actual, + ); + } + + Some(effect_symbol) + } else { + None + }; + // Desugar operators (convert them to Apply calls, taking into account // operator precedence and associativity rules), before doing other canonicalization. // @@ -83,7 +120,6 @@ where })); } - let mut env = Env::new(home, dep_idents, module_ids, exposed_ident_ids); let mut lookups = Vec::with_capacity(num_deps); let mut rigid_variables = MutMap::default(); @@ -144,7 +180,7 @@ where } } - let (defs, scope, output, symbols_introduced) = canonicalize_defs( + let (defs, mut scope, output, symbols_introduced) = canonicalize_defs( &mut env, Output::default(), var_store, @@ -205,10 +241,34 @@ where // symbols from this set let mut exposed_but_not_defined = exposed_symbols.clone(); + let hosted_declarations = if let Some(effect_symbol) = effect_symbol { + let mut declarations = Vec::new(); + let mut exposed_symbols = MutSet::default(); + + // NOTE this currently builds all functions, not just the ones that the user requested + crate::effect_module::build_effect_builtins( + &mut env, + &mut scope, + effect_symbol, + var_store, + &mut exposed_symbols, + &mut declarations, + ); + + declarations + } else { + Vec::new() + }; + match sort_can_defs(&mut env, defs, Output::default()) { (Ok(mut declarations), output) => { use crate::def::Declaration::*; + for x in hosted_declarations { + // TODO should this be `insert(0, x)`? + declarations.push(x); + } + for decl in declarations.iter() { match decl { Declare(def) => { @@ -254,6 +314,18 @@ where let mut aliases = MutMap::default(); + if let Some(effect_symbol) = effect_symbol { + // Remove this from exposed_symbols, + // so that at the end of the process, + // we can see if there were any + // exposed symbols which did not have + // corresponding defs. + exposed_but_not_defined.remove(&effect_symbol); + + let hosted_alias = scope.lookup_alias(effect_symbol).unwrap().clone(); + aliases.insert(effect_symbol, hosted_alias); + } + for (symbol, alias) in output.aliases { // Remove this from exposed_symbols, // so that at the end of the process, diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 9069000931..ca470f12b5 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -3395,7 +3395,7 @@ fn fabricate_effects_module<'a>( let declared_name: ModuleName = name.into(); let hardcoded_effect_symbols = { - let mut functions: Vec<_> = crate::effect_module::BUILTIN_EFFECT_FUNCTIONS + let mut functions: Vec<_> = roc_can::effect_module::BUILTIN_EFFECT_FUNCTIONS .iter() .map(|x| x.0) .collect(); @@ -3499,7 +3499,7 @@ fn fabricate_effects_module<'a>( let alias = { let a_var = var_store.fresh(); - let actual = crate::effect_module::build_effect_actual( + let actual = roc_can::effect_module::build_effect_actual( effect_tag_name, Type::Variable(a_var), &mut var_store, @@ -3543,7 +3543,7 @@ fn fabricate_effects_module<'a>( &mut var_store, ); - let def = crate::effect_module::build_host_exposed_def( + let def = roc_can::effect_module::build_host_exposed_def( &mut can_env, &mut scope, symbol, @@ -3559,7 +3559,7 @@ fn fabricate_effects_module<'a>( } // define Effect.after, Effect.map etc. - crate::effect_module::build_effect_builtins( + roc_can::effect_module::build_effect_builtins( &mut can_env, &mut scope, effect_symbol, diff --git a/compiler/load/src/lib.rs b/compiler/load/src/lib.rs index 7b73ccefd5..a40812dc89 100644 --- a/compiler/load/src/lib.rs +++ b/compiler/load/src/lib.rs @@ -2,5 +2,4 @@ // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. #![allow(clippy::large_enum_variant)] pub mod docs; -pub mod effect_module; pub mod file; From 5699db99b376ddc0d8df4e954ee3915768f2e0f3 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Feb 2022 22:29:05 +0100 Subject: [PATCH 441/541] generate hosted functions (foreign functions supplied by the linker) --- compiler/can/src/module.rs | 56 +++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index ff5faba9c8..74c5d385b3 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -69,6 +69,7 @@ where } let effect_symbol = if let ModuleNameEnum::Hosted(_) = module_name { + // TODO extract effect name from the header let name = "Effect"; let effect_symbol = scope .introduce( @@ -269,7 +270,7 @@ where declarations.push(x); } - for decl in declarations.iter() { + for decl in declarations.iter_mut() { match decl { Declare(def) => { for (symbol, _) in def.pattern_vars.iter() { @@ -282,6 +283,59 @@ where exposed_but_not_defined.remove(symbol); } } + + // Temporary hack: we don't know exactly what symbols are hosted symbols, + // and which are meant to be normal definitions without a body. So for now + // we just assume they are hosted functions (meant to be provided by the platform) + if let Some(effect_symbol) = effect_symbol { + macro_rules! make_hosted_def { + () => { + let symbol = def.pattern_vars.iter().next().unwrap().0; + let ident_id = symbol.ident_id(); + let ident = + env.ident_ids.get_name(ident_id).unwrap().to_string(); + let def_annotation = def.annotation.clone().unwrap(); + let annotation = crate::annotation::Annotation { + typ: def_annotation.signature, + introduced_variables: def_annotation.introduced_variables, + references: Default::default(), + aliases: Default::default(), + }; + + let hosted_def = crate::effect_module::build_host_exposed_def( + &mut env, + &mut scope, + *symbol, + &ident, + TagName::Private(effect_symbol), + var_store, + annotation, + ); + + *def = hosted_def; + }; + } + + match &def.loc_expr.value { + Expr::RuntimeError(RuntimeError::NoImplementationNamed { + .. + }) => { + make_hosted_def!(); + } + Expr::Closure(closure_data) + if matches!( + closure_data.loc_body.value, + Expr::RuntimeError( + RuntimeError::NoImplementationNamed { .. } + ) + ) => + { + make_hosted_def!(); + } + + _ => {} + } + } } DeclareRec(defs) => { for def in defs { From 881e43336cfd975b8b68346537aeaa90b6e102fd Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 2 Feb 2022 21:35:53 +0000 Subject: [PATCH 442/541] repl: rename jit function for dynamic size and change its doc comment --- repl_eval/src/eval.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index 7e5dfedb60..ad3c4e9c49 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -62,6 +62,8 @@ impl From> for Result { } } +/// Run user code that returns a builtin layout +/// Size is determined at REPL compile time using the equivalent Rust type pub fn run_jit_function<'a, T: Sized, F: Fn(T) -> Expr<'a>>( lib: Library, main_fn_name: &str, @@ -85,10 +87,8 @@ pub fn run_jit_function<'a, T: Sized, F: Fn(T) -> Expr<'a>>( } } -/// In the repl, we don't know the type that is returned; if it's large enough to not fit in 2 -/// registers (i.e. size bigger than 16 bytes on 64-bit systems), then we use this function. -/// It explicitly allocates a buffer that the roc main function can write its result into. -pub fn run_jit_function_dynamic_type<'a, T: Sized, F: Fn(usize) -> T>( +/// Run user code that returns a struct or union, where the size is provided at runtime +pub fn run_jit_function_dynamic_size<'a, T: Sized, F: Fn(usize) -> T>( lib: Library, main_fn_name: &str, bytes: usize, @@ -482,7 +482,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( let result_stack_size = layout.stack_size(env.target_info); - run_jit_function_dynamic_type( + run_jit_function_dynamic_size( app, main_fn_name, result_stack_size as usize, @@ -491,7 +491,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( } Layout::Union(UnionLayout::NonRecursive(_)) => { let size = layout.stack_size(env.target_info); - Ok(run_jit_function_dynamic_type( + Ok(run_jit_function_dynamic_size( app, main_fn_name, size as usize, @@ -503,7 +503,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( | Layout::Union(UnionLayout::NullableUnwrapped { .. }) | Layout::Union(UnionLayout::NullableWrapped { .. }) => { let size = layout.stack_size(env.target_info); - Ok(run_jit_function_dynamic_type( + Ok(run_jit_function_dynamic_size( app, main_fn_name, size as usize, From 3829c629f15e0a72269bc11c4744cc8ed2691a5a Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Feb 2022 22:57:24 +0100 Subject: [PATCH 443/541] thread hosted info through --- compiler/load/src/file.rs | 93 +++++++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 33 deletions(-) diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index ca470f12b5..955454ffc8 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -681,6 +681,10 @@ enum HeaderFor<'a> { App { to_platform: To<'a>, }, + Hosted { + generates: UppercaseIdent<'a>, + generates_with: &'a [Loc>], + }, PkgConfig { /// usually `pf` config_shorthand: &'a str, @@ -861,6 +865,7 @@ enum PlatformPath<'a> { NotSpecified, Valid(To<'a>), RootIsInterface, + RootIsHosted, RootIsPkgConfig, } @@ -1742,6 +1747,12 @@ fn update<'a>( state.platform_path = PlatformPath::RootIsInterface; } } + Hosted { .. } => { + if header.is_root_module { + debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified)); + state.platform_path = PlatformPath::RootIsHosted; + } + } } // store an ID to name mapping, so we know the file to read when fetching dependencies' headers @@ -2551,7 +2562,33 @@ fn parse_header<'a>( packages: &[], exposes: unspace(arena, header.exposes.items), imports: unspace(arena, header.imports.items), - to_platform: None, + extra: HeaderFor::Interface, + }; + + Ok(send_header( + info, + parse_state, + module_ids, + ident_ids_by_module, + module_timing, + )) + } + Ok((ast::Module::Hosted { header }, parse_state)) => { + let info = HeaderInfo { + loc_name: Loc { + region: header.name.region, + value: ModuleNameEnum::Hosted(header.name.value), + }, + filename, + is_root_module, + opt_shorthand, + packages: &[], + exposes: unspace(arena, header.exposes.items), + imports: unspace(arena, header.imports.items), + extra: HeaderFor::Hosted { + generates: header.generates, + generates_with: unspace(arena, header.generates_with.items), + }, }; Ok(send_header( @@ -2593,7 +2630,9 @@ fn parse_header<'a>( packages, exposes, imports: unspace(arena, header.imports.items), - to_platform: Some(header.to.value), + extra: HeaderFor::App { + to_platform: header.to.value, + }, }; let (module_id, app_module_header_msg) = send_header( @@ -2669,29 +2708,6 @@ fn parse_header<'a>( header, module_timing, )), - Ok((ast::Module::Hosted { header }, parse_state)) => { - let info = HeaderInfo { - loc_name: Loc { - region: header.name.region, - value: ModuleNameEnum::Hosted(header.name.value), - }, - filename, - is_root_module, - opt_shorthand, - packages: &[], - exposes: unspace(arena, header.exposes.items), - imports: unspace(arena, header.imports.items), - to_platform: None, - }; - - Ok(send_header( - info, - parse_state, - module_ids, - ident_ids_by_module, - module_timing, - )) - } Err(fail) => Err(LoadingProblem::ParsingFailed( fail.map_problem(SyntaxError::Header) .into_file_error(filename), @@ -2769,7 +2785,7 @@ struct HeaderInfo<'a> { packages: &'a [Loc>], exposes: &'a [Loc>], imports: &'a [Loc>], - to_platform: Option>, + extra: HeaderFor<'a>, } #[allow(clippy::too_many_arguments)] @@ -2790,7 +2806,7 @@ fn send_header<'a>( packages, exposes, imports, - to_platform, + extra, } = info; let declared_name: ModuleName = match &loc_name.value { @@ -2927,11 +2943,6 @@ fn send_header<'a>( // We always need to send these, even if deps is empty, // because the coordinator thread needs to receive this message // to decrement its "pending" count. - let extra = match to_platform { - Some(to_platform) => HeaderFor::App { to_platform }, - None => HeaderFor::Interface, - }; - let mut package_qualified_imported_modules = MutSet::default(); for (pq_module_name, module_id) in &deps_by_name { match pq_module_name { @@ -4424,7 +4435,23 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin } RootIsInterface => { let doc = alloc.stack(vec![ - alloc.reflow(r"The input file is a interface file, but only app modules can be ran."), + alloc.reflow(r"The input file is an interface module, but only app modules can be ran."), + alloc.concat(vec![ + alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "), + alloc.reflow(r"but won't output any executable."), + ]) + ]); + + Report { + filename: "UNKNOWN.roc".into(), + doc, + title: "NO PLATFORM".to_string(), + severity: Severity::RuntimeError, + } + } + RootIsHosted => { + let doc = alloc.stack(vec![ + alloc.reflow(r"The input file is a hosted module, but only app modules can be ran."), alloc.concat(vec![ alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "), alloc.reflow(r"but won't output any executable."), From e8ab649f73797dd2d7dda1a2f39b31c0df775355 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 2 Feb 2022 21:57:16 +0000 Subject: [PATCH 444/541] repl: remove redundant str <-> &[u8] conversions on input source code --- repl_cli/src/lib.rs | 2 +- repl_eval/src/gen.rs | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/repl_cli/src/lib.rs b/repl_cli/src/lib.rs index 1d1f8b2901..0b20e8fe1b 100644 --- a/repl_cli/src/lib.rs +++ b/repl_cli/src/lib.rs @@ -231,7 +231,7 @@ fn eval_and_format<'a>(src: &str) -> Result> { use roc_mono::ir::OptLevel; use target_lexicon::Triple; - gen_and_eval(src.as_bytes(), Triple::host(), OptLevel::Normal).map(|output| match output { + gen_and_eval(src, Triple::host(), OptLevel::Normal).map(|output| match output { ReplOutput::NoProblems { expr, expr_type } => { format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type) } diff --git a/repl_eval/src/gen.rs b/repl_eval/src/gen.rs index d7fa969862..7167f8f9cc 100644 --- a/repl_eval/src/gen.rs +++ b/repl_eval/src/gen.rs @@ -1,6 +1,5 @@ use bumpalo::Bump; use std::path::{Path, PathBuf}; -use std::str::from_utf8_unchecked; use target_lexicon::Triple; use roc_can::builtins::builtin_defs_map; @@ -32,7 +31,7 @@ pub enum ReplOutput { } pub fn gen_and_eval<'a>( - src: &[u8], + src: &str, target: Triple, opt_level: OptLevel, ) -> Result> { @@ -44,7 +43,7 @@ pub fn gen_and_eval<'a>( } pub fn gen_and_eval_llvm<'a>( - src: &[u8], + src: &str, target: Triple, opt_level: OptLevel, ) -> Result> { @@ -209,21 +208,18 @@ fn format_answer( fn compile_to_mono<'a>( arena: &'a Bump, - src: &[u8], + src: &str, target_info: TargetInfo, ) -> Result, Vec> { use roc_reporting::report::{ can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE, }; - // SAFETY: we've already verified that this is valid UTF-8 during parsing. - let src_str: &str = unsafe { from_utf8_unchecked(src) }; - let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); let filename = PathBuf::from("REPL.roc"); let src_dir = Path::new("fake/test/path"); - let module_src = arena.alloc(promote_expr_to_module(src_str)); + let module_src = arena.alloc(promote_expr_to_module(src)); let exposed_types = MutMap::default(); let loaded = roc_load::file::load_and_monomorphize_from_str( From 16a2b660c3f4c549dee98126eba0490d8cd8819e Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 2 Feb 2022 23:11:52 +0100 Subject: [PATCH 445/541] bookkeeping --- compiler/can/src/module.rs | 8 +-- compiler/load/src/file.rs | 109 ++++++++++++++--------------------- compiler/parse/src/header.rs | 22 +++++++ compiler/parse/src/ident.rs | 6 ++ 4 files changed, 76 insertions(+), 69 deletions(-) diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 74c5d385b3..1c1aaebfc5 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -10,7 +10,7 @@ use roc_module::ident::Lowercase; use roc_module::ident::{Ident, TagName}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_parse::ast; -use roc_parse::header::ModuleNameEnum; +use roc_parse::header::HeaderFor; use roc_parse::pattern::PatternType; use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Loc, Region}; @@ -45,7 +45,7 @@ pub struct ModuleOutput { pub fn canonicalize_module_defs<'a, F>( arena: &Bump, loc_defs: &'a [Loc>], - module_name: &roc_parse::header::ModuleNameEnum, + header_for: &roc_parse::header::HeaderFor, home: ModuleId, module_ids: &ModuleIds, exposed_ident_ids: IdentIds, @@ -68,9 +68,9 @@ where scope.add_alias(name, alias.region, alias.type_variables, alias.typ); } - let effect_symbol = if let ModuleNameEnum::Hosted(_) = module_name { + let effect_symbol = if let HeaderFor::Hosted { generates, .. } = header_for { // TODO extract effect name from the header - let name = "Effect"; + let name: &str = generates.into(); let effect_symbol = scope .introduce( name.into(), diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 955454ffc8..47bd839320 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -25,7 +25,7 @@ use roc_mono::ir::{ use roc_mono::layout::{Layout, LayoutCache, LayoutProblem}; use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation}; use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent}; -use roc_parse::header::{ModuleNameEnum, PackageName}; +use roc_parse::header::{HeaderFor, ModuleNameEnum, PackageName}; use roc_parse::ident::UppercaseIdent; use roc_parse::module::module_defs; use roc_parse::parser::{FileError, Parser, SyntaxError}; @@ -674,28 +674,7 @@ struct ModuleHeader<'a> { exposed_imports: MutMap, parse_state: roc_parse::state::State<'a>, module_timing: ModuleTiming, -} - -#[derive(Debug)] -enum HeaderFor<'a> { - App { - to_platform: To<'a>, - }, - Hosted { - generates: UppercaseIdent<'a>, - generates_with: &'a [Loc>], - }, - PkgConfig { - /// usually `pf` - config_shorthand: &'a str, - /// the type scheme of the main function (required by the platform) - /// (currently unused) - #[allow(dead_code)] - platform_main_type: TypedIdent<'a>, - /// provided symbol to host (commonly `mainForHost`) - main_for_host: Symbol, - }, - Interface, + header_for: HeaderFor<'a>, } #[derive(Debug)] @@ -778,7 +757,6 @@ impl<'a> MonomorphizedModule<'a> { #[derive(Debug)] struct ParsedModule<'a> { module_id: ModuleId, - module_name: ModuleNameEnum<'a>, module_path: PathBuf, src: &'a str, module_timing: ModuleTiming, @@ -787,6 +765,8 @@ struct ParsedModule<'a> { exposed_ident_ids: IdentIds, exposed_imports: MutMap, parsed_defs: &'a [Loc>], + module_name: ModuleNameEnum<'a>, + header_for: HeaderFor<'a>, } /// A message sent out _from_ a worker thread, @@ -794,7 +774,7 @@ struct ParsedModule<'a> { #[derive(Debug)] enum Msg<'a> { Many(Vec>), - Header(ModuleHeader<'a>, HeaderFor<'a>), + Header(ModuleHeader<'a>), Parsed(ParsedModule<'a>), CanonicalizedAndConstrained { constrained_module: ConstrainedModule, @@ -1700,7 +1680,7 @@ fn update<'a>( Ok(state) } - Header(header, header_extra) => { + Header(header) => { use HeaderFor::*; log!("loaded header for {:?}", header.module_id); @@ -1717,13 +1697,13 @@ fn update<'a>( if let PkgConfig { config_shorthand, .. - } = header_extra + } = header.header_for { work.extend(state.dependencies.notify_package(config_shorthand)); } } - match header_extra { + match header.header_for { App { to_platform } => { debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified)); state.platform_path = PlatformPath::Valid(to_platform); @@ -2959,24 +2939,22 @@ fn send_header<'a>( ( home, - Msg::Header( - ModuleHeader { - module_id: home, - module_path: filename, - is_root_module, - exposed_ident_ids: ident_ids, - module_name: loc_name.value, - packages: package_entries, - imported_modules, - package_qualified_imported_modules, - deps_by_name, - exposes: exposed, - parse_state, - exposed_imports: scope, - module_timing, - }, - extra, - ), + Msg::Header(ModuleHeader { + module_id: home, + module_path: filename, + is_root_module, + exposed_ident_ids: ident_ids, + module_name: loc_name.value, + packages: package_entries, + imported_modules, + package_qualified_imported_modules, + deps_by_name, + exposes: exposed, + parse_state, + exposed_imports: scope, + module_timing, + header_for: extra, + }), ) } @@ -3200,24 +3178,22 @@ fn send_header_two<'a>( ( home, - Msg::Header( - ModuleHeader { - module_id: home, - module_path: filename, - is_root_module, - exposed_ident_ids: ident_ids, - module_name, - packages: package_entries, - imported_modules, - package_qualified_imported_modules, - deps_by_name, - exposes: exposed, - parse_state, - exposed_imports: scope, - module_timing, - }, - extra, - ), + Msg::Header(ModuleHeader { + module_id: home, + module_path: filename, + is_root_module, + exposed_ident_ids: ident_ids, + module_name, + packages: package_entries, + imported_modules, + package_qualified_imported_modules, + deps_by_name, + exposes: exposed, + parse_state, + exposed_imports: scope, + module_timing, + header_for: extra, + }), ) } @@ -3670,6 +3646,7 @@ where let ParsedModule { module_id, module_name, + header_for, exposed_ident_ids, parsed_defs, exposed_imports, @@ -3682,7 +3659,7 @@ where let canonicalized = canonicalize_module_defs( arena, parsed_defs, - &module_name, + &header_for, module_id, module_ids, exposed_ident_ids, @@ -3789,6 +3766,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result, Loadi exposed_ident_ids, exposed_imports, module_path, + header_for, .. } = header; @@ -3803,6 +3781,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result, Loadi exposed_ident_ids, exposed_imports, parsed_defs, + header_for, }; Ok(Msg::Parsed(parsed)) diff --git a/compiler/parse/src/header.rs b/compiler/parse/src/header.rs index cb2c4b7677..52b41448fd 100644 --- a/compiler/parse/src/header.rs +++ b/compiler/parse/src/header.rs @@ -8,6 +8,28 @@ use crate::string_literal; use bumpalo::collections::Vec; use roc_region::all::Loc; +#[derive(Debug)] +pub enum HeaderFor<'a> { + App { + to_platform: To<'a>, + }, + Hosted { + generates: UppercaseIdent<'a>, + generates_with: &'a [Loc>], + }, + PkgConfig { + /// usually `pf` + config_shorthand: &'a str, + /// the type scheme of the main function (required by the platform) + /// (currently unused) + #[allow(dead_code)] + platform_main_type: TypedIdent<'a>, + /// provided symbol to host (commonly `mainForHost`) + main_for_host: roc_module::symbol::Symbol, + }, + Interface, +} + #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub enum Version<'a> { Exact(&'a str), diff --git a/compiler/parse/src/ident.rs b/compiler/parse/src/ident.rs index 73044aa6a9..a310525f88 100644 --- a/compiler/parse/src/ident.rs +++ b/compiler/parse/src/ident.rs @@ -22,6 +22,12 @@ impl<'a> From> for &'a str { } } +impl<'a> From<&'a UppercaseIdent<'a>> for &'a str { + fn from(ident: &'a UppercaseIdent<'a>) -> Self { + ident.0 + } +} + /// The parser accepts all of these in any position where any one of them could /// appear. This way, canonicalization can give more helpful error messages like /// "you can't redefine this tag!" if you wrote `Foo = ...` or From 380c24ba806c68bc34dc1a961dabd926b4e034ef Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 2 Feb 2022 22:30:36 +0000 Subject: [PATCH 446/541] repl: move LLVM specific logic into roc_repl_cli --- Cargo.lock | 8 ++ repl_cli/Cargo.toml | 8 ++ repl_cli/src/lib.rs | 326 ++++++++++++++++++++++++++++++++++++++++--- repl_eval/src/gen.rs | 177 +---------------------- repl_eval/src/lib.rs | 42 +++++- 5 files changed, 371 insertions(+), 190 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cfbd14e68d..3fa833cf8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3670,10 +3670,18 @@ dependencies = [ "bumpalo", "const_format", "indoc", + "inkwell 0.1.0", + "libloading 0.7.1", + "roc_build", + "roc_collections", + "roc_gen_llvm", + "roc_load", "roc_mono", "roc_parse", "roc_repl_eval", + "roc_target", "roc_test_utils", + "roc_types", "rustyline", "rustyline-derive", "strip-ansi-escapes", diff --git a/repl_cli/Cargo.toml b/repl_cli/Cargo.toml index 00ab87922c..56fea8f4c0 100644 --- a/repl_cli/Cargo.toml +++ b/repl_cli/Cargo.toml @@ -8,13 +8,21 @@ version = "0.1.0" [dependencies] bumpalo = {version = "3.8.0", features = ["collections"]} const_format = "0.2.22" +inkwell = {path = "../vendor/inkwell"} +libloading = {version = "0.7.1"} rustyline = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"} rustyline-derive = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"} target-lexicon = "0.12.2" +roc_build = {path = "../compiler/build"} +roc_collections = {path = "../compiler/collections"} +roc_gen_llvm = {path = "../compiler/gen_llvm"} +roc_load = {path = "../compiler/load"} roc_mono = {path = "../compiler/mono"} roc_parse = {path = "../compiler/parse"} roc_repl_eval = {path = "../repl_eval"} +roc_target = {path = "../compiler/roc_target"} +roc_types = {path = "../compiler/types"} [dev-dependencies] indoc = "1.0.3" diff --git a/repl_cli/src/lib.rs b/repl_cli/src/lib.rs index 0b20e8fe1b..8d0316546f 100644 --- a/repl_cli/src/lib.rs +++ b/repl_cli/src/lib.rs @@ -1,12 +1,32 @@ +use bumpalo::Bump; use const_format::concatcp; +use inkwell::context::Context; +use inkwell::module::Linkage; +use libloading::{Library, Symbol}; +use roc_mono::ir::OptLevel; +use roc_parse::ast::Expr; +use roc_repl_eval::app_memory::AppMemoryInternal; use rustyline::highlight::{Highlighter, PromptInfo}; use rustyline::validate::{self, ValidationContext, ValidationResult, Validator}; use rustyline_derive::{Completer, Helper, Hinter}; use std::borrow::Cow; +use std::ffi::CString; use std::io; +use std::mem::MaybeUninit; +use std::os::raw::c_char; +use target_lexicon::Triple; +use roc_build::link::module_to_dylib; +use roc_build::program::FunctionIterator; +use roc_collections::all::MutSet; +use roc_gen_llvm::llvm::externs::add_default_roc_externs; +use roc_load::file::MonomorphizedModule; use roc_parse::parser::{EExpr, ELambda, SyntaxError}; -use roc_repl_eval::gen::{gen_and_eval, ReplOutput}; +use roc_repl_eval::eval::jit_to_ast; +use roc_repl_eval::gen::{compile_to_mono, format_answer, ReplOutput}; +use roc_repl_eval::ReplApp; +use roc_target::TargetInfo; +use roc_types::pretty_print::{content_to_string, name_all_type_vars}; #[cfg(test)] mod tests; @@ -105,6 +125,294 @@ impl Validator for InputValidator { } } +#[repr(C)] +pub struct RocCallResult { + tag: u64, + error_msg: *mut c_char, + value: MaybeUninit, +} + +impl From> for Result { + fn from(call_result: RocCallResult) -> Self { + match call_result.tag { + 0 => Ok(unsafe { call_result.value.assume_init() }), + _ => Err({ + let raw = unsafe { CString::from_raw(call_result.error_msg) }; + + let result = format!("{:?}", raw); + + // make sure rust does not try to free the Roc string + std::mem::forget(raw); + + result + }), + } + } +} + +struct CliReplApp { + lib: Library, +} + +macro_rules! deref_number { + ($name: ident, $t: ty) => { + fn $name(&self, addr: usize) -> $t { + let ptr = addr as *const _; + unsafe { *ptr } + } + }; +} + +impl ReplApp for CliReplApp { + deref_number!(deref_bool, bool); + + deref_number!(deref_u8, u8); + deref_number!(deref_u16, u16); + deref_number!(deref_u32, u32); + deref_number!(deref_u64, u64); + deref_number!(deref_u128, u128); + deref_number!(deref_usize, usize); + + deref_number!(deref_i8, i8); + deref_number!(deref_i16, i16); + deref_number!(deref_i32, i32); + deref_number!(deref_i64, i64); + deref_number!(deref_i128, i128); + deref_number!(deref_isize, isize); + + deref_number!(deref_f32, f32); + deref_number!(deref_f64, f64); + + fn deref_str(&self, addr: usize) -> &str { + unsafe { *(addr as *const &'static str) } + } + + /// Run user code that returns a type with a `Builtin` layout + /// Size of the return value is statically determined from its Rust type + fn call_function<'a, T: Sized, F: Fn(T) -> Expr<'a>>( + &self, + main_fn_name: &str, + transform: F, + ) -> Expr<'a> { + unsafe { + let main: Symbol) -> ()> = self + .lib + .get(main_fn_name.as_bytes()) + .ok() + .ok_or(format!("Unable to JIT compile `{}`", main_fn_name)) + .expect("errored"); + + let mut result = MaybeUninit::uninit(); + + main(result.as_mut_ptr()); + + match result.assume_init().into() { + Ok(success) => transform(success), + Err(error_msg) => panic!("Roc failed with message: {}", error_msg), + } + } + } + + /// Run user code that returns a struct or union, whose size is provided as an argument + fn call_function_dynamic_size<'a, T: Sized, F: Fn(usize) -> T>( + &self, + main_fn_name: &str, + bytes: usize, + transform: F, + ) -> T { + unsafe { + let main: Symbol = self + .lib + .get(main_fn_name.as_bytes()) + .ok() + .ok_or(format!("Unable to JIT compile `{}`", main_fn_name)) + .expect("errored"); + + let size = std::mem::size_of::>() + bytes; + let layout = std::alloc::Layout::array::(size).unwrap(); + let result = std::alloc::alloc(layout); + main(result); + + let flag = *result; + + if flag == 0 { + transform(result.add(std::mem::size_of::>()) as usize) + } else { + // first field is a char pointer (to the error message) + // read value, and transmute to a pointer + let ptr_as_int = *(result as *const u64).offset(1); + let ptr = std::mem::transmute::(ptr_as_int); + + // make CString (null-terminated) + let raw = CString::from_raw(ptr); + + let result = format!("{:?}", raw); + + // make sure rust doesn't try to free the Roc constant string + std::mem::forget(raw); + + eprintln!("{}", result); + panic!("Roc hit an error"); + } + } + } +} + +fn gen_and_eval_llvm<'a>( + src: &str, + target: Triple, + opt_level: OptLevel, +) -> Result> { + let arena = Bump::new(); + let target_info = TargetInfo::from(&target); + + let loaded = match compile_to_mono(&arena, src, target_info) { + Ok(x) => x, + Err(prob_strings) => { + return Ok(ReplOutput::Problems(prob_strings)); + } + }; + + let MonomorphizedModule { + procedures, + entry_point, + interns, + exposed_to_host, + mut subs, + module_id: home, + .. + } = loaded; + + let context = Context::create(); + let builder = context.create_builder(); + let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins( + &target, &context, "", + )); + + // mark our zig-defined builtins as internal + for function in FunctionIterator::from_module(module) { + let name = function.get_name().to_str().unwrap(); + if name.starts_with("roc_builtins") { + function.set_linkage(Linkage::Internal); + } + } + + debug_assert_eq!(exposed_to_host.values.len(), 1); + let (main_fn_symbol, main_fn_var) = exposed_to_host.values.iter().next().unwrap(); + let main_fn_symbol = *main_fn_symbol; + let main_fn_var = *main_fn_var; + + // pretty-print the expr type string for later. + name_all_type_vars(main_fn_var, &mut subs); + let content = subs.get_content_without_compacting(main_fn_var); + let expr_type_str = content_to_string(content, &subs, home, &interns); + + let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { + Some(layout) => *layout, + None => { + return Ok(ReplOutput::NoProblems { + expr: "".to_string(), + expr_type: expr_type_str, + }); + } + }; + + let module = arena.alloc(module); + let (module_pass, function_pass) = + roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level); + + let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module); + + // Compile and add all the Procs before adding main + let env = roc_gen_llvm::llvm::build::Env { + arena: &arena, + builder: &builder, + dibuilder: &dibuilder, + compile_unit: &compile_unit, + context: &context, + interns, + module, + target_info, + is_gen_test: true, // so roc_panic is generated + // important! we don't want any procedures to get the C calling convention + exposed_to_host: MutSet::default(), + }; + + // Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no + // platform to provide them. + add_default_roc_externs(&env); + + let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main( + &env, + opt_level, + procedures, + entry_point, + ); + + env.dibuilder.finalize(); + + // we don't use the debug info, and it causes weird errors. + module.strip_debug_info(); + + // Uncomment this to see the module's un-optimized LLVM instruction output: + // env.module.print_to_stderr(); + + if main_fn.verify(true) { + function_pass.run_on(&main_fn); + } else { + panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name); + } + + module_pass.run_on(env.module); + + // Uncomment this to see the module's optimized LLVM instruction output: + // env.module.print_to_stderr(); + + // Verify the module + if let Err(errors) = env.module.verify() { + panic!( + "Errors defining module:\n{}\n\nUncomment things nearby to see more details.", + errors.to_string() + ); + } + + let lib = module_to_dylib(env.module, &target, opt_level) + .expect("Error loading compiled dylib for test"); + + let res_answer = unsafe { + jit_to_ast( + &arena, + lib, + main_fn_name, + main_fn_layout, + content, + &env.interns, + home, + &subs, + target_info, + &AppMemoryInternal, + ) + }; + + let formatted = format_answer(&arena, res_answer, expr_type_str); + Ok(formatted) +} + +fn eval_and_format<'a>(src: &str) -> Result> { + let format_output = |output| match output { + ReplOutput::NoProblems { expr, expr_type } => { + format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type) + } + ReplOutput::Problems(lines) => format!("\n{}\n", lines.join("\n\n")), + }; + + gen_and_eval_llvm(src, Triple::host(), OptLevel::Normal).map(format_output) +} + +fn report_parse_error(fail: SyntaxError) { + println!("TODO Gracefully report parse error in repl: {:?}", fail); +} + pub fn main() -> io::Result<()> { use rustyline::error::ReadlineError; use rustyline::Editor; @@ -222,19 +530,3 @@ pub fn main() -> io::Result<()> { Ok(()) } - -fn report_parse_error(fail: SyntaxError) { - println!("TODO Gracefully report parse error in repl: {:?}", fail); -} - -fn eval_and_format<'a>(src: &str) -> Result> { - use roc_mono::ir::OptLevel; - use target_lexicon::Triple; - - gen_and_eval(src, Triple::host(), OptLevel::Normal).map(|output| match output { - ReplOutput::NoProblems { expr, expr_type } => { - format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type) - } - ReplOutput::Problems(lines) => format!("\n{}\n", lines.join("\n\n")), - }) -} diff --git a/repl_eval/src/gen.rs b/repl_eval/src/gen.rs index 7167f8f9cc..538eb1c146 100644 --- a/repl_eval/src/gen.rs +++ b/repl_eval/src/gen.rs @@ -1,195 +1,30 @@ use bumpalo::Bump; +use roc_load::file::{LoadingProblem, MonomorphizedModule}; use std::path::{Path, PathBuf}; -use target_lexicon::Triple; use roc_can::builtins::builtin_defs_map; -use roc_collections::all::{MutMap, MutSet}; +use roc_collections::all::MutMap; use roc_fmt::annotation::Formattable; use roc_fmt::annotation::{Newlines, Parens}; -use roc_gen_llvm::llvm::externs::add_default_roc_externs; -use roc_load::file::LoadingProblem; -use roc_load::file::MonomorphizedModule; -use roc_mono::ir::OptLevel; use roc_parse::ast::Expr; -use roc_parse::parser::SyntaxError; use roc_region::all::LineInfo; use roc_target::TargetInfo; -use roc_types::pretty_print::{content_to_string, name_all_type_vars}; -#[cfg(feature = "llvm")] -use { - inkwell::context::Context, inkwell::module::Linkage, roc_build::link::module_to_dylib, - roc_build::program::FunctionIterator, -}; - -use crate::app_memory::AppMemoryInternal; -use crate::eval::{self, ToAstProblem}; +use crate::eval::ToAstProblem; pub enum ReplOutput { Problems(Vec), NoProblems { expr: String, expr_type: String }, } -pub fn gen_and_eval<'a>( - src: &str, - target: Triple, - opt_level: OptLevel, -) -> Result> { - if cfg!(feature = "llvm") { - gen_and_eval_llvm(src, target, opt_level) - } else { - todo!("REPL must be compiled with LLVM feature for now") - } -} - -pub fn gen_and_eval_llvm<'a>( - src: &str, - target: Triple, - opt_level: OptLevel, -) -> Result> { - let arena = Bump::new(); - let target_info = TargetInfo::from(&target); - - let loaded = match compile_to_mono(&arena, src, target_info) { - Ok(x) => x, - Err(prob_strings) => { - return Ok(ReplOutput::Problems(prob_strings)); - } - }; - - let MonomorphizedModule { - procedures, - entry_point, - interns, - exposed_to_host, - mut subs, - module_id: home, - .. - } = loaded; - - let context = Context::create(); - let builder = context.create_builder(); - let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins( - &target, &context, "", - )); - - // mark our zig-defined builtins as internal - for function in FunctionIterator::from_module(module) { - let name = function.get_name().to_str().unwrap(); - if name.starts_with("roc_builtins") { - function.set_linkage(Linkage::Internal); - } - } - - debug_assert_eq!(exposed_to_host.values.len(), 1); - let (main_fn_symbol, main_fn_var) = exposed_to_host.values.iter().next().unwrap(); - let main_fn_symbol = *main_fn_symbol; - let main_fn_var = *main_fn_var; - - // pretty-print the expr type string for later. - name_all_type_vars(main_fn_var, &mut subs); - let content = subs.get_content_without_compacting(main_fn_var); - let expr_type_str = content_to_string(content, &subs, home, &interns); - - let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { - Some(layout) => *layout, - None => { - return Ok(ReplOutput::NoProblems { - expr: "".to_string(), - expr_type: expr_type_str, - }); - } - }; - - let module = arena.alloc(module); - let (module_pass, function_pass) = - roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level); - - let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module); - - // Compile and add all the Procs before adding main - let env = roc_gen_llvm::llvm::build::Env { - arena: &arena, - builder: &builder, - dibuilder: &dibuilder, - compile_unit: &compile_unit, - context: &context, - interns, - module, - target_info, - is_gen_test: true, // so roc_panic is generated - // important! we don't want any procedures to get the C calling convention - exposed_to_host: MutSet::default(), - }; - - // Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no - // platform to provide them. - add_default_roc_externs(&env); - - let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main( - &env, - opt_level, - procedures, - entry_point, - ); - - env.dibuilder.finalize(); - - // we don't use the debug info, and it causes weird errors. - module.strip_debug_info(); - - // Uncomment this to see the module's un-optimized LLVM instruction output: - // env.module.print_to_stderr(); - - if main_fn.verify(true) { - function_pass.run_on(&main_fn); - } else { - panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name); - } - - module_pass.run_on(env.module); - - // Uncomment this to see the module's optimized LLVM instruction output: - // env.module.print_to_stderr(); - - // Verify the module - if let Err(errors) = env.module.verify() { - panic!( - "Errors defining module:\n{}\n\nUncomment things nearby to see more details.", - errors.to_string() - ); - } - - let lib = module_to_dylib(env.module, &target, opt_level) - .expect("Error loading compiled dylib for test"); - - let res_answer = unsafe { - eval::jit_to_ast( - &arena, - lib, - main_fn_name, - main_fn_layout, - content, - &env.interns, - home, - &subs, - target_info, - &AppMemoryInternal, - ) - }; - - let formatted = format_answer(&arena, res_answer, expr_type_str); - Ok(formatted) -} - -fn format_answer( +pub fn format_answer( arena: &Bump, res_answer: Result, expr_type_str: String, ) -> ReplOutput { let mut expr = roc_fmt::Buf::new_in(arena); - use eval::ToAstProblem::*; + use ToAstProblem::*; match res_answer { Ok(answer) => { answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0); @@ -206,7 +41,7 @@ fn format_answer( } } -fn compile_to_mono<'a>( +pub fn compile_to_mono<'a>( arena: &'a Bump, src: &str, target_info: TargetInfo, diff --git a/repl_eval/src/lib.rs b/repl_eval/src/lib.rs index 358e05e325..0d009a5bc3 100644 --- a/repl_eval/src/lib.rs +++ b/repl_eval/src/lib.rs @@ -1,3 +1,41 @@ -mod app_memory; -mod eval; +use roc_parse::ast::Expr; + +pub mod app_memory; +pub mod eval; pub mod gen; + +pub trait ReplApp { + fn deref_bool(&self, addr: usize) -> bool; + + fn deref_u8(&self, addr: usize) -> u8; + fn deref_u16(&self, addr: usize) -> u16; + fn deref_u32(&self, addr: usize) -> u32; + fn deref_u64(&self, addr: usize) -> u64; + fn deref_u128(&self, addr: usize) -> u128; + fn deref_usize(&self, addr: usize) -> usize; + + fn deref_i8(&self, addr: usize) -> i8; + fn deref_i16(&self, addr: usize) -> i16; + fn deref_i32(&self, addr: usize) -> i32; + fn deref_i64(&self, addr: usize) -> i64; + fn deref_i128(&self, addr: usize) -> i128; + fn deref_isize(&self, addr: usize) -> isize; + + fn deref_f32(&self, addr: usize) -> f32; + fn deref_f64(&self, addr: usize) -> f64; + + fn deref_str(&self, addr: usize) -> &str; + + fn call_function<'a, Return: Sized, F: Fn(Return) -> Expr<'a>>( + &self, + main_fn_name: &str, + transform: F, + ) -> Expr<'a>; + + fn call_function_dynamic_size<'a, T: Sized, F: Fn(usize) -> T>( + &self, + main_fn_name: &str, + ret_bytes: usize, + transform: F, + ) -> T; +} From fdea13ac5509ad65efa373ac51cd00c97e2d6a55 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 2 Feb 2022 22:54:18 +0000 Subject: [PATCH 447/541] repl: thread the ReplApp through the evaluator --- repl_cli/src/lib.rs | 6 +- repl_eval/src/eval.rs | 263 +++++++++++++----------------------------- 2 files changed, 83 insertions(+), 186 deletions(-) diff --git a/repl_cli/src/lib.rs b/repl_cli/src/lib.rs index 8d0316546f..4b41e8b173 100644 --- a/repl_cli/src/lib.rs +++ b/repl_cli/src/lib.rs @@ -5,7 +5,6 @@ use inkwell::module::Linkage; use libloading::{Library, Symbol}; use roc_mono::ir::OptLevel; use roc_parse::ast::Expr; -use roc_repl_eval::app_memory::AppMemoryInternal; use rustyline::highlight::{Highlighter, PromptInfo}; use rustyline::validate::{self, ValidationContext, ValidationResult, Validator}; use rustyline_derive::{Completer, Helper, Hinter}; @@ -379,10 +378,12 @@ fn gen_and_eval_llvm<'a>( let lib = module_to_dylib(env.module, &target, opt_level) .expect("Error loading compiled dylib for test"); + let app = CliReplApp { lib }; + let res_answer = unsafe { jit_to_ast( &arena, - lib, + &app, main_fn_name, main_fn_layout, content, @@ -390,7 +391,6 @@ fn gen_and_eval_llvm<'a>( home, &subs, target_info, - &AppMemoryInternal, ) }; diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index ad3c4e9c49..9c3485fc8e 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -1,10 +1,6 @@ use bumpalo::collections::Vec; use bumpalo::Bump; -use libloading::Library; use std::cmp::{max_by_key, min_by_key}; -use std::ffi::CString; -use std::mem::MaybeUninit; -use std::os::raw::c_char; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::MutMap; @@ -20,14 +16,12 @@ use roc_region::all::{Loc, Region}; use roc_target::TargetInfo; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; -type AppExecutable = libloading::Library; +use crate::ReplApp; -use super::app_memory::AppMemory; - -struct Env<'a, 'env, M> { +struct Env<'a, 'env, A> { arena: &'a Bump, subs: &'env Subs, - app_memory: &'a M, + app: &'a A, target_info: TargetInfo, interns: &'env Interns, home: ModuleId, @@ -37,99 +31,6 @@ pub enum ToAstProblem { FunctionLayout, } -#[repr(C)] -pub struct RocCallResult { - tag: u64, - error_msg: *mut c_char, - value: MaybeUninit, -} - -impl From> for Result { - fn from(call_result: RocCallResult) -> Self { - match call_result.tag { - 0 => Ok(unsafe { call_result.value.assume_init() }), - _ => Err({ - let raw = unsafe { CString::from_raw(call_result.error_msg) }; - - let result = format!("{:?}", raw); - - // make sure rust does not try to free the Roc string - std::mem::forget(raw); - - result - }), - } - } -} - -/// Run user code that returns a builtin layout -/// Size is determined at REPL compile time using the equivalent Rust type -pub fn run_jit_function<'a, T: Sized, F: Fn(T) -> Expr<'a>>( - lib: Library, - main_fn_name: &str, - transform: F, -) -> Expr<'a> { - unsafe { - let main: libloading::Symbol) -> ()> = lib - .get(main_fn_name.as_bytes()) - .ok() - .ok_or(format!("Unable to JIT compile `{}`", main_fn_name)) - .expect("errored"); - - let mut result = MaybeUninit::uninit(); - - main(result.as_mut_ptr()); - - match result.assume_init().into() { - Ok(success) => transform(success), - Err(error_msg) => panic!("Roc failed with message: {}", error_msg), - } - } -} - -/// Run user code that returns a struct or union, where the size is provided at runtime -pub fn run_jit_function_dynamic_size<'a, T: Sized, F: Fn(usize) -> T>( - lib: Library, - main_fn_name: &str, - bytes: usize, - transform: F, -) -> T { - unsafe { - let main: libloading::Symbol = lib - .get(main_fn_name.as_bytes()) - .ok() - .ok_or(format!("Unable to JIT compile `{}`", main_fn_name)) - .expect("errored"); - - let size = std::mem::size_of::>() + bytes; - let layout = std::alloc::Layout::array::(size).unwrap(); - let result = std::alloc::alloc(layout); - main(result); - - let flag = *result; - - if flag == 0 { - transform(result.add(std::mem::size_of::>()) as usize) - } else { - // first field is a char pointer (to the error message) - // read value, and transmute to a pointer - let ptr_as_int = *(result as *const u64).offset(1); - let ptr = std::mem::transmute::(ptr_as_int); - - // make CString (null-terminated) - let raw = CString::from_raw(ptr); - - let result = format!("{:?}", raw); - - // make sure rust doesn't try to free the Roc constant string - std::mem::forget(raw); - - eprintln!("{}", result); - panic!("Roc hit an error"); - } - } -} - /// JIT execute the given main function, and then wrap its results in an Expr /// so we can display them to the user using the formatter. /// @@ -139,9 +40,9 @@ pub fn run_jit_function_dynamic_size<'a, T: Sized, F: Fn(usize) -> T>( /// we get to a struct or tag, we know what the labels are and can turn them /// back into the appropriate user-facing literals. #[allow(clippy::too_many_arguments)] -pub unsafe fn jit_to_ast<'a, M: AppMemory>( +pub unsafe fn jit_to_ast<'a, A: ReplApp>( arena: &'a Bump, - app: AppExecutable, + app: &'a A, main_fn_name: &str, layout: ProcLayout<'a>, content: &'a Content, @@ -149,12 +50,11 @@ pub unsafe fn jit_to_ast<'a, M: AppMemory>( home: ModuleId, subs: &'a Subs, target_info: TargetInfo, - app_memory: &'a M, ) -> Result, ToAstProblem> { let env = Env { arena, subs, - app_memory, + app, target_info, interns, home, @@ -166,7 +66,7 @@ pub unsafe fn jit_to_ast<'a, M: AppMemory>( result, } => { // this is a thunk - jit_to_ast_help(&env, app, main_fn_name, &result, content) + jit_to_ast_help(&env, main_fn_name, &result, content) } _ => Err(ToAstProblem::FunctionLayout), } @@ -186,8 +86,8 @@ enum NewtypeKind<'a> { /// /// The returned list of newtype containers is ordered by increasing depth. As an example, /// `A ({b : C 123})` will have the unrolled list `[Tag(A), RecordField(b), Tag(C)]`. -fn unroll_newtypes<'a, M: AppMemory>( - env: &Env<'a, 'a, M>, +fn unroll_newtypes<'a, A: ReplApp>( + env: &Env<'a, 'a, A>, mut content: &'a Content, ) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) { let mut newtype_containers = Vec::with_capacity_in(1, env.arena); @@ -220,8 +120,8 @@ fn unroll_newtypes<'a, M: AppMemory>( } } -fn apply_newtypes<'a, M: AppMemory>( - env: &Env<'a, '_, M>, +fn apply_newtypes<'a, A: ReplApp>( + env: &Env<'a, '_, A>, newtype_containers: Vec<'a, NewtypeKind<'a>>, mut expr: Expr<'a>, ) -> Expr<'a> { @@ -248,15 +148,15 @@ fn apply_newtypes<'a, M: AppMemory>( expr } -fn unroll_aliases<'a, M: AppMemory>(env: &Env<'a, 'a, M>, mut content: &'a Content) -> &'a Content { +fn unroll_aliases<'a, A: ReplApp>(env: &Env<'a, 'a, A>, mut content: &'a Content) -> &'a Content { while let Content::Alias(_, _, real) = content { content = env.subs.get_content_without_compacting(*real); } content } -fn unroll_recursion_var<'a, M: AppMemory>( - env: &Env<'a, 'a, M>, +fn unroll_recursion_var<'a, A: ReplApp>( + env: &Env<'a, 'a, A>, mut content: &'a Content, ) -> &'a Content { while let Content::RecursionVar { structure, .. } = content { @@ -265,8 +165,8 @@ fn unroll_recursion_var<'a, M: AppMemory>( content } -fn get_tags_vars_and_variant<'a, M: AppMemory>( - env: &Env<'a, '_, M>, +fn get_tags_vars_and_variant<'a, A: ReplApp>( + env: &Env<'a, '_, A>, tags: &UnionTags, opt_rec_var: Option, ) -> (MutMap>, UnionVariant<'a>) { @@ -283,8 +183,8 @@ fn get_tags_vars_and_variant<'a, M: AppMemory>( (vars_of_tag, union_variant) } -fn expr_of_tag<'a, M: AppMemory>( - env: &Env<'a, 'a, M>, +fn expr_of_tag<'a, A: ReplApp>( + env: &Env<'a, 'a, A>, data_addr: usize, tag_name: &TagName, arg_layouts: &'a [Layout<'a>], @@ -306,8 +206,8 @@ fn expr_of_tag<'a, M: AppMemory>( /// Gets the tag ID of a union variant, assuming that the tag ID is stored alongside (after) the /// tag data. The caller is expected to check that the tag ID is indeed stored this way. -fn tag_id_from_data<'a, M: AppMemory>( - env: &Env<'a, 'a, M>, +fn tag_id_from_data<'a, A: ReplApp>( + env: &Env<'a, 'a, A>, union_layout: UnionLayout, data_addr: usize, ) -> i64 { @@ -317,13 +217,13 @@ fn tag_id_from_data<'a, M: AppMemory>( let tag_id_addr = data_addr + offset as usize; match union_layout.tag_id_builtin() { - Builtin::Bool => env.app_memory.deref_bool(tag_id_addr) as i64, - Builtin::Int(IntWidth::U8) => env.app_memory.deref_u8(tag_id_addr) as i64, - Builtin::Int(IntWidth::U16) => env.app_memory.deref_u16(tag_id_addr) as i64, + Builtin::Bool => env.app.deref_bool(tag_id_addr) as i64, + Builtin::Int(IntWidth::U8) => env.app.deref_u8(tag_id_addr) as i64, + Builtin::Int(IntWidth::U16) => env.app.deref_u16(tag_id_addr) as i64, Builtin::Int(IntWidth::U64) => { // used by non-recursive unions at the // moment, remove if that is no longer the case - env.app_memory.deref_i64(tag_id_addr) + env.app.deref_i64(tag_id_addr) } _ => unreachable!("invalid tag id layout"), } @@ -333,13 +233,13 @@ fn tag_id_from_data<'a, M: AppMemory>( /// pointer to the data of the union variant). Returns /// - the tag ID /// - the address of the data of the union variant, unmasked if the pointer held the tag ID -fn tag_id_from_recursive_ptr<'a, M: AppMemory>( - env: &Env<'a, 'a, M>, +fn tag_id_from_recursive_ptr<'a, A: ReplApp>( + env: &Env<'a, 'a, A>, union_layout: UnionLayout, rec_addr: usize, ) -> (i64, usize) { let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.target_info); - let addr_with_id = env.app_memory.deref_usize(rec_addr); + let addr_with_id = env.app.deref_usize(rec_addr); if tag_in_ptr { let (_, tag_id_mask) = UnionLayout::tag_id_pointer_bits_and_mask(env.target_info); @@ -357,9 +257,8 @@ const OPAQUE_FUNCTION: Expr = Expr::Var { ident: "", }; -fn jit_to_ast_help<'a, M: AppMemory>( - env: &Env<'a, 'a, M>, - app: AppExecutable, +fn jit_to_ast_help<'a, A: ReplApp>( + env: &Env<'a, 'a, A>, main_fn_name: &str, layout: &Layout<'a>, content: &'a Content, @@ -367,15 +266,15 @@ fn jit_to_ast_help<'a, M: AppMemory>( let (newtype_containers, content) = unroll_newtypes(env, content); let content = unroll_aliases(env, content); let result = match layout { - Layout::Builtin(Builtin::Bool) => Ok(run_jit_function(app, main_fn_name, |num: bool| { - bool_to_ast(env, num, content) - })), + Layout::Builtin(Builtin::Bool) => Ok(env + .app + .call_function(main_fn_name, |num: bool| bool_to_ast(env, num, content))), Layout::Builtin(Builtin::Int(int_width)) => { use IntWidth::*; macro_rules! helper { ($ty:ty) => { - run_jit_function(app, main_fn_name, |num: $ty| { + env.app.call_function(main_fn_name, |num: $ty| { num_to_ast(env, number_literal_to_ast(env.arena, num), content) }) }; @@ -384,7 +283,8 @@ fn jit_to_ast_help<'a, M: AppMemory>( let result = match int_width { U8 | I8 => { // NOTE: this is does not handle 8-bit numbers yet - run_jit_function(app, main_fn_name, |num: u8| byte_to_ast(env, num, content)) + env.app + .call_function(main_fn_name, |num: u8| byte_to_ast(env, num, content)) } U16 => helper!(u16), U32 => helper!(u32), @@ -403,7 +303,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( macro_rules! helper { ($ty:ty) => { - run_jit_function(app, main_fn_name, |num: $ty| { + env.app.call_function(main_fn_name, |num: $ty| { num_to_ast(env, number_literal_to_ast(env.arena, num), content) }) }; @@ -417,16 +317,16 @@ fn jit_to_ast_help<'a, M: AppMemory>( Ok(result) } - Layout::Builtin(Builtin::Str) => Ok(run_jit_function( - app, - main_fn_name, - |string: &'static str| str_to_ast(env.arena, env.arena.alloc(string)), - )), - Layout::Builtin(Builtin::List(elem_layout)) => Ok(run_jit_function( - app, - main_fn_name, - |(addr, len): (usize, usize)| list_to_ast(env, addr, len, elem_layout, content), - )), + Layout::Builtin(Builtin::Str) => { + Ok(env.app.call_function(main_fn_name, |string: &'static str| { + str_to_ast(env.arena, env.arena.alloc(string)) + })) + } + Layout::Builtin(Builtin::List(elem_layout)) => Ok(env + .app + .call_function(main_fn_name, |(addr, len): (usize, usize)| { + list_to_ast(env, addr, len, elem_layout, content) + })), Layout::Builtin(other) => { todo!("add support for rendering builtin {:?} to the REPL", other) } @@ -482,8 +382,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( let result_stack_size = layout.stack_size(env.target_info); - run_jit_function_dynamic_size( - app, + env.app.call_function_dynamic_size( main_fn_name, result_stack_size as usize, |bytes_addr: usize| struct_addr_to_ast(bytes_addr), @@ -491,24 +390,22 @@ fn jit_to_ast_help<'a, M: AppMemory>( } Layout::Union(UnionLayout::NonRecursive(_)) => { let size = layout.stack_size(env.target_info); - Ok(run_jit_function_dynamic_size( - app, - main_fn_name, - size as usize, - |addr: usize| addr_to_ast(env, addr, layout, WhenRecursive::Unreachable, content), - )) + Ok(env + .app + .call_function_dynamic_size(main_fn_name, size as usize, |addr: usize| { + addr_to_ast(env, addr, layout, WhenRecursive::Unreachable, content) + })) } Layout::Union(UnionLayout::Recursive(_)) | Layout::Union(UnionLayout::NonNullableUnwrapped(_)) | Layout::Union(UnionLayout::NullableUnwrapped { .. }) | Layout::Union(UnionLayout::NullableWrapped { .. }) => { let size = layout.stack_size(env.target_info); - Ok(run_jit_function_dynamic_size( - app, - main_fn_name, - size as usize, - |addr: usize| addr_to_ast(env, addr, layout, WhenRecursive::Loop(*layout), content), - )) + Ok(env + .app + .call_function_dynamic_size(main_fn_name, size as usize, |addr: usize| { + addr_to_ast(env, addr, layout, WhenRecursive::Loop(*layout), content) + })) } Layout::RecursivePointer => { unreachable!("RecursivePointers can only be inside structures") @@ -518,7 +415,7 @@ fn jit_to_ast_help<'a, M: AppMemory>( result.map(|e| apply_newtypes(env, newtype_containers, e)) } -fn tag_name_to_expr<'a, M: AppMemory>(env: &Env<'a, '_, M>, tag_name: &TagName) -> Expr<'a> { +fn tag_name_to_expr<'a, A: ReplApp>(env: &Env<'a, '_, A>, tag_name: &TagName) -> Expr<'a> { match tag_name { TagName::Global(_) => Expr::GlobalTag( env.arena @@ -540,8 +437,8 @@ enum WhenRecursive<'a> { Loop(Layout<'a>), } -fn addr_to_ast<'a, M: AppMemory>( - env: &Env<'a, 'a, M>, +fn addr_to_ast<'a, A: ReplApp>( + env: &Env<'a, 'a, A>, addr: usize, layout: &Layout<'a>, when_recursive: WhenRecursive<'a>, @@ -549,7 +446,7 @@ fn addr_to_ast<'a, M: AppMemory>( ) -> Expr<'a> { macro_rules! helper { ($method: ident, $ty: ty) => {{ - let num: $ty = env.app_memory.$method(addr); + let num: $ty = env.app.$method(addr); num_to_ast(env, number_literal_to_ast(env.arena, num), content) }}; @@ -563,7 +460,7 @@ fn addr_to_ast<'a, M: AppMemory>( (_, Layout::Builtin(Builtin::Bool)) => { // TODO: bits are not as expected here. // num is always false at the moment. - let num: bool = env.app_memory.deref_bool(addr); + let num: bool = env.app.deref_bool(addr); bool_to_ast(env, num, content) } @@ -593,13 +490,13 @@ fn addr_to_ast<'a, M: AppMemory>( } } (_, Layout::Builtin(Builtin::List(elem_layout))) => { - let elem_addr = env.app_memory.deref_usize(addr); - let len = env.app_memory.deref_usize(addr + env.target_info.ptr_width() as usize); + let elem_addr = env.app.deref_usize(addr); + let len = env.app.deref_usize(addr + env.target_info.ptr_width() as usize); list_to_ast(env, elem_addr, len, elem_layout, content) } (_, Layout::Builtin(Builtin::Str)) => { - let arena_str = env.app_memory.deref_str(addr); + let arena_str = env.app.deref_str(addr); str_to_ast(env.arena, arena_str) } @@ -719,7 +616,7 @@ fn addr_to_ast<'a, M: AppMemory>( _ => unreachable!("any other variant would have a different layout"), }; - let data_addr = env.app_memory.deref_usize(addr); + let data_addr = env.app.deref_usize(addr); expr_of_tag( env, @@ -749,7 +646,7 @@ fn addr_to_ast<'a, M: AppMemory>( _ => unreachable!("any other variant would have a different layout"), }; - let data_addr = env.app_memory.deref_usize(addr); + let data_addr = env.app.deref_usize(addr); if data_addr == 0 { tag_name_to_expr(env, &nullable_name) } else { @@ -780,7 +677,7 @@ fn addr_to_ast<'a, M: AppMemory>( _ => unreachable!("any other variant would have a different layout"), }; - let data_addr = env.app_memory.deref_usize(addr); + let data_addr = env.app.deref_usize(addr); if data_addr == 0 { tag_name_to_expr(env, &nullable_name) } else { @@ -809,8 +706,8 @@ fn addr_to_ast<'a, M: AppMemory>( apply_newtypes(env, newtype_containers, expr) } -fn list_to_ast<'a, M: AppMemory>( - env: &Env<'a, 'a, M>, +fn list_to_ast<'a, A: ReplApp>( + env: &Env<'a, 'a, A>, addr: usize, len: usize, elem_layout: &Layout<'a>, @@ -858,8 +755,8 @@ fn list_to_ast<'a, M: AppMemory>( Expr::List(Collection::with_items(output)) } -fn single_tag_union_to_ast<'a, M: AppMemory>( - env: &Env<'a, 'a, M>, +fn single_tag_union_to_ast<'a, A: ReplApp>( + env: &Env<'a, 'a, A>, addr: usize, field_layouts: &'a [Layout<'a>], tag_name: &TagName, @@ -884,8 +781,8 @@ fn single_tag_union_to_ast<'a, M: AppMemory>( Expr::Apply(loc_tag_expr, output, CalledVia::Space) } -fn sequence_of_expr<'a, I, M: AppMemory>( - env: &Env<'a, 'a, M>, +fn sequence_of_expr<'a, I, A: ReplApp>( + env: &Env<'a, 'a, A>, addr: usize, sequence: I, when_recursive: WhenRecursive<'a>, @@ -915,8 +812,8 @@ where output } -fn struct_to_ast<'a, M: AppMemory>( - env: &Env<'a, 'a, M>, +fn struct_to_ast<'a, A: ReplApp>( + env: &Env<'a, 'a, A>, addr: usize, field_layouts: &'a [Layout<'a>], record_fields: RecordFields, @@ -1032,7 +929,7 @@ fn unpack_two_element_tag_union( (tag_name1, payload_vars1, tag_name2, payload_vars2) } -fn bool_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: bool, content: &Content) -> Expr<'a> { +fn bool_to_ast<'a, A: ReplApp>(env: &Env<'a, '_, A>, value: bool, content: &Content) -> Expr<'a> { use Content::*; let arena = env.arena; @@ -1110,7 +1007,7 @@ fn bool_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: bool, content: &Co } } -fn byte_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: u8, content: &Content) -> Expr<'a> { +fn byte_to_ast<'a, A: ReplApp>(env: &Env<'a, '_, A>, value: u8, content: &Content) -> Expr<'a> { use Content::*; let arena = env.arena; @@ -1202,8 +1099,8 @@ fn byte_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: u8, content: &Cont } } -fn num_to_ast<'a, M: AppMemory>( - env: &Env<'a, '_, M>, +fn num_to_ast<'a, A: ReplApp>( + env: &Env<'a, '_, A>, num_expr: Expr<'a>, content: &Content, ) -> Expr<'a> { From 9ace2fd9a3ceeff0d31f31f98f9c6ab01a7485d8 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 2 Feb 2022 23:05:09 +0000 Subject: [PATCH 448/541] repl: delete app_memory and put "external memory" logic into repl_wasm --- Cargo.lock | 8 ++ Cargo.toml | 1 + repl_eval/src/app_memory.rs | 159 ------------------------------------ repl_eval/src/lib.rs | 1 - repl_wasm/Cargo.toml | 10 +++ repl_wasm/src/lib.rs | 66 +++++++++++++++ 6 files changed, 85 insertions(+), 160 deletions(-) delete mode 100644 repl_eval/src/app_memory.rs create mode 100644 repl_wasm/Cargo.toml create mode 100644 repl_wasm/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 3fa833cf8d..7b8f5eb764 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3712,6 +3712,14 @@ dependencies = [ "target-lexicon", ] +[[package]] +name = "roc_repl_wasm" +version = "0.1.0" +dependencies = [ + "roc_parse", + "roc_repl_eval", +] + [[package]] name = "roc_reporting" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index f685a9058c..076b1f8925 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ members = [ "reporting", "repl_cli", "repl_eval", + "repl_wasm", "roc_std", "test_utils", "utils", diff --git a/repl_eval/src/app_memory.rs b/repl_eval/src/app_memory.rs deleted file mode 100644 index a7612dbb4d..0000000000 --- a/repl_eval/src/app_memory.rs +++ /dev/null @@ -1,159 +0,0 @@ -use std::mem::size_of; - -pub trait AppMemory { - fn deref_bool(&self, addr: usize) -> bool; - - fn deref_u8(&self, addr: usize) -> u8; - fn deref_u16(&self, addr: usize) -> u16; - fn deref_u32(&self, addr: usize) -> u32; - fn deref_u64(&self, addr: usize) -> u64; - fn deref_u128(&self, addr: usize) -> u128; - fn deref_usize(&self, addr: usize) -> usize; - - fn deref_i8(&self, addr: usize) -> i8; - fn deref_i16(&self, addr: usize) -> i16; - fn deref_i32(&self, addr: usize) -> i32; - fn deref_i64(&self, addr: usize) -> i64; - fn deref_i128(&self, addr: usize) -> i128; - fn deref_isize(&self, addr: usize) -> isize; - - fn deref_f32(&self, addr: usize) -> f32; - fn deref_f64(&self, addr: usize) -> f64; - - fn deref_str(&self, addr: usize) -> &str; -} - -/// A block of app memory in the same address space as the compiler -pub struct AppMemoryInternal; - -macro_rules! internal_number_type { - ($name: ident, $t: ty) => { - fn $name(&self, addr: usize) -> $t { - let ptr = addr as *const _; - unsafe { *ptr } - } - }; -} - -impl AppMemory for AppMemoryInternal { - internal_number_type!(deref_bool, bool); - - internal_number_type!(deref_u8, u8); - internal_number_type!(deref_u16, u16); - internal_number_type!(deref_u32, u32); - internal_number_type!(deref_u64, u64); - internal_number_type!(deref_u128, u128); - internal_number_type!(deref_usize, usize); - - internal_number_type!(deref_i8, i8); - internal_number_type!(deref_i16, i16); - internal_number_type!(deref_i32, i32); - internal_number_type!(deref_i64, i64); - internal_number_type!(deref_i128, i128); - internal_number_type!(deref_isize, isize); - - internal_number_type!(deref_f32, f32); - internal_number_type!(deref_f64, f64); - - fn deref_str(&self, addr: usize) -> &str { - unsafe { *(addr as *const &'static str) } - } -} - -/// A block of app memory copied from an exteral address space outside the compiler -/// (e.g. compiler and app are in separate Wasm modules) -pub struct AppMemoryExternal<'a> { - bytes: &'a [u8], -} - -macro_rules! external_number_type { - ($name: ident, $t: ty) => { - fn $name(&self, address: usize) -> $t { - const N: usize = size_of::<$t>(); - let mut array = [0; N]; - array.copy_from_slice(&self.bytes[address..][..N]); - <$t>::from_le_bytes(array) - } - }; -} - -impl<'a> AppMemory for AppMemoryExternal<'a> { - fn deref_bool(&self, address: usize) -> bool { - self.bytes[address] != 0 - } - - external_number_type!(deref_u8, u8); - external_number_type!(deref_u16, u16); - external_number_type!(deref_u32, u32); - external_number_type!(deref_u64, u64); - external_number_type!(deref_u128, u128); - external_number_type!(deref_usize, usize); - - external_number_type!(deref_i8, i8); - external_number_type!(deref_i16, i16); - external_number_type!(deref_i32, i32); - external_number_type!(deref_i64, i64); - external_number_type!(deref_i128, i128); - external_number_type!(deref_isize, isize); - - external_number_type!(deref_f32, f32); - external_number_type!(deref_f64, f64); - - fn deref_str(&self, addr: usize) -> &str { - let elems_addr = self.deref_usize(addr); - let len = self.deref_usize(addr + size_of::()); - let bytes = &self.bytes[elems_addr..][..len]; - std::str::from_utf8(bytes).unwrap() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn internal_u8() { - let value: u8 = 123; - let ptr = &value as *const u8; - let addr = ptr as usize; - let memory = AppMemoryInternal; - let recovered: u8 = memory.deref_u8(addr); - assert_eq!(value, recovered); - } - - #[test] - fn external_u8() { - let value: u8 = 123; - let memory = AppMemoryExternal { - bytes: &[0, 0, value, 0, 0], - }; - let addr = 2; - let recovered: u8 = memory.deref_u8(addr); - assert_eq!(value, recovered); - } - - #[test] - fn internal_i64() { - let value: i64 = -123 << 33; - let ptr = &value as *const i64; - let addr = ptr as usize; - let memory = AppMemoryInternal; - let recovered: i64 = memory.deref_i64(addr); - assert_eq!(value, recovered); - } - - #[test] - fn external_i64() { - let value: i64 = -1 << 33; - let memory = AppMemoryExternal { - bytes: &[ - 0, 0, // - 0, 0, 0, 0, 0xfe, 0xff, 0xff, 0xff, // - 0, 0, - ], - }; - let addr = 2; - let recovered: i64 = memory.deref_i64(addr); - assert_eq!(value, recovered); - } -} diff --git a/repl_eval/src/lib.rs b/repl_eval/src/lib.rs index 0d009a5bc3..8d403d6996 100644 --- a/repl_eval/src/lib.rs +++ b/repl_eval/src/lib.rs @@ -1,6 +1,5 @@ use roc_parse::ast::Expr; -pub mod app_memory; pub mod eval; pub mod gen; diff --git a/repl_wasm/Cargo.toml b/repl_wasm/Cargo.toml new file mode 100644 index 0000000000..e3e6ceae95 --- /dev/null +++ b/repl_wasm/Cargo.toml @@ -0,0 +1,10 @@ +[package] +edition = "2021" +name = "roc_repl_wasm" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +roc_parse = {path = "../compiler/parse"} +roc_repl_eval = {path = "../repl_eval"} diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs new file mode 100644 index 0000000000..d5e0c58a11 --- /dev/null +++ b/repl_wasm/src/lib.rs @@ -0,0 +1,66 @@ +use std::mem::size_of; + +use roc_parse::ast::Expr; +use roc_repl_eval::ReplApp; + +pub struct WasmReplApp<'a> { + bytes: &'a [u8], +} + +macro_rules! deref_number { + ($name: ident, $t: ty) => { + fn $name(&self, address: usize) -> $t { + const N: usize = size_of::<$t>(); + let mut array = [0; N]; + array.copy_from_slice(&self.bytes[address..][..N]); + <$t>::from_le_bytes(array) + } + }; +} + +impl<'a> ReplApp for WasmReplApp<'a> { + fn deref_bool(&self, address: usize) -> bool { + self.bytes[address] != 0 + } + + deref_number!(deref_u8, u8); + deref_number!(deref_u16, u16); + deref_number!(deref_u32, u32); + deref_number!(deref_u64, u64); + deref_number!(deref_u128, u128); + deref_number!(deref_usize, usize); + + deref_number!(deref_i8, i8); + deref_number!(deref_i16, i16); + deref_number!(deref_i32, i32); + deref_number!(deref_i64, i64); + deref_number!(deref_i128, i128); + deref_number!(deref_isize, isize); + + deref_number!(deref_f32, f32); + deref_number!(deref_f64, f64); + + fn deref_str(&self, addr: usize) -> &str { + let elems_addr = self.deref_usize(addr); + let len = self.deref_usize(addr + size_of::()); + let bytes = &self.bytes[elems_addr..][..len]; + std::str::from_utf8(bytes).unwrap() + } + + fn call_function<'e, Return: Sized, F: Fn(Return) -> Expr<'e>>( + &self, + _main_fn_name: &str, + _transform: F, + ) -> Expr<'e> { + todo!() + } + + fn call_function_dynamic_size T>( + &self, + _main_fn_name: &str, + _ret_bytes: usize, + _transform: F, + ) -> T { + todo!() + } +} From 03c99bdb44888742c2d4484c4ea619858d5d9f86 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 2 Feb 2022 23:42:21 +0000 Subject: [PATCH 449/541] repl: move tests into their own package --- Cargo.lock | 13 ++- Cargo.toml | 1 + repl_cli/Cargo.toml | 5 - repl_test/Cargo.toml | 21 ++++ repl_test/src/cli.rs | 149 ++++++++++++++++++++++++++ {repl_cli => repl_test}/src/tests.rs | 152 +-------------------------- 6 files changed, 184 insertions(+), 157 deletions(-) create mode 100644 repl_test/Cargo.toml create mode 100644 repl_test/src/cli.rs rename {repl_cli => repl_test}/src/tests.rs (82%) diff --git a/Cargo.lock b/Cargo.lock index 7b8f5eb764..3db175311e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3204,6 +3204,16 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" +[[package]] +name = "repl_test" +version = "0.1.0" +dependencies = [ + "indoc", + "roc_repl_cli", + "roc_test_utils", + "strip-ansi-escapes", +] + [[package]] name = "rkyv" version = "0.6.7" @@ -3669,7 +3679,6 @@ version = "0.1.0" dependencies = [ "bumpalo", "const_format", - "indoc", "inkwell 0.1.0", "libloading 0.7.1", "roc_build", @@ -3680,11 +3689,9 @@ dependencies = [ "roc_parse", "roc_repl_eval", "roc_target", - "roc_test_utils", "roc_types", "rustyline", "rustyline-derive", - "strip-ansi-escapes", "target-lexicon", ] diff --git a/Cargo.toml b/Cargo.toml index 076b1f8925..874eeb705d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ members = [ "reporting", "repl_cli", "repl_eval", + "repl_test", "repl_wasm", "roc_std", "test_utils", diff --git a/repl_cli/Cargo.toml b/repl_cli/Cargo.toml index 56fea8f4c0..043a959259 100644 --- a/repl_cli/Cargo.toml +++ b/repl_cli/Cargo.toml @@ -24,11 +24,6 @@ roc_repl_eval = {path = "../repl_eval"} roc_target = {path = "../compiler/roc_target"} roc_types = {path = "../compiler/types"} -[dev-dependencies] -indoc = "1.0.3" -roc_test_utils = {path = "../test_utils"} -strip-ansi-escapes = "0.1.1" - [lib] name = "roc_repl_cli" path = "src/lib.rs" diff --git a/repl_test/Cargo.toml b/repl_test/Cargo.toml new file mode 100644 index 0000000000..b04f087e85 --- /dev/null +++ b/repl_test/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2021" +name = "repl_test" +version = "0.1.0" + +[[test]] +name = "repl_test" +path = "src/tests.rs" + +[dev-dependencies] +indoc = "1.0.3" +roc_test_utils = {path = "../test_utils"} +strip-ansi-escapes = "0.1.1" + +roc_repl_cli = {path = "../repl_cli"} +# roc_repl_wasm = {path = "../repl_wasm"} + +[features] +default = ["cli"] +cli = [] +wasm = [] diff --git a/repl_test/src/cli.rs b/repl_test/src/cli.rs new file mode 100644 index 0000000000..7176af6f2c --- /dev/null +++ b/repl_test/src/cli.rs @@ -0,0 +1,149 @@ +use roc_repl_cli::{INSTRUCTIONS, WELCOME_MESSAGE}; +use roc_test_utils::assert_multiline_str_eq; + +const ERROR_MESSAGE_START: char = '─'; + +#[derive(Debug)] +pub struct Out { + pub stdout: String, + pub stderr: String, + pub status: ExitStatus, +} + +pub fn path_to_roc_binary() -> PathBuf { + // Adapted from https://github.com/volta-cli/volta/blob/cefdf7436a15af3ce3a38b8fe53bb0cfdb37d3dd/tests/acceptance/support/sandbox.rs#L680 + // by the Volta Contributors - license information can be found in + // the LEGAL_DETAILS file in the root directory of this distribution. + // + // Thank you, Volta contributors! + let mut path = env::var_os("CARGO_BIN_PATH") + .map(PathBuf::from) + .or_else(|| { + env::current_exe().ok().map(|mut path| { + path.pop(); + if path.ends_with("deps") { + path.pop(); + } + path + }) + }) + .unwrap_or_else(|| panic!("CARGO_BIN_PATH wasn't set, and couldn't be inferred from context. Can't run CLI tests.")); + + path.push("roc"); + + path +} + +fn repl_eval(input: &str) -> Out { + let mut cmd = Command::new(path_to_roc_binary()); + + cmd.arg("repl"); + + let mut child = cmd + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("failed to execute compiled `roc` binary in CLI test"); + + { + let stdin = child.stdin.as_mut().expect("Failed to open stdin"); + + // Send the input expression + stdin + .write_all(input.as_bytes()) + .expect("Failed to write input to stdin"); + + // Evaluate the expression + stdin + .write_all(b"\n") + .expect("Failed to write newline to stdin"); + + // Gracefully exit the repl + stdin + .write_all(b":exit\n") + .expect("Failed to write :exit to stdin"); + } + + let output = child + .wait_with_output() + .expect("Error waiting for REPL child process to exit."); + + // Remove the initial instructions from the output. + + let expected_instructions = format!("{}{}", WELCOME_MESSAGE, INSTRUCTIONS); + let stdout = String::from_utf8(output.stdout).unwrap(); + + assert!( + stdout.starts_with(&expected_instructions), + "Unexpected repl output: {}", + stdout + ); + + let (_, answer) = stdout.split_at(expected_instructions.len()); + let answer = if answer.is_empty() { + // The repl crashed before completing the evaluation. + // This is most likely due to a segfault. + if output.status.to_string() == "signal: 11" { + panic!( + "repl segfaulted during the test. Stderr was {:?}", + String::from_utf8(output.stderr).unwrap() + ); + } else { + panic!("repl exited unexpectedly before finishing evaluation. Exit status was {:?} and stderr was {:?}", output.status, String::from_utf8(output.stderr).unwrap()); + } + } else { + let expected_after_answer = "\n".to_string(); + + assert!( + answer.ends_with(&expected_after_answer), + "Unexpected repl output after answer: {}", + answer + ); + + // Use [1..] to trim the leading '\n' + // and (len - 1) to trim the trailing '\n' + let (answer, _) = answer[1..].split_at(answer.len() - expected_after_answer.len() - 1); + + // Remove ANSI escape codes from the answer - for example: + // + // Before: "42 \u{1b}[35m:\u{1b}[0m Num *" + // After: "42 : Num *" + strip_ansi_escapes::strip(answer).unwrap() + }; + + Out { + stdout: String::from_utf8(answer).unwrap(), + stderr: String::from_utf8(output.stderr).unwrap(), + status: output.status, + } +} + +fn expect_success(input: &str, expected: &str) { + let out = repl_eval(input); + + assert_multiline_str_eq!("", out.stderr.as_str()); + assert_multiline_str_eq!(expected, out.stdout.as_str()); + assert!(out.status.success()); +} + +fn expect_failure(input: &str, expected: &str) { + let out = repl_eval(input); + + // there may be some other stuff printed (e.g. unification errors) + // so skip till the header of the first error + match out.stdout.find(ERROR_MESSAGE_START) { + Some(index) => { + assert_multiline_str_eq!("", out.stderr.as_str()); + assert_multiline_str_eq!(expected, &out.stdout[index..]); + assert!(out.status.success()); + } + None => { + assert_multiline_str_eq!("", out.stderr.as_str()); + assert!(out.status.success()); + panic!( + "I expected a failure, but there is no error message in stdout:\n\n{}", + &out.stdout + ); + } + } +} diff --git a/repl_cli/src/tests.rs b/repl_test/src/tests.rs similarity index 82% rename from repl_cli/src/tests.rs rename to repl_test/src/tests.rs index aa0022384f..07571e7b7d 100644 --- a/repl_cli/src/tests.rs +++ b/repl_test/src/tests.rs @@ -4,156 +4,10 @@ use std::io::Write; use std::path::PathBuf; use std::process::{Command, ExitStatus, Stdio}; -use roc_test_utils::assert_multiline_str_eq; +mod cli; -use crate::{INSTRUCTIONS, WELCOME_MESSAGE}; - -const ERROR_MESSAGE_START: char = '─'; - -#[derive(Debug)] -pub struct Out { - pub stdout: String, - pub stderr: String, - pub status: ExitStatus, -} - -pub fn path_to_roc_binary() -> PathBuf { - // Adapted from https://github.com/volta-cli/volta/blob/cefdf7436a15af3ce3a38b8fe53bb0cfdb37d3dd/tests/acceptance/support/sandbox.rs#L680 - // by the Volta Contributors - license information can be found in - // the LEGAL_DETAILS file in the root directory of this distribution. - // - // Thank you, Volta contributors! - let mut path = env::var_os("CARGO_BIN_PATH") - .map(PathBuf::from) - .or_else(|| { - env::current_exe().ok().map(|mut path| { - path.pop(); - if path.ends_with("deps") { - path.pop(); - } - path - }) - }) - .unwrap_or_else(|| panic!("CARGO_BIN_PATH wasn't set, and couldn't be inferred from context. Can't run CLI tests.")); - - path.push("roc"); - - path -} - -fn repl_eval(input: &str) -> Out { - let mut cmd = Command::new(path_to_roc_binary()); - - cmd.arg("repl"); - - let mut child = cmd - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .expect("failed to execute compiled `roc` binary in CLI test"); - - { - let stdin = child.stdin.as_mut().expect("Failed to open stdin"); - - // Send the input expression - stdin - .write_all(input.as_bytes()) - .expect("Failed to write input to stdin"); - - // Evaluate the expression - stdin - .write_all(b"\n") - .expect("Failed to write newline to stdin"); - - // Gracefully exit the repl - stdin - .write_all(b":exit\n") - .expect("Failed to write :exit to stdin"); - } - - let output = child - .wait_with_output() - .expect("Error waiting for REPL child process to exit."); - - // Remove the initial instructions from the output. - - let expected_instructions = format!("{}{}", WELCOME_MESSAGE, INSTRUCTIONS); - let stdout = String::from_utf8(output.stdout).unwrap(); - - assert!( - stdout.starts_with(&expected_instructions), - "Unexpected repl output: {}", - stdout - ); - - let (_, answer) = stdout.split_at(expected_instructions.len()); - let answer = if answer.is_empty() { - // The repl crashed before completing the evaluation. - // This is most likely due to a segfault. - if output.status.to_string() == "signal: 11" { - panic!( - "repl segfaulted during the test. Stderr was {:?}", - String::from_utf8(output.stderr).unwrap() - ); - } else { - panic!("repl exited unexpectedly before finishing evaluation. Exit status was {:?} and stderr was {:?}", output.status, String::from_utf8(output.stderr).unwrap()); - } - } else { - let expected_after_answer = "\n".to_string(); - - assert!( - answer.ends_with(&expected_after_answer), - "Unexpected repl output after answer: {}", - answer - ); - - // Use [1..] to trim the leading '\n' - // and (len - 1) to trim the trailing '\n' - let (answer, _) = answer[1..].split_at(answer.len() - expected_after_answer.len() - 1); - - // Remove ANSI escape codes from the answer - for example: - // - // Before: "42 \u{1b}[35m:\u{1b}[0m Num *" - // After: "42 : Num *" - strip_ansi_escapes::strip(answer).unwrap() - }; - - Out { - stdout: String::from_utf8(answer).unwrap(), - stderr: String::from_utf8(output.stderr).unwrap(), - status: output.status, - } -} - -fn expect_success(input: &str, expected: &str) { - let out = repl_eval(input); - - assert_multiline_str_eq!("", out.stderr.as_str()); - assert_multiline_str_eq!(expected, out.stdout.as_str()); - assert!(out.status.success()); -} - -fn expect_failure(input: &str, expected: &str) { - let out = repl_eval(input); - - // there may be some other stuff printed (e.g. unification errors) - // so skip till the header of the first error - match out.stdout.find(ERROR_MESSAGE_START) { - Some(index) => { - assert_multiline_str_eq!("", out.stderr.as_str()); - assert_multiline_str_eq!(expected, &out.stdout[index..]); - assert!(out.status.success()); - } - None => { - assert_multiline_str_eq!("", out.stderr.as_str()); - assert!(out.status.success()); - panic!( - "I expected a failure, but there is no error message in stdout:\n\n{}", - &out.stdout - ); - } - } -} +#[cfg(feature = "cli")] +use cli::{expect_failure, expect_success}; #[test] fn literal_0() { From 3cc64209b5165cf1e6f98e5bbfe655de086e2834 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 2 Feb 2022 23:45:05 +0000 Subject: [PATCH 450/541] wasm: include package tests in Cargo alias --- .cargo/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cargo/config b/.cargo/config index e291ebf3e9..7f9d89b452 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,4 +1,4 @@ [alias] test-gen-llvm = "test -p test_gen" test-gen-dev = "test -p roc_gen_dev -p test_gen --no-default-features --features gen-dev" -test-gen-wasm = "test -p test_gen --no-default-features --features gen-wasm" +test-gen-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --features gen-wasm" From dd8f63b1ab40574a7ecce7ca5c412ac8a9f28b0c Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 2 Feb 2022 18:47:02 -0500 Subject: [PATCH 451/541] Add some #[repr(C)] annotations --- compiler/gen_llvm/src/run_roc.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/compiler/gen_llvm/src/run_roc.rs b/compiler/gen_llvm/src/run_roc.rs index 4f46e97de0..dee60df7f5 100644 --- a/compiler/gen_llvm/src/run_roc.rs +++ b/compiler/gen_llvm/src/run_roc.rs @@ -47,6 +47,7 @@ macro_rules! run_jit_function { use std::mem::MaybeUninit; #[derive(Debug, Copy, Clone)] + #[repr(C)] struct Failure { start_line: u32, end_line: u32, @@ -60,9 +61,14 @@ macro_rules! run_jit_function { .ok() .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) .expect("errored"); - let get_expect_failures: libloading::Symbol< - unsafe extern "C" fn() -> (*const Failure, usize), - > = $lib + + #[repr(C)] + struct Failures { + failures: *const Failure, + count: usize, + } + + let get_expect_failures: libloading::Symbol Failures> = $lib .get(bitcode::UTILS_GET_EXPECT_FAILURES.as_bytes()) .ok() .ok_or(format!( @@ -73,15 +79,13 @@ macro_rules! run_jit_function { let mut main_result = MaybeUninit::uninit(); main(main_result.as_mut_ptr()); - let (failures_ptr, num_failures) = get_expect_failures(); - let mut failures = std::vec::Vec::new(); + let failures = get_expect_failures(); - for index in 0..num_failures { - failures.push(*failures_ptr.add(index)); - } + if failures.count > 0 { + let failures = + unsafe { core::slice::from_raw_parts(failures.failures, failures.count) }; - if (num_failures > 0) { - panic!("Failed with {} failures. Failures: ", num_failures); + panic!("Failed with {} failures. Failures: ", failures.len()); } match main_result.assume_init().into() { From 3e9eef4d0ec0e15d6ef03ed1135ebb77f9689d9c Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 2 Feb 2022 18:47:18 -0500 Subject: [PATCH 452/541] Add some TODO comments --- compiler/gen_llvm/src/llvm/build.rs | 2 ++ compiler/gen_llvm/src/run_roc.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 800f67a3c4..3862b0455b 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -6061,6 +6061,8 @@ fn run_low_level<'a, 'ctx, 'env>( .module .get_function(bitcode::UTILS_EXPECT_FAILED) .unwrap(); + // TODO get the actual line info instead of + // hardcoding as zero! let callable = CallableValue::try_from(func).unwrap(); let start_line = context.i32_type().const_int(0, false); let end_line = context.i32_type().const_int(0, false); diff --git a/compiler/gen_llvm/src/run_roc.rs b/compiler/gen_llvm/src/run_roc.rs index dee60df7f5..8835b00b37 100644 --- a/compiler/gen_llvm/src/run_roc.rs +++ b/compiler/gen_llvm/src/run_roc.rs @@ -82,6 +82,7 @@ macro_rules! run_jit_function { let failures = get_expect_failures(); if failures.count > 0 { + // TODO tell the user about the failures! let failures = unsafe { core::slice::from_raw_parts(failures.failures, failures.count) }; From 3c07066aceffd9cc1945a86c3db1b158c702234a Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 2 Feb 2022 23:55:00 +0000 Subject: [PATCH 453/541] repl: update Earthfile for new packages --- Earthfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Earthfile b/Earthfile index d4ff49f1ab..2513e59005 100644 --- a/Earthfile +++ b/Earthfile @@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt: copy-dirs: FROM +install-zig-llvm-valgrind-clippy-rustfmt - COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros utils test_utils reporting repl_cli repl_eval roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./ + COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros utils test_utils reporting repl_cli repl_eval repl_test repl_wasm roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./ test-zig: FROM +install-zig-llvm-valgrind-clippy-rustfmt From 06203163ebad657093c73e3c943bf6454411f4e4 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 2 Feb 2022 18:59:34 -0500 Subject: [PATCH 454/541] Use mutex more in expect failures --- compiler/builtins/bitcode/src/utils.zig | 34 ++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 5995db3339..193d75f464 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -311,13 +311,33 @@ pub fn expectFailedC( } pub fn getExpectFailures() []Failure { - return failures[0..failure_length]; + // TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d + // to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig + // + // ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9: + // + // failures_mutex.lock(); + // defer failures_mutex.release(); + // + // 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆 + const held = failures_mutex.acquire(); + defer held.release(); + + // defensively clone failures, in case someone modifies the originals after the mutex has been released. + const num_bytes = failure_length * @sizeOf(Failure); + const raw_clones = roc_alloc(num_bytes, @alignOf(Failure)); + const clones = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_clones)); + + roc_memcpy(@ptrCast([*]u8, clones), @ptrCast([*]u8, raw_clones), num_bytes); + + return clones[0..failure_length]; } const CSlice = extern struct { pointer: *c_void, len: usize, }; + pub fn getExpectFailuresC() callconv(.C) CSlice { var bytes = @ptrCast(*c_void, failures); @@ -325,6 +345,18 @@ pub fn getExpectFailuresC() callconv(.C) CSlice { } pub fn deinitFailures() void { + // TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d + // to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig + // + // ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9: + // + // failures_mutex.lock(); + // defer failures_mutex.release(); + // + // 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆 + const held = failures_mutex.acquire(); + defer held.release(); + roc_dealloc(failures, @alignOf(Failure)); failure_length = 0; } From b317e051871e13a9caa9bce5e5db4d718322396e Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 3 Feb 2022 00:02:12 +0000 Subject: [PATCH 455/541] repl: formatting and Clippy --- repl_cli/src/lib.rs | 29 ++++++++++++----------------- repl_eval/src/eval.rs | 4 ++-- repl_eval/src/lib.rs | 2 +- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/repl_cli/src/lib.rs b/repl_cli/src/lib.rs index 4b41e8b173..5ffabf01fe 100644 --- a/repl_cli/src/lib.rs +++ b/repl_cli/src/lib.rs @@ -27,9 +27,6 @@ use roc_repl_eval::ReplApp; use roc_target::TargetInfo; use roc_types::pretty_print::{content_to_string, name_all_type_vars}; -#[cfg(test)] -mod tests; - const BLUE: &str = "\u{001b}[36m"; const PINK: &str = "\u{001b}[35m"; const END_COL: &str = "\u{001b}[0m"; @@ -213,7 +210,7 @@ impl ReplApp for CliReplApp { } /// Run user code that returns a struct or union, whose size is provided as an argument - fn call_function_dynamic_size<'a, T: Sized, F: Fn(usize) -> T>( + fn call_function_dynamic_size T>( &self, main_fn_name: &str, bytes: usize, @@ -380,19 +377,17 @@ fn gen_and_eval_llvm<'a>( let app = CliReplApp { lib }; - let res_answer = unsafe { - jit_to_ast( - &arena, - &app, - main_fn_name, - main_fn_layout, - content, - &env.interns, - home, - &subs, - target_info, - ) - }; + let res_answer = jit_to_ast( + &arena, + &app, + main_fn_name, + main_fn_layout, + content, + &env.interns, + home, + &subs, + target_info, + ); let formatted = format_answer(&arena, res_answer, expr_type_str); Ok(formatted) diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index 9c3485fc8e..019618e086 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -40,7 +40,7 @@ pub enum ToAstProblem { /// we get to a struct or tag, we know what the labels are and can turn them /// back into the appropriate user-facing literals. #[allow(clippy::too_many_arguments)] -pub unsafe fn jit_to_ast<'a, A: ReplApp>( +pub fn jit_to_ast<'a, A: ReplApp>( arena: &'a Bump, app: &'a A, main_fn_name: &str, @@ -385,7 +385,7 @@ fn jit_to_ast_help<'a, A: ReplApp>( env.app.call_function_dynamic_size( main_fn_name, result_stack_size as usize, - |bytes_addr: usize| struct_addr_to_ast(bytes_addr), + struct_addr_to_ast, ) } Layout::Union(UnionLayout::NonRecursive(_)) => { diff --git a/repl_eval/src/lib.rs b/repl_eval/src/lib.rs index 8d403d6996..53dba07a58 100644 --- a/repl_eval/src/lib.rs +++ b/repl_eval/src/lib.rs @@ -31,7 +31,7 @@ pub trait ReplApp { transform: F, ) -> Expr<'a>; - fn call_function_dynamic_size<'a, T: Sized, F: Fn(usize) -> T>( + fn call_function_dynamic_size T>( &self, main_fn_name: &str, ret_bytes: usize, From 9b05d8dd5094fe804edc5a4c38fa8bc9bbc4436c Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 2 Feb 2022 19:15:47 -0500 Subject: [PATCH 456/541] Move `expect` zig code out of utils --- compiler/builtins/bitcode/src/expect.zig | 147 +++++++++++++++++++++++ compiler/builtins/bitcode/src/main.zig | 11 +- compiler/builtins/bitcode/src/utils.zig | 147 +---------------------- compiler/builtins/src/bitcode.rs | 6 +- compiler/test_gen/src/helpers/llvm.rs | 2 +- 5 files changed, 164 insertions(+), 149 deletions(-) create mode 100644 compiler/builtins/bitcode/src/expect.zig diff --git a/compiler/builtins/bitcode/src/expect.zig b/compiler/builtins/bitcode/src/expect.zig new file mode 100644 index 0000000000..59c200e271 --- /dev/null +++ b/compiler/builtins/bitcode/src/expect.zig @@ -0,0 +1,147 @@ +const std = @import("std"); +const utils = @import("utils.zig"); +const CSlice = utils.CSlice; +const always_inline = std.builtin.CallOptions.Modifier.always_inline; + +const Failure = struct { + start_line: u32, + end_line: u32, + start_col: u16, + end_col: u16, +}; + +// BEGIN FAILURES GLOBALS /////////////////// +var failures_mutex = std.Thread.Mutex{}; +var failures: [*]Failure = undefined; +var failure_length: usize = 0; +var failure_capacity: usize = 0; +// END FAILURES GLOBALS ///////////////////// + +pub fn expectFailed( + start_line: u32, + end_line: u32, + start_col: u16, + end_col: u16, +) void { + const new_failure = Failure{ .start_line = start_line, .end_line = end_line, .start_col = start_col, .end_col = end_col }; + + // Lock the failures mutex before reading from any of the failures globals, + // and then release the lock once we're done modifying things. + + // TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d + // to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig + // + // ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9: + // + // failures_mutex.lock(); + // defer failures_mutex.release(); + // + // 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆 + const held = failures_mutex.acquire(); + defer held.release(); + + // If we don't have enough capacity to add a failure, allocate a new failures pointer. + if (failure_length >= failure_capacity) { + if (failure_capacity > 0) { + // We already had previous failures allocated, so try to realloc in order + // to grow the size in-place without having to memcpy bytes over. + const old_pointer = failures; + const old_bytes = failure_capacity * @sizeOf(Failure); + + failure_capacity *= 2; + + const new_bytes = failure_capacity * @sizeOf(Failure); + const failures_u8 = @ptrCast([*]u8, @alignCast(@alignOf(Failure), failures)); + const raw_pointer = utils.realloc(failures_u8, new_bytes, old_bytes, @alignOf(Failure)); + + failures = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_pointer)); + + // If realloc wasn't able to expand in-place (that is, it returned a different pointer), + // then copy the data into the new pointer and dealloc the old one. + if (failures != old_pointer) { + const old_pointer_u8 = @ptrCast([*]u8, old_pointer); + utils.memcpy(@ptrCast([*]u8, failures), old_pointer_u8, old_bytes); + utils.dealloc(old_pointer_u8, @alignOf(Failure)); + } + } else { + // We've never had any failures before, so allocate the failures for the first time. + failure_capacity = 10; + + const raw_pointer = utils.alloc(failure_capacity * @sizeOf(Failure), @alignOf(Failure)); + + failures = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_pointer)); + } + } + + failures[failure_length] = new_failure; + failure_length += 1; +} + +pub fn expectFailedC( + start_line: u32, + end_line: u32, + start_col: u16, + end_col: u16, +) callconv(.C) void { + return @call(.{ .modifier = always_inline }, expectFailed, .{ start_line, end_line, start_col, end_col }); +} + +pub fn getExpectFailures() []Failure { + // TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d + // to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig + // + // ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9: + // + // failures_mutex.lock(); + // defer failures_mutex.release(); + // + // 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆 + const held = failures_mutex.acquire(); + defer held.release(); + + // defensively clone failures, in case someone modifies the originals after the mutex has been released. + const num_bytes = failure_length * @sizeOf(Failure); + const raw_clones = utils.alloc(num_bytes, @alignOf(Failure)); + const clones = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_clones)); + + utils.memcpy(@ptrCast([*]u8, clones), @ptrCast([*]u8, raw_clones), num_bytes); + + return clones[0..failure_length]; +} + +pub fn getExpectFailuresC() callconv(.C) CSlice { + var bytes = @ptrCast(*c_void, failures); + + return .{ .pointer = bytes, .len = failure_length }; +} + +pub fn deinitFailures() void { + // TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d + // to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig + // + // ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9: + // + // failures_mutex.lock(); + // defer failures_mutex.release(); + // + // 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆 + const held = failures_mutex.acquire(); + defer held.release(); + + utils.dealloc(@ptrCast([*]u8, failures), @alignOf(Failure)); + failure_length = 0; +} + +pub fn deinitFailuresC() callconv(.C) void { + return @call(.{ .modifier = always_inline }, deinitFailures, .{}); +} + +test "expectFailure does something" { + defer deinitFailures(); + + try std.testing.expectEqual(getExpectFailures().len, 0); + expectFailed(1, 2, 3, 4); + try std.testing.expectEqual(getExpectFailures().len, 1); + const what_it_should_look_like = Failure{ .start_line = 1, .end_line = 2, .start_col = 3, .end_col = 4 }; + try std.testing.expectEqual(getExpectFailures()[0], what_it_should_look_like); +} diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 92d9e28c72..ce1cc10757 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -1,6 +1,7 @@ const std = @import("std"); const math = std.math; const utils = @import("utils.zig"); +const expect = @import("expect.zig"); const ROC_BUILTINS = "roc_builtins"; const NUM = "num"; @@ -146,9 +147,9 @@ comptime { exportUtilsFn(utils.increfC, "incref"); exportUtilsFn(utils.decrefC, "decref"); exportUtilsFn(utils.decrefCheckNullC, "decref_check_null"); - exportUtilsFn(utils.expectFailedC, "expect_failed"); - exportUtilsFn(utils.getExpectFailuresC, "get_expect_failures"); - exportUtilsFn(utils.deinitFailuresC, "deinit_failures"); + exportExpectFn(expect.expectFailedC, "expect_failed"); + exportExpectFn(expect.getExpectFailuresC, "get_expect_failures"); + exportExpectFn(expect.deinitFailuresC, "deinit_failures"); @export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak }); } @@ -177,6 +178,10 @@ fn exportUtilsFn(comptime func: anytype, comptime func_name: []const u8) void { exportBuiltinFn(func, "utils." ++ func_name); } +fn exportExpectFn(comptime func: anytype, comptime func_name: []const u8) void { + exportBuiltinFn(func, "expect." ++ func_name); +} + // Custom panic function, as builtin Zig version errors during LLVM verification pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn { const builtin = @import("builtin"); diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 193d75f464..f39215ec65 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -82,6 +82,10 @@ pub fn panic(c_ptr: *c_void, alignment: u32) callconv(.C) void { return @call(.{ .modifier = always_inline }, roc_panic, .{ c_ptr, alignment }); } +pub fn memcpy(dst: [*]u8, src: [*]u8, size: usize) void { + @call(.{ .modifier = always_inline }, roc_memcpy, .{ dst, src, size }); +} + // indirection because otherwise zig creates an alias to the panic function which our LLVM code // does not know how to deal with pub fn test_panic(c_ptr: *c_void, alignment: u32) callconv(.C) void { @@ -229,142 +233,11 @@ pub fn allocateWithRefcount( } } -const Failure = struct { - start_line: u32, - end_line: u32, - start_col: u16, - end_col: u16, -}; - -// BEGIN FAILURES GLOBALS /////////////////// -var failures_mutex = std.Thread.Mutex{}; -var failures: [*]Failure = undefined; -var failure_length: usize = 0; -var failure_capacity: usize = 0; -// END FAILURES GLOBALS ///////////////////// - -pub fn expectFailed( - start_line: u32, - end_line: u32, - start_col: u16, - end_col: u16, -) void { - const new_failure = Failure{ .start_line = start_line, .end_line = end_line, .start_col = start_col, .end_col = end_col }; - - // Lock the failures mutex before reading from any of the failures globals, - // and then release the lock once we're done modifying things. - - // TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d - // to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig - // - // ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9: - // - // failures_mutex.lock(); - // defer failures_mutex.release(); - // - // 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆 - const held = failures_mutex.acquire(); - defer held.release(); - - // If we don't have enough capacity to add a failure, allocate a new failures pointer. - if (failure_length >= failure_capacity) { - if (failure_capacity > 0) { - // We already had previous failures allocated, so try to realloc in order - // to grow the size in-place without having to memcpy bytes over. - const old_pointer = failures; - const old_bytes = failure_capacity * @sizeOf(Failure); - - failure_capacity *= 2; - - const new_bytes = failure_capacity * @sizeOf(Failure); - const raw_pointer = roc_realloc(failures, new_bytes, old_bytes, @alignOf(Failure)); - - failures = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_pointer)); - - // If realloc wasn't able to expand in-place (that is, it returned a different pointer), - // then copy the data into the new pointer and dealloc the old one. - if (failures != old_pointer) { - roc_memcpy(@ptrCast([*]u8, failures), @ptrCast([*]u8, old_pointer), old_bytes); - roc_dealloc(old_pointer, @alignOf(Failure)); - } - } else { - // We've never had any failures before, so allocate the failures for the first time. - failure_capacity = 10; - - const raw_pointer = roc_alloc(failure_capacity * @sizeOf(Failure), @alignOf(Failure)); - - failures = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_pointer)); - } - } - - failures[failure_length] = new_failure; - failure_length += 1; -} - -pub fn expectFailedC( - start_line: u32, - end_line: u32, - start_col: u16, - end_col: u16, -) callconv(.C) void { - return @call(.{ .modifier = always_inline }, expectFailed, .{ start_line, end_line, start_col, end_col }); -} - -pub fn getExpectFailures() []Failure { - // TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d - // to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig - // - // ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9: - // - // failures_mutex.lock(); - // defer failures_mutex.release(); - // - // 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆 - const held = failures_mutex.acquire(); - defer held.release(); - - // defensively clone failures, in case someone modifies the originals after the mutex has been released. - const num_bytes = failure_length * @sizeOf(Failure); - const raw_clones = roc_alloc(num_bytes, @alignOf(Failure)); - const clones = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_clones)); - - roc_memcpy(@ptrCast([*]u8, clones), @ptrCast([*]u8, raw_clones), num_bytes); - - return clones[0..failure_length]; -} - -const CSlice = extern struct { +pub const CSlice = extern struct { pointer: *c_void, len: usize, }; -pub fn getExpectFailuresC() callconv(.C) CSlice { - var bytes = @ptrCast(*c_void, failures); - - return .{ .pointer = bytes, .len = failure_length }; -} - -pub fn deinitFailures() void { - // TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d - // to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig - // - // ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9: - // - // failures_mutex.lock(); - // defer failures_mutex.release(); - // - // 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆 - const held = failures_mutex.acquire(); - defer held.release(); - - roc_dealloc(failures, @alignOf(Failure)); - failure_length = 0; -} - -pub fn deinitFailuresC() callconv(.C) void { - return @call(.{ .modifier = always_inline }, deinitFailures, .{}); -} - pub fn unsafeReallocate( source_ptr: [*]u8, alignment: u32, @@ -429,13 +302,3 @@ test "increfC, static data" { increfC(ptr_to_refcount, 2); try std.testing.expectEqual(mock_rc, REFCOUNT_MAX_ISIZE); } - -test "expectFailure does something" { - defer deinitFailures(); - - try std.testing.expectEqual(getExpectFailures().len, 0); - expectFailed(1, 2, 3, 4); - try std.testing.expectEqual(getExpectFailures().len, 1); - const what_it_should_look_like = Failure{ .start_line = 1, .end_line = 2, .start_col = 3, .end_col = 4 }; - try std.testing.expectEqual(getExpectFailures()[0], what_it_should_look_like); -} diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 4261864e30..c52c30cf09 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -336,6 +336,6 @@ pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic"; pub const UTILS_INCREF: &str = "roc_builtins.utils.incref"; pub const UTILS_DECREF: &str = "roc_builtins.utils.decref"; pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null"; -pub const UTILS_EXPECT_FAILED: &str = "roc_builtins.utils.expect_failed"; -pub const UTILS_GET_EXPECT_FAILURES: &str = "roc_builtins.utils.get_expect_failures"; -pub const UTILS_DEINIT_FAILURES: &str = "roc_builtins.utils.deinit_failures"; +pub const UTILS_EXPECT_FAILED: &str = "roc_builtins.expect.expect_failed"; +pub const UTILS_GET_EXPECT_FAILURES: &str = "roc_builtins.expect.get_expect_failures"; +pub const UTILS_DEINIT_FAILURES: &str = "roc_builtins.expect.deinit_failures"; diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index 338172ffd6..2868cb7094 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -192,7 +192,7 @@ fn create_llvm_module<'a>( for function in FunctionIterator::from_module(module) { let name = function.get_name().to_str().unwrap(); if name.starts_with("roc_builtins") { - if name.starts_with("roc_builtins.utils") { + if name.starts_with("roc_builtins.expect") { function.set_linkage(Linkage::External); } else { function.set_linkage(Linkage::Internal); From 778d32f6b22e412f8f0fc41e535646a2ccd3e6a8 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 2 Feb 2022 19:22:45 -0500 Subject: [PATCH 457/541] Acknowledge possibility of roc_alloc failing --- compiler/builtins/bitcode/src/dict.zig | 3 ++- compiler/builtins/bitcode/src/expect.zig | 3 ++- compiler/builtins/bitcode/src/list.zig | 12 ++++++++---- compiler/builtins/bitcode/src/utils.zig | 13 ++++++++----- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/compiler/builtins/bitcode/src/dict.zig b/compiler/builtins/bitcode/src/dict.zig index 3812fda619..d45008fbd2 100644 --- a/compiler/builtins/bitcode/src/dict.zig +++ b/compiler/builtins/bitcode/src/dict.zig @@ -750,7 +750,8 @@ pub fn dictWalk( const alignment_u32 = alignment.toU32(); // allocate space to write the result of the stepper into // experimentally aliasing the accum and output pointers is not a good idea - const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment_u32); + // TODO handle alloc failing! + const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment_u32) orelse unreachable; var b1 = output orelse unreachable; var b2 = bytes_ptr; diff --git a/compiler/builtins/bitcode/src/expect.zig b/compiler/builtins/bitcode/src/expect.zig index 59c200e271..322fff7205 100644 --- a/compiler/builtins/bitcode/src/expect.zig +++ b/compiler/builtins/bitcode/src/expect.zig @@ -101,7 +101,8 @@ pub fn getExpectFailures() []Failure { // defensively clone failures, in case someone modifies the originals after the mutex has been released. const num_bytes = failure_length * @sizeOf(Failure); - const raw_clones = utils.alloc(num_bytes, @alignOf(Failure)); + // TODO handle the possibility of alloc failing + const raw_clones = utils.alloc(num_bytes, @alignOf(Failure)) orelse unreachable; const clones = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_clones)); utils.memcpy(@ptrCast([*]u8, clones), @ptrCast([*]u8, raw_clones), num_bytes); diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 2bacf0400e..35e16c1489 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -550,7 +550,8 @@ pub fn listKeepResult( var output = RocList.allocate(alignment, list.len(), list.len() * after_width); const target_ptr = output.bytes orelse unreachable; - var temporary = @ptrCast([*]u8, utils.alloc(result_width, alignment)); + // TODO handle alloc failing! + var temporary = utils.alloc(result_width, alignment) orelse unreachable; if (data_is_owned) { inc_n_data(data, size); @@ -614,7 +615,8 @@ pub fn listWalk( inc_n_data(data, list.len()); } - const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment); + // TODO handle alloc failing! + const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment) orelse unreachable; var b1 = output orelse unreachable; var b2 = bytes_ptr; @@ -660,7 +662,8 @@ pub fn listWalkBackwards( inc_n_data(data, list.len()); } - const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment); + // TODO handle alloc failing! + const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment) orelse unreachable; var b1 = output orelse unreachable; var b2 = bytes_ptr; @@ -708,7 +711,8 @@ pub fn listWalkUntil( return; } - const bytes_ptr: [*]u8 = utils.alloc(continue_stop_width, alignment); + // TODO handle alloc failing! + const bytes_ptr: [*]u8 = utils.alloc(continue_stop_width, alignment) orelse unreachable; // NOTE: assumes data bytes are the first bytes in a tag @memcpy(bytes_ptr, accum orelse unreachable, accum_width); diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index f39215ec65..810d75b27f 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -65,8 +65,8 @@ fn testing_roc_memcpy(dest: *c_void, src: *c_void, bytes: usize) callconv(.C) ?* return dest; } -pub fn alloc(size: usize, alignment: u32) [*]u8 { - return @ptrCast([*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment })); +pub fn alloc(size: usize, alignment: u32) ?[*]u8 { + return @ptrCast(?[*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment })); } pub fn realloc(c_ptr: [*]u8, new_size: usize, old_size: usize, alignment: u32) [*]u8 { @@ -189,7 +189,8 @@ pub fn allocateWithRefcount( switch (alignment) { 16 => { - var new_bytes: [*]align(16) u8 = @alignCast(16, alloc(length, alignment)); + // TODO handle alloc failing! + var new_bytes: [*]align(16) u8 = @alignCast(16, alloc(length, alignment) orelse unreachable); var as_usize_array = @ptrCast([*]usize, new_bytes); as_usize_array[0] = 0; @@ -201,7 +202,8 @@ pub fn allocateWithRefcount( return first_slot; }, 8 => { - var raw = alloc(length, alignment); + // TODO handle alloc failing! + var raw = alloc(length, alignment) orelse unreachable; var new_bytes: [*]align(8) u8 = @alignCast(8, raw); var as_isize_array = @ptrCast([*]isize, new_bytes); @@ -213,7 +215,8 @@ pub fn allocateWithRefcount( return first_slot; }, 4 => { - var raw = alloc(length, alignment); + // TODO handle alloc failing! + var raw = alloc(length, alignment) orelse unreachable; var new_bytes: [*]align(@alignOf(isize)) u8 = @alignCast(@alignOf(isize), raw); var as_isize_array = @ptrCast([*]isize, new_bytes); From eb1a16d42990efb025930534efe33be32cdcb55f Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 3 Feb 2022 00:08:36 +0000 Subject: [PATCH 458/541] repl: fix tests --- Cargo.lock | 4 +--- repl_eval/Cargo.toml | 7 ------- repl_test/Cargo.toml | 3 +++ repl_test/src/cli.rs | 19 ++++++++++++------- repl_test/src/tests.rs | 6 +----- 5 files changed, 17 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3db175311e..a878b309ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3209,6 +3209,7 @@ name = "repl_test" version = "0.1.0" dependencies = [ "indoc", + "roc_cli", "roc_repl_cli", "roc_test_utils", "strip-ansi-escapes", @@ -3700,8 +3701,6 @@ name = "roc_repl_eval" version = "0.1.0" dependencies = [ "bumpalo", - "inkwell 0.1.0", - "libloading 0.7.1", "roc_build", "roc_builtins", "roc_can", @@ -3716,7 +3715,6 @@ dependencies = [ "roc_reporting", "roc_target", "roc_types", - "target-lexicon", ] [[package]] diff --git a/repl_eval/Cargo.toml b/repl_eval/Cargo.toml index 2f6ba54a65..cd2b9e940b 100644 --- a/repl_eval/Cargo.toml +++ b/repl_eval/Cargo.toml @@ -5,15 +5,8 @@ version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[features] -default = ["llvm"] -llvm = ["inkwell", "libloading", "roc_gen_llvm", "roc_build/llvm"] - [dependencies] bumpalo = {version = "3.8.0", features = ["collections"]} -inkwell = {path = "../vendor/inkwell", optional = true} -libloading = {version = "0.7.1", optional = true} -target-lexicon = "0.12.2" roc_build = {path = "../compiler/build", default-features = false} roc_builtins = {path = "../compiler/builtins"} diff --git a/repl_test/Cargo.toml b/repl_test/Cargo.toml index b04f087e85..2d86ad19d4 100644 --- a/repl_test/Cargo.toml +++ b/repl_test/Cargo.toml @@ -7,6 +7,9 @@ version = "0.1.0" name = "repl_test" path = "src/tests.rs" +[build-dependencies] +roc_cli = {path = "../cli"} + [dev-dependencies] indoc = "1.0.3" roc_test_utils = {path = "../test_utils"} diff --git a/repl_test/src/cli.rs b/repl_test/src/cli.rs index 7176af6f2c..74775b88c5 100644 --- a/repl_test/src/cli.rs +++ b/repl_test/src/cli.rs @@ -1,16 +1,21 @@ +use std::env; +use std::io::Write; +use std::path::PathBuf; +use std::process::{Command, ExitStatus, Stdio}; + use roc_repl_cli::{INSTRUCTIONS, WELCOME_MESSAGE}; use roc_test_utils::assert_multiline_str_eq; const ERROR_MESSAGE_START: char = '─'; #[derive(Debug)] -pub struct Out { - pub stdout: String, - pub stderr: String, - pub status: ExitStatus, +struct Out { + stdout: String, + stderr: String, + status: ExitStatus, } -pub fn path_to_roc_binary() -> PathBuf { +fn path_to_roc_binary() -> PathBuf { // Adapted from https://github.com/volta-cli/volta/blob/cefdf7436a15af3ce3a38b8fe53bb0cfdb37d3dd/tests/acceptance/support/sandbox.rs#L680 // by the Volta Contributors - license information can be found in // the LEGAL_DETAILS file in the root directory of this distribution. @@ -118,7 +123,7 @@ fn repl_eval(input: &str) -> Out { } } -fn expect_success(input: &str, expected: &str) { +pub fn expect_success(input: &str, expected: &str) { let out = repl_eval(input); assert_multiline_str_eq!("", out.stderr.as_str()); @@ -126,7 +131,7 @@ fn expect_success(input: &str, expected: &str) { assert!(out.status.success()); } -fn expect_failure(input: &str, expected: &str) { +pub fn expect_failure(input: &str, expected: &str) { let out = repl_eval(input); // there may be some other stuff printed (e.g. unification errors) diff --git a/repl_test/src/tests.rs b/repl_test/src/tests.rs index 09f4e47358..49a166386b 100644 --- a/repl_test/src/tests.rs +++ b/repl_test/src/tests.rs @@ -1,13 +1,9 @@ use indoc::indoc; -use std::env; -use std::io::Write; -use std::path::PathBuf; -use std::process::{Command, ExitStatus, Stdio}; mod cli; #[cfg(feature = "cli")] -use cli::{expect_failure, expect_success}; +use crate::cli::{expect_failure, expect_success}; #[test] fn literal_0() { From 5d60677b5bd15c2681cf53d10167d35a89908777 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 2 Feb 2022 23:08:12 -0500 Subject: [PATCH 459/541] Fix defensive cloning --- compiler/builtins/bitcode/src/expect.zig | 34 +++++++++++++++++------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/compiler/builtins/bitcode/src/expect.zig b/compiler/builtins/bitcode/src/expect.zig index 322fff7205..3734b578ce 100644 --- a/compiler/builtins/bitcode/src/expect.zig +++ b/compiler/builtins/bitcode/src/expect.zig @@ -99,15 +99,20 @@ pub fn getExpectFailures() []Failure { const held = failures_mutex.acquire(); defer held.release(); - // defensively clone failures, in case someone modifies the originals after the mutex has been released. - const num_bytes = failure_length * @sizeOf(Failure); - // TODO handle the possibility of alloc failing - const raw_clones = utils.alloc(num_bytes, @alignOf(Failure)) orelse unreachable; - const clones = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_clones)); + if (failure_length > 0) { + // defensively clone failures, in case someone modifies the originals after the mutex has been released. + const num_bytes = failure_length * @sizeOf(Failure); + // TODO handle the possibility of alloc failing + const raw_clones = utils.alloc(num_bytes, @alignOf(Failure)) orelse unreachable; - utils.memcpy(@ptrCast([*]u8, clones), @ptrCast([*]u8, raw_clones), num_bytes); + utils.memcpy(raw_clones, @ptrCast([*]u8, failures), num_bytes); - return clones[0..failure_length]; + const clones = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_clones)); + + return clones[0..failure_length]; + } else { + return failures[0..0]; + } } pub fn getExpectFailuresC() callconv(.C) CSlice { @@ -140,9 +145,18 @@ pub fn deinitFailuresC() callconv(.C) void { test "expectFailure does something" { defer deinitFailures(); - try std.testing.expectEqual(getExpectFailures().len, 0); + var fails = getExpectFailures(); + try std.testing.expectEqual(fails.len, 0); + expectFailed(1, 2, 3, 4); - try std.testing.expectEqual(getExpectFailures().len, 1); + + fails = getExpectFailures(); + try std.testing.expectEqual(fails.len, 1); + utils.dealloc(@ptrCast([*]u8, fails.ptr), @alignOf([*]Failure)); + const what_it_should_look_like = Failure{ .start_line = 1, .end_line = 2, .start_col = 3, .end_col = 4 }; - try std.testing.expectEqual(getExpectFailures()[0], what_it_should_look_like); + + fails = getExpectFailures(); + try std.testing.expectEqual(fails[0], what_it_should_look_like); + utils.dealloc(@ptrCast([*]u8, fails.ptr), @alignOf([*]Failure)); } From 097c5afc73c4a64c5a5c05a8978960f6076c1e23 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Wed, 2 Feb 2022 23:35:57 -0500 Subject: [PATCH 460/541] Check lower bounds for numeric literals, and permit 128-bit literals --- ast/src/lang/core/expr/expr_to_expr2.rs | 12 +- ast/src/lang/core/pattern.rs | 21 +- compiler/can/src/builtins.rs | 14 +- compiler/can/src/expr.rs | 35 +- compiler/can/src/num.rs | 393 ++++++++++++++--------- compiler/can/src/pattern.rs | 24 +- compiler/can/tests/test_can.rs | 12 +- compiler/gen_llvm/src/llvm/build.rs | 17 +- compiler/mono/src/alias_analysis.rs | 4 +- compiler/mono/src/decision_tree.rs | 28 ++ compiler/mono/src/exhaustive.rs | 2 + compiler/mono/src/ir.rs | 58 +++- compiler/problem/src/can.rs | 10 + reporting/src/error/canonicalize.rs | 68 +++- reporting/src/error/mono.rs | 1 + reporting/tests/test_reporting.rs | 404 ++++++++++++++++++++++-- 16 files changed, 883 insertions(+), 220 deletions(-) diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index 1d0553797d..5bcd4edd19 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -1,5 +1,5 @@ use bumpalo::Bump; -use roc_can::expr::Recursive; +use roc_can::expr::{IntValue, Recursive}; use roc_can::num::{ finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult, }; @@ -78,7 +78,10 @@ pub fn expr_to_expr2<'a>( match finish_parsing_num(string) { Ok(ParsedNumResult::UnknownNum(int) | ParsedNumResult::Int(int, _)) => { let expr = Expr2::SmallInt { - number: IntVal::I64(int), + number: IntVal::I64(match int { + IntValue::U128(_) => todo!(), + IntValue::I128(n) => n as i64, // FIXME + }), var: env.var_store.fresh(), // TODO non-hardcode style: IntStyle::Decimal, @@ -120,7 +123,10 @@ pub fn expr_to_expr2<'a>( match finish_parsing_base(string, *base, *is_negative) { Ok((int, _bound)) => { let expr = Expr2::SmallInt { - number: IntVal::I64(int), + number: IntVal::I64(match int { + IntValue::U128(_) => todo!(), + IntValue::I128(n) => n as i64, // FIXME + }), var: env.var_store.fresh(), // TODO non-hardcode style: IntStyle::from_base(*base), diff --git a/ast/src/lang/core/pattern.rs b/ast/src/lang/core/pattern.rs index 4dcf759964..aabb06254f 100644 --- a/ast/src/lang/core/pattern.rs +++ b/ast/src/lang/core/pattern.rs @@ -3,7 +3,7 @@ #![allow(unused_imports)] use bumpalo::collections::Vec as BumpVec; -use roc_can::expr::unescape_char; +use roc_can::expr::{unescape_char, IntValue}; use roc_can::num::{ finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult, }; @@ -197,9 +197,20 @@ pub fn to_pattern2<'a>( malformed_pattern(env, problem, region) } Ok(ParsedNumResult::UnknownNum(int)) => { - Pattern2::NumLiteral(env.var_store.fresh(), int) + Pattern2::NumLiteral( + env.var_store.fresh(), + match int { + IntValue::U128(_) => todo!(), + IntValue::I128(n) => n as i64, // FIXME + }, + ) + } + Ok(ParsedNumResult::Int(int, _bound)) => { + Pattern2::IntLiteral(IntVal::I64(match int { + IntValue::U128(_) => todo!(), + IntValue::I128(n) => n as i64, // FIXME + })) } - Ok(ParsedNumResult::Int(int, _bound)) => Pattern2::IntLiteral(IntVal::I64(int)), Ok(ParsedNumResult::Float(int, _bound)) => { Pattern2::FloatLiteral(FloatVal::F64(int)) } @@ -218,6 +229,10 @@ pub fn to_pattern2<'a>( malformed_pattern(env, problem, region) } Ok((int, _bound)) => { + let int = match int { + IntValue::U128(_) => todo!(), + IntValue::I128(n) => n as i64, // FIXME + }; if *is_negative { Pattern2::IntLiteral(IntVal::I64(-int)) } else { diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 42e27f49ed..93f6b6947b 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1,5 +1,5 @@ use crate::def::Def; -use crate::expr::{self, ClosureData, Expr::*}; +use crate::expr::{self, ClosureData, Expr::*, IntValue}; use crate::expr::{Expr, Field, Recursive}; use crate::num::{FloatWidth, IntWidth, NumWidth, NumericBound}; use crate::pattern::Pattern; @@ -5197,7 +5197,7 @@ where num_var, precision_var, ii.to_string().into_boxed_str(), - ii, + IntValue::I128(ii), bound, ) } @@ -5219,6 +5219,12 @@ fn float( } #[inline(always)] -fn num(num_var: Variable, i: i64, bound: NumericBound) -> Expr { - Num(num_var, i.to_string().into_boxed_str(), i, bound) +fn num>(num_var: Variable, i: I, bound: NumericBound) -> Expr { + let i = i.into(); + Num( + num_var, + i.to_string().into_boxed_str(), + IntValue::I128(i), + bound, + ) } diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 1f12b281ce..3d4954e5ad 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -20,7 +20,7 @@ use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError}; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; use roc_types::types::Alias; -use std::fmt::Debug; +use std::fmt::{Debug, Display}; use std::{char, u32}; #[derive(Clone, Default, Debug, PartialEq)] @@ -46,16 +46,37 @@ impl Output { } } +#[derive(Clone, Debug, PartialEq)] +pub enum IntValue { + I128(i128), + U128(u128), +} + +impl Display for IntValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + IntValue::I128(n) => Display::fmt(&n, f), + IntValue::U128(n) => Display::fmt(&n, f), + } + } +} + #[derive(Clone, Debug, PartialEq)] pub enum Expr { // Literals // Num stores the `a` variable in `Num a`. Not the same as the variable // stored in Int and Float below, which is strictly for better error messages - Num(Variable, Box, i64, NumericBound), + Num(Variable, Box, IntValue, NumericBound), // Int and Float store a variable to generate better error messages - Int(Variable, Variable, Box, i128, NumericBound), + Int( + Variable, + Variable, + Box, + IntValue, + NumericBound, + ), Float(Variable, Variable, Box, f64, NumericBound), Str(Box), List { @@ -802,13 +823,7 @@ pub fn canonicalize_expr<'a>( // to keep borrowed values around and make this compile let int_string = int.to_string(); let int_str = int_string.as_str(); - int_expr_from_result( - var_store, - Ok((int_str, int as i128, bound)), - region, - base, - env, - ) + int_expr_from_result(var_store, Ok((int_str, int, bound)), region, base, env) } Err(e) => int_expr_from_result(var_store, Err(e), region, base, env), }; diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index cffbec95db..510833ccd9 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -1,5 +1,5 @@ use crate::env::Env; -use crate::expr::Expr; +use crate::expr::{Expr, IntValue}; use roc_parse::ast::Base; use roc_problem::can::Problem; use roc_problem::can::RuntimeError::*; @@ -9,11 +9,6 @@ use roc_types::subs::VarStore; use std::i64; use std::str; -// TODO use rust's integer parsing again -// -// We're waiting for libcore here, see https://github.com/rust-lang/rust/issues/22639 -// There is a nightly API for exposing the parse error. - #[inline(always)] pub fn num_expr_from_result( var_store: &mut VarStore, @@ -55,7 +50,7 @@ pub fn num_expr_from_result( #[inline(always)] pub fn int_expr_from_result( var_store: &mut VarStore, - result: Result<(&str, i128, NumericBound), (&str, IntErrorKind)>, + result: Result<(&str, IntValue, NumericBound), (&str, IntErrorKind)>, region: Region, base: Base, env: &mut Env, @@ -106,9 +101,9 @@ pub fn float_expr_from_result( } pub enum ParsedNumResult { - Int(i64, NumericBound), + Int(IntValue, NumericBound), Float(f64, NumericBound), - UnknownNum(i64), + UnknownNum(IntValue), } #[inline(always)] @@ -116,7 +111,7 @@ pub fn finish_parsing_num(raw: &str) -> Result(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e.kind))?; + from_str_radix(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e))?; // Let's try to specialize the number Ok(match bound { NumericBound::None => ParsedNumResult::UnknownNum(num), @@ -124,7 +119,11 @@ pub fn finish_parsing_num(raw: &str) -> Result { - ParsedNumResult::Float(num as f64, NumericBound::Exact(fw)) + let num = match num { + IntValue::I128(n) => n as f64, + IntValue::U128(n) => n as f64, + }; + ParsedNumResult::Float(num, NumericBound::Exact(fw)) } }) } @@ -134,7 +133,7 @@ pub fn finish_parsing_base( raw: &str, base: Base, is_negative: bool, -) -> Result<(i64, NumericBound), (&str, IntErrorKind)> { +) -> Result<(IntValue, NumericBound), (&str, IntErrorKind)> { let radix = match base { Base::Hex => 16, Base::Decimal => 10, @@ -144,11 +143,10 @@ pub fn finish_parsing_base( // Ignore underscores, insert - when negative to get correct underflow/overflow behavior (if is_negative { - from_str_radix::(format!("-{}", raw.replace("_", "")).as_str(), radix) + from_str_radix(format!("-{}", raw.replace("_", "")).as_str(), radix) } else { - from_str_radix::(raw.replace("_", "").as_str(), radix) + from_str_radix(raw.replace("_", "").as_str(), radix) }) - .map_err(|e| e.kind) .and_then(|(n, bound)| { let bound = match bound { NumericBound::None => NumericBound::None, @@ -164,15 +162,12 @@ pub fn finish_parsing_base( pub fn finish_parsing_float( raw: &str, ) -> Result<(f64, NumericBound), (&str, FloatErrorKind)> { - let (bound, raw_without_suffix) = parse_literal_suffix(raw.as_bytes()); - // Safety: `raw` is valid UTF8, and `parse_literal_suffix` will only chop UTF8 - // characters off the end, if it chops off anything at all. - let raw_without_suffix = unsafe { str::from_utf8_unchecked(raw_without_suffix) }; + let (opt_bound, raw_without_suffix) = parse_literal_suffix(raw); - let bound = match bound { - NumericBound::None => NumericBound::None, - NumericBound::Exact(NumWidth::Float(fw)) => NumericBound::Exact(fw), - NumericBound::Exact(NumWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)), + let bound = match opt_bound { + None => NumericBound::None, + Some(NumWidth::Float(fw)) => NumericBound::Exact(fw), + Some(NumWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)), }; // Ignore underscores. @@ -189,33 +184,33 @@ pub fn finish_parsing_float( } } -fn parse_literal_suffix(num_str: &[u8]) -> (NumericBound, &[u8]) { +fn parse_literal_suffix(num_str: &str) -> (Option, &str) { macro_rules! parse_num_suffix { ($($suffix:expr, $width:expr)*) => {$( if num_str.ends_with($suffix) { - return (NumericBound::Exact($width), num_str.get(0..num_str.len() - $suffix.len()).unwrap()); + return (Some($width), num_str.get(0..num_str.len() - $suffix.len()).unwrap()); } )*} } parse_num_suffix! { - b"u8", NumWidth::Int(IntWidth::U8) - b"u16", NumWidth::Int(IntWidth::U16) - b"u32", NumWidth::Int(IntWidth::U32) - b"u64", NumWidth::Int(IntWidth::U64) - b"u128", NumWidth::Int(IntWidth::U128) - b"i8", NumWidth::Int(IntWidth::I8) - b"i16", NumWidth::Int(IntWidth::I16) - b"i32", NumWidth::Int(IntWidth::I32) - b"i64", NumWidth::Int(IntWidth::I64) - b"i128", NumWidth::Int(IntWidth::I128) - b"nat", NumWidth::Int(IntWidth::Nat) - b"dec", NumWidth::Float(FloatWidth::Dec) - b"f32", NumWidth::Float(FloatWidth::F32) - b"f64", NumWidth::Float(FloatWidth::F64) + "u8", NumWidth::Int(IntWidth::U8) + "u16", NumWidth::Int(IntWidth::U16) + "u32", NumWidth::Int(IntWidth::U32) + "u64", NumWidth::Int(IntWidth::U64) + "u128", NumWidth::Int(IntWidth::U128) + "i8", NumWidth::Int(IntWidth::I8) + "i16", NumWidth::Int(IntWidth::I16) + "i32", NumWidth::Int(IntWidth::I32) + "i64", NumWidth::Int(IntWidth::I64) + "i128", NumWidth::Int(IntWidth::I128) + "nat", NumWidth::Int(IntWidth::Nat) + "dec", NumWidth::Float(FloatWidth::Dec) + "f32", NumWidth::Float(FloatWidth::F32) + "f64", NumWidth::Float(FloatWidth::F64) } - (NumericBound::None, num_str) + (None, num_str) } /// Integer parsing code taken from the rust libcore, @@ -226,47 +221,11 @@ fn parse_literal_suffix(num_str: &[u8]) -> (NumericBound, &[u8]) { /// the LEGAL_DETAILS file in the root directory of this distribution. /// /// Thanks to the Rust project and its contributors! -trait FromStrRadixHelper: PartialOrd + Copy { - fn min_value() -> Self; - fn max_value() -> Self; - fn from_u32(u: u32) -> Self; - fn checked_mul(&self, other: u32) -> Option; - fn checked_sub(&self, other: u32) -> Option; - fn checked_add(&self, other: u32) -> Option; -} - -macro_rules! doit { - ($($t:ty)*) => ($(impl FromStrRadixHelper for $t { - #[inline] - fn min_value() -> Self { Self::min_value() } - #[inline] - fn max_value() -> Self { Self::max_value() } - #[inline] - fn from_u32(u: u32) -> Self { u as Self } - #[inline] - fn checked_mul(&self, other: u32) -> Option { - Self::checked_mul(*self, other as Self) - } - #[inline] - fn checked_sub(&self, other: u32) -> Option { - Self::checked_sub(*self, other as Self) - } - #[inline] - fn checked_add(&self, other: u32) -> Option { - Self::checked_add(*self, other as Self) - } - })*) -} -// We only need the i64 implementation, but libcore defines -// doit! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize } -doit! { i64 } - -fn from_str_radix( +fn from_str_radix( src: &str, radix: u32, -) -> Result<(T, NumericBound), ParseIntError> { +) -> Result<(IntValue, NumericBound), IntErrorKind> { use self::IntErrorKind::*; - use self::ParseIntError as PIE; assert!( (2..=36).contains(&radix), @@ -274,65 +233,117 @@ fn from_str_radix( radix ); - if src.is_empty() { - return Err(PIE { kind: Empty }); - } + let (opt_exact_bound, src) = parse_literal_suffix(src); - let is_signed_ty = T::from_u32(0) > T::min_value(); - - // all valid digits are ascii, so we will just iterate over the utf8 bytes - // and cast them to chars. .to_digit() will safely return None for anything - // other than a valid ascii digit for the given radix, including the first-byte - // of multi-byte sequences - let src = src.as_bytes(); - - let (is_positive, digits) = match src[0] { - b'+' => (true, &src[1..]), - b'-' if is_signed_ty => (false, &src[1..]), - _ => (true, src), + use std::num::IntErrorKind as StdIEK; + let result = match i128::from_str_radix(src, radix) { + Ok(result) => IntValue::I128(result), + Err(pie) => match pie.kind() { + StdIEK::Empty => return Err(IntErrorKind::Empty), + StdIEK::InvalidDigit => return Err(IntErrorKind::InvalidDigit), + StdIEK::NegOverflow => return Err(IntErrorKind::Underflow), + StdIEK::PosOverflow => { + // try a u128 + match u128::from_str_radix(src, radix) { + Ok(result) => IntValue::U128(result), + Err(pie) => match pie.kind() { + StdIEK::InvalidDigit => return Err(IntErrorKind::InvalidDigit), + StdIEK::PosOverflow => return Err(IntErrorKind::Overflow), + StdIEK::Empty | StdIEK::Zero | StdIEK::NegOverflow => unreachable!(), + _ => unreachable!("I thought all possibilities were exhausted, but std::num added a new one") + }, + } + } + StdIEK::Zero => unreachable!("Parsed a i128"), + _ => unreachable!( + "I thought all possibilities were exhausted, but std::num added a new one" + ), + }, }; - let (bound, digits) = parse_literal_suffix(digits); + let (lower_bound, is_negative) = match result { + IntValue::I128(num) => (lower_bound_of_int(num), num <= 0), + IntValue::U128(_) => (IntWidth::U128, false), + }; - if digits.is_empty() { - return Err(PIE { kind: Empty }); + match opt_exact_bound { + None => { + // TODO: use the lower bound + Ok((result, NumericBound::None)) + } + Some(bound @ NumWidth::Float(_)) => { + // For now, assume floats can represent all integers + // TODO: this is somewhat incorrect, revisit + Ok((result, NumericBound::Exact(bound))) + } + Some(NumWidth::Int(exact_width)) => { + // We need to check if the exact bound >= lower bound. + if exact_width.is_superset(&lower_bound, is_negative) { + // Great! Use the exact bound. + Ok((result, NumericBound::Exact(NumWidth::Int(exact_width)))) + } else { + // This is something like 200i8; the lower bound is u8, which holds strictly more + // ints on the positive side than i8 does. Report an error depending on which side + // of the integers we checked. + let err = if is_negative { + UnderflowsSuffix { + suffix_type: exact_width.type_str(), + min_value: exact_width.min_value(), + } + } else { + OverflowsSuffix { + suffix_type: exact_width.type_str(), + max_value: exact_width.max_value(), + } + }; + Err(err) + } + } } +} - let mut result = T::from_u32(0); - if is_positive { - // The number is positive - for &c in digits { - let x = match (c as char).to_digit(radix) { - Some(x) => x, - None => return Err(PIE { kind: InvalidDigit }), - }; - result = match result.checked_mul(radix) { - Some(result) => result, - None => return Err(PIE { kind: Overflow }), - }; - result = match result.checked_add(x) { - Some(result) => result, - None => return Err(PIE { kind: Overflow }), - }; +fn lower_bound_of_int(result: i128) -> IntWidth { + use IntWidth::*; + if result >= 0 { + let result = result as u128; + if result > U64.max_value() { + I128 + } else if result > I64.max_value() { + U64 + } else if result > U32.max_value() { + I64 + } else if result > I32.max_value() { + U32 + } else if result > U16.max_value() { + I32 + } else if result > I16.max_value() { + U16 + } else if result > U8.max_value() { + I16 + } else if result > I8.max_value() { + U8 + } else { + I8 } } else { - // The number is negative - for &c in digits { - let x = match (c as char).to_digit(radix) { - Some(x) => x, - None => return Err(PIE { kind: InvalidDigit }), - }; - result = match result.checked_mul(radix) { - Some(result) => result, - None => return Err(PIE { kind: Underflow }), - }; - result = match result.checked_sub(x) { - Some(result) => result, - None => return Err(PIE { kind: Underflow }), - }; + if result < I64.min_value() { + I128 + } else if result < I32.min_value() { + I64 + } else if result < I16.min_value() { + I32 + } else if result < I8.min_value() { + I16 + } else { + I8 } } - Ok((result, bound)) +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum IntSign { + Unsigned, + Signed, } #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -350,6 +361,109 @@ pub enum IntWidth { Nat, } +impl IntWidth { + /// Returns the `IntSign` and bit width of a variant. + fn sign_and_width(&self) -> (IntSign, u32) { + use IntSign::*; + use IntWidth::*; + match self { + U8 => (Unsigned, 8), + U16 => (Unsigned, 16), + U32 => (Unsigned, 32), + U64 => (Unsigned, 64), + U128 => (Unsigned, 128), + I8 => (Signed, 8), + I16 => (Signed, 16), + I32 => (Signed, 32), + I64 => (Signed, 64), + I128 => (Signed, 128), + // TODO: this is platform specific! + Nat => (Unsigned, 64), + } + } + + fn type_str(&self) -> &'static str { + use IntWidth::*; + match self { + U8 => "U8", + U16 => "U16", + U32 => "U32", + U64 => "U64", + U128 => "U128", + I8 => "I8", + I16 => "I16", + I32 => "I32", + I64 => "I64", + I128 => "I128", + Nat => "Nat", + } + } + + fn max_value(&self) -> u128 { + use IntWidth::*; + match self { + U8 => u8::MAX as u128, + U16 => u16::MAX as u128, + U32 => u32::MAX as u128, + U64 => u64::MAX as u128, + U128 => u128::MAX, + I8 => i8::MAX as u128, + I16 => i16::MAX as u128, + I32 => i32::MAX as u128, + I64 => i64::MAX as u128, + I128 => i128::MAX as u128, + // TODO: this is platform specific! + Nat => u64::MAX as u128, + } + } + + fn min_value(&self) -> i128 { + use IntWidth::*; + match self { + U8 | U16 | U32 | U64 | U128 | Nat => 0, + I8 => i8::MIN as i128, + I16 => i16::MIN as i128, + I32 => i32::MIN as i128, + I64 => i64::MIN as i128, + I128 => i128::MIN, + } + } + + /// Checks if `self` represents superset of integers that `lower_bound` represents, on a particular + /// side of the integers relative to 0. + /// + /// If `is_negative` is true, the negative side is checked; otherwise the positive side is checked. + pub fn is_superset(&self, lower_bound: &Self, is_negative: bool) -> bool { + use IntSign::*; + + if is_negative { + match (self.sign_and_width(), lower_bound.sign_and_width()) { + ((Signed, us), (Signed, lower_bound)) => us >= lower_bound, + // Unsigned ints can never represent negative numbers; signed (non-zero width) + // ints always can. + ((Unsigned, _), (Signed, _)) => false, + ((Signed, _), (Unsigned, _)) => true, + // Trivially true; both can only express 0. + ((Unsigned, _), (Unsigned, _)) => true, + } + } else { + match (self.sign_and_width(), lower_bound.sign_and_width()) { + ((Signed, us), (Signed, lower_bound)) + | ((Unsigned, us), (Unsigned, lower_bound)) => us >= lower_bound, + + // Unsigned ints with the same bit width as their unsigned counterparts can always + // express 2x more integers on the positive side as unsigned ints. + ((Unsigned, us), (Signed, lower_bound)) => us >= lower_bound, + + // ...but that means signed int widths can represent less than their unsigned + // counterparts, so the below is true iff the bit width is strictly greater. E.g. + // i16 is a superset of u8, but i16 is not a superset of u16. + ((Signed, us), (Unsigned, lower_bound)) => us > lower_bound, + } + } + } +} + #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum FloatWidth { Dec, @@ -374,28 +488,3 @@ where /// Must have exactly the width `W`. Exact(W), } - -/// An error which can be returned when parsing an integer. -/// -/// This error is used as the error type for the `from_str_radix()` functions -/// on the primitive integer types, such as [`i8::from_str_radix`]. -/// -/// # Potential causes -/// -/// Among other causes, `ParseIntError` can be thrown because of leading or trailing whitespace -/// in the string e.g., when it is obtained from the standard input. -/// Using the [`str.trim()`] method ensures that no whitespace remains before parsing. -/// -/// [`str.trim()`]: ../../std/primitive.str.html#method.trim -/// [`i8::from_str_radix`]: ../../std/primitive.i8.html#method.from_str_radix -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ParseIntError { - kind: IntErrorKind, -} - -impl ParseIntError { - /// Outputs the detailed cause of parsing an integer failing. - pub fn kind(&self) -> &IntErrorKind { - &self.kind - } -} diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index eeb8c4b7ce..ac85b53306 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -1,5 +1,5 @@ use crate::env::Env; -use crate::expr::{canonicalize_expr, unescape_char, Expr, Output}; +use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output}; use crate::num::{ finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatWidth, IntWidth, NumWidth, NumericBound, ParsedNumResult, @@ -29,8 +29,14 @@ pub enum Pattern { ext_var: Variable, destructs: Vec>, }, - NumLiteral(Variable, Box, i64, NumericBound), - IntLiteral(Variable, Variable, Box, i64, NumericBound), + NumLiteral(Variable, Box, IntValue, NumericBound), + IntLiteral( + Variable, + Variable, + Box, + IntValue, + NumericBound, + ), FloatLiteral(Variable, Variable, Box, f64, NumericBound), StrLiteral(Box), Underscore, @@ -247,10 +253,20 @@ pub fn canonicalize_pattern<'a>( let problem = MalformedPatternProblem::MalformedBase(base); malformed_pattern(env, problem, region) } + Ok((IntValue::U128(_), _)) if is_negative => { + // Can't negate a u128; that doesn't fit in any integer literal type we support. + let problem = MalformedPatternProblem::MalformedInt; + malformed_pattern(env, problem, region) + } Ok((int, bound)) => { let sign_str = if is_negative { "-" } else { "" }; let int_str = format!("{}{}", sign_str, int.to_string()).into_boxed_str(); - let i = if is_negative { -int } else { int }; + let i = match int { + // Safety: this is fine because I128::MAX = |I128::MIN| - 1 + IntValue::I128(n) if is_negative => IntValue::I128(-n), + IntValue::I128(n) => IntValue::I128(n), + IntValue::U128(_) => unreachable!(), + }; Pattern::IntLiteral(var_store.fresh(), var_store.fresh(), int_str, i, bound) } }, diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index e526334175..abc690cc5b 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -15,7 +15,7 @@ mod test_can { use crate::helpers::{can_expr_with, test_home, CanExprOut}; use bumpalo::Bump; use roc_can::expr::Expr::{self, *}; - use roc_can::expr::{ClosureData, Recursive}; + use roc_can::expr::{ClosureData, IntValue, Recursive}; use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError}; use roc_region::all::{Position, Region}; use std::{f64, i64}; @@ -47,7 +47,7 @@ mod test_can { match actual_out.loc_expr.value { Expr::Int(_, _, _, actual, _) => { - assert_eq!(expected, actual); + assert_eq!(IntValue::I128(expected), actual); } actual => { panic!("Expected an Int *, but got: {:?}", actual); @@ -55,13 +55,13 @@ mod test_can { } } - fn assert_can_num(input: &str, expected: i64) { + fn assert_can_num(input: &str, expected: i128) { let arena = Bump::new(); let actual_out = can_expr_with(&arena, test_home(), input); match actual_out.loc_expr.value { Expr::Num(_, _, actual, _) => { - assert_eq!(expected, actual); + assert_eq!(IntValue::I128(expected), actual); } actual => { panic!("Expected a Num, but got: {:?}", actual); @@ -186,12 +186,12 @@ mod test_can { #[test] fn num_max() { - assert_can_num(&(i64::MAX.to_string()), i64::MAX); + assert_can_num(&(i64::MAX.to_string()), i64::MAX.into()); } #[test] fn num_min() { - assert_can_num(&(i64::MIN.to_string()), i64::MIN); + assert_can_num(&(i64::MIN.to_string()), i64::MIN.into()); } #[test] diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 031ce7b1db..de90098631 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -771,11 +771,13 @@ pub fn build_exp_literal<'a, 'ctx, 'env>( env.context.bool_type().const_int(*int as u64, false).into() } Layout::Builtin(Builtin::Int(int_width)) => { - int_with_precision(env, *int as i128, *int_width).into() + int_with_precision(env, *int, *int_width).into() } _ => panic!("Invalid layout for int literal = {:?}", layout), }, + U128(int) => const_u128(env, *int).into(), + Float(float) => match layout { Layout::Builtin(Builtin::Float(float_width)) => { float_with_precision(env, *float, *float_width) @@ -3073,6 +3075,19 @@ fn const_i128<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, value: i128) -> IntValu .const_int_arbitrary_precision(&[a, b]) } +fn const_u128<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, value: u128) -> IntValue<'ctx> { + // truncate the lower 64 bits + let value = value as u128; + let a = value as u64; + + // get the upper 64 bits + let b = (value >> 64) as u64; + + env.context + .i128_type() + .const_int_arbitrary_precision(&[a, b]) +} + fn build_switch_ir<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index 6481f924c4..e52409c240 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -1638,7 +1638,9 @@ fn literal_spec( match literal { Str(_) => new_static_string(builder, block), - Int(_) | Float(_) | Decimal(_) | Bool(_) | Byte(_) => builder.add_make_tuple(block, &[]), + Int(_) | U128(_) | Float(_) | Decimal(_) | Bool(_) | Byte(_) => { + builder.add_make_tuple(block, &[]) + } } } diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index 8f78a32458..b37391d9cd 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -87,6 +87,7 @@ enum Test<'a> { arguments: Vec<(Pattern<'a>, Layout<'a>)>, }, IsInt(i128, IntWidth), + IsU128(u128), IsFloat(u64, FloatWidth), IsDecimal(RocDec), IsStr(Box), @@ -136,6 +137,10 @@ impl<'a> Hash for Test<'a> { state.write_u8(6); v.0.hash(state); } + IsU128(v) => { + state.write_u8(7); + v.hash(state); + } } } } @@ -311,6 +316,7 @@ fn tests_are_complete_help(last_test: &Test, number_of_tests: usize) -> bool { Test::IsByte { num_alts, .. } => number_of_tests == *num_alts, Test::IsBit(_) => number_of_tests == 2, Test::IsInt(_, _) => false, + Test::IsU128(_) => false, Test::IsFloat(_, _) => false, Test::IsDecimal(_) => false, Test::IsStr(_) => false, @@ -565,6 +571,7 @@ fn test_at_path<'a>( num_alts: union.alternatives.len(), }, IntLiteral(v, precision) => IsInt(*v, *precision), + U128Literal(v) => IsU128(*v), FloatLiteral(v, precision) => IsFloat(*v, *precision), DecimalLiteral(v) => IsDecimal(*v), StrLiteral(v) => IsStr(v.clone()), @@ -823,6 +830,18 @@ fn to_relevant_branch_help<'a>( _ => None, }, + U128Literal(int) => match test { + IsU128(is_int) if int == *is_int => { + start.extend(end); + Some(Branch { + goal: branch.goal, + guard: branch.guard.clone(), + patterns: start, + }) + } + _ => None, + }, + FloatLiteral(float, p1) => match test { IsFloat(test_float, p2) if float == *test_float => { debug_assert_eq!(p1, *p2); @@ -934,6 +953,7 @@ fn needs_tests(pattern: &Pattern) -> bool { | BitLiteral { .. } | EnumLiteral { .. } | IntLiteral(_, _) + | U128Literal(_) | FloatLiteral(_, _) | DecimalLiteral(_) | StrLiteral(_) => true, @@ -1305,6 +1325,14 @@ fn test_to_equality<'a>( (stores, lhs_symbol, rhs_symbol, None) } + Test::IsU128(test_int) => { + let lhs = Expr::Literal(Literal::U128(test_int)); + let lhs_symbol = env.unique_symbol(); + stores.push((lhs_symbol, Layout::int_width(IntWidth::U128), lhs)); + + (stores, lhs_symbol, rhs_symbol, None) + } + Test::IsFloat(test_int, precision) => { // TODO maybe we can actually use i64 comparison here? let test_float = f64::from_bits(test_int as u64); diff --git a/compiler/mono/src/exhaustive.rs b/compiler/mono/src/exhaustive.rs index a29ca6cf12..2fac242662 100644 --- a/compiler/mono/src/exhaustive.rs +++ b/compiler/mono/src/exhaustive.rs @@ -54,6 +54,7 @@ pub enum Pattern { #[derive(Clone, Debug, PartialEq)] pub enum Literal { Int(i128), + U128(u128), Bit(bool), Byte(u8), Float(u64), @@ -66,6 +67,7 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern { match pattern { IntLiteral(v, _) => Literal(Literal::Int(*v)), + U128Literal(v) => Literal(Literal::U128(*v)), FloatLiteral(v, _) => Literal(Literal::Float(*v)), DecimalLiteral(v) => Literal(Literal::Decimal(*v)), StrLiteral(v) => Literal(Literal::Str(v.clone())), diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 2296d5b55d..75f89ae6fa 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -8,7 +8,7 @@ use crate::layout::{ use bumpalo::collections::Vec; use bumpalo::Bump; use roc_builtins::bitcode::{FloatWidth, IntWidth}; -use roc_can::expr::ClosureData; +use roc_can::expr::{ClosureData, IntValue}; use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; @@ -1278,6 +1278,7 @@ impl ModifyRc { pub enum Literal<'a> { // Literals Int(i128), + U128(u128), Float(f64), Decimal(RocDec), Str(&'a str), @@ -1524,6 +1525,7 @@ impl<'a> Literal<'a> { match self { Int(lit) => alloc.text(format!("{}i64", lit)), + U128(lit) => alloc.text(format!("{}u128", lit)), Float(lit) => alloc.text(format!("{}f64", lit)), // TODO: Add proper Dec.to_str Decimal(lit) => alloc.text(format!("{}Dec", lit.0)), @@ -3011,7 +3013,10 @@ fn try_make_literal<'a>( match can_expr { Int(_, precision, _, int, _bound) => { match num_argument_to_int_or_float(env.subs, env.target_info, *precision, false) { - IntOrFloat::Int(_) => Some(Literal::Int(*int)), + IntOrFloat::Int(_) => Some(match *int { + IntValue::I128(n) => Literal::Int(n), + IntValue::U128(n) => Literal::U128(n), + }), _ => unreachable!("unexpected float precision for integer"), } } @@ -3039,8 +3044,14 @@ fn try_make_literal<'a>( Num(var, num_str, num, _bound) => { // first figure out what kind of number this is match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { - IntOrFloat::Int(_) => Some(Literal::Int((*num).into())), - IntOrFloat::Float(_) => Some(Literal::Float(*num as f64)), + IntOrFloat::Int(_) => Some(match *num { + IntValue::I128(n) => Literal::Int(n), + IntValue::U128(n) => Literal::U128(n), + }), + IntOrFloat::Float(_) => Some(match *num { + IntValue::I128(n) => Literal::Float(n as f64), + IntValue::U128(n) => Literal::Float(n as f64), + }), IntOrFloat::DecimalFloatType => { let dec = match RocDec::from_str(num_str) { Some(d) => d, @@ -3076,7 +3087,10 @@ pub fn with_hole<'a>( match num_argument_to_int_or_float(env.subs, env.target_info, precision, false) { IntOrFloat::Int(precision) => Stmt::Let( assigned, - Expr::Literal(Literal::Int(int)), + Expr::Literal(match int { + IntValue::I128(n) => Literal::Int(n), + IntValue::U128(n) => Literal::U128(n), + }), Layout::Builtin(Builtin::Int(precision)), hole, ), @@ -3120,13 +3134,19 @@ pub fn with_hole<'a>( match num_argument_to_int_or_float(env.subs, env.target_info, var, false) { IntOrFloat::Int(precision) => Stmt::Let( assigned, - Expr::Literal(Literal::Int(num.into())), + Expr::Literal(match num { + IntValue::I128(n) => Literal::Int(n), + IntValue::U128(n) => Literal::U128(n), + }), Layout::int_width(precision), hole, ), IntOrFloat::Float(precision) => Stmt::Let( assigned, - Expr::Literal(Literal::Float(num as f64)), + Expr::Literal(match num { + IntValue::I128(n) => Literal::Float(n as f64), + IntValue::U128(n) => Literal::Float(n as f64), + }), Layout::float_width(precision), hole, ), @@ -6211,6 +6231,7 @@ fn store_pattern_help<'a>( return StorePattern::NotProductive(stmt); } IntLiteral(_, _) + | U128Literal(_) | FloatLiteral(_, _) | DecimalLiteral(_) | EnumLiteral { .. } @@ -7583,6 +7604,7 @@ fn call_specialized_proc<'a>( pub enum Pattern<'a> { Identifier(Symbol), Underscore, + U128Literal(u128), IntLiteral(i128, IntWidth), FloatLiteral(u64, FloatWidth), DecimalLiteral(RocDec), @@ -7664,7 +7686,13 @@ fn from_can_pattern_help<'a>( Identifier(symbol) => Ok(Pattern::Identifier(*symbol)), IntLiteral(_, precision_var, _, int, _bound) => { match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) { - IntOrFloat::Int(precision) => Ok(Pattern::IntLiteral(*int as i128, precision)), + IntOrFloat::Int(precision) => { + let int = match *int { + IntValue::I128(n) => Pattern::IntLiteral(n, precision), + IntValue::U128(n) => Pattern::U128Literal(n), + }; + Ok(int) + } other => { panic!( "Invalid precision for int pattern: {:?} has {:?}", @@ -7706,8 +7734,18 @@ fn from_can_pattern_help<'a>( } NumLiteral(var, num_str, num, _bound) => { match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { - IntOrFloat::Int(precision) => Ok(Pattern::IntLiteral(*num as i128, precision)), - IntOrFloat::Float(precision) => Ok(Pattern::FloatLiteral(*num as u64, precision)), + IntOrFloat::Int(precision) => Ok(match num { + IntValue::I128(num) => Pattern::IntLiteral(*num, precision), + IntValue::U128(num) => Pattern::U128Literal(*num), + }), + IntOrFloat::Float(precision) => { + // TODO: this may be lossy + let num = match *num { + IntValue::I128(n) => f64::to_bits(n as f64), + IntValue::U128(n) => f64::to_bits(n as f64), + }; + Ok(Pattern::FloatLiteral(num, precision)) + } IntOrFloat::DecimalFloatType => { let dec = match RocDec::from_str(num_str) { Some(d) => d, diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index f7011d6b1d..f8d964e8fc 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -109,6 +109,16 @@ pub enum IntErrorKind { Underflow, /// This is an integer, but it has a float numeric suffix. FloatSuffix, + /// The integer literal overflows the width of the suffix associated with it. + OverflowsSuffix { + suffix_type: &'static str, + max_value: u128, + }, + /// The integer literal underflows the width of the suffix associated with it. + UnderflowsSuffix { + suffix_type: &'static str, + min_value: i128, + }, } /// Enum to store the various types of errors that can cause parsing a float to fail. diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index fd34e795c3..48db9aa376 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -26,6 +26,8 @@ const VALUE_NOT_EXPOSED: &str = "NOT EXPOSED"; const MODULE_NOT_IMPORTED: &str = "MODULE NOT IMPORTED"; const NESTED_DATATYPE: &str = "NESTED DATATYPE"; const CONFLICTING_NUMBER_SUFFIX: &str = "CONFLICTING NUMBER SUFFIX"; +const NUMBER_OVERFLOWS_SUFFIX: &str = "NUMBER OVERFLOWS SUFFIX"; +const NUMBER_UNDERFLOWS_SUFFIX: &str = "NUMBER UNDERFLOWS SUFFIX"; pub fn can_problem<'b>( alloc: &'b RocDocAllocator<'b>, @@ -1136,10 +1138,20 @@ fn pretty_runtime_error<'b>( } RuntimeError::InvalidInt(error_kind @ IntErrorKind::Underflow, _base, region, _raw_str) | RuntimeError::InvalidInt(error_kind @ IntErrorKind::Overflow, _base, region, _raw_str) => { - let big_or_small = if let IntErrorKind::Underflow = error_kind { - "small" + let (big_or_small, info) = if let IntErrorKind::Underflow = error_kind { + ( + "small", + alloc.reflow( + "The smallest number representable in Roc is the minimum I128 value, -170_141_183_460_469_231_731_687_303_715_884_105_728.", + ), + ) } else { - "big" + ( + "big", + alloc.reflow( + "The largest number representable in Roc is the maximum U128 value, 340_282_366_920_938_463_463_374_607_431_768_211_455.", + ), + ) }; let tip = alloc @@ -1153,7 +1165,7 @@ fn pretty_runtime_error<'b>( alloc.reflow(":"), ]), alloc.region(lines.convert_region(region)), - alloc.reflow("Roc uses signed 64-bit integers, allowing values between −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807."), + info, tip, ]); @@ -1169,6 +1181,54 @@ fn pretty_runtime_error<'b>( title = CONFLICTING_NUMBER_SUFFIX; } + RuntimeError::InvalidInt( + IntErrorKind::OverflowsSuffix { + suffix_type, + max_value, + }, + _base, + region, + _raw_str, + ) => { + doc = alloc.stack(vec![ + alloc.concat(vec![alloc.reflow( + "This integer literal overflows the type indicated by its suffix:", + )]), + alloc.region(lines.convert_region(region)), + alloc.tip().append(alloc.concat(vec![ + alloc.reflow("The suffix indicates this integer is a "), + alloc.type_str(suffix_type), + alloc.reflow(", whose maximum value is "), + alloc.text(format!("{}.", max_value)), + ])), + ]); + + title = NUMBER_OVERFLOWS_SUFFIX; + } + RuntimeError::InvalidInt( + IntErrorKind::UnderflowsSuffix { + suffix_type, + min_value, + }, + _base, + region, + _raw_str, + ) => { + doc = alloc.stack(vec![ + alloc.concat(vec![alloc.reflow( + "This integer literal underflows the type indicated by its suffix:", + )]), + alloc.region(lines.convert_region(region)), + alloc.tip().append(alloc.concat(vec![ + alloc.reflow("The suffix indicates this integer is a "), + alloc.type_str(suffix_type), + alloc.reflow(", whose minimum value is "), + alloc.text(format!("{}.", min_value)), + ])), + ]); + + title = NUMBER_UNDERFLOWS_SUFFIX; + } RuntimeError::InvalidOptionalValue { field_name, field_region, diff --git a/reporting/src/error/mono.rs b/reporting/src/error/mono.rs index a3df3d2338..6b5518a717 100644 --- a/reporting/src/error/mono.rs +++ b/reporting/src/error/mono.rs @@ -148,6 +148,7 @@ fn pattern_to_doc_help<'b>( Anything => alloc.text("_"), Literal(l) => match l { Int(i) => alloc.text(i.to_string()), + U128(i) => alloc.text(i.to_string()), Bit(true) => alloc.text("True"), Bit(false) => alloc.text("False"), Byte(b) => alloc.text(b.to_string()), diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index e81c0919a5..8fbbecb906 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -3409,15 +3409,15 @@ mod test_reporting { report_problem_as( indoc!( r#" - x = 9_223_372_036_854_775_807_000 + x = 170_141_183_460_469_231_731_687_303_715_884_105_728_000 - y = -9_223_372_036_854_775_807_000 + y = -170_141_183_460_469_231_731_687_303_715_884_105_728_000 - h = 0x8FFF_FFFF_FFFF_FFFF - l = -0x8FFF_FFFF_FFFF_FFFF + h = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF + l = -0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF - minlit = -9_223_372_036_854_775_808 - maxlit = 9_223_372_036_854_775_807 + minlit = -170_141_183_460_469_231_731_687_303_715_884_105_728 + maxlit = 340_282_366_920_938_463_463_374_607_431_768_211_455 x + y + h + l + minlit + maxlit "# @@ -3428,11 +3428,11 @@ mod test_reporting { This integer literal is too big: - 1│ x = 9_223_372_036_854_775_807_000 - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 1│ x = 170_141_183_460_469_231_731_687_303_715_884_105_728_000 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Roc uses signed 64-bit integers, allowing values between - −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. + The largest number representable in Roc is the maximum U128 value, + 340_282_366_920_938_463_463_374_607_431_768_211_455. Tip: Learn more about number literals at TODO @@ -3440,11 +3440,11 @@ mod test_reporting { This integer literal is too small: - 3│ y = -9_223_372_036_854_775_807_000 - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 3│ y = -170_141_183_460_469_231_731_687_303_715_884_105_728_000 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Roc uses signed 64-bit integers, allowing values between - −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. + The smallest number representable in Roc is the minimum I128 value, + -170_141_183_460_469_231_731_687_303_715_884_105_728. Tip: Learn more about number literals at TODO @@ -3452,11 +3452,11 @@ mod test_reporting { This integer literal is too big: - 5│ h = 0x8FFF_FFFF_FFFF_FFFF - ^^^^^^^^^^^^^^^^^^^^^ + 5│ h = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Roc uses signed 64-bit integers, allowing values between - −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. + The largest number representable in Roc is the maximum U128 value, + 340_282_366_920_938_463_463_374_607_431_768_211_455. Tip: Learn more about number literals at TODO @@ -3464,11 +3464,11 @@ mod test_reporting { This integer literal is too small: - 6│ l = -0x8FFF_FFFF_FFFF_FFFF - ^^^^^^^^^^^^^^^^^^^^^^ + 6│ l = -0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Roc uses signed 64-bit integers, allowing values between - −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. + The smallest number representable in Roc is the minimum I128 value, + -170_141_183_460_469_231_731_687_303_715_884_105_728. Tip: Learn more about number literals at TODO "# @@ -7527,4 +7527,364 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn u8_overflow() { + report_problem_as( + "256u8", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 256u8 + ^^^^^ + + Tip: The suffix indicates this integer is a U8, whose maximum value is + 255. + "# + ), + ) + } + + #[test] + fn negative_u8() { + report_problem_as( + "-1u8", + indoc!( + r#" + ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + + This integer literal underflows the type indicated by its suffix: + + 1│ -1u8 + ^^^^ + + Tip: The suffix indicates this integer is a U8, whose minimum value is + 0. + "# + ), + ) + } + + #[test] + fn u16_overflow() { + report_problem_as( + "65536u16", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 65536u16 + ^^^^^^^^ + + Tip: The suffix indicates this integer is a U16, whose maximum value + is 65535. + "# + ), + ) + } + + #[test] + fn negative_u16() { + report_problem_as( + "-1u16", + indoc!( + r#" + ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + + This integer literal underflows the type indicated by its suffix: + + 1│ -1u16 + ^^^^^ + + Tip: The suffix indicates this integer is a U16, whose minimum value + is 0. + "# + ), + ) + } + + #[test] + fn u32_overflow() { + report_problem_as( + "4_294_967_296u32", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 4_294_967_296u32 + ^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a U32, whose maximum value + is 4294967295. + "# + ), + ) + } + + #[test] + fn negative_u32() { + report_problem_as( + "-1u32", + indoc!( + r#" + ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + + This integer literal underflows the type indicated by its suffix: + + 1│ -1u32 + ^^^^^ + + Tip: The suffix indicates this integer is a U32, whose minimum value + is 0. + "# + ), + ) + } + + #[test] + fn u64_overflow() { + report_problem_as( + "18_446_744_073_709_551_616u64", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 18_446_744_073_709_551_616u64 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a U64, whose maximum value + is 18446744073709551615. + "# + ), + ) + } + + #[test] + fn negative_u64() { + report_problem_as( + "-1u64", + indoc!( + r#" + ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + + This integer literal underflows the type indicated by its suffix: + + 1│ -1u64 + ^^^^^ + + Tip: The suffix indicates this integer is a U64, whose minimum value + is 0. + "# + ), + ) + } + + #[test] + fn negative_u128() { + report_problem_as( + "-1u128", + indoc!( + r#" + ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + + This integer literal underflows the type indicated by its suffix: + + 1│ -1u128 + ^^^^^^ + + Tip: The suffix indicates this integer is a U128, whose minimum value + is 0. + "# + ), + ) + } + + #[test] + fn i8_overflow() { + report_problem_as( + "128i8", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 128i8 + ^^^^^ + + Tip: The suffix indicates this integer is a I8, whose maximum value is + 127. + "# + ), + ) + } + + #[test] + fn i8_underflow() { + report_problem_as( + "-129i8", + indoc!( + r#" + ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + + This integer literal underflows the type indicated by its suffix: + + 1│ -129i8 + ^^^^^^ + + Tip: The suffix indicates this integer is a I8, whose minimum value is + -128. + "# + ), + ) + } + + #[test] + fn i16_overflow() { + report_problem_as( + "32768i16", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 32768i16 + ^^^^^^^^ + + Tip: The suffix indicates this integer is a I16, whose maximum value + is 32767. + "# + ), + ) + } + + #[test] + fn i16_underflow() { + report_problem_as( + "-32769i16", + indoc!( + r#" + ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + + This integer literal underflows the type indicated by its suffix: + + 1│ -32769i16 + ^^^^^^^^^ + + Tip: The suffix indicates this integer is a I16, whose minimum value + is -32768. + "# + ), + ) + } + + #[test] + fn i32_overflow() { + report_problem_as( + "2_147_483_648i32", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 2_147_483_648i32 + ^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a I32, whose maximum value + is 2147483647. + "# + ), + ) + } + + #[test] + fn i32_underflow() { + report_problem_as( + "-2_147_483_649i32", + indoc!( + r#" + ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + + This integer literal underflows the type indicated by its suffix: + + 1│ -2_147_483_649i32 + ^^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a I32, whose minimum value + is -2147483648. + "# + ), + ) + } + + #[test] + fn i64_overflow() { + report_problem_as( + "9_223_372_036_854_775_808i64", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 9_223_372_036_854_775_808i64 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a I64, whose maximum value + is 9223372036854775807. + "# + ), + ) + } + + #[test] + fn i64_underflow() { + report_problem_as( + "-9_223_372_036_854_775_809i64", + indoc!( + r#" + ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + + This integer literal underflows the type indicated by its suffix: + + 1│ -9_223_372_036_854_775_809i64 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a I64, whose minimum value + is -9223372036854775808. + "# + ), + ) + } + + #[test] + fn i128_overflow() { + report_problem_as( + "170_141_183_460_469_231_731_687_303_715_884_105_728i128", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 170_141_183_460_469_231_731_687_303_715_884_105_728i128 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a I128, whose maximum value + is 170141183460469231731687303715884105727. + "# + ), + ) + } } From 5362ed1e6c33811a8f24f29ec17e83d322e16a54 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Wed, 2 Feb 2022 23:38:54 -0500 Subject: [PATCH 461/541] Clippy --- compiler/can/src/num.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index 510833ccd9..847d729466 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -24,7 +24,7 @@ pub fn num_expr_from_result( var_store.fresh(), var_store.fresh(), (*str).into(), - num.into(), + num, bound, ), Ok((str, ParsedNumResult::Float(num, bound))) => Expr::Float( @@ -305,6 +305,7 @@ fn from_str_radix( fn lower_bound_of_int(result: i128) -> IntWidth { use IntWidth::*; if result >= 0 { + // Positive let result = result as u128; if result > U64.max_value() { I128 @@ -326,6 +327,7 @@ fn lower_bound_of_int(result: i128) -> IntWidth { I8 } } else { + // Negative if result < I64.min_value() { I128 } else if result < I32.min_value() { From 706640a1faa000288dc47db574cc5ec4b3535bb4 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 3 Feb 2022 00:18:24 -0500 Subject: [PATCH 462/541] Add underscore separators for large numbers in reporting --- reporting/src/error/canonicalize.rs | 26 ++++++++++++++++------- reporting/src/report.rs | 33 +++++++++++++++++++++++++++++ reporting/tests/test_reporting.rs | 14 ++++++------ 3 files changed, 58 insertions(+), 15 deletions(-) diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index 48db9aa376..3ad20cebf6 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -1141,16 +1141,24 @@ fn pretty_runtime_error<'b>( let (big_or_small, info) = if let IntErrorKind::Underflow = error_kind { ( "small", - alloc.reflow( - "The smallest number representable in Roc is the minimum I128 value, -170_141_183_460_469_231_731_687_303_715_884_105_728.", - ), + alloc.concat(vec![ + alloc.reflow( + "The smallest number representable in Roc is the minimum I128 value, ", + ), + alloc.int_literal(i128::MIN), + alloc.text("."), + ]), ) } else { ( "big", - alloc.reflow( - "The largest number representable in Roc is the maximum U128 value, 340_282_366_920_938_463_463_374_607_431_768_211_455.", - ), + alloc.concat(vec![ + alloc.reflow( + "The largest number representable in Roc is the maximum U128 value, ", + ), + alloc.int_literal(u128::MAX), + alloc.text("."), + ]), ) }; @@ -1199,7 +1207,8 @@ fn pretty_runtime_error<'b>( alloc.reflow("The suffix indicates this integer is a "), alloc.type_str(suffix_type), alloc.reflow(", whose maximum value is "), - alloc.text(format!("{}.", max_value)), + alloc.int_literal(max_value), + alloc.reflow("."), ])), ]); @@ -1223,7 +1232,8 @@ fn pretty_runtime_error<'b>( alloc.reflow("The suffix indicates this integer is a "), alloc.type_str(suffix_type), alloc.reflow(", whose minimum value is "), - alloc.text(format!("{}.", min_value)), + alloc.int_literal(min_value), + alloc.reflow("."), ])), ]); diff --git a/reporting/src/report.rs b/reporting/src/report.rs index 07a7cbfca7..a0dc1261a2 100644 --- a/reporting/src/report.rs +++ b/reporting/src/report.rs @@ -632,6 +632,39 @@ impl<'a> RocDocAllocator<'a> { self.text(format!("{}", ident.as_inline_str())) .annotate(Annotation::Symbol) } + + pub fn int_literal(&'a self, int: I) -> DocBuilder<'a, Self, Annotation> + where + I: ToString, + { + let s = int.to_string(); + + let is_negative = s.starts_with("-"); + + if s.len() < 7 + (is_negative as usize) { + // If the number is not at least in the millions, return it as-is. + return self.text(s); + } + + // Otherwise, let's add numeric separators to make it easier to read. + let mut result = String::with_capacity(s.len() + s.len() / 3); + for (idx, c) in s + .get((is_negative as usize)..) + .unwrap() + .chars() + .rev() + .enumerate() + { + if idx != 0 && idx % 3 == 0 { + result.push('_'); + } + result.push(c); + } + if is_negative { + result.push('-'); + } + self.text(result.chars().rev().collect::()) + } } #[derive(Copy, Clone)] diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 8fbbecb906..8535556e6c 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -7622,7 +7622,7 @@ I need all branches in an `if` to have the same type! ^^^^^^^^^^^^^^^^ Tip: The suffix indicates this integer is a U32, whose maximum value - is 4294967295. + is 4_294_967_295. "# ), ) @@ -7662,7 +7662,7 @@ I need all branches in an `if` to have the same type! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Tip: The suffix indicates this integer is a U64, whose maximum value - is 18446744073709551615. + is 18_446_744_073_709_551_615. "# ), ) @@ -7802,7 +7802,7 @@ I need all branches in an `if` to have the same type! ^^^^^^^^^^^^^^^^ Tip: The suffix indicates this integer is a I32, whose maximum value - is 2147483647. + is 2_147_483_647. "# ), ) @@ -7822,7 +7822,7 @@ I need all branches in an `if` to have the same type! ^^^^^^^^^^^^^^^^^ Tip: The suffix indicates this integer is a I32, whose minimum value - is -2147483648. + is -2_147_483_648. "# ), ) @@ -7842,7 +7842,7 @@ I need all branches in an `if` to have the same type! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Tip: The suffix indicates this integer is a I64, whose maximum value - is 9223372036854775807. + is 9_223_372_036_854_775_807. "# ), ) @@ -7862,7 +7862,7 @@ I need all branches in an `if` to have the same type! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Tip: The suffix indicates this integer is a I64, whose minimum value - is -9223372036854775808. + is -9_223_372_036_854_775_808. "# ), ) @@ -7882,7 +7882,7 @@ I need all branches in an `if` to have the same type! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Tip: The suffix indicates this integer is a I128, whose maximum value - is 170141183460469231731687303715884105727. + is 170_141_183_460_469_231_731_687_303_715_884_105_727. "# ), ) From 29f733b69bcfc919223b9ea585c1ae52451b6dfb Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 3 Feb 2022 00:19:18 -0500 Subject: [PATCH 463/541] Clippy --- reporting/src/report.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reporting/src/report.rs b/reporting/src/report.rs index a0dc1261a2..c74de2e0ff 100644 --- a/reporting/src/report.rs +++ b/reporting/src/report.rs @@ -639,7 +639,7 @@ impl<'a> RocDocAllocator<'a> { { let s = int.to_string(); - let is_negative = s.starts_with("-"); + let is_negative = s.starts_with('-'); if s.len() < 7 + (is_negative as usize) { // If the number is not at least in the millions, return it as-is. From 21d1cd786c343fe9b8f8236197945e1cc39a55ce Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Thu, 3 Feb 2022 00:43:04 -0500 Subject: [PATCH 464/541] Fix can tests --- compiler/can/tests/test_can.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index abc690cc5b..3489b0677f 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -79,7 +79,7 @@ mod test_can { fn int_too_large() { use roc_parse::ast::Base; - let string = (i64::MAX as i128 + 1).to_string(); + let string = "340_282_366_920_938_463_463_374_607_431_768_211_456".to_string(); assert_can( &string.clone(), @@ -96,7 +96,7 @@ mod test_can { fn int_too_small() { use roc_parse::ast::Base; - let string = (i64::MIN as i128 - 1).to_string(); + let string = "-170_141_183_460_469_231_731_687_303_715_884_105_729".to_string(); assert_can( &string.clone(), From d9bd98a19450f3ef78975806c796e34b79b81a25 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 3 Feb 2022 08:50:26 +0000 Subject: [PATCH 465/541] repl: bring back LLVM jit macros, as they're used in tests --- compiler/gen_llvm/src/lib.rs | 2 + compiler/gen_llvm/src/run_roc.rs | 121 +++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 compiler/gen_llvm/src/run_roc.rs diff --git a/compiler/gen_llvm/src/lib.rs b/compiler/gen_llvm/src/lib.rs index afe4ba885a..bef97f894c 100644 --- a/compiler/gen_llvm/src/lib.rs +++ b/compiler/gen_llvm/src/lib.rs @@ -5,3 +5,5 @@ #![allow(clippy::float_cmp)] pub mod llvm; + +pub mod run_roc; diff --git a/compiler/gen_llvm/src/run_roc.rs b/compiler/gen_llvm/src/run_roc.rs new file mode 100644 index 0000000000..d6237ce629 --- /dev/null +++ b/compiler/gen_llvm/src/run_roc.rs @@ -0,0 +1,121 @@ +use std::ffi::CString; +use std::mem::MaybeUninit; +use std::os::raw::c_char; + +/// This must have the same size as the repr() of RocCallResult! +pub const ROC_CALL_RESULT_DISCRIMINANT_SIZE: usize = std::mem::size_of::(); + +#[repr(C)] +pub struct RocCallResult { + tag: u64, + error_msg: *mut c_char, + value: MaybeUninit, +} + +impl From> for Result { + fn from(call_result: RocCallResult) -> Self { + match call_result.tag { + 0 => Ok(unsafe { call_result.value.assume_init() }), + _ => Err({ + let raw = unsafe { CString::from_raw(call_result.error_msg) }; + + let result = format!("{:?}", raw); + + // make sure rust does not try to free the Roc string + std::mem::forget(raw); + + result + }), + } + } +} + +#[macro_export] +macro_rules! run_jit_function { + ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{ + let v: String = String::new(); + run_jit_function!($lib, $main_fn_name, $ty, $transform, v) + }}; + + ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr) => {{ + use inkwell::context::Context; + use roc_gen_llvm::run_roc::RocCallResult; + use std::mem::MaybeUninit; + + unsafe { + let main: libloading::Symbol) -> ()> = + $lib.get($main_fn_name.as_bytes()) + .ok() + .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) + .expect("errored"); + + let mut result = MaybeUninit::uninit(); + + main(result.as_mut_ptr()); + + match result.assume_init().into() { + Ok(success) => { + // only if there are no exceptions thrown, check for errors + assert!($errors.is_empty(), "Encountered errors:\n{}", $errors); + + $transform(success) + } + Err(error_msg) => panic!("Roc failed with message: {}", error_msg), + } + } + }}; +} + +/// In the repl, we don't know the type that is returned; it it's large enough to not fit in 2 +/// registers (i.e. size bigger than 16 bytes on 64-bit systems), then we use this macro. +/// It explicitly allocates a buffer that the roc main function can write its result into. +#[macro_export] +macro_rules! run_jit_function_dynamic_type { + ($lib: expr, $main_fn_name: expr, $bytes:expr, $transform:expr) => {{ + let v: String = String::new(); + run_jit_function_dynamic_type!($lib, $main_fn_name, $bytes, $transform, v) + }}; + + ($lib: expr, $main_fn_name: expr, $bytes:expr, $transform:expr, $errors:expr) => {{ + use inkwell::context::Context; + use roc_gen_llvm::run_roc::RocCallResult; + + unsafe { + let main: libloading::Symbol = $lib + .get($main_fn_name.as_bytes()) + .ok() + .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) + .expect("errored"); + + let size = std::mem::size_of::>() + $bytes; + let layout = std::alloc::Layout::array::(size).unwrap(); + let result = std::alloc::alloc(layout); + main(result); + + let flag = *result; + + if flag == 0 { + $transform(result.add(std::mem::size_of::>()) as usize) + } else { + use std::ffi::CString; + use std::os::raw::c_char; + + // first field is a char pointer (to the error message) + // read value, and transmute to a pointer + let ptr_as_int = *(result as *const u64).offset(1); + let ptr = std::mem::transmute::(ptr_as_int); + + // make CString (null-terminated) + let raw = CString::from_raw(ptr); + + let result = format!("{:?}", raw); + + // make sure rust doesn't try to free the Roc constant string + std::mem::forget(raw); + + eprintln!("{}", result); + panic!("Roc hit an error"); + } + } + }}; +} From 11b83929de06a112ec284bd3a1089f981343f5c5 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 3 Feb 2022 08:59:17 +0000 Subject: [PATCH 466/541] repl: use llvm macros in ReplApp implementation --- repl_cli/src/lib.rs | 89 +++------------------------------------------ 1 file changed, 6 insertions(+), 83 deletions(-) diff --git a/repl_cli/src/lib.rs b/repl_cli/src/lib.rs index 5ffabf01fe..e37fcccbbf 100644 --- a/repl_cli/src/lib.rs +++ b/repl_cli/src/lib.rs @@ -2,24 +2,22 @@ use bumpalo::Bump; use const_format::concatcp; use inkwell::context::Context; use inkwell::module::Linkage; -use libloading::{Library, Symbol}; -use roc_mono::ir::OptLevel; -use roc_parse::ast::Expr; +use libloading::Library; use rustyline::highlight::{Highlighter, PromptInfo}; use rustyline::validate::{self, ValidationContext, ValidationResult, Validator}; use rustyline_derive::{Completer, Helper, Hinter}; use std::borrow::Cow; -use std::ffi::CString; use std::io; -use std::mem::MaybeUninit; -use std::os::raw::c_char; use target_lexicon::Triple; use roc_build::link::module_to_dylib; use roc_build::program::FunctionIterator; use roc_collections::all::MutSet; use roc_gen_llvm::llvm::externs::add_default_roc_externs; +use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type}; use roc_load::file::MonomorphizedModule; +use roc_mono::ir::OptLevel; +use roc_parse::ast::Expr; use roc_parse::parser::{EExpr, ELambda, SyntaxError}; use roc_repl_eval::eval::jit_to_ast; use roc_repl_eval::gen::{compile_to_mono, format_answer, ReplOutput}; @@ -121,31 +119,6 @@ impl Validator for InputValidator { } } -#[repr(C)] -pub struct RocCallResult { - tag: u64, - error_msg: *mut c_char, - value: MaybeUninit, -} - -impl From> for Result { - fn from(call_result: RocCallResult) -> Self { - match call_result.tag { - 0 => Ok(unsafe { call_result.value.assume_init() }), - _ => Err({ - let raw = unsafe { CString::from_raw(call_result.error_msg) }; - - let result = format!("{:?}", raw); - - // make sure rust does not try to free the Roc string - std::mem::forget(raw); - - result - }), - } - } -} - struct CliReplApp { lib: Library, } @@ -190,23 +163,7 @@ impl ReplApp for CliReplApp { main_fn_name: &str, transform: F, ) -> Expr<'a> { - unsafe { - let main: Symbol) -> ()> = self - .lib - .get(main_fn_name.as_bytes()) - .ok() - .ok_or(format!("Unable to JIT compile `{}`", main_fn_name)) - .expect("errored"); - - let mut result = MaybeUninit::uninit(); - - main(result.as_mut_ptr()); - - match result.assume_init().into() { - Ok(success) => transform(success), - Err(error_msg) => panic!("Roc failed with message: {}", error_msg), - } - } + run_jit_function!(self.lib, main_fn_name, T, transform) } /// Run user code that returns a struct or union, whose size is provided as an argument @@ -216,41 +173,7 @@ impl ReplApp for CliReplApp { bytes: usize, transform: F, ) -> T { - unsafe { - let main: Symbol = self - .lib - .get(main_fn_name.as_bytes()) - .ok() - .ok_or(format!("Unable to JIT compile `{}`", main_fn_name)) - .expect("errored"); - - let size = std::mem::size_of::>() + bytes; - let layout = std::alloc::Layout::array::(size).unwrap(); - let result = std::alloc::alloc(layout); - main(result); - - let flag = *result; - - if flag == 0 { - transform(result.add(std::mem::size_of::>()) as usize) - } else { - // first field is a char pointer (to the error message) - // read value, and transmute to a pointer - let ptr_as_int = *(result as *const u64).offset(1); - let ptr = std::mem::transmute::(ptr_as_int); - - // make CString (null-terminated) - let raw = CString::from_raw(ptr); - - let result = format!("{:?}", raw); - - // make sure rust doesn't try to free the Roc constant string - std::mem::forget(raw); - - eprintln!("{}", result); - panic!("Roc hit an error"); - } - } + run_jit_function_dynamic_type!(self.lib, main_fn_name, bytes, transform) } } From 5904bb05ada304388ca46149761f1c00854f35b2 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 3 Feb 2022 09:06:38 +0000 Subject: [PATCH 467/541] repl: remove unused dependencies from roc_repl_eval --- Cargo.lock | 2 -- repl_eval/Cargo.toml | 2 -- repl_eval/src/gen.rs | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a878b309ea..2f066cc404 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3701,12 +3701,10 @@ name = "roc_repl_eval" version = "0.1.0" dependencies = [ "bumpalo", - "roc_build", "roc_builtins", "roc_can", "roc_collections", "roc_fmt", - "roc_gen_llvm", "roc_load", "roc_module", "roc_mono", diff --git a/repl_eval/Cargo.toml b/repl_eval/Cargo.toml index cd2b9e940b..562cb27c9e 100644 --- a/repl_eval/Cargo.toml +++ b/repl_eval/Cargo.toml @@ -8,12 +8,10 @@ version = "0.1.0" [dependencies] bumpalo = {version = "3.8.0", features = ["collections"]} -roc_build = {path = "../compiler/build", default-features = false} roc_builtins = {path = "../compiler/builtins"} roc_can = {path = "../compiler/can"} roc_collections = {path = "../compiler/collections"} roc_fmt = {path = "../compiler/fmt"} -roc_gen_llvm = {path = "../compiler/gen_llvm", optional = true} roc_load = {path = "../compiler/load"} roc_module = {path = "../compiler/module"} roc_mono = {path = "../compiler/mono"} diff --git a/repl_eval/src/gen.rs b/repl_eval/src/gen.rs index 538eb1c146..0874c42b49 100644 --- a/repl_eval/src/gen.rs +++ b/repl_eval/src/gen.rs @@ -1,11 +1,11 @@ use bumpalo::Bump; -use roc_load::file::{LoadingProblem, MonomorphizedModule}; use std::path::{Path, PathBuf}; use roc_can::builtins::builtin_defs_map; use roc_collections::all::MutMap; use roc_fmt::annotation::Formattable; use roc_fmt::annotation::{Newlines, Parens}; +use roc_load::file::{LoadingProblem, MonomorphizedModule}; use roc_parse::ast::Expr; use roc_region::all::LineInfo; use roc_target::TargetInfo; From 8df69d856cda1c56f9428e3c220b66625392f73a Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 3 Feb 2022 12:08:32 +0000 Subject: [PATCH 468/541] repl: update a comment --- repl_eval/src/eval.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index 019618e086..0b946a73a1 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -282,7 +282,7 @@ fn jit_to_ast_help<'a, A: ReplApp>( let result = match int_width { U8 | I8 => { - // NOTE: this is does not handle 8-bit numbers yet + // NOTE: `helper!` does not handle 8-bit numbers yet env.app .call_function(main_fn_name, |num: u8| byte_to_ast(env, num, content)) } From a32e0bbd4cb134952cb59ef2ed8761cc6c623b3d Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 3 Feb 2022 21:56:44 +0100 Subject: [PATCH 469/541] use new hosted module for effect example --- examples/effect/Main.roc | 2 +- examples/effect/thing/platform-dir/Effect.roc | 8 ++++++++ examples/effect/thing/platform-dir/Package-Config.roc | 8 ++------ 3 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 examples/effect/thing/platform-dir/Effect.roc diff --git a/examples/effect/Main.roc b/examples/effect/Main.roc index 5687bfa94e..d980596cfe 100644 --- a/examples/effect/Main.roc +++ b/examples/effect/Main.roc @@ -1,6 +1,6 @@ app "effect-example" packages { pf: "thing/platform-dir" } - imports [ fx.Effect ] + imports [ pf.Effect ] provides [ main ] to pf main : Effect.Effect {} diff --git a/examples/effect/thing/platform-dir/Effect.roc b/examples/effect/thing/platform-dir/Effect.roc new file mode 100644 index 0000000000..64c1897183 --- /dev/null +++ b/examples/effect/thing/platform-dir/Effect.roc @@ -0,0 +1,8 @@ +hosted Effect + exposes [ Effect, after, map, always, forever, putLine, getLine ] + imports [] + generates Effect with [ after, map, always, forever ] + +putLine : Str -> Effect {} + +getLine : Effect Str diff --git a/examples/effect/thing/platform-dir/Package-Config.roc b/examples/effect/thing/platform-dir/Package-Config.roc index 667e960a28..11a7a185e5 100644 --- a/examples/effect/thing/platform-dir/Package-Config.roc +++ b/examples/effect/thing/platform-dir/Package-Config.roc @@ -2,13 +2,9 @@ platform "folkertdev/foo" requires {} { main : Effect {} } exposes [] packages {} - imports [ fx.Effect ] + imports [ pf.Effect ] provides [ mainForHost ] - effects fx.Effect - { - putLine : Str -> Effect {}, - getLine : Effect Str - } + effects fx.Unused { } mainForHost : Effect.Effect {} as Fx mainForHost = main From db2d437d78413e444e1369197d1d673ddeac7488 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 3 Feb 2022 22:02:18 +0100 Subject: [PATCH 470/541] use new hosted module for benchmarks example --- examples/benchmarks/platform/Effect.roc | 10 ++++++++++ examples/benchmarks/platform/Package-Config.roc | 7 +------ examples/benchmarks/platform/Task.roc | 2 +- examples/effect/thing/platform-dir/Package-Config.roc | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 examples/benchmarks/platform/Effect.roc diff --git a/examples/benchmarks/platform/Effect.roc b/examples/benchmarks/platform/Effect.roc new file mode 100644 index 0000000000..dfbd8c4db4 --- /dev/null +++ b/examples/benchmarks/platform/Effect.roc @@ -0,0 +1,10 @@ +hosted Effect + exposes [ Effect, after, map, always, forever, loop, putLine, putInt, getInt ] + imports [] + generates Effect with [ after, map, always, forever, loop ] + +putLine : Str -> Effect {} + +putInt : I64 -> Effect {} + +getInt : Effect { value : I64, errorCode : [ A, B ], isError : Bool } diff --git a/examples/benchmarks/platform/Package-Config.roc b/examples/benchmarks/platform/Package-Config.roc index 619534f070..3e4d6f0911 100644 --- a/examples/benchmarks/platform/Package-Config.roc +++ b/examples/benchmarks/platform/Package-Config.roc @@ -4,12 +4,7 @@ platform "folkertdev/foo" packages {} imports [ Task.{ Task } ] provides [ mainForHost ] - effects fx.Effect - { - putLine : Str -> Effect {}, - putInt : I64 -> Effect {}, - getInt : Effect { value : I64, errorCode : [ A, B ], isError : Bool } - } + effects fx.Unused {} mainForHost : Task {} [] as Fx mainForHost = main diff --git a/examples/benchmarks/platform/Task.roc b/examples/benchmarks/platform/Task.roc index 61b67e321c..df1188794c 100644 --- a/examples/benchmarks/platform/Task.roc +++ b/examples/benchmarks/platform/Task.roc @@ -1,6 +1,6 @@ interface Task exposes [ Task, succeed, fail, after, map, putLine, putInt, getInt, forever, loop ] - imports [ fx.Effect ] + imports [ pf.Effect ] Task ok err : Effect.Effect (Result ok err) diff --git a/examples/effect/thing/platform-dir/Package-Config.roc b/examples/effect/thing/platform-dir/Package-Config.roc index 11a7a185e5..6d2301e980 100644 --- a/examples/effect/thing/platform-dir/Package-Config.roc +++ b/examples/effect/thing/platform-dir/Package-Config.roc @@ -4,7 +4,7 @@ platform "folkertdev/foo" packages {} imports [ pf.Effect ] provides [ mainForHost ] - effects fx.Unused { } + effects fx.Unused {} mainForHost : Effect.Effect {} as Fx mainForHost = main From 6f9550d79271174b966d1956b75bafa8eb16c393 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 3 Feb 2022 22:39:33 +0100 Subject: [PATCH 471/541] update cli example --- examples/cli/platform/Package-Config.roc | 6 +----- examples/cli/platform/Stdin.roc | 2 +- examples/cli/platform/Stdout.roc | 2 +- examples/cli/platform/Task.roc | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/examples/cli/platform/Package-Config.roc b/examples/cli/platform/Package-Config.roc index 93a045a908..cfe7db49a5 100644 --- a/examples/cli/platform/Package-Config.roc +++ b/examples/cli/platform/Package-Config.roc @@ -4,11 +4,7 @@ platform "examples/cli" packages {} imports [ Task.{ Task } ] provides [ mainForHost ] - effects fx.Effect - { - putLine : Str -> Effect {}, - getLine : Effect Str - } + effects fx.Unused {} mainForHost : Task {} [] as Fx mainForHost = main diff --git a/examples/cli/platform/Stdin.roc b/examples/cli/platform/Stdin.roc index 4e5bafa0df..d6f2ad2ae2 100644 --- a/examples/cli/platform/Stdin.roc +++ b/examples/cli/platform/Stdin.roc @@ -1,6 +1,6 @@ interface Stdin exposes [ line ] - imports [ fx.Effect, Task ] + imports [ pf.Effect, Task ] line : Task.Task Str * line = Effect.after Effect.getLine Task.succeed# TODO FIXME Effect.getLine should suffice diff --git a/examples/cli/platform/Stdout.roc b/examples/cli/platform/Stdout.roc index c843296ce2..c69441bab9 100644 --- a/examples/cli/platform/Stdout.roc +++ b/examples/cli/platform/Stdout.roc @@ -1,6 +1,6 @@ interface Stdout exposes [ line ] - imports [ fx.Effect, Task.{ Task } ] + imports [ pf.Effect, Task.{ Task } ] # line : Str -> Task.Task {} * # line = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {}) diff --git a/examples/cli/platform/Task.roc b/examples/cli/platform/Task.roc index c61a74bba1..1721e641f2 100644 --- a/examples/cli/platform/Task.roc +++ b/examples/cli/platform/Task.roc @@ -1,6 +1,6 @@ interface Task exposes [ Task, succeed, fail, await, map, onFail, attempt, forever, loop ] - imports [ fx.Effect ] + imports [ pf.Effect ] Task ok err : Effect.Effect (Result ok err) From a98635ed068e199b6900102b9574074d31132348 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 3 Feb 2022 22:45:15 +0100 Subject: [PATCH 472/541] update other examples --- cli/tests/fixtures/multi-dep-str/platform/Package-Config.roc | 2 +- cli/tests/fixtures/multi-dep-thunk/platform/Package-Config.roc | 2 +- compiler/fmt/tests/test_fmt.rs | 2 +- .../parse/tests/snapshots/pass/function_effect_types.header.roc | 2 +- .../tests/snapshots/pass/nonempty_platform_header.header.roc | 2 +- compiler/parse/tests/snapshots/pass/requires_type.header.roc | 2 +- editor/src/editor/resources/strings.rs | 2 +- examples/fib/platform/Package-Config.roc | 2 +- examples/hello-rust/platform/Package-Config.roc | 2 +- examples/hello-swift/platform/Package-Config.roc | 2 +- examples/hello-web/platform/Package-Config.roc | 2 +- examples/hello-world/platform/Package-Config.roc | 2 +- examples/hello-zig/platform/Package-Config.roc | 2 +- examples/quicksort/platform/Package-Config.roc | 2 +- examples/tui/platform/Package-Config.roc | 2 +- reporting/tests/test_reporting.rs | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cli/tests/fixtures/multi-dep-str/platform/Package-Config.roc b/cli/tests/fixtures/multi-dep-str/platform/Package-Config.roc index ac9d12d39f..41aef42ca8 100644 --- a/cli/tests/fixtures/multi-dep-str/platform/Package-Config.roc +++ b/cli/tests/fixtures/multi-dep-str/platform/Package-Config.roc @@ -4,7 +4,7 @@ platform "examples/multi-module" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} + effects fx.Unused {} mainForHost : Str mainForHost = main diff --git a/cli/tests/fixtures/multi-dep-thunk/platform/Package-Config.roc b/cli/tests/fixtures/multi-dep-thunk/platform/Package-Config.roc index 274980c20f..9b972cdad5 100644 --- a/cli/tests/fixtures/multi-dep-thunk/platform/Package-Config.roc +++ b/cli/tests/fixtures/multi-dep-thunk/platform/Package-Config.roc @@ -4,7 +4,7 @@ platform "examples/multi-dep-thunk" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} + effects fx.Unused {} mainForHost : Str mainForHost = main diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index e6a182a809..cae6b9c128 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -2657,7 +2657,7 @@ mod test_fmt { packages {} \ imports [ Task.{ Task } ] \ provides [ mainForHost ] \ - effects fx.Effect \ + effects fx.Unused \ { \ putLine : Str -> Effect {}, \ putInt : I64 -> Effect {}, \ diff --git a/compiler/parse/tests/snapshots/pass/function_effect_types.header.roc b/compiler/parse/tests/snapshots/pass/function_effect_types.header.roc index 1135057391..ec2bf49b07 100644 --- a/compiler/parse/tests/snapshots/pass/function_effect_types.header.roc +++ b/compiler/parse/tests/snapshots/pass/function_effect_types.header.roc @@ -4,7 +4,7 @@ platform "examples/cli" packages {} imports [ Task.{ Task } ] provides [ mainForHost ] - effects fx.Effect + effects fx.Unused { getLine : Effect Str, putLine : Str -> Effect {}, diff --git a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc index 97673c692a..9cc5fbdb32 100644 --- a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc +++ b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc @@ -4,4 +4,4 @@ platform "foo/barbaz" packages { foo: "./foo" } imports [] provides [ mainForHost ] - effects fx.Effect {} + effects fx.Unused {} diff --git a/compiler/parse/tests/snapshots/pass/requires_type.header.roc b/compiler/parse/tests/snapshots/pass/requires_type.header.roc index 343674511a..8f02770567 100644 --- a/compiler/parse/tests/snapshots/pass/requires_type.header.roc +++ b/compiler/parse/tests/snapshots/pass/requires_type.header.roc @@ -4,7 +4,7 @@ platform "test/types" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} + effects fx.Unused {} mainForHost : App Flags Model mainForHost = main diff --git a/editor/src/editor/resources/strings.rs b/editor/src/editor/resources/strings.rs index 4175ffc1c9..311a495de6 100644 --- a/editor/src/editor/resources/strings.rs +++ b/editor/src/editor/resources/strings.rs @@ -33,7 +33,7 @@ platform "test-platform" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} + effects fx.Unused {} mainForHost : Str mainForHost = main diff --git a/examples/fib/platform/Package-Config.roc b/examples/fib/platform/Package-Config.roc index 2391724dda..187c6d5a58 100644 --- a/examples/fib/platform/Package-Config.roc +++ b/examples/fib/platform/Package-Config.roc @@ -4,7 +4,7 @@ platform "examples/add" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} + effects fx.Unused {} mainForHost : I64 -> I64 mainForHost = \a -> main a diff --git a/examples/hello-rust/platform/Package-Config.roc b/examples/hello-rust/platform/Package-Config.roc index 5ef87710de..c58cba9769 100644 --- a/examples/hello-rust/platform/Package-Config.roc +++ b/examples/hello-rust/platform/Package-Config.roc @@ -4,7 +4,7 @@ platform "examples/hello-world" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} + effects fx.Unused {} mainForHost : Str mainForHost = main diff --git a/examples/hello-swift/platform/Package-Config.roc b/examples/hello-swift/platform/Package-Config.roc index 9285a43b6b..296678266b 100644 --- a/examples/hello-swift/platform/Package-Config.roc +++ b/examples/hello-swift/platform/Package-Config.roc @@ -4,7 +4,7 @@ platform "examples/hello-swift" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} + effects fx.Unused {} mainForHost : Str mainForHost = main diff --git a/examples/hello-web/platform/Package-Config.roc b/examples/hello-web/platform/Package-Config.roc index 5ef87710de..c58cba9769 100644 --- a/examples/hello-web/platform/Package-Config.roc +++ b/examples/hello-web/platform/Package-Config.roc @@ -4,7 +4,7 @@ platform "examples/hello-world" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} + effects fx.Unused {} mainForHost : Str mainForHost = main diff --git a/examples/hello-world/platform/Package-Config.roc b/examples/hello-world/platform/Package-Config.roc index 5ef87710de..c58cba9769 100644 --- a/examples/hello-world/platform/Package-Config.roc +++ b/examples/hello-world/platform/Package-Config.roc @@ -4,7 +4,7 @@ platform "examples/hello-world" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} + effects fx.Unused {} mainForHost : Str mainForHost = main diff --git a/examples/hello-zig/platform/Package-Config.roc b/examples/hello-zig/platform/Package-Config.roc index 5ef87710de..c58cba9769 100644 --- a/examples/hello-zig/platform/Package-Config.roc +++ b/examples/hello-zig/platform/Package-Config.roc @@ -4,7 +4,7 @@ platform "examples/hello-world" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} + effects fx.Unused {} mainForHost : Str mainForHost = main diff --git a/examples/quicksort/platform/Package-Config.roc b/examples/quicksort/platform/Package-Config.roc index a29fb7925d..6139bfd048 100644 --- a/examples/quicksort/platform/Package-Config.roc +++ b/examples/quicksort/platform/Package-Config.roc @@ -4,7 +4,7 @@ platform "examples/quicksort" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} + effects fx.Unused {} mainForHost : List I64 -> List I64 mainForHost = \list -> quicksort list diff --git a/examples/tui/platform/Package-Config.roc b/examples/tui/platform/Package-Config.roc index edb97d61bd..4881b6c823 100644 --- a/examples/tui/platform/Package-Config.roc +++ b/examples/tui/platform/Package-Config.roc @@ -4,7 +4,7 @@ platform "folkertdev/foo" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} + effects fx.Unused {} mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View } mainForHost = main diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index f736655cea..c6bbca2041 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -6132,7 +6132,7 @@ I need all branches in an `if` to have the same type! packages {} imports [Task] provides [ mainForHost ] - effects fx.Effect + effects fx.Unused { putChar : I64 -> Effect {}, putLine : Str -> Effect {}, From 885500712ca6cda40f6600c3e876603bf65b20e9 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 3 Feb 2022 23:55:02 +0100 Subject: [PATCH 473/541] remove old Effect module things --- cli/src/format.rs | 10 +- .../multi-dep-str/platform/Package-Config.roc | 1 - .../platform/Package-Config.roc | 1 - compiler/fmt/src/module.rs | 22 +- compiler/fmt/tests/test_fmt.rs | 8 +- compiler/load/src/file.rs | 394 +----------------- compiler/parse/src/header.rs | 12 - compiler/parse/src/module.rs | 68 +-- compiler/parse/src/parser.rs | 17 - .../pass/function_effect_types.header.roc | 6 - .../pass/nonempty_platform_header.header.roc | 1 - .../snapshots/pass/requires_type.header.roc | 1 - editor/src/editor/resources/strings.rs | 1 - .../benchmarks/platform/Package-Config.roc | 1 - examples/cli/platform/Package-Config.roc | 1 - .../thing/platform-dir/Package-Config.roc | 1 - .../platform/Package-Config.roc | 13 - examples/false-interpreter/platform/Task.roc | 2 +- examples/fib/platform/Package-Config.roc | 1 - .../hello-rust/platform/Package-Config.roc | 1 - .../hello-swift/platform/Package-Config.roc | 1 - .../hello-web/platform/Package-Config.roc | 1 - .../hello-world/platform/Package-Config.roc | 1 - .../hello-zig/platform/Package-Config.roc | 1 - .../quicksort/platform/Package-Config.roc | 1 - examples/tui/platform/Package-Config.roc | 1 - reporting/src/error/parse.rs | 41 -- reporting/tests/test_reporting.rs | 6 - 28 files changed, 20 insertions(+), 595 deletions(-) diff --git a/cli/src/format.rs b/cli/src/format.rs index 365bb0034e..00f427e41c 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -12,7 +12,7 @@ use roc_parse::ast::{ TypeAnnotation, WhenBranch, }; use roc_parse::header::{ - AppHeader, Effects, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, + AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, }; use roc_parse::{ @@ -199,14 +199,6 @@ impl<'a> RemoveSpaces<'a> for Module<'a> { packages: header.packages.remove_spaces(arena), imports: header.imports.remove_spaces(arena), provides: header.provides.remove_spaces(arena), - effects: Effects { - spaces_before_effects_keyword: &[], - spaces_after_effects_keyword: &[], - spaces_after_type_name: &[], - effect_shortname: header.effects.effect_shortname.remove_spaces(arena), - effect_type_name: header.effects.effect_type_name.remove_spaces(arena), - entries: header.effects.entries.remove_spaces(arena), - }, before_header: &[], after_platform_keyword: &[], before_requires: &[], diff --git a/cli/tests/fixtures/multi-dep-str/platform/Package-Config.roc b/cli/tests/fixtures/multi-dep-str/platform/Package-Config.roc index 41aef42ca8..eb69cce73a 100644 --- a/cli/tests/fixtures/multi-dep-str/platform/Package-Config.roc +++ b/cli/tests/fixtures/multi-dep-str/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/multi-module" packages {} imports [] provides [ mainForHost ] - effects fx.Unused {} mainForHost : Str mainForHost = main diff --git a/cli/tests/fixtures/multi-dep-thunk/platform/Package-Config.roc b/cli/tests/fixtures/multi-dep-thunk/platform/Package-Config.roc index 9b972cdad5..68c2ca0962 100644 --- a/cli/tests/fixtures/multi-dep-thunk/platform/Package-Config.roc +++ b/cli/tests/fixtures/multi-dep-thunk/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/multi-dep-thunk" packages {} imports [] provides [ mainForHost ] - effects fx.Unused {} mainForHost : Str mainForHost = main diff --git a/compiler/fmt/src/module.rs b/compiler/fmt/src/module.rs index 9660d427fe..1d86802bc4 100644 --- a/compiler/fmt/src/module.rs +++ b/compiler/fmt/src/module.rs @@ -5,8 +5,8 @@ use crate::spaces::{fmt_default_spaces, fmt_spaces, INDENT}; use crate::Buf; use roc_parse::ast::{Collection, Module, Spaced}; use roc_parse::header::{ - AppHeader, Effects, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, - PackageEntry, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, + AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, + PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, }; use roc_parse::ident::UppercaseIdent; use roc_region::all::Loc; @@ -170,8 +170,6 @@ pub fn fmt_platform_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a PlatformHe buf.push_str("provides"); fmt_default_spaces(buf, header.after_provides, indent); fmt_provides(buf, header.provides, None, indent); - - fmt_effects(buf, &header.effects, indent); } fn fmt_requires<'a, 'buf>(buf: &mut Buf<'buf>, requires: &PlatformRequires<'a>, indent: u16) { @@ -183,22 +181,6 @@ fn fmt_requires<'a, 'buf>(buf: &mut Buf<'buf>, requires: &PlatformRequires<'a>, buf.push_str(" }"); } -fn fmt_effects<'a, 'buf>(buf: &mut Buf<'buf>, effects: &Effects<'a>, indent: u16) { - fmt_default_spaces(buf, effects.spaces_before_effects_keyword, indent); - buf.indent(indent); - buf.push_str("effects"); - fmt_default_spaces(buf, effects.spaces_after_effects_keyword, indent); - - buf.indent(indent); - buf.push_str(effects.effect_shortname); - buf.push('.'); - buf.push_str(effects.effect_type_name); - - fmt_default_spaces(buf, effects.spaces_after_type_name, indent); - - fmt_collection(buf, indent, '{', '}', effects.entries, Newlines::No) -} - impl<'a> Formattable for TypedIdent<'a> { fn is_multiline(&self) -> bool { false diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index cae6b9c128..a5b45b5ebd 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -2656,13 +2656,7 @@ mod test_fmt { exposes [] \ packages {} \ imports [ Task.{ Task } ] \ - provides [ mainForHost ] \ - effects fx.Unused \ - { \ - putLine : Str -> Effect {}, \ - putInt : I64 -> Effect {}, \ - getInt : Effect { value : I64, errorCode : [ A, B ], isError : Bool } \ - }", + provides [ mainForHost ]", ); } diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 47bd839320..86af55ba4d 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -13,7 +13,7 @@ use roc_constrain::module::{ constrain_imports, pre_constrain_imports, ConstrainableImports, Import, }; use roc_constrain::module::{constrain_module, ExposedModuleTypes, SubsByModule}; -use roc_module::ident::{Ident, ModuleName, QualifiedModuleName, TagName}; +use roc_module::ident::{Ident, ModuleName, QualifiedModuleName}; use roc_module::symbol::{ IdentIds, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds, PackageQualified, Symbol, @@ -23,7 +23,7 @@ use roc_mono::ir::{ UpdateModeIds, }; use roc_mono::layout::{Layout, LayoutCache, LayoutProblem}; -use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation}; +use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral}; use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent}; use roc_parse::header::{HeaderFor, ModuleNameEnum, PackageName}; use roc_parse::ident::UppercaseIdent; @@ -35,7 +35,7 @@ use roc_solve::solve; use roc_target::TargetInfo; use roc_types::solved_types::Solved; use roc_types::subs::{Subs, VarStore, Variable}; -use roc_types::types::{Alias, Type}; +use roc_types::types::Alias; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::{HashMap, HashSet}; use std::io; @@ -165,37 +165,6 @@ impl<'a> Dependencies<'a> { output } - pub fn add_effect_module( - &mut self, - module_id: ModuleId, - dependencies: &MutSet, - goal_phase: Phase, - ) -> MutSet<(ModuleId, Phase)> { - // add dependencies for self - // phase i + 1 of a file always depends on phase i being completed - { - let mut i = 2; - - // platform modules should only start at CanonicalizeAndConstrain - debug_assert!(PHASES[i] == Phase::CanonicalizeAndConstrain); - while PHASES[i] < goal_phase { - self.add_dependency_help(module_id, module_id, PHASES[i + 1], PHASES[i]); - i += 1; - } - } - - self.add_to_status(module_id, goal_phase); - - let mut output = MutSet::default(); - - // all the dependencies can be loaded - for dep in dependencies { - output.insert((*dep, Phase::LoadHeader)); - } - - output - } - fn add_to_status(&mut self, module_id: ModuleId, goal_phase: Phase) { for phase in PHASES.iter() { if *phase > goal_phase { @@ -781,12 +750,6 @@ enum Msg<'a> { canonicalization_problems: Vec, module_docs: Option, }, - MadeEffectModule { - type_shortname: &'a str, - constrained_module: ConstrainedModule, - canonicalization_problems: Vec, - module_docs: ModuleDocumentation, - }, SolvedTypes { module_id: ModuleId, ident_ids: IdentIds, @@ -1854,57 +1817,6 @@ fn update<'a>( Ok(state) } - MadeEffectModule { - constrained_module, - canonicalization_problems, - module_docs, - type_shortname, - } => { - let module_id = constrained_module.module.module_id; - - log!("made effect module for {:?}", module_id); - state - .module_cache - .can_problems - .insert(module_id, canonicalization_problems); - - state - .module_cache - .documentation - .insert(module_id, module_docs); - - state - .module_cache - .aliases - .insert(module_id, constrained_module.module.aliases.clone()); - - state - .module_cache - .constrained - .insert(module_id, constrained_module); - - let mut work = state.dependencies.add_effect_module( - module_id, - &MutSet::default(), - state.goal_phase, - ); - - work.extend(state.dependencies.notify_package(type_shortname)); - - work.extend(state.dependencies.notify(module_id, Phase::LoadHeader)); - - work.extend(state.dependencies.notify(module_id, Phase::Parse)); - - work.extend( - state - .dependencies - .notify(module_id, Phase::CanonicalizeAndConstrain), - ); - - start_tasks(arena, &mut state, work, injector, worker_listeners)?; - - Ok(state) - } SolvedTypes { module_id, ident_ids, @@ -2357,14 +2269,10 @@ fn load_pkg_config<'a>( // Insert the first entries for this module's timings let mut pkg_module_timing = ModuleTiming::new(module_start_time); - let mut effect_module_timing = ModuleTiming::new(module_start_time); pkg_module_timing.read_roc_file = file_io_duration; pkg_module_timing.parse_header = parse_header_duration; - effect_module_timing.read_roc_file = file_io_duration; - effect_module_timing.parse_header = parse_header_duration; - match parsed { Ok((ast::Module::Interface { header }, _parse_state)) => { Err(LoadingProblem::UnexpectedHeader(format!( @@ -2387,23 +2295,13 @@ fn load_pkg_config<'a>( filename, parser_state, module_ids.clone(), - ident_ids_by_module.clone(), + ident_ids_by_module, &header, pkg_module_timing, ) .1; - let effects_module_msg = fabricate_effects_module( - arena, - header.effects.effect_shortname, - module_ids, - ident_ids_by_module, - header, - effect_module_timing, - ) - .1; - - Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg])) + Ok(pkg_config_module_msg) } Ok((ast::Module::Hosted { header }, _parse_state)) => { Err(LoadingProblem::UnexpectedHeader(format!( @@ -2680,14 +2578,13 @@ fn parse_header<'a>( To::NewPackage(_package_name) => Ok((module_id, app_module_header_msg)), } } - Ok((ast::Module::Platform { header }, _parse_state)) => Ok(fabricate_effects_module( - arena, - "", - module_ids, - ident_ids_by_module, - header, - module_timing, - )), + Ok((ast::Module::Platform { header }, _parse_state)) => { + Err(LoadingProblem::UnexpectedHeader(format!( + "got an unexpected platform header\n{:?}", + header + ))) + } + Err(fail) => Err(LoadingProblem::ParsingFailed( fail.map_problem(SyntaxError::Header) .into_file_error(filename), @@ -3003,7 +2900,6 @@ fn send_header_two<'a>( HashMap::with_capacity_and_hasher(num_exposes, default_hasher()); // add standard imports - // TODO add Effect by default imported_modules.insert(app_module_id, Region::zero()); deps_by_name.insert( PQModuleName::Unqualified(ModuleName::APP.into()), @@ -3361,272 +3257,6 @@ fn fabricate_pkg_config_module<'a>( ) } -#[allow(clippy::too_many_arguments)] -fn fabricate_effects_module<'a>( - arena: &'a Bump, - shorthand: &'a str, - module_ids: Arc>>, - ident_ids_by_module: Arc>>, - header: PlatformHeader<'a>, - module_timing: ModuleTiming, -) -> (ModuleId, Msg<'a>) { - let num_exposes = header.provides.len() + 1; - let mut exposed: Vec = Vec::with_capacity(num_exposes); - - let effects = header.effects; - - let module_id: ModuleId; - - let effect_entries = unpack_exposes_entries(arena, effects.entries.items); - let name = effects.effect_type_name; - let declared_name: ModuleName = name.into(); - - let hardcoded_effect_symbols = { - let mut functions: Vec<_> = roc_can::effect_module::BUILTIN_EFFECT_FUNCTIONS - .iter() - .map(|x| x.0) - .collect(); - functions.push(name); - - functions - }; - - { - let mut module_ids = (*module_ids).lock(); - - for exposed in header.exposes.iter() { - let module_name = exposed.value.extract_spaces().item; - - module_ids.get_or_insert(&PQModuleName::Qualified( - shorthand, - module_name.as_str().into(), - )); - } - } - - let exposed_ident_ids = { - // Lock just long enough to perform the minimal operations necessary. - let mut module_ids = (*module_ids).lock(); - let mut ident_ids_by_module = (*ident_ids_by_module).lock(); - - let name = PQModuleName::Qualified(shorthand, declared_name); - module_id = module_ids.get_or_insert(&name); - - // Ensure this module has an entry in the exposed_ident_ids map. - ident_ids_by_module - .entry(module_id) - .or_insert_with(IdentIds::default); - - let ident_ids = ident_ids_by_module.get_mut(&module_id).unwrap(); - - // Generate IdentIds entries for all values this module exposes. - // This way, when we encounter them in Defs later, they already - // have an IdentIds entry. - // - // We must *not* add them to scope yet, or else the Defs will - // incorrectly think they're shadowing them! - for (loc_exposed, _) in effect_entries.iter() { - // Use get_or_insert here because the ident_ids may already - // created an IdentId for this, when it was imported exposed - // in a dependent module. - // - // For example, if module A has [ B.{ foo } ], then - // when we get here for B, `foo` will already have - // an IdentId. We must reuse that! - let ident_id = ident_ids.get_or_insert(&loc_exposed.value.into()); - let symbol = Symbol::new(module_id, ident_id); - - exposed.push(symbol); - } - - for hardcoded in hardcoded_effect_symbols { - // Use get_or_insert here because the ident_ids may already - // created an IdentId for this, when it was imported exposed - // in a dependent module. - // - // For example, if module A has [ B.{ foo } ], then - // when we get here for B, `foo` will already have - // an IdentId. We must reuse that! - let ident_id = ident_ids.get_or_insert(&hardcoded.into()); - let symbol = Symbol::new(module_id, ident_id); - - exposed.push(symbol); - } - - if cfg!(debug_assertions) { - module_id.register_debug_idents(ident_ids); - } - - ident_ids.clone() - }; - - // a platform module has no dependencies, hence empty - let dep_idents: MutMap = IdentIds::exposed_builtins(0); - - let mut var_store = VarStore::default(); - - let module_ids = { (*module_ids).lock().clone() }.into_module_ids(); - - let mut scope = roc_can::scope::Scope::new(module_id, &mut var_store); - let mut can_env = - roc_can::env::Env::new(module_id, &dep_idents, &module_ids, exposed_ident_ids); - - let effect_symbol = scope - .introduce( - name.into(), - &can_env.exposed_ident_ids, - &mut can_env.ident_ids, - Region::zero(), - ) - .unwrap(); - - let effect_tag_name = TagName::Private(effect_symbol); - - let mut aliases = MutMap::default(); - let alias = { - let a_var = var_store.fresh(); - - let actual = roc_can::effect_module::build_effect_actual( - effect_tag_name, - Type::Variable(a_var), - &mut var_store, - ); - - scope.add_alias( - effect_symbol, - Region::zero(), - vec![Loc::at_zero(("a".into(), a_var))], - actual, - ); - - scope.lookup_alias(effect_symbol).unwrap().clone() - }; - - aliases.insert(effect_symbol, alias); - - let mut declarations = Vec::new(); - - let exposed_symbols: MutSet = { - let mut exposed_symbols = MutSet::default(); - - { - for (ident, ann) in effect_entries { - let symbol = { - scope - .introduce( - ident.value.into(), - &can_env.exposed_ident_ids, - &mut can_env.ident_ids, - Region::zero(), - ) - .unwrap() - }; - - let annotation = roc_can::annotation::canonicalize_annotation( - &mut can_env, - &mut scope, - &ann.value, - Region::zero(), - &mut var_store, - ); - - let def = roc_can::effect_module::build_host_exposed_def( - &mut can_env, - &mut scope, - symbol, - ident.value, - TagName::Private(effect_symbol), - &mut var_store, - annotation, - ); - exposed_symbols.insert(symbol); - - declarations.push(Declaration::Declare(def)); - } - } - - // define Effect.after, Effect.map etc. - roc_can::effect_module::build_effect_builtins( - &mut can_env, - &mut scope, - effect_symbol, - &mut var_store, - &mut exposed_symbols, - &mut declarations, - ); - - exposed_symbols - }; - - use roc_can::module::ModuleOutput; - let module_output = ModuleOutput { - aliases, - rigid_variables: MutMap::default(), - declarations, - exposed_imports: MutMap::default(), - lookups: Vec::new(), - problems: can_env.problems, - ident_ids: can_env.ident_ids, - references: MutSet::default(), - scope, - }; - - let constraint = constrain_module(&module_output.declarations, module_id); - - let module = Module { - module_id, - exposed_imports: module_output.exposed_imports, - exposed_symbols, - references: module_output.references, - aliases: module_output.aliases, - rigid_variables: module_output.rigid_variables, - }; - - let imported_modules = MutMap::default(); - - // Should a effect module ever have a ModuleDocumentation? - let module_docs = ModuleDocumentation { - name: String::from(name), - entries: Vec::new(), - scope: module_output.scope, - }; - - let constrained_module = ConstrainedModule { - module, - declarations: module_output.declarations, - imported_modules, - var_store, - constraint, - ident_ids: module_output.ident_ids, - dep_idents, - module_timing, - }; - - ( - module_id, - Msg::MadeEffectModule { - type_shortname: effects.effect_shortname, - constrained_module, - canonicalization_problems: module_output.problems, - module_docs, - }, - ) -} - -fn unpack_exposes_entries<'a>( - arena: &'a Bump, - entries: &'a [Loc>>], -) -> bumpalo::collections::Vec<'a, (Loc<&'a str>, Loc>)> { - use bumpalo::collections::Vec; - - let iter = entries.iter().map(|entry| { - let entry: TypedIdent<'a> = entry.value.extract_spaces().item; - (entry.ident, entry.ann) - }); - - Vec::from_iter_in(iter, arena) -} - #[allow(clippy::too_many_arguments)] #[allow(clippy::unnecessary_wraps)] fn canonicalize_and_constrain<'a, F>( diff --git a/compiler/parse/src/header.rs b/compiler/parse/src/header.rs index 52b41448fd..6688b845fe 100644 --- a/compiler/parse/src/header.rs +++ b/compiler/parse/src/header.rs @@ -193,7 +193,6 @@ pub struct PlatformHeader<'a> { pub packages: Collection<'a, Loc>>>, pub imports: Collection<'a, Loc>>>, pub provides: Collection<'a, Loc>>>, - pub effects: Effects<'a>, // Potential comments and newlines - these will typically all be empty. pub before_header: &'a [CommentOrNewline<'a>], @@ -210,17 +209,6 @@ pub struct PlatformHeader<'a> { pub after_provides: &'a [CommentOrNewline<'a>], } -/// e.g. fx.Effects -#[derive(Clone, Debug, PartialEq)] -pub struct Effects<'a> { - pub spaces_before_effects_keyword: &'a [CommentOrNewline<'a>], - pub spaces_after_effects_keyword: &'a [CommentOrNewline<'a>], - pub spaces_after_type_name: &'a [CommentOrNewline<'a>], - pub effect_shortname: &'a str, - pub effect_type_name: &'a str, - pub entries: Collection<'a, Loc>>>, -} - #[derive(Copy, Clone, Debug, PartialEq)] pub enum ImportsEntry<'a> { /// e.g. `Task` or `Task.{ Task, after }` diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index 8e6539f964..e9db8397ce 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -1,15 +1,13 @@ use crate::ast::{Collection, CommentOrNewline, Def, Module, Spaced}; use crate::blankspace::{space0_around_ee, space0_before_e, space0_e}; use crate::header::{ - package_entry, package_name, AppHeader, Effects, ExposedName, HostedHeader, ImportsEntry, + package_entry, package_name, AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, PlatformHeader, PlatformRequires, To, TypedIdent, }; -use crate::ident::{ - self, lowercase_ident, unqualified_ident, uppercase, uppercase_ident, UppercaseIdent, -}; +use crate::ident::{self, lowercase_ident, unqualified_ident, uppercase, UppercaseIdent}; use crate::parser::Progress::{self, *}; use crate::parser::{ - backtrackable, optional, specialize, specialize_region, word1, EEffects, EExposes, EGenerates, + backtrackable, optional, specialize, specialize_region, word1, EExposes, EGenerates, EGeneratesWith, EHeader, EImports, EPackages, EProvides, ERequires, ETypedIdent, Parser, SourceError, SyntaxError, }; @@ -323,8 +321,6 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> { let (_, ((before_provides, after_provides), (provides, _provides_type)), state) = specialize(EHeader::Provides, provides_without_to()).parse(arena, state)?; - let (_, effects, state) = specialize(EHeader::Effects, effects()).parse(arena, state)?; - let header = PlatformHeader { name, requires, @@ -332,7 +328,6 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> { packages: packages.entries, imports, provides, - effects, before_header: &[] as &[_], after_platform_keyword, before_requires, @@ -822,63 +817,6 @@ fn imports<'a>() -> impl Parser< ) } -#[inline(always)] -fn effects<'a>() -> impl Parser<'a, Effects<'a>, EEffects<'a>> { - move |arena, state| { - let min_indent = 1; - - let (_, (spaces_before_effects_keyword, spaces_after_effects_keyword), state) = - spaces_around_keyword( - min_indent, - "effects", - EEffects::Effects, - EEffects::Space, - EEffects::IndentEffects, - EEffects::IndentListStart, - ) - .parse(arena, state)?; - - // e.g. `fx.` - let (_, type_shortname, state) = skip_second!( - specialize(|_, pos| EEffects::Shorthand(pos), lowercase_ident()), - word1(b'.', EEffects::ShorthandDot) - ) - .parse(arena, state)?; - - // the type name, e.g. Effects - let (_, (type_name, spaces_after_type_name), state) = and!( - specialize(|_, pos| EEffects::TypeName(pos), uppercase_ident()), - space0_e(min_indent, EEffects::Space, EEffects::IndentListStart) - ) - .parse(arena, state)?; - let (_, entries, state) = collection_trailing_sep_e!( - word1(b'{', EEffects::ListStart), - specialize(EEffects::TypedIdent, loc!(typed_ident())), - word1(b',', EEffects::ListEnd), - word1(b'}', EEffects::ListEnd), - min_indent, - EEffects::Open, - EEffects::Space, - EEffects::IndentListEnd, - Spaced::SpaceBefore - ) - .parse(arena, state)?; - - Ok(( - MadeProgress, - Effects { - spaces_before_effects_keyword, - spaces_after_effects_keyword, - spaces_after_type_name, - effect_shortname: type_shortname, - effect_type_name: type_name, - entries, - }, - state, - )) - } -} - #[inline(always)] fn typed_ident<'a>() -> impl Parser<'a, Spaced<'a, TypedIdent<'a>>, ETypedIdent<'a>> { // e.g. diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index 6542f83003..25471207a8 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -71,7 +71,6 @@ pub enum EHeader<'a> { Imports(EImports, Position), Requires(ERequires<'a>, Position), Packages(EPackages<'a>, Position), - Effects(EEffects<'a>, Position), Generates(EGenerates, Position), GeneratesWith(EGeneratesWith, Position), @@ -167,22 +166,6 @@ pub enum EPackageEntry<'a> { Space(BadInputError, Position), } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum EEffects<'a> { - Space(BadInputError, Position), - Effects(Position), - Open(Position), - IndentEffects(Position), - ListStart(Position), - ListEnd(Position), - IndentListStart(Position), - IndentListEnd(Position), - TypedIdent(ETypedIdent<'a>, Position), - ShorthandDot(Position), - Shorthand(Position), - TypeName(Position), -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum EImports { Open(Position), diff --git a/compiler/parse/tests/snapshots/pass/function_effect_types.header.roc b/compiler/parse/tests/snapshots/pass/function_effect_types.header.roc index ec2bf49b07..8142e6a4fe 100644 --- a/compiler/parse/tests/snapshots/pass/function_effect_types.header.roc +++ b/compiler/parse/tests/snapshots/pass/function_effect_types.header.roc @@ -4,9 +4,3 @@ platform "examples/cli" packages {} imports [ Task.{ Task } ] provides [ mainForHost ] - effects fx.Unused - { - getLine : Effect Str, - putLine : Str -> Effect {}, - twoArguments : Int, Int -> Effect {} - } diff --git a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc index 9cc5fbdb32..789dae3055 100644 --- a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc +++ b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc @@ -4,4 +4,3 @@ platform "foo/barbaz" packages { foo: "./foo" } imports [] provides [ mainForHost ] - effects fx.Unused {} diff --git a/compiler/parse/tests/snapshots/pass/requires_type.header.roc b/compiler/parse/tests/snapshots/pass/requires_type.header.roc index 8f02770567..1755f5a066 100644 --- a/compiler/parse/tests/snapshots/pass/requires_type.header.roc +++ b/compiler/parse/tests/snapshots/pass/requires_type.header.roc @@ -4,7 +4,6 @@ platform "test/types" packages {} imports [] provides [ mainForHost ] - effects fx.Unused {} mainForHost : App Flags Model mainForHost = main diff --git a/editor/src/editor/resources/strings.rs b/editor/src/editor/resources/strings.rs index 311a495de6..c99234e626 100644 --- a/editor/src/editor/resources/strings.rs +++ b/editor/src/editor/resources/strings.rs @@ -33,7 +33,6 @@ platform "test-platform" packages {} imports [] provides [ mainForHost ] - effects fx.Unused {} mainForHost : Str mainForHost = main diff --git a/examples/benchmarks/platform/Package-Config.roc b/examples/benchmarks/platform/Package-Config.roc index 3e4d6f0911..fe634bbc7f 100644 --- a/examples/benchmarks/platform/Package-Config.roc +++ b/examples/benchmarks/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "folkertdev/foo" packages {} imports [ Task.{ Task } ] provides [ mainForHost ] - effects fx.Unused {} mainForHost : Task {} [] as Fx mainForHost = main diff --git a/examples/cli/platform/Package-Config.roc b/examples/cli/platform/Package-Config.roc index cfe7db49a5..883aa59a7a 100644 --- a/examples/cli/platform/Package-Config.roc +++ b/examples/cli/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/cli" packages {} imports [ Task.{ Task } ] provides [ mainForHost ] - effects fx.Unused {} mainForHost : Task {} [] as Fx mainForHost = main diff --git a/examples/effect/thing/platform-dir/Package-Config.roc b/examples/effect/thing/platform-dir/Package-Config.roc index 6d2301e980..ab87461db4 100644 --- a/examples/effect/thing/platform-dir/Package-Config.roc +++ b/examples/effect/thing/platform-dir/Package-Config.roc @@ -4,7 +4,6 @@ platform "folkertdev/foo" packages {} imports [ pf.Effect ] provides [ mainForHost ] - effects fx.Unused {} mainForHost : Effect.Effect {} as Fx mainForHost = main diff --git a/examples/false-interpreter/platform/Package-Config.roc b/examples/false-interpreter/platform/Package-Config.roc index 55003feeea..4f72530b71 100644 --- a/examples/false-interpreter/platform/Package-Config.roc +++ b/examples/false-interpreter/platform/Package-Config.roc @@ -4,19 +4,6 @@ platform "examples/cli" packages {} imports [ Task.{ Task } ] provides [ mainForHost ] - effects fx.Effect - { - openFile : Str -> Effect U64, - closeFile : U64 -> Effect {}, - withFileOpen : Str, (U64 -> Effect (Result ok err)) -> Effect {}, - getFileLine : U64 -> Effect Str, - getFileBytes : U64 -> Effect (List U8), - putLine : Str -> Effect {}, - putRaw : Str -> Effect {}, - # Is there a limit to the number of effect, uncomment the next line and it crashes - # getLine : Effect Str, - getChar : Effect U8 - } mainForHost : Str -> Task {} [] as Fx mainForHost = \file -> main file diff --git a/examples/false-interpreter/platform/Task.roc b/examples/false-interpreter/platform/Task.roc index 0ad7c223b2..fde39ed9b2 100644 --- a/examples/false-interpreter/platform/Task.roc +++ b/examples/false-interpreter/platform/Task.roc @@ -1,6 +1,6 @@ interface Task exposes [ Task, succeed, fail, await, map, onFail, attempt, fromResult, loop ] - imports [ fx.Effect ] + imports [ pf.Effect ] Task ok err : Effect.Effect (Result ok err) diff --git a/examples/fib/platform/Package-Config.roc b/examples/fib/platform/Package-Config.roc index 187c6d5a58..8d28005b1a 100644 --- a/examples/fib/platform/Package-Config.roc +++ b/examples/fib/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/add" packages {} imports [] provides [ mainForHost ] - effects fx.Unused {} mainForHost : I64 -> I64 mainForHost = \a -> main a diff --git a/examples/hello-rust/platform/Package-Config.roc b/examples/hello-rust/platform/Package-Config.roc index c58cba9769..61e4c96bf2 100644 --- a/examples/hello-rust/platform/Package-Config.roc +++ b/examples/hello-rust/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/hello-world" packages {} imports [] provides [ mainForHost ] - effects fx.Unused {} mainForHost : Str mainForHost = main diff --git a/examples/hello-swift/platform/Package-Config.roc b/examples/hello-swift/platform/Package-Config.roc index 296678266b..5eca9b81b4 100644 --- a/examples/hello-swift/platform/Package-Config.roc +++ b/examples/hello-swift/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/hello-swift" packages {} imports [] provides [ mainForHost ] - effects fx.Unused {} mainForHost : Str mainForHost = main diff --git a/examples/hello-web/platform/Package-Config.roc b/examples/hello-web/platform/Package-Config.roc index c58cba9769..61e4c96bf2 100644 --- a/examples/hello-web/platform/Package-Config.roc +++ b/examples/hello-web/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/hello-world" packages {} imports [] provides [ mainForHost ] - effects fx.Unused {} mainForHost : Str mainForHost = main diff --git a/examples/hello-world/platform/Package-Config.roc b/examples/hello-world/platform/Package-Config.roc index c58cba9769..61e4c96bf2 100644 --- a/examples/hello-world/platform/Package-Config.roc +++ b/examples/hello-world/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/hello-world" packages {} imports [] provides [ mainForHost ] - effects fx.Unused {} mainForHost : Str mainForHost = main diff --git a/examples/hello-zig/platform/Package-Config.roc b/examples/hello-zig/platform/Package-Config.roc index c58cba9769..61e4c96bf2 100644 --- a/examples/hello-zig/platform/Package-Config.roc +++ b/examples/hello-zig/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/hello-world" packages {} imports [] provides [ mainForHost ] - effects fx.Unused {} mainForHost : Str mainForHost = main diff --git a/examples/quicksort/platform/Package-Config.roc b/examples/quicksort/platform/Package-Config.roc index 6139bfd048..08d8bf0f0b 100644 --- a/examples/quicksort/platform/Package-Config.roc +++ b/examples/quicksort/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/quicksort" packages {} imports [] provides [ mainForHost ] - effects fx.Unused {} mainForHost : List I64 -> List I64 mainForHost = \list -> quicksort list diff --git a/examples/tui/platform/Package-Config.roc b/examples/tui/platform/Package-Config.roc index 4881b6c823..8196beeb56 100644 --- a/examples/tui/platform/Package-Config.roc +++ b/examples/tui/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "folkertdev/foo" packages {} imports [] provides [ mainForHost ] - effects fx.Unused {} mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View } mainForHost = main diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index cb1660597f..90b398d5c0 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -2980,8 +2980,6 @@ fn to_header_report<'a>( to_packages_report(alloc, lines, filename, packages, *pos) } - EHeader::Effects(effects, pos) => to_effects_report(alloc, lines, filename, effects, *pos), - EHeader::IndentStart(pos) => { let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); @@ -3572,45 +3570,6 @@ fn to_packages_report<'a>( } } -fn to_effects_report<'a>( - alloc: &'a RocDocAllocator<'a>, - lines: &LineInfo, - filename: PathBuf, - parse_problem: &roc_parse::parser::EEffects, - start: Position, -) -> Report<'a> { - use roc_parse::parser::EEffects; - - match *parse_problem { - EEffects::Effects(pos) => { - let surroundings = Region::new(start, pos); - let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - - let doc = alloc.stack(vec![ - alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ - alloc.reflow("I am expecting the "), - alloc.keyword("effects"), - alloc.reflow(" keyword next, like "), - ]), - alloc.parser_suggestion("effects {}").indent(4), - ]); - - Report { - filename, - doc, - title: "MISSING PACKAGES".to_string(), - severity: Severity::RuntimeError, - } - } - - EEffects::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), - - _ => todo!("unhandled parse error {:?}", parse_problem), - } -} - fn to_space_report<'a>( alloc: &'a RocDocAllocator<'a>, lines: &LineInfo, diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index c6bbca2041..859c78f43f 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -6132,12 +6132,6 @@ I need all branches in an `if` to have the same type! packages {} imports [Task] provides [ mainForHost ] - effects fx.Unused - { - putChar : I64 -> Effect {}, - putLine : Str -> Effect {}, - getLine : Effect Str - } "# ), indoc!( From 35efa3462d5693ab35c788555d3174b0f48798d0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 4 Feb 2022 00:04:08 +0100 Subject: [PATCH 474/541] fix my mistakes --- examples/false-interpreter/platform/File.roc | 4 ++-- examples/false-interpreter/platform/Stdin.roc | 2 +- examples/false-interpreter/platform/Stdout.roc | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/false-interpreter/platform/File.roc b/examples/false-interpreter/platform/File.roc index 36d8887640..939f31b7e8 100644 --- a/examples/false-interpreter/platform/File.roc +++ b/examples/false-interpreter/platform/File.roc @@ -1,6 +1,6 @@ interface File exposes [ line, Handle, withOpen, chunk ] - imports [ fx.Effect, Task.{ Task } ] + imports [ pf.Effect, Task.{ Task } ] Handle : [ @Handle U64 ] @@ -24,4 +24,4 @@ withOpen = \path, callback -> handle <- Task.await (open path) result <- Task.attempt (callback handle) { } <- Task.await (close handle) - Task.fromResult result \ No newline at end of file + Task.fromResult result diff --git a/examples/false-interpreter/platform/Stdin.roc b/examples/false-interpreter/platform/Stdin.roc index 9ff2a3de2a..22f397fd0c 100644 --- a/examples/false-interpreter/platform/Stdin.roc +++ b/examples/false-interpreter/platform/Stdin.roc @@ -1,6 +1,6 @@ interface Stdin exposes [ char ] - imports [ fx.Effect, Task ] + imports [ pf.Effect, Task ] # line : Task.Task Str * # line = Effect.after Effect.getLine Task.succeed # TODO FIXME Effect.getLine should suffice diff --git a/examples/false-interpreter/platform/Stdout.roc b/examples/false-interpreter/platform/Stdout.roc index 6acf849588..424ec0318a 100644 --- a/examples/false-interpreter/platform/Stdout.roc +++ b/examples/false-interpreter/platform/Stdout.roc @@ -1,9 +1,9 @@ interface Stdout exposes [ line, raw ] - imports [ fx.Effect, Task.{ Task } ] + imports [ pf.Effect, Task.{ Task } ] line : Str -> Task {} * line = \str -> Effect.map (Effect.putLine str) (\_ -> Ok {}) raw : Str -> Task {} * -raw = \str -> Effect.map (Effect.putRaw str) (\_ -> Ok {}) \ No newline at end of file +raw = \str -> Effect.map (Effect.putRaw str) (\_ -> Ok {}) From 6ebdc171653a11a4ca75fb41dc823cccd15c1e7f Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 4 Feb 2022 00:06:50 +0100 Subject: [PATCH 475/541] formatting --- cli/src/format.rs | 4 +-- cli_utils/Cargo.lock | 79 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 67 insertions(+), 16 deletions(-) diff --git a/cli/src/format.rs b/cli/src/format.rs index 00f427e41c..8ced266bce 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -12,8 +12,8 @@ use roc_parse::ast::{ TypeAnnotation, WhenBranch, }; use roc_parse::header::{ - AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, - PackageEntry, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, + AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, + PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, }; use roc_parse::{ ast::{Def, Module}, diff --git a/cli_utils/Cargo.lock b/cli_utils/Cargo.lock index c9b9c20929..4c5fb429e6 100644 --- a/cli_utils/Cargo.lock +++ b/cli_utils/Cargo.lock @@ -2475,12 +2475,13 @@ dependencies = [ "roc_builtins", "roc_can", "roc_collections", + "roc_error_macros", "roc_load", "roc_module", "roc_parse", "roc_problem", "roc_region", - "roc_reporting", + "roc_target", "roc_types", "roc_unify", "snafu", @@ -2510,6 +2511,7 @@ dependencies = [ "roc_reporting", "roc_solve", "roc_std", + "roc_target", "roc_types", "roc_unify", "serde_json", @@ -2524,6 +2526,7 @@ dependencies = [ "roc_collections", "roc_module", "roc_region", + "roc_target", "roc_types", ] @@ -2549,31 +2552,24 @@ dependencies = [ "bumpalo", "clap 3.0.0-beta.5", "const_format", - "inkwell 0.1.0", - "libloading 0.7.1", "mimalloc", "roc_build", "roc_builtins", "roc_can", "roc_collections", - "roc_constrain", "roc_docs", "roc_editor", + "roc_error_macros", "roc_fmt", - "roc_gen_llvm", "roc_linker", "roc_load", "roc_module", "roc_mono", "roc_parse", - "roc_problem", "roc_region", + "roc_repl_cli", "roc_reporting", - "roc_solve", - "roc_types", - "roc_unify", - "rustyline", - "rustyline-derive", + "roc_target", "target-lexicon", "tempfile", ] @@ -2631,6 +2627,7 @@ dependencies = [ "roc_module", "roc_parse", "roc_region", + "roc_target", "roc_types", "snafu", ] @@ -2680,6 +2677,10 @@ dependencies = [ "winit", ] +[[package]] +name = "roc_error_macros" +version = "0.1.0" + [[package]] name = "roc_fmt" version = "0.1.0" @@ -2700,12 +2701,13 @@ dependencies = [ "packed_struct", "roc_builtins", "roc_collections", + "roc_error_macros", "roc_module", "roc_mono", "roc_problem", "roc_region", - "roc_reporting", "roc_solve", + "roc_target", "roc_types", "roc_unify", "target-lexicon", @@ -2720,10 +2722,11 @@ dependencies = [ "morphic_lib", "roc_builtins", "roc_collections", + "roc_error_macros", "roc_module", "roc_mono", - "roc_reporting", "roc_std", + "roc_target", "target-lexicon", ] @@ -2734,10 +2737,11 @@ dependencies = [ "bumpalo", "roc_builtins", "roc_collections", + "roc_error_macros", "roc_module", "roc_mono", - "roc_reporting", "roc_std", + "roc_target", ] [[package]] @@ -2782,6 +2786,7 @@ dependencies = [ "roc_region", "roc_reporting", "roc_solve", + "roc_target", "roc_types", "roc_unify", "ven_pretty", @@ -2815,6 +2820,7 @@ dependencies = [ "roc_region", "roc_solve", "roc_std", + "roc_target", "roc_types", "roc_unify", "static_assertions", @@ -2850,6 +2856,44 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "roc_repl_cli" +version = "0.1.0" +dependencies = [ + "bumpalo", + "const_format", + "roc_mono", + "roc_parse", + "roc_repl_eval", + "rustyline", + "rustyline-derive", + "target-lexicon", +] + +[[package]] +name = "roc_repl_eval" +version = "0.1.0" +dependencies = [ + "bumpalo", + "inkwell 0.1.0", + "libloading 0.7.1", + "roc_build", + "roc_builtins", + "roc_can", + "roc_collections", + "roc_fmt", + "roc_gen_llvm", + "roc_load", + "roc_module", + "roc_mono", + "roc_parse", + "roc_region", + "roc_reporting", + "roc_target", + "roc_types", + "target-lexicon", +] + [[package]] name = "roc_reporting" version = "0.1.0" @@ -2886,6 +2930,13 @@ dependencies = [ name = "roc_std" version = "0.1.0" +[[package]] +name = "roc_target" +version = "0.1.0" +dependencies = [ + "target-lexicon", +] + [[package]] name = "roc_types" version = "0.1.0" From 9dea0e3a04dab39d407407aaa3da14ed72f3bd22 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 3 Feb 2022 22:17:50 -0500 Subject: [PATCH 476/541] Automatically deallocate Failures in Rust --- compiler/gen_llvm/src/run_roc.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/compiler/gen_llvm/src/run_roc.rs b/compiler/gen_llvm/src/run_roc.rs index 8835b00b37..812fbdede6 100644 --- a/compiler/gen_llvm/src/run_roc.rs +++ b/compiler/gen_llvm/src/run_roc.rs @@ -68,6 +68,22 @@ macro_rules! run_jit_function { count: usize, } + impl Drop for Failures { + fn drop(&mut self) { + use std::alloc::{dealloc, Layout}; + use std::mem; + + unsafe { + let layout = Layout::from_size_align_unchecked( + mem::size_of::(), + mem::align_of::(), + ); + + dealloc(self.failures as *mut u8, layout); + } + } + } + let get_expect_failures: libloading::Symbol Failures> = $lib .get(bitcode::UTILS_GET_EXPECT_FAILURES.as_bytes()) .ok() From a44a99c6cfdc4525b16672052ef6151d352880c6 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 4 Feb 2022 20:06:47 +0100 Subject: [PATCH 477/541] add new Effect.roc files --- examples/cli/platform/Effect.roc | 8 +++++++ .../false-interpreter/platform/Effect.roc | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 examples/cli/platform/Effect.roc create mode 100644 examples/false-interpreter/platform/Effect.roc diff --git a/examples/cli/platform/Effect.roc b/examples/cli/platform/Effect.roc new file mode 100644 index 0000000000..d883c2b685 --- /dev/null +++ b/examples/cli/platform/Effect.roc @@ -0,0 +1,8 @@ +hosted Effect + exposes [ Effect, after, map, always, forever, loop, putLine, getLine ] + imports [] + generates Effect with [ after, map, always, forever, loop ] + +putLine : Str -> Effect {} + +getLine : Effect Str diff --git a/examples/false-interpreter/platform/Effect.roc b/examples/false-interpreter/platform/Effect.roc new file mode 100644 index 0000000000..3486bb2cb0 --- /dev/null +++ b/examples/false-interpreter/platform/Effect.roc @@ -0,0 +1,22 @@ +hosted Effect + exposes [ Effect, after, map, always, forever, loop, openFile, closeFile, withFileOpen, getFileLine, getFileBytes, putLine, putRaw, getChar ] + imports [] + generates Effect with [ after, map, always, forever, loop ] + +openFile : Str -> Effect U64 + +closeFile : U64 -> Effect {} + +withFileOpen : Str, (U64 -> Effect (Result ok err)) -> Effect {} + +getFileLine : U64 -> Effect Str + +getFileBytes : U64 -> Effect (List U8) + +putLine : Str -> Effect {} + +putRaw : Str -> Effect {} + +# getLine : Effect Str + +getChar : Effect U8 From 5f1e2c60f850eda7a088cd3767718fc1fb760617 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 4 Feb 2022 21:56:57 +0100 Subject: [PATCH 478/541] change how hosted declarations are pushed --- compiler/can/src/module.rs | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 1698a95525..ba4d5ce0b9 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -242,32 +242,22 @@ where // symbols from this set let mut exposed_but_not_defined = exposed_symbols.clone(); - let hosted_declarations = if let Some(effect_symbol) = effect_symbol { - let mut declarations = Vec::new(); - let mut exposed_symbols = MutSet::default(); - - // NOTE this currently builds all functions, not just the ones that the user requested - crate::effect_module::build_effect_builtins( - &mut env, - &mut scope, - effect_symbol, - var_store, - &mut exposed_symbols, - &mut declarations, - ); - - declarations - } else { - Vec::new() - }; - match sort_can_defs(&mut env, defs, Output::default()) { (Ok(mut declarations), output) => { use crate::def::Declaration::*; - for x in hosted_declarations { - // TODO should this be `insert(0, x)`? - declarations.push(x); + if let Some(effect_symbol) = effect_symbol { + let mut exposed_symbols = MutSet::default(); + + // NOTE this currently builds all functions, not just the ones that the user requested + crate::effect_module::build_effect_builtins( + &mut env, + &mut scope, + effect_symbol, + var_store, + &mut exposed_symbols, + &mut declarations, + ); } for decl in declarations.iter_mut() { From 43066dcd50607b88d76d0061d372a8e4040edf6b Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 4 Feb 2022 22:26:31 +0100 Subject: [PATCH 479/541] fix formatting --- examples/false-interpreter/platform/Effect.roc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/false-interpreter/platform/Effect.roc b/examples/false-interpreter/platform/Effect.roc index 3486bb2cb0..b54ae3fd1d 100644 --- a/examples/false-interpreter/platform/Effect.roc +++ b/examples/false-interpreter/platform/Effect.roc @@ -1,5 +1,5 @@ hosted Effect - exposes [ Effect, after, map, always, forever, loop, openFile, closeFile, withFileOpen, getFileLine, getFileBytes, putLine, putRaw, getChar ] + exposes [ Effect, after, map, always, forever, loop, openFile, closeFile, withFileOpen, getFileLine, getFileBytes, putLine, putRaw, getLine, getChar ] imports [] generates Effect with [ after, map, always, forever, loop ] @@ -17,6 +17,6 @@ putLine : Str -> Effect {} putRaw : Str -> Effect {} -# getLine : Effect Str +getLine : Effect Str getChar : Effect U8 From f02db3961a04fc78fe17bbc4240a6262a849fd0d Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 4 Feb 2022 22:51:18 +0100 Subject: [PATCH 480/541] fix parse tests --- .../empty_platform_header.header.result-ast | 8 -- .../pass/empty_platform_header.header.roc | 2 +- .../function_effect_types.header.result-ast | 99 ------------------- ...nonempty_platform_header.header.result-ast | 10 -- .../pass/requires_type.header.result-ast | 10 -- 5 files changed, 1 insertion(+), 128 deletions(-) diff --git a/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast b/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast index 6821a8a463..736414d3a8 100644 --- a/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast @@ -18,14 +18,6 @@ Platform { packages: [], imports: [], provides: [], - effects: Effects { - spaces_before_effects_keyword: [], - spaces_after_effects_keyword: [], - spaces_after_type_name: [], - effect_shortname: "fx", - effect_type_name: "Blah", - entries: [], - }, before_header: [], after_platform_keyword: [], before_requires: [], diff --git a/compiler/parse/tests/snapshots/pass/empty_platform_header.header.roc b/compiler/parse/tests/snapshots/pass/empty_platform_header.header.roc index ce0e4468a5..04d71d738d 100644 --- a/compiler/parse/tests/snapshots/pass/empty_platform_header.header.roc +++ b/compiler/parse/tests/snapshots/pass/empty_platform_header.header.roc @@ -1 +1 @@ -platform "rtfeldman/blah" requires {} { main : {} } exposes [] packages {} imports [] provides [] effects fx.Blah {} \ No newline at end of file +platform "rtfeldman/blah" requires {} { main : {} } exposes [] packages {} imports [] provides [] diff --git a/compiler/parse/tests/snapshots/pass/function_effect_types.header.result-ast b/compiler/parse/tests/snapshots/pass/function_effect_types.header.result-ast index 08f163c4f0..9075843166 100644 --- a/compiler/parse/tests/snapshots/pass/function_effect_types.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/function_effect_types.header.result-ast @@ -43,105 +43,6 @@ Platform { "mainForHost", ), ], - effects: Effects { - spaces_before_effects_keyword: [ - Newline, - ], - spaces_after_effects_keyword: [], - spaces_after_type_name: [ - Newline, - ], - effect_shortname: "fx", - effect_type_name: "Effect", - entries: [ - @208-228 SpaceBefore( - TypedIdent { - ident: @208-215 "getLine", - spaces_before_colon: [], - ann: @218-228 Apply( - "", - "Effect", - [ - @225-228 Apply( - "", - "Str", - [], - ), - ], - ), - }, - [ - Newline, - ], - ), - @242-268 SpaceBefore( - TypedIdent { - ident: @242-249 "putLine", - spaces_before_colon: [], - ann: @259-268 Function( - [ - @252-255 Apply( - "", - "Str", - [], - ), - ], - @259-268 Apply( - "", - "Effect", - [ - @266-268 Record { - fields: [], - ext: None, - }, - ], - ), - ), - }, - [ - Newline, - ], - ), - @282-318 SpaceBefore( - SpaceAfter( - TypedIdent { - ident: @282-294 "twoArguments", - spaces_before_colon: [], - ann: @309-318 Function( - [ - @297-300 Apply( - "", - "Int", - [], - ), - @302-305 Apply( - "", - "Int", - [], - ), - ], - @309-318 Apply( - "", - "Effect", - [ - @316-318 Record { - fields: [], - ext: None, - }, - ], - ), - ), - }, - [ - Newline, - ], - ), - [ - Newline, - ], - ), - ], - }, before_header: [], after_platform_keyword: [], before_requires: [ diff --git a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast index 5dddd9d923..38b5d6f691 100644 --- a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast @@ -34,16 +34,6 @@ Platform { "mainForHost", ), ], - effects: Effects { - spaces_before_effects_keyword: [ - Newline, - ], - spaces_after_effects_keyword: [], - spaces_after_type_name: [], - effect_shortname: "fx", - effect_type_name: "Effect", - entries: [], - }, before_header: [], after_platform_keyword: [], before_requires: [ diff --git a/compiler/parse/tests/snapshots/pass/requires_type.header.result-ast b/compiler/parse/tests/snapshots/pass/requires_type.header.result-ast index 2195dcac19..35b2f64345 100644 --- a/compiler/parse/tests/snapshots/pass/requires_type.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/requires_type.header.result-ast @@ -41,16 +41,6 @@ Platform { "mainForHost", ), ], - effects: Effects { - spaces_before_effects_keyword: [ - Newline, - ], - spaces_after_effects_keyword: [], - spaces_after_type_name: [], - effect_shortname: "fx", - effect_type_name: "Effect", - entries: [], - }, before_header: [], after_platform_keyword: [], before_requires: [ From 71cae79b02e1579a90a703b437bd7615f3126084 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 4 Feb 2022 23:00:10 +0100 Subject: [PATCH 481/541] fix annoying spaces debug_assert failure --- compiler/fmt/src/def.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/fmt/src/def.rs b/compiler/fmt/src/def.rs index e3cae69f2a..8ea8ed9639 100644 --- a/compiler/fmt/src/def.rs +++ b/compiler/fmt/src/def.rs @@ -46,7 +46,9 @@ impl<'a> Formattable for Def<'a> { indent + INDENT, ); } else { - buf.push_str(" : "); + buf.spaces(1); + buf.push_str(":"); + buf.spaces(1); loc_annotation.format_with_options( buf, Parens::NotNeeded, From f49ae01d067d93395bc3edbf7bfc9c2d95a70817 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 4 Feb 2022 18:41:36 -0500 Subject: [PATCH 482/541] Drop an obsolete comment --- examples/cli/platform/Package-Config.roc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cli/platform/Package-Config.roc b/examples/cli/platform/Package-Config.roc index 883aa59a7a..4f9c8bdf62 100644 --- a/examples/cli/platform/Package-Config.roc +++ b/examples/cli/platform/Package-Config.roc @@ -1,5 +1,5 @@ platform "examples/cli" - requires {} { main : Task {} [] }# TODO FIXME + requires {} { main : Task {} [] } exposes [] packages {} imports [ Task.{ Task } ] From e216d74948b2026994d386296fa560ce6454b3c8 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 5 Feb 2022 20:55:13 -0500 Subject: [PATCH 483/541] Add Drew Lazzeri to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 117f57c396..41dcc89d13 100644 --- a/AUTHORS +++ b/AUTHORS @@ -62,3 +62,4 @@ Tankor Smash Matthias Devlamynck Jan Van Bruggen Mats Sigge <> +Drew Lazzeri From 2b5a66017845f5715219874bc2b49648fe80f8b0 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 5 Feb 2022 22:52:17 -0500 Subject: [PATCH 484/541] Add Display to RocStr --- roc_std/src/lib.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index 7f27585556..f633ed8f40 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -2,9 +2,10 @@ #![no_std] use core::convert::From; use core::ffi::c_void; +use core::fmt::{self, Display, Formatter}; use core::mem::{ManuallyDrop, MaybeUninit}; use core::ops::Drop; -use core::{fmt, mem, ptr, slice}; +use core::{mem, ptr, slice}; // A list of C functions that are being imported extern "C" { @@ -674,6 +675,12 @@ impl From<&str> for RocStr { } } +impl Display for RocStr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.as_str().fmt(f) + } +} + impl fmt::Debug for RocStr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // RocStr { is_small_str: false, storage: Refcounted(3), elements: [ 1,2,3,4] } From 1e4d2b3372228481a300d433bdab95b5063d60eb Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Feb 2022 12:24:19 +0100 Subject: [PATCH 485/541] add test for ExposedButNotDefined --- cli/tests/cli_run.rs | 19 +++++++++++++++++++ cli/tests/known_bad/ExposedNotDefined.roc | 3 +++ 2 files changed, 22 insertions(+) create mode 100644 cli/tests/known_bad/ExposedNotDefined.roc diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index c9af664349..8c7ed82b3d 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -801,6 +801,25 @@ mod cli_run { ), ); } + + #[test] + fn exposed_not_defined() { + check_compile_error( + &known_bad_file("ExposedNotDefined.roc"), + &[], + indoc!( + r#" + ── MISSING DEFINITION ────────────────────────────────────────────────────────── + + bar is listed as exposed, but it isn't defined in this module. + + You can fix this by adding a definition for bar, or by removing it + from exposes. + + ────────────────────────────────────────────────────────────────────────────────"# + ), + ); + } } #[allow(dead_code)] diff --git a/cli/tests/known_bad/ExposedNotDefined.roc b/cli/tests/known_bad/ExposedNotDefined.roc new file mode 100644 index 0000000000..b94dc7de59 --- /dev/null +++ b/cli/tests/known_bad/ExposedNotDefined.roc @@ -0,0 +1,3 @@ +interface Foo + exposes [ bar ] + imports [] From 5e16515d22f921bfeb9724c96a0cf2327a7abc7d Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Feb 2022 12:26:34 +0100 Subject: [PATCH 486/541] only generate the functions that the user wants --- compiler/can/src/effect_module.rs | 118 +++++++++++++++------------- compiler/can/src/module.rs | 59 ++++++++++++-- compiler/problem/src/can.rs | 1 + reporting/src/error/canonicalize.rs | 17 +++- reporting/src/error/parse.rs | 20 +++++ 5 files changed, 155 insertions(+), 60 deletions(-) diff --git a/compiler/can/src/effect_module.rs b/compiler/can/src/effect_module.rs index 0d4a276f01..3735c85239 100644 --- a/compiler/can/src/effect_module.rs +++ b/compiler/can/src/effect_module.rs @@ -12,69 +12,81 @@ use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; use roc_types::types::Type; -/// Functions that are always implemented for Effect -type Builder = for<'r, 's, 't0, 't1> fn( - &'r mut Env<'s>, - &'t0 mut Scope, - Symbol, - TagName, - &'t1 mut VarStore, -) -> (Symbol, Def); +#[derive(Default, Clone, Copy)] +pub(crate) struct HostedGeneratedFunctions { + pub(crate) after: bool, + pub(crate) map: bool, + pub(crate) always: bool, + pub(crate) loop_: bool, + pub(crate) forever: bool, +} -pub const BUILTIN_EFFECT_FUNCTIONS: &[(&str, Builder)] = &[ - // Effect.after : Effect a, (a -> Effect b) -> Effect b - ("after", build_effect_after), - // Effect.map : Effect a, (a -> b) -> Effect b - ("map", build_effect_map), - // Effect.always : a -> Effect a - ("always", build_effect_always), - // Effect.forever : Effect a -> Effect b - ("forever", build_effect_forever), - // Effect.loop : a, (a -> Effect [ Step a, Done b ]) -> Effect b - ("loop", build_effect_loop), -]; - -const RECURSIVE_BUILTIN_EFFECT_FUNCTIONS: &[&str] = &["forever", "loop"]; - -// the Effects alias & associated functions -// -// A platform can define an Effect type in its header. It can have an arbitrary name -// (e.g. Task, IO), but we'll call it an Effect in general. -// -// From that name, we generate an effect module, an effect alias, and some functions. -// -// The effect alias is implemented as -// -// Effect a : [ @Effect ({} -> a) ] -// -// For this alias we implement the functions defined in BUILTIN_EFFECT_FUNCTIONS with the -// standard implementation. - -pub fn build_effect_builtins( +/// the Effects alias & associated functions +/// +/// A platform can define an Effect type in its header. It can have an arbitrary name +/// (e.g. Task, IO), but we'll call it an Effect in general. +/// +/// From that name, we generate an effect module, an effect alias, and some functions. +/// +/// The effect alias is implemented as +/// +/// Effect a : [ @Effect ({} -> a) ] +/// +/// For this alias we implement the functions specified in HostedGeneratedFunctions with the +/// standard implementation. +pub(crate) fn build_effect_builtins( env: &mut Env, scope: &mut Scope, effect_symbol: Symbol, var_store: &mut VarStore, exposed_symbols: &mut MutSet, declarations: &mut Vec, + generated_functions: HostedGeneratedFunctions, ) { - for (name, f) in BUILTIN_EFFECT_FUNCTIONS.iter() { - let (symbol, def) = f( - env, - scope, - effect_symbol, - TagName::Private(effect_symbol), - var_store, - ); + macro_rules! helper { + ($f:expr) => {{ + let (symbol, def) = $f( + env, + scope, + effect_symbol, + TagName::Private(effect_symbol), + var_store, + ); - exposed_symbols.insert(symbol); + // make the outside world know this symbol exists + exposed_symbols.insert(symbol); - let is_recursive = RECURSIVE_BUILTIN_EFFECT_FUNCTIONS.iter().any(|n| n == name); - if is_recursive { - declarations.push(Declaration::DeclareRec(vec![def])); - } else { - declarations.push(Declaration::Declare(def)); - } + def + }}; + } + + if generated_functions.after { + let def = helper!(build_effect_after); + declarations.push(Declaration::Declare(def)); + } + + // Effect.map : Effect a, (a -> b) -> Effect b + if generated_functions.map { + let def = helper!(build_effect_map); + declarations.push(Declaration::Declare(def)); + } + + // Effect.always : a -> Effect a + if generated_functions.always { + let def = helper!(build_effect_always); + declarations.push(Declaration::Declare(def)); + } + + // Effect.forever : Effect a -> Effect b + if generated_functions.forever { + let def = helper!(build_effect_forever); + declarations.push(Declaration::DeclareRec(vec![def])); + } + + // Effect.loop : a, (a -> Effect [ Step a, Done b ]) -> Effect b + if generated_functions.loop_ { + let def = helper!(build_effect_loop); + declarations.push(Declaration::DeclareRec(vec![def])); } // Useful when working on functions in this module. By default symbols that we named do now diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index ba4d5ce0b9..8fe6ff5cef 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -1,4 +1,5 @@ use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def}; +use crate::effect_module::HostedGeneratedFunctions; use crate::env::Env; use crate::expr::{ClosureData, Expr, Output}; use crate::operator::desugar_def; @@ -40,6 +41,30 @@ pub struct ModuleOutput { pub scope: Scope, } +fn validate_generate_with<'a>( + generate_with: &'a [Loc>], +) -> (HostedGeneratedFunctions, Vec>) { + let mut functions = HostedGeneratedFunctions::default(); + let mut unknown = Vec::new(); + + for generated in generate_with { + match generated.value.as_str() { + "after" => functions.after = true, + "map" => functions.map = true, + "always" => functions.always = true, + "loop" => functions.loop_ = true, + "forever" => functions.forever = true, + other => { + // we don't know how to generate this function + let ident = Ident::from(other); + unknown.push(Loc::at(generated.region, ident)); + } + } + } + + (functions, unknown) +} + // TODO trim these down #[allow(clippy::too_many_arguments)] pub fn canonicalize_module_defs<'a, F>( @@ -68,9 +93,23 @@ where scope.add_alias(name, alias.region, alias.type_variables, alias.typ); } - let effect_symbol = if let HeaderFor::Hosted { generates, .. } = header_for { - // TODO extract effect name from the header + struct Hosted { + effect_symbol: Symbol, + generated_functions: HostedGeneratedFunctions, + } + + let hosted_info = if let HeaderFor::Hosted { + generates, + generates_with, + } = header_for + { let name: &str = generates.into(); + let (generated_functions, unknown_generated) = validate_generate_with(generates_with); + + for unknown in unknown_generated { + env.problem(Problem::UnknownGeneratesWith(unknown)); + } + let effect_symbol = scope .introduce( name.into(), @@ -99,7 +138,10 @@ where ); } - Some(effect_symbol) + Some(Hosted { + effect_symbol, + generated_functions, + }) } else { None }; @@ -246,7 +288,11 @@ where (Ok(mut declarations), output) => { use crate::def::Declaration::*; - if let Some(effect_symbol) = effect_symbol { + if let Some(Hosted { + effect_symbol, + generated_functions, + }) = hosted_info + { let mut exposed_symbols = MutSet::default(); // NOTE this currently builds all functions, not just the ones that the user requested @@ -257,6 +303,7 @@ where var_store, &mut exposed_symbols, &mut declarations, + generated_functions, ); } @@ -277,7 +324,7 @@ where // Temporary hack: we don't know exactly what symbols are hosted symbols, // and which are meant to be normal definitions without a body. So for now // we just assume they are hosted functions (meant to be provided by the platform) - if let Some(effect_symbol) = effect_symbol { + if let Some(Hosted { effect_symbol, .. }) = hosted_info { macro_rules! make_hosted_def { () => { let symbol = def.pattern_vars.iter().next().unwrap().0; @@ -358,7 +405,7 @@ where let mut aliases = MutMap::default(); - if let Some(effect_symbol) = effect_symbol { + if let Some(Hosted { effect_symbol, .. }) = hosted_info { // Remove this from exposed_symbols, // so that at the end of the process, // we can see if there were any diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index f8d964e8fc..a4eeddebe7 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -25,6 +25,7 @@ pub enum Problem { UnusedDef(Symbol, Region), UnusedImport(ModuleId, Region), ExposedButNotDefined(Symbol), + UnknownGeneratesWith(Loc), /// First symbol is the name of the closure with that argument /// Second symbol is the name of the argument that is unused UnusedArgument(Symbol, Symbol, Region), diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index 3ad20cebf6..1dfd07ad61 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -16,7 +16,7 @@ const UNUSED_DEF: &str = "UNUSED DEFINITION"; const UNUSED_IMPORT: &str = "UNUSED IMPORT"; const UNUSED_ALIAS_PARAM: &str = "UNUSED TYPE ALIAS PARAMETER"; const UNUSED_ARG: &str = "UNUSED ARGUMENT"; -const MISSING_DEFINITION: &str = "MISSING_DEFINITION"; +const MISSING_DEFINITION: &str = "MISSING DEFINITION"; const DUPLICATE_FIELD_NAME: &str = "DUPLICATE FIELD NAME"; const DUPLICATE_TAG_NAME: &str = "DUPLICATE TAG NAME"; const INVALID_UNICODE: &str = "INVALID UNICODE"; @@ -92,6 +92,21 @@ pub fn can_problem<'b>( title = MISSING_DEFINITION.to_string(); severity = Severity::RuntimeError; } + Problem::UnknownGeneratesWith(loc_ident) => { + doc = alloc.stack(vec![ + alloc + .reflow("I don't know how to generate the ") + .append(alloc.ident(loc_ident.value)) + .append(alloc.reflow("function.")), + alloc.region(lines.convert_region(loc_ident.region)), + alloc + .reflow("Only specific functions like `after` and `map` can be generated.") + .append(alloc.reflow("Learn more about hosted modules at TODO.")), + ]); + + title = MISSING_DEFINITION.to_string(); + severity = Severity::RuntimeError; + } Problem::UnusedArgument(closure_symbol, argument_symbol, region) => { let line = "\". Adding an underscore at the start of a variable name is a way of saying that the variable is not used."; diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index 1e767a38fd..79cccf96a2 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -517,6 +517,26 @@ fn to_expr_report<'a>( } } + EExpr::Record(_erecord, pos) => { + let surroundings = Region::new(start, *pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing an record, but I got stuck here:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat(vec![ + alloc.reflow("TODO provide more context.") + ]), + ]); + + Report { + filename, + doc, + title: "RECORD PARSE PROBLEM".to_string(), + severity: Severity::RuntimeError, + } + } + EExpr::Space(error, pos) => to_space_report(alloc, lines, filename, error, *pos), &EExpr::Number(ENumber::End, pos) => { From 2f453cfea2a181ba69ba8c28f17099f620309512 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Feb 2022 12:32:25 +0100 Subject: [PATCH 487/541] add test for UnusedImport --- cli/tests/cli_run.rs | 20 +++++++++++++++++ cli/tests/known_bad/Symbol.roc | 6 +++++ cli/tests/known_bad/UnusedImport.roc | 7 ++++++ reporting/tests/test_reporting.rs | 33 ---------------------------- 4 files changed, 33 insertions(+), 33 deletions(-) create mode 100644 cli/tests/known_bad/Symbol.roc create mode 100644 cli/tests/known_bad/UnusedImport.roc diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 8c7ed82b3d..bd8ed07859 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -820,6 +820,26 @@ mod cli_run { ), ); } + + #[test] + fn unused_import() { + check_compile_error( + &known_bad_file("UnusedImport.roc"), + &[], + indoc!( + r#" + ── UNUSED IMPORT ─────────────────────────────────────────────────────────────── + + Nothing from Symbol is used in this module. + + 3│ imports [ Symbol.{ Ident } ] + ^^^^^^^^^^^^^^^^ + + Since Symbol isn't used, you don't need to import it. + ────────────────────────────────────────────────────────────────────────────────"# + ), + ); + } } #[allow(dead_code)] diff --git a/cli/tests/known_bad/Symbol.roc b/cli/tests/known_bad/Symbol.roc new file mode 100644 index 0000000000..73e6cce822 --- /dev/null +++ b/cli/tests/known_bad/Symbol.roc @@ -0,0 +1,6 @@ +interface Symbol + exposes [ Ident ] + imports [] + +# NOTE: this module is fine, but used by UnusedImport.roc to uselessly import +Ident : Str diff --git a/cli/tests/known_bad/UnusedImport.roc b/cli/tests/known_bad/UnusedImport.roc new file mode 100644 index 0000000000..1968d0fc76 --- /dev/null +++ b/cli/tests/known_bad/UnusedImport.roc @@ -0,0 +1,7 @@ +interface UnusedImport + exposes [ plainText, emText ] + imports [ Symbol.{ Ident } ] + +plainText = \str -> PlainText str + +emText = \str -> EmText str diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index c2b8699716..90ff591ae0 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -678,39 +678,6 @@ mod test_reporting { ); } - // #[test] - // fn report_unused_import() { - // report_problem_as( - // indoc!( - // r#" - // interface Report - // exposes [ - // plainText, - // emText - // ] - // imports [ - // Symbol.{ Interns } - // ] - - // plainText = \str -> PlainText str - - // emText = \str -> EmText str - // "# - // ), - // indoc!( - // r#" - // Nothing from Symbol is used in this module. - - // 6│ imports [ - // 7│ Symbol.{ Interns } - // ^^^^^^ - // 8│ ] - - // Since Symbol isn't used, you don't need to import it."# - // ), - // ); - // } - #[test] fn report_value_color() { let src: &str = indoc!( From e6568868616904207a41d9f5d27cef4f30e079a4 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Feb 2022 12:50:50 +0100 Subject: [PATCH 488/541] add test for UnknownGeneratesWith --- cli/tests/cli_run.rs | 23 ++++++++++++++++++++ cli/tests/known_bad/UnknownGeneratesWith.roc | 4 ++++ reporting/src/error/canonicalize.rs | 5 +++-- 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 cli/tests/known_bad/UnknownGeneratesWith.roc diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index bd8ed07859..a4a105349b 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -836,6 +836,29 @@ mod cli_run { ^^^^^^^^^^^^^^^^ Since Symbol isn't used, you don't need to import it. + + ────────────────────────────────────────────────────────────────────────────────"# + ), + ); + } + + #[test] + fn unknown_generates_with() { + check_compile_error( + &known_bad_file("UnknownGeneratesWith.roc"), + &[], + indoc!( + r#" + ── UNKNOWN GENERATES FUNCTION ────────────────────────────────────────────────── + + I don't know how to generate the foobar function. + + 4│ generates Effect with [ after, map, always, foobar ] + ^^^^^^ + + Only specific functions like `after` and `map` can be generated.Learn + more about hosted modules at TODO. + ────────────────────────────────────────────────────────────────────────────────"# ), ); diff --git a/cli/tests/known_bad/UnknownGeneratesWith.roc b/cli/tests/known_bad/UnknownGeneratesWith.roc new file mode 100644 index 0000000000..828c6d1a2c --- /dev/null +++ b/cli/tests/known_bad/UnknownGeneratesWith.roc @@ -0,0 +1,4 @@ +hosted UnknownGeneratesWith + exposes [ Effect, after, map, always ] + imports [] + generates Effect with [ after, map, always, foobar ] diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index 1dfd07ad61..3759c26afa 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -17,6 +17,7 @@ const UNUSED_IMPORT: &str = "UNUSED IMPORT"; const UNUSED_ALIAS_PARAM: &str = "UNUSED TYPE ALIAS PARAMETER"; const UNUSED_ARG: &str = "UNUSED ARGUMENT"; const MISSING_DEFINITION: &str = "MISSING DEFINITION"; +const UNKNOWN_GENERATES_WITH: &str = "UNKNOWN GENERATES FUNCTION"; const DUPLICATE_FIELD_NAME: &str = "DUPLICATE FIELD NAME"; const DUPLICATE_TAG_NAME: &str = "DUPLICATE TAG NAME"; const INVALID_UNICODE: &str = "INVALID UNICODE"; @@ -97,14 +98,14 @@ pub fn can_problem<'b>( alloc .reflow("I don't know how to generate the ") .append(alloc.ident(loc_ident.value)) - .append(alloc.reflow("function.")), + .append(alloc.reflow(" function.")), alloc.region(lines.convert_region(loc_ident.region)), alloc .reflow("Only specific functions like `after` and `map` can be generated.") .append(alloc.reflow("Learn more about hosted modules at TODO.")), ]); - title = MISSING_DEFINITION.to_string(); + title = UNKNOWN_GENERATES_WITH.to_string(); severity = Severity::RuntimeError; } Problem::UnusedArgument(closure_symbol, argument_symbol, region) => { From d9eea360fdaa45b6b5253b744c5a78170166606a Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Feb 2022 13:26:32 +0100 Subject: [PATCH 489/541] formatting --- cli_utils/Cargo.lock | 13 ++++++++----- reporting/src/error/parse.rs | 4 +--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/cli_utils/Cargo.lock b/cli_utils/Cargo.lock index 4c5fb429e6..f6cf4cf5a9 100644 --- a/cli_utils/Cargo.lock +++ b/cli_utils/Cargo.lock @@ -2862,9 +2862,17 @@ version = "0.1.0" dependencies = [ "bumpalo", "const_format", + "inkwell 0.1.0", + "libloading 0.7.1", + "roc_build", + "roc_collections", + "roc_gen_llvm", + "roc_load", "roc_mono", "roc_parse", "roc_repl_eval", + "roc_target", + "roc_types", "rustyline", "rustyline-derive", "target-lexicon", @@ -2875,14 +2883,10 @@ name = "roc_repl_eval" version = "0.1.0" dependencies = [ "bumpalo", - "inkwell 0.1.0", - "libloading 0.7.1", - "roc_build", "roc_builtins", "roc_can", "roc_collections", "roc_fmt", - "roc_gen_llvm", "roc_load", "roc_module", "roc_mono", @@ -2891,7 +2895,6 @@ dependencies = [ "roc_reporting", "roc_target", "roc_types", - "target-lexicon", ] [[package]] diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index 79cccf96a2..7a13b4f547 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -524,9 +524,7 @@ fn to_expr_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing an record, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ - alloc.reflow("TODO provide more context.") - ]), + alloc.concat(vec![alloc.reflow("TODO provide more context.")]), ]); Report { From 7f5fc376125a622288fd3c1decbfee8b77eb598c Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 6 Feb 2022 08:04:38 -0500 Subject: [PATCH 490/541] Fix panic on parse error in Package-Config.roc This previously led to the following panic if there was a parse error in Package-Config.roc: 'There were still outstanding Arc references to module_ids', /Users/rtfeldman/code/roc/compiler/load/src/file.rs:1530:33 --- compiler/load/src/file.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 86af55ba4d..d6860ca74e 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -1525,13 +1525,7 @@ where Msg::FailedToParse(problem) => { shut_down_worker_threads!(); - let module_ids = Arc::try_unwrap(state.arc_modules) - .unwrap_or_else(|_| { - panic!("There were still outstanding Arc references to module_ids") - }) - .into_inner() - .into_module_ids(); - + let module_ids = (*state.arc_modules).lock().clone().into_module_ids(); let buf = to_parse_problem_report( problem, module_ids, From dba03374db745dec94fa4cb583865a14f95adf50 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 6 Feb 2022 08:05:26 -0500 Subject: [PATCH 491/541] Fix indentation in macro --- compiler/load/src/file.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index d6860ca74e..676e55d2e9 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -1557,11 +1557,11 @@ where shut_down_worker_threads!(); let module_ids = Arc::try_unwrap(arc_modules) - .unwrap_or_else(|_| { - panic!(r"There were still outstanding Arc references to module_ids") - }) - .into_inner() - .into_module_ids(); + .unwrap_or_else(|_| { + panic!(r"There were still outstanding Arc references to module_ids") + }) + .into_inner() + .into_module_ids(); let buf = to_parse_problem_report( problem, From 3e388e2651a106c5ba2ca78d976b032475c7f4fd Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 6 Feb 2022 08:05:33 -0500 Subject: [PATCH 492/541] Don't print "no hints" on Package-Config parse error --- reporting/src/error/parse.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index 1e767a38fd..25019acd42 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -109,7 +109,6 @@ fn to_syntax_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"I expected to reach the end of the file, but got stuck here:"), alloc.region(region), - alloc.concat(vec![alloc.reflow("no hints")]), ]); Report { From 589140983b61a7a5b96eba3a8487aeb1458dc218 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 6 Feb 2022 08:20:13 -0500 Subject: [PATCH 493/541] Support Package-Config.roc in test_load --- compiler/load/tests/test_load.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index 136e7ca5cf..8f0144df4d 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -66,7 +66,7 @@ mod test_load { arena: &'a Bump, mut files: Vec<(&str, &str)>, ) -> Result>, std::io::Error> { - use std::fs::File; + use std::fs::{self, File}; use std::io::Write; use std::path::PathBuf; use tempfile::tempdir; @@ -80,12 +80,15 @@ mod test_load { let dir = tempdir()?; let app_module = files.pop().unwrap(); - let interfaces = files; - for (name, source) in interfaces { + for (name, source) in files { let mut filename = PathBuf::from(name); filename.set_extension("roc"); let file_path = dir.path().join(filename.clone()); + + // Create any necessary intermediate directories (e.g. /platform) + fs::create_dir_all(file_path.parent().unwrap())?; + let mut file = File::create(file_path)?; writeln!(file, "{}", source)?; file_handles.push(file); From 2c0948f69b0923d1bcc0018d2c69f9d9939c227c Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 6 Feb 2022 08:20:25 -0500 Subject: [PATCH 494/541] Test Package-Config parse error in test_load --- compiler/load/tests/test_load.rs | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index 8f0144df4d..4634f65d75 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -605,4 +605,47 @@ mod test_load { Ok(_) => unreachable!("we expect failure here"), } } + + #[test] + fn platform_parse_error() { + let modules = vec![ + ( + "platform/Package-Config.roc", + indoc!( + r#" + platform "examples/hello-world" + requires {} { main : Str } + exposes [] + packages {} + imports [] + provides [ mainForHost ] + blah 1 2 3 # causing a parse error on purpose + + mainForHost : Str + "# + ), + ), + ( + "Main", + indoc!( + r#" + app "hello-world" + packages { pf: "platform" } + imports [] + provides [ main ] to pf + + main = "Hello, World!\n" + "# + ), + ), + ]; + + match multiple_modules(modules) { + Err(report) => { + assert!(report.contains("NOT END OF FILE")); + assert!(report.contains("blah 1 2 3 # causing a parse error on purpose")); + } + Ok(_) => unreachable!("we expect failure here"), + } + } } From 5e0d90ac53bd959dce6bb1b81ddf28c32881c587 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Fri, 4 Feb 2022 08:46:27 -0500 Subject: [PATCH 495/541] First pass --- compiler/can/src/builtins.rs | 193 ++++++++--------------------- compiler/can/src/constraint.rs | 15 +++ compiler/can/src/expr.rs | 14 +-- compiler/can/src/num.rs | 147 ++++++++++++++-------- compiler/can/src/pattern.rs | 14 +-- compiler/constrain/src/builtins.rs | 116 ++++++++++++++--- compiler/solve/src/solve.rs | 45 +++++++ reporting/tests/test_reporting.rs | 110 +++++++++++----- 8 files changed, 391 insertions(+), 263 deletions(-) diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 93f6b6947b..3a8d1ba9e4 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1,7 +1,7 @@ use crate::def::Def; use crate::expr::{self, ClosureData, Expr::*, IntValue}; use crate::expr::{Expr, Field, Recursive}; -use crate::num::{FloatWidth, IntWidth, NumWidth, NumericBound}; +use crate::num::{FloatBound, IntBound, IntWidth, NumericBound}; use crate::pattern::Pattern; use roc_collections::all::SendMap; use roc_module::called_via::CalledVia; @@ -867,7 +867,7 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def { args: vec![ ( arg_var, - int::(var_store.fresh(), var_store.fresh(), 1, num_no_bound()), + int::(var_store.fresh(), var_store.fresh(), 1, int_no_bound()), ), ( arg_var, @@ -965,7 +965,7 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def { (float_var, Var(Symbol::ARG_1)), ( float_var, - float(unbound_zero_var, precision_var, 0.0, num_no_bound()), + float(unbound_zero_var, precision_var, 0.0, float_no_bound()), ), ], ret_var: bool_var, @@ -1014,7 +1014,7 @@ fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def { (float_var, Var(Symbol::ARG_1)), ( float_var, - float(unbound_zero_var, precision_var, 0.0, num_no_bound()), + float(unbound_zero_var, precision_var, 0.0, float_no_bound()), ), ], ret_var: bool_var, @@ -1253,162 +1253,82 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def { /// Num.minI8: I8 fn num_min_i8(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - i8::MIN, - NumericBound::Exact(IntWidth::I8), - ) + int_min_or_max::(symbol, var_store, i8::MIN, IntBound::Exact(IntWidth::I8)) } /// Num.maxI8: I8 fn num_max_i8(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - i8::MAX, - NumericBound::Exact(IntWidth::I8), - ) + int_min_or_max::(symbol, var_store, i8::MAX, IntBound::Exact(IntWidth::I8)) } /// Num.minU8: U8 fn num_min_u8(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - u8::MIN, - NumericBound::Exact(IntWidth::U8), - ) + int_min_or_max::(symbol, var_store, u8::MIN, IntBound::Exact(IntWidth::U8)) } /// Num.maxU8: U8 fn num_max_u8(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - u8::MAX, - NumericBound::Exact(IntWidth::U8), - ) + int_min_or_max::(symbol, var_store, u8::MAX, IntBound::Exact(IntWidth::U8)) } /// Num.minI16: I16 fn num_min_i16(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - i16::MIN, - NumericBound::Exact(IntWidth::I16), - ) + int_min_or_max::(symbol, var_store, i16::MIN, IntBound::Exact(IntWidth::I16)) } /// Num.maxI16: I16 fn num_max_i16(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - i16::MAX, - NumericBound::Exact(IntWidth::I16), - ) + int_min_or_max::(symbol, var_store, i16::MAX, IntBound::Exact(IntWidth::I16)) } /// Num.minU16: U16 fn num_min_u16(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - u16::MIN, - NumericBound::Exact(IntWidth::U16), - ) + int_min_or_max::(symbol, var_store, u16::MIN, IntBound::Exact(IntWidth::U16)) } /// Num.maxU16: U16 fn num_max_u16(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - u16::MAX, - NumericBound::Exact(IntWidth::U16), - ) + int_min_or_max::(symbol, var_store, u16::MAX, IntBound::Exact(IntWidth::U16)) } /// Num.minI32: I32 fn num_min_i32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - i32::MIN, - NumericBound::Exact(IntWidth::I32), - ) + int_min_or_max::(symbol, var_store, i32::MIN, IntBound::Exact(IntWidth::I32)) } /// Num.maxI32: I32 fn num_max_i32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - i32::MAX, - NumericBound::Exact(IntWidth::I32), - ) + int_min_or_max::(symbol, var_store, i32::MAX, IntBound::Exact(IntWidth::I32)) } /// Num.minU32: U32 fn num_min_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - u32::MIN, - NumericBound::Exact(IntWidth::U32), - ) + int_min_or_max::(symbol, var_store, u32::MIN, IntBound::Exact(IntWidth::U32)) } /// Num.maxU32: U32 fn num_max_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - u32::MAX, - NumericBound::Exact(IntWidth::U32), - ) + int_min_or_max::(symbol, var_store, u32::MAX, IntBound::Exact(IntWidth::U32)) } /// Num.minI64: I64 fn num_min_i64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - i64::MIN, - NumericBound::Exact(IntWidth::I64), - ) + int_min_or_max::(symbol, var_store, i64::MIN, IntBound::Exact(IntWidth::I64)) } /// Num.maxI64: I64 fn num_max_i64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - i64::MAX, - NumericBound::Exact(IntWidth::I64), - ) + int_min_or_max::(symbol, var_store, i64::MAX, IntBound::Exact(IntWidth::I64)) } /// Num.minU64: U64 fn num_min_u64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - u64::MIN, - NumericBound::Exact(IntWidth::U64), - ) + int_min_or_max::(symbol, var_store, u64::MIN, IntBound::Exact(IntWidth::U64)) } /// Num.maxU64: U64 fn num_max_u64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - u64::MAX, - NumericBound::Exact(IntWidth::U64), - ) + int_min_or_max::(symbol, var_store, u64::MAX, IntBound::Exact(IntWidth::U64)) } /// Num.minI128: I128 @@ -1417,7 +1337,7 @@ fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { symbol, var_store, i128::MIN, - NumericBound::Exact(IntWidth::I128), + IntBound::Exact(IntWidth::I128), ) } @@ -1427,7 +1347,7 @@ fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { symbol, var_store, i128::MAX, - NumericBound::Exact(IntWidth::I128), + IntBound::Exact(IntWidth::I128), ) } @@ -1559,7 +1479,7 @@ fn str_to_num(symbol: Symbol, var_store: &mut VarStore) -> Def { errorcode_var, Variable::UNSIGNED8, 0, - NumericBound::Exact(IntWidth::U8), + IntBound::Exact(IntWidth::U8), ), ), ], @@ -2307,7 +2227,7 @@ fn list_take_first(symbol: Symbol, var_store: &mut VarStore) -> Def { len_var, Variable::NATURAL, 0, - NumericBound::Exact(IntWidth::Nat), + IntBound::Exact(IntWidth::Nat), ); let body = RunLowLevel { @@ -2338,7 +2258,7 @@ fn list_take_last(symbol: Symbol, var_store: &mut VarStore) -> Def { len_var, Variable::NATURAL, 0, - NumericBound::Exact(IntWidth::Nat), + IntBound::Exact(IntWidth::Nat), ); let bool_var = var_store.fresh(); @@ -2453,7 +2373,7 @@ fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def { int_var, Variable::NATURAL, 0, - NumericBound::Exact(IntWidth::Nat), + IntBound::Exact(IntWidth::Nat), ); // \acc, elem -> acc |> List.append sep |> List.append elem @@ -2538,7 +2458,7 @@ fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def { index_var, Variable::NATURAL, 0, - NumericBound::Exact(IntWidth::Nat), + IntBound::Exact(IntWidth::Nat), ); let clos = Closure(ClosureData { @@ -2703,7 +2623,7 @@ fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def { (list_var, Var(Symbol::ARG_1)), ( index_var, - int::(num_var, num_precision_var, 0, num_no_bound()), + int::(num_var, num_precision_var, 0, int_no_bound()), ), ], ret_var: list_var, @@ -2803,7 +2723,7 @@ fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def { ), ( arg_var, - int::(num_var, num_precision_var, 1, num_no_bound()), + int::(num_var, num_precision_var, 1, int_no_bound()), ), ], ret_var: len_var, @@ -3004,7 +2924,7 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def { args: vec![ ( len_var, - int::(num_var, num_precision_var, 0, num_no_bound()), + int::(num_var, num_precision_var, 0, int_no_bound()), ), ( len_var, @@ -3042,7 +2962,7 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def { num_var, num_precision_var, 0, - num_no_bound(), + int_no_bound(), ), ), ], @@ -3145,7 +3065,7 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def { args: vec![ ( len_var, - int::(num_var, num_precision_var, 0, num_no_bound()), + int::(num_var, num_precision_var, 0, int_no_bound()), ), ( len_var, @@ -3183,7 +3103,7 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def { num_var, num_precision_var, 0, - num_no_bound(), + int_no_bound(), ), ), ], @@ -4076,7 +3996,7 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def { (num_var, Var(Symbol::ARG_2)), ( num_var, - float(unbound_zero_var, precision_var, 0.0, num_no_bound()), + float(unbound_zero_var, precision_var, 0.0, float_no_bound()), ), ], ret_var: bool_var, @@ -4146,7 +4066,7 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def { unbound_zero_var, unbound_zero_precision_var, 0, - num_no_bound(), + int_no_bound(), ), ), ], @@ -4217,7 +4137,7 @@ fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def { unbound_zero_var, unbound_zero_precision_var, 0, - num_no_bound(), + int_no_bound(), ), ), ], @@ -4290,7 +4210,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def { args: vec![ ( len_var, - int::(zero_var, zero_precision_var, 0, num_no_bound()), + int::(zero_var, zero_precision_var, 0, int_no_bound()), ), ( len_var, @@ -4317,7 +4237,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def { (list_var, Var(Symbol::ARG_1)), ( len_var, - int::(zero_var, zero_precision_var, 0, num_no_bound()), + int::(zero_var, zero_precision_var, 0, int_no_bound()), ), ], ret_var: list_elem_var, @@ -4377,7 +4297,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def { args: vec![ ( len_var, - int::(num_var, num_precision_var, 0, num_no_bound()), + int::(num_var, num_precision_var, 0, int_no_bound()), ), ( len_var, @@ -4423,7 +4343,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def { num_var, num_precision_var, 1, - num_no_bound(), + int_no_bound(), ), ), ], @@ -5144,12 +5064,7 @@ fn defn_help( } #[inline(always)] -fn int_min_or_max( - symbol: Symbol, - var_store: &mut VarStore, - i: I128, - bound: NumericBound, -) -> Def +fn int_min_or_max(symbol: Symbol, var_store: &mut VarStore, i: I128, bound: IntBound) -> Def where I128: Into, { @@ -5178,17 +5093,20 @@ where } } -fn num_no_bound() -> NumericBound { +fn num_no_bound() -> NumericBound { NumericBound::None } +fn int_no_bound() -> IntBound { + IntBound::None +} + +fn float_no_bound() -> FloatBound { + FloatBound::None +} + #[inline(always)] -fn int( - num_var: Variable, - precision_var: Variable, - i: I128, - bound: NumericBound, -) -> Expr +fn int(num_var: Variable, precision_var: Variable, i: I128, bound: IntBound) -> Expr where I128: Into, { @@ -5203,12 +5121,7 @@ where } #[inline(always)] -fn float( - num_var: Variable, - precision_var: Variable, - f: f64, - bound: NumericBound, -) -> Expr { +fn float(num_var: Variable, precision_var: Variable, f: f64, bound: FloatBound) -> Expr { Float( num_var, precision_var, @@ -5219,7 +5132,7 @@ fn float( } #[inline(always)] -fn num>(num_var: Variable, i: I, bound: NumericBound) -> Expr { +fn num>(num_var: Variable, i: I, bound: NumericBound) -> Expr { let i = i.into(); Num( num_var, diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index 03b758320f..c56168c827 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -27,6 +27,14 @@ pub enum Constraint { Let(Box), And(Vec), Present(Type, PresenceConstraint), + + /// `EqBoundedRange(Ts, U, ...)` means there must be at least one `T` in the *ordered* range `Ts` + /// that unifies (via `Eq`) with `U`. + /// + /// This is only used for integers, where we may see e.g. the number literal `-1` and know it + /// has the bounded range `[I8, I16, I32, I64, I128]`, at least one of which must unify with + /// the type the number literal is used as. + EqBoundedRange(Type, Expected>, Category, Region), } #[derive(Debug, Clone, PartialEq)] @@ -87,6 +95,7 @@ impl Constraint { } Constraint::And(cs) => cs.iter().any(|c| c.contains_save_the_environment()), Constraint::Present(_, _) => false, + Constraint::EqBoundedRange(_, _, _, _) => false, } } } @@ -171,5 +180,11 @@ fn validate_help(constraint: &Constraint, declared: &Declared, accum: &mut Varia } } } + Constraint::EqBoundedRange(typ, one_of, _, _) => { + subtract(declared, &typ.variables_detail(), accum); + for typ in one_of.get_type_ref() { + subtract(declared, &typ.variables_detail(), accum); + } + } } } diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 3d4954e5ad..86579c248f 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -4,7 +4,7 @@ use crate::def::{can_defs_with_return, Def}; use crate::env::Env; use crate::num::{ finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result, - int_expr_from_result, num_expr_from_result, FloatWidth, IntWidth, NumWidth, NumericBound, + int_expr_from_result, num_expr_from_result, FloatBound, IntBound, NumericBound, }; use crate::pattern::{canonicalize_pattern, Pattern}; use crate::procedure::References; @@ -67,17 +67,11 @@ pub enum Expr { // Num stores the `a` variable in `Num a`. Not the same as the variable // stored in Int and Float below, which is strictly for better error messages - Num(Variable, Box, IntValue, NumericBound), + Num(Variable, Box, IntValue, NumericBound), // Int and Float store a variable to generate better error messages - Int( - Variable, - Variable, - Box, - IntValue, - NumericBound, - ), - Float(Variable, Variable, Box, f64, NumericBound), + Int(Variable, Variable, Box, IntValue, IntBound), + Float(Variable, Variable, Box, f64, FloatBound), Str(Box), List { elem_var: Variable, diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index 847d729466..e93ca3eedc 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -50,7 +50,7 @@ pub fn num_expr_from_result( #[inline(always)] pub fn int_expr_from_result( var_store: &mut VarStore, - result: Result<(&str, IntValue, NumericBound), (&str, IntErrorKind)>, + result: Result<(&str, IntValue, IntBound), (&str, IntErrorKind)>, region: Region, base: Base, env: &mut Env, @@ -77,7 +77,7 @@ pub fn int_expr_from_result( #[inline(always)] pub fn float_expr_from_result( var_store: &mut VarStore, - result: Result<(&str, f64, NumericBound), (&str, FloatErrorKind)>, + result: Result<(&str, f64, FloatBound), (&str, FloatErrorKind)>, region: Region, env: &mut Env, ) -> Expr { @@ -101,8 +101,8 @@ pub fn float_expr_from_result( } pub enum ParsedNumResult { - Int(IntValue, NumericBound), - Float(f64, NumericBound), + Int(IntValue, IntBound), + Float(f64, FloatBound), UnknownNum(IntValue), } @@ -115,15 +115,13 @@ pub fn finish_parsing_num(raw: &str) -> Result ParsedNumResult::UnknownNum(num), - NumericBound::Exact(NumWidth::Int(iw)) => { - ParsedNumResult::Int(num, NumericBound::Exact(iw)) - } - NumericBound::Exact(NumWidth::Float(fw)) => { + NumericBound::Int(ib) => ParsedNumResult::Int(num, ib), + NumericBound::Float(fb) => { let num = match num { IntValue::I128(n) => n as f64, IntValue::U128(n) => n as f64, }; - ParsedNumResult::Float(num, NumericBound::Exact(fw)) + ParsedNumResult::Float(num, fb) } }) } @@ -133,7 +131,7 @@ pub fn finish_parsing_base( raw: &str, base: Base, is_negative: bool, -) -> Result<(IntValue, NumericBound), (&str, IntErrorKind)> { +) -> Result<(IntValue, IntBound), (&str, IntErrorKind)> { let radix = match base { Base::Hex => 16, Base::Decimal => 10, @@ -149,9 +147,9 @@ pub fn finish_parsing_base( }) .and_then(|(n, bound)| { let bound = match bound { - NumericBound::None => NumericBound::None, - NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(iw), - NumericBound::Exact(NumWidth::Float(_)) => return Err(IntErrorKind::FloatSuffix), + NumericBound::None => IntBound::None, + NumericBound::Int(ib) => ib, + NumericBound::Float(_) => return Err(IntErrorKind::FloatSuffix), }; Ok((n, bound)) }) @@ -159,15 +157,13 @@ pub fn finish_parsing_base( } #[inline(always)] -pub fn finish_parsing_float( - raw: &str, -) -> Result<(f64, NumericBound), (&str, FloatErrorKind)> { +pub fn finish_parsing_float(raw: &str) -> Result<(f64, FloatBound), (&str, FloatErrorKind)> { let (opt_bound, raw_without_suffix) = parse_literal_suffix(raw); let bound = match opt_bound { - None => NumericBound::None, - Some(NumWidth::Float(fw)) => NumericBound::Exact(fw), - Some(NumWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)), + None => FloatBound::None, + Some(ParsedWidth::Float(fw)) => FloatBound::Exact(fw), + Some(ParsedWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)), }; // Ignore underscores. @@ -184,7 +180,13 @@ pub fn finish_parsing_float( } } -fn parse_literal_suffix(num_str: &str) -> (Option, &str) { +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum ParsedWidth { + Int(IntWidth), + Float(FloatWidth), +} + +fn parse_literal_suffix(num_str: &str) -> (Option, &str) { macro_rules! parse_num_suffix { ($($suffix:expr, $width:expr)*) => {$( if num_str.ends_with($suffix) { @@ -194,20 +196,20 @@ fn parse_literal_suffix(num_str: &str) -> (Option, &str) { } parse_num_suffix! { - "u8", NumWidth::Int(IntWidth::U8) - "u16", NumWidth::Int(IntWidth::U16) - "u32", NumWidth::Int(IntWidth::U32) - "u64", NumWidth::Int(IntWidth::U64) - "u128", NumWidth::Int(IntWidth::U128) - "i8", NumWidth::Int(IntWidth::I8) - "i16", NumWidth::Int(IntWidth::I16) - "i32", NumWidth::Int(IntWidth::I32) - "i64", NumWidth::Int(IntWidth::I64) - "i128", NumWidth::Int(IntWidth::I128) - "nat", NumWidth::Int(IntWidth::Nat) - "dec", NumWidth::Float(FloatWidth::Dec) - "f32", NumWidth::Float(FloatWidth::F32) - "f64", NumWidth::Float(FloatWidth::F64) + "u8", ParsedWidth::Int(IntWidth::U8) + "u16", ParsedWidth::Int(IntWidth::U16) + "u32", ParsedWidth::Int(IntWidth::U32) + "u64", ParsedWidth::Int(IntWidth::U64) + "u128", ParsedWidth::Int(IntWidth::U128) + "i8", ParsedWidth::Int(IntWidth::I8) + "i16", ParsedWidth::Int(IntWidth::I16) + "i32", ParsedWidth::Int(IntWidth::I32) + "i64", ParsedWidth::Int(IntWidth::I64) + "i128", ParsedWidth::Int(IntWidth::I128) + "nat", ParsedWidth::Int(IntWidth::Nat) + "dec", ParsedWidth::Float(FloatWidth::Dec) + "f32", ParsedWidth::Float(FloatWidth::F32) + "f64", ParsedWidth::Float(FloatWidth::F64) } (None, num_str) @@ -221,10 +223,7 @@ fn parse_literal_suffix(num_str: &str) -> (Option, &str) { /// the LEGAL_DETAILS file in the root directory of this distribution. /// /// Thanks to the Rust project and its contributors! -fn from_str_radix( - src: &str, - radix: u32, -) -> Result<(IntValue, NumericBound), IntErrorKind> { +fn from_str_radix(src: &str, radix: u32) -> Result<(IntValue, NumericBound), IntErrorKind> { use self::IntErrorKind::*; assert!( @@ -268,19 +267,31 @@ fn from_str_radix( match opt_exact_bound { None => { - // TODO: use the lower bound - Ok((result, NumericBound::None)) + // There's no exact bound, but we do have a lower bound. + let sign_demand = if is_negative { + SignDemand::Signed + } else { + SignDemand::NoDemand + }; + Ok(( + result, + IntBound::AtLeast { + sign: sign_demand, + width: lower_bound, + } + .into(), + )) } - Some(bound @ NumWidth::Float(_)) => { + Some(ParsedWidth::Float(fw)) => { // For now, assume floats can represent all integers // TODO: this is somewhat incorrect, revisit - Ok((result, NumericBound::Exact(bound))) + Ok((result, FloatBound::Exact(fw).into())) } - Some(NumWidth::Int(exact_width)) => { + Some(ParsedWidth::Int(exact_width)) => { // We need to check if the exact bound >= lower bound. if exact_width.is_superset(&lower_bound, is_negative) { // Great! Use the exact bound. - Ok((result, NumericBound::Exact(NumWidth::Int(exact_width)))) + Ok((result, IntBound::Exact(exact_width).into())) } else { // This is something like 200i8; the lower bound is u8, which holds strictly more // ints on the positive side than i8 does. Report an error depending on which side @@ -474,19 +485,47 @@ pub enum FloatWidth { } #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum NumWidth { - Int(IntWidth), - Float(FloatWidth), +pub enum SignDemand { + /// Can be signed or unsigned. + NoDemand, + /// Must be signed. + Signed, } -/// Describes a bound on the width of a numeric literal. +/// Describes a bound on the width of an integer. #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum NumericBound -where - W: Copy, -{ +pub enum IntBound { /// There is no bound on the width. None, - /// Must have exactly the width `W`. - Exact(W), + /// Must have an exact width. + Exact(IntWidth), + /// Must have a certain sign and a minimum width. + AtLeast { sign: SignDemand, width: IntWidth }, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum FloatBound { + None, + Exact(FloatWidth), +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum NumericBound { + None, + Int(IntBound), + Float(FloatBound), +} + +impl From for NumericBound { + #[inline(always)] + fn from(ib: IntBound) -> Self { + Self::Int(ib) + } +} + +impl From for NumericBound { + #[inline(always)] + fn from(fb: FloatBound) -> Self { + Self::Float(fb) + } } diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index ac85b53306..2e574796c2 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -1,7 +1,7 @@ use crate::env::Env; use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output}; use crate::num::{ - finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatWidth, IntWidth, NumWidth, + finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatBound, IntBound, NumericBound, ParsedNumResult, }; use crate::scope::Scope; @@ -29,15 +29,9 @@ pub enum Pattern { ext_var: Variable, destructs: Vec>, }, - NumLiteral(Variable, Box, IntValue, NumericBound), - IntLiteral( - Variable, - Variable, - Box, - IntValue, - NumericBound, - ), - FloatLiteral(Variable, Variable, Box, f64, NumericBound), + NumLiteral(Variable, Box, IntValue, NumericBound), + IntLiteral(Variable, Variable, Box, IntValue, IntBound), + FloatLiteral(Variable, Variable, Box, f64, FloatBound), StrLiteral(Box), Underscore, diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 020d5b4564..e72010899a 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -1,7 +1,7 @@ use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::LetConstraint; use roc_can::expected::Expected::{self, *}; -use roc_can::num::{FloatWidth, IntWidth, NumWidth, NumericBound}; +use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumericBound, SignDemand}; use roc_collections::all::SendMap; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; @@ -28,13 +28,31 @@ pub fn add_numeric_bound_constr( } } +pub fn add_numeric_range_constr( + constrs: &mut Vec, + num_type: Type, + bound: impl TypedNumericBound, + region: Region, + category: Category, +) { + let range = bound.bounded_range(); + if !range.is_empty() { + constrs.push(EqBoundedRange( + num_type, + Expected::ForReason(Reason::NumericLiteralSuffix, range, region), + category, + region, + )); + } +} + #[inline(always)] pub fn int_literal( num_var: Variable, precision_var: Variable, expected: Expected, region: Region, - bound: NumericBound, + bound: IntBound, ) -> Constraint { let num_type = Variable(num_var); let reason = Reason::IntLiteral; @@ -50,8 +68,15 @@ pub fn int_literal( Category::Int, region, ), - Eq(num_type, expected, Category::Int, region), + Eq(num_type, expected.clone(), Category::Int, region), ]); + add_numeric_range_constr( + &mut constrs, + expected.get_type(), + bound, + region, + Category::Int, + ); exists(vec![num_var], And(constrs)) } @@ -62,7 +87,7 @@ pub fn float_literal( precision_var: Variable, expected: Expected, region: Region, - bound: NumericBound, + bound: FloatBound, ) -> Constraint { let num_type = Variable(num_var); let reason = Reason::FloatLiteral; @@ -93,7 +118,7 @@ pub fn num_literal( num_var: Variable, expected: Expected, region: Region, - bound: NumericBound, + bound: NumericBound, ) -> Constraint { let num_type = crate::builtins::num_num(Type::Variable(num_var)); @@ -290,13 +315,15 @@ pub trait TypedNumericBound { /// Get a concrete type for this number, if one exists. /// Returns `None` e.g. if the bound is open, like `Int *`. fn concrete_num_type(&self) -> Option; + + fn bounded_range(&self) -> Vec; } -impl TypedNumericBound for NumericBound { +impl TypedNumericBound for IntBound { fn concrete_num_type(&self) -> Option { match self { - NumericBound::None => None, - NumericBound::Exact(w) => Some(match w { + IntBound::None | IntBound::AtLeast { .. } => None, + IntBound::Exact(w) => Some(match w { IntWidth::U8 => num_u8(), IntWidth::U16 => num_u16(), IntWidth::U32 => num_u32(), @@ -311,29 +338,80 @@ impl TypedNumericBound for NumericBound { }), } } + + fn bounded_range(&self) -> Vec { + match self { + IntBound::None => vec![], + IntBound::Exact(_) => vec![], + IntBound::AtLeast { sign, width } => { + let whole_range: &[(IntWidth, Variable)] = match sign { + SignDemand::NoDemand => { + &[ + (IntWidth::I8, Variable::I8), + (IntWidth::U8, Variable::U8), + (IntWidth::I16, Variable::I16), + (IntWidth::U16, Variable::U16), + (IntWidth::I32, Variable::I32), + (IntWidth::U32, Variable::U32), + (IntWidth::I64, Variable::I64), + (IntWidth::Nat, Variable::NAT), // FIXME: Nat's order here depends on the platform! + (IntWidth::U64, Variable::U64), + (IntWidth::I128, Variable::I128), + (IntWidth::U128, Variable::U128), + ] + } + SignDemand::Signed => &[ + (IntWidth::I8, Variable::I8), + (IntWidth::I16, Variable::I16), + (IntWidth::I32, Variable::I32), + (IntWidth::I64, Variable::I64), + (IntWidth::I128, Variable::I128), + ], + }; + whole_range + .iter() + .skip_while(|(lower_bound, _)| *lower_bound != *width) + .map(|(_, var)| Type::Variable(*var)) + .collect() + } + } + } } -impl TypedNumericBound for NumericBound { +impl TypedNumericBound for FloatBound { fn concrete_num_type(&self) -> Option { match self { - NumericBound::None => None, - NumericBound::Exact(w) => Some(match w { + FloatBound::None => None, + FloatBound::Exact(w) => Some(match w { FloatWidth::Dec => num_dec(), FloatWidth::F32 => num_f32(), FloatWidth::F64 => num_f64(), }), } } -} -impl TypedNumericBound for NumericBound { - fn concrete_num_type(&self) -> Option { + fn bounded_range(&self) -> Vec { match self { - NumericBound::None => None, - NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(*iw).concrete_num_type(), - NumericBound::Exact(NumWidth::Float(fw)) => { - NumericBound::Exact(*fw).concrete_num_type() - } + FloatBound::None => vec![], + FloatBound::Exact(_) => vec![], + } + } +} + +impl TypedNumericBound for NumericBound { + fn concrete_num_type(&self) -> Option { + match self { + NumericBound::None => None, + NumericBound::Int(ib) => ib.concrete_num_type(), + NumericBound::Float(fb) => fb.concrete_num_type(), + } + } + + fn bounded_range(&self) -> Vec { + match self { + NumericBound::None => vec![], + NumericBound::Int(ib) => ib.bounded_range(), + NumericBound::Float(fb) => fb.bounded_range(), } } } diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index edb027a235..c1e9bc7e19 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -689,6 +689,51 @@ fn solve( } } } + EqBoundedRange(typ, expect_one_of, category, region) => { + let actual = type_to_var(subs, rank, pools, cached_aliases, typ); + + let mut it = expect_one_of.get_type_ref().iter().peekable(); + + while let Some(expected) = it.next() { + let expected = type_to_var(subs, rank, pools, cached_aliases, expected); + let snapshot = subs.snapshot(); + match unify(subs, actual, expected, Mode::Eq) { + Success(vars) => { + introduce(subs, rank, pools, &vars); + + return state; + } + Failure(..) if it.peek().is_some() => { + subs.rollback_to(snapshot); + + continue; + } + Failure(vars, actual_type, expected_type) => { + // This is the last type we could have tried and failed; record the error. + introduce(subs, rank, pools, &vars); + + let problem = TypeError::BadExpr( + *region, + category.clone(), + actual_type, + expect_one_of.clone().replace(expected_type), + ); + + problems.push(problem); + + return state; + } + BadType(vars, problem) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + + return state; + } + } + } + unreachable!() + } } } diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 90ff591ae0..3a7ad4dd87 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -862,9 +862,9 @@ mod test_reporting { 2│> 2 if 1 -> 0x0 3│ _ -> 0x1 - Right now it’s a number of type: + Right now it’s an integer of type: - Num a + Int a But I need every `if` guard condition to evaluate to a Bool—either `True` or `False`. @@ -896,7 +896,7 @@ mod test_reporting { but the `then` branch has the type: - Num a + Int a I need all branches in an `if` to have the same type! "# @@ -927,7 +927,7 @@ mod test_reporting { But all the previous branches have type: - Num a + Int a I need all branches in an `if` to have the same type! "# @@ -993,7 +993,7 @@ mod test_reporting { However, the preceding elements in the list all have the type: - Num a + Int a I need every element in a list to have the same type! "# @@ -1424,7 +1424,7 @@ mod test_reporting { But the expression between `when` and `is` has the type: - Num a + Int a "# ), ) @@ -1455,7 +1455,7 @@ mod test_reporting { But all the previous branches match: - Num a + Int a "# ), ) @@ -1485,7 +1485,7 @@ mod test_reporting { But the expression between `when` and `is` has the type: - { foo : Num a } + { foo : Int a } "# ), ) @@ -1605,13 +1605,13 @@ mod test_reporting { 2│ {} | 1 -> 3 ^^^^^^ - The first pattern is trying to match numbers: + The first pattern is trying to match integers: - Num a + Int a But the expression between `when` and `is` has the type: - { foo : Num a } + { foo : Int a } "# ), ) @@ -1637,9 +1637,9 @@ mod test_reporting { 1│ (Foo x) = 42 ^^ - It is a number of type: + It is an integer of type: - Num a + Int a But you are trying to use it as: @@ -2162,8 +2162,8 @@ mod test_reporting { This is usually a typo. Here are the `x` fields that are most similar: - { fo : Num b - , bar : Num a + { fo : Int b + , bar : Int a } So maybe `.foo` should be `.fo`? @@ -2229,10 +2229,10 @@ mod test_reporting { This is usually a typo. Here are the `x` fields that are most similar: - { fo : Num c - , foobar : Num d - , bar : Num a - , baz : Num b + { fo : Int c + , foobar : Int d + , bar : Int a + , baz : Int b , ... } @@ -2327,7 +2327,7 @@ mod test_reporting { But `add` needs the 2nd argument to be: - Num a + Num (Integer a) "# ), ) @@ -3360,8 +3360,8 @@ mod test_reporting { This `ACons` global tag application has the type: - [ ACons Num (Integer Signed64) [ BCons (Num a) [ ACons Str [ BNil - ]b ]c ]d, ANil ] + [ ACons Int Signed64 [ BCons (Int a) [ ACons Str [ BNil ]b ]c ]d, + ANil ] But the type annotation on `x` says it should be: @@ -4973,7 +4973,7 @@ mod test_reporting { This `insert` call produces: - Dict Str (Num a) + Dict Str (Int a) But the type annotation on `myDict` says it should be: @@ -5635,7 +5635,7 @@ mod test_reporting { but the `then` branch has the type: - Num a + Int a I need all branches in an `if` to have the same type! "# @@ -6276,7 +6276,7 @@ I need all branches in an `if` to have the same type! This `map` call produces: - List [ Foo Num a ] + List [ Foo Int a ] But the type annotation on `x` says it should be: @@ -6526,11 +6526,11 @@ I need all branches in an `if` to have the same type! This argument is an anonymous function of type: - Num a -> Num a + Num (Integer a) -> Num (Integer a) But `map` needs the 2nd argument to be: - Str -> Num a + Str -> Num (Integer a) "# ), ) @@ -6632,6 +6632,21 @@ I need all branches in an `if` to have the same type! But the type annotation on `mult` says it should be: F64 + + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 2nd argument to `mult` is not what I expect: + + 4│ mult 0 0 + ^ + + This argument is an integer of type: + + Int a + + But `mult` needs the 2nd argument to be: + + F64 "# ), ) @@ -6680,6 +6695,21 @@ I need all branches in an `if` to have the same type! But the type annotation on `mult` says it should be: F64 + + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 2nd argument to `mult` is not what I expect: + + 4│ mult 0 0 + ^ + + This argument is an integer of type: + + Int a + + But `mult` needs the 2nd argument to be: + + F64 "# ), ) @@ -7031,9 +7061,9 @@ I need all branches in an `if` to have the same type! 5│ f = \c -> c 6 ^ - This argument is a number of type: + This argument is an integer of type: - Num a + Int a But `c` needs the 1st argument to be: @@ -7041,7 +7071,7 @@ I need all branches in an `if` to have the same type! Tip: The type annotation uses the type variable `a` to say that this definition can produce any type of value. But in the body I see that - it will only produce a `Num` value of a single specific type. Maybe + it will only produce a `Int` value of a single specific type. Maybe change the type annotation to be more specific? Maybe change the code to be more general? "# @@ -7848,4 +7878,24 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn list_get_negative_number() { + report_problem_as( + "List.get [1, 2, 3] -1", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 170_141_183_460_469_231_731_687_303_715_884_105_728i128 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a I128, whose maximum value + is 170_141_183_460_469_231_731_687_303_715_884_105_727. + "# + ), + ) + } } From 8dc92ccd97fe55a7dbe2d1bb1d1b6f7477df99bb Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Feb 2022 00:15:09 -0500 Subject: [PATCH 496/541] Second pass --- compiler/can/src/constraint.rs | 15 -- compiler/can/src/num.rs | 2 +- compiler/constrain/src/builtins.rs | 241 ++++++++++++----------------- compiler/constrain/src/pattern.rs | 12 +- compiler/mono/src/layout.rs | 5 +- compiler/mono/src/layout_soa.rs | 3 + compiler/solve/src/solve.rs | 118 +++++++++----- compiler/solve/tests/solve_expr.rs | 105 +++++++------ compiler/types/src/pretty_print.rs | 14 ++ compiler/types/src/solved_types.rs | 2 + compiler/types/src/subs.rs | 219 ++++++++++++++++++++++++-- compiler/types/src/types.rs | 27 ++++ compiler/unify/src/unify.rs | 96 +++++++++++- reporting/src/error/type.rs | 26 ++++ reporting/tests/test_reporting.rs | 41 ++++- 15 files changed, 651 insertions(+), 275 deletions(-) diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index c56168c827..03b758320f 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -27,14 +27,6 @@ pub enum Constraint { Let(Box), And(Vec), Present(Type, PresenceConstraint), - - /// `EqBoundedRange(Ts, U, ...)` means there must be at least one `T` in the *ordered* range `Ts` - /// that unifies (via `Eq`) with `U`. - /// - /// This is only used for integers, where we may see e.g. the number literal `-1` and know it - /// has the bounded range `[I8, I16, I32, I64, I128]`, at least one of which must unify with - /// the type the number literal is used as. - EqBoundedRange(Type, Expected>, Category, Region), } #[derive(Debug, Clone, PartialEq)] @@ -95,7 +87,6 @@ impl Constraint { } Constraint::And(cs) => cs.iter().any(|c| c.contains_save_the_environment()), Constraint::Present(_, _) => false, - Constraint::EqBoundedRange(_, _, _, _) => false, } } } @@ -180,11 +171,5 @@ fn validate_help(constraint: &Constraint, declared: &Declared, accum: &mut Varia } } } - Constraint::EqBoundedRange(typ, one_of, _, _) => { - subtract(declared, &typ.variables_detail(), accum); - for typ in one_of.get_type_ref() { - subtract(declared, &typ.variables_detail(), accum); - } - } } } diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index e93ca3eedc..c1d8511eb4 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -261,7 +261,7 @@ fn from_str_radix(src: &str, radix: u32) -> Result<(IntValue, NumericBound), Int }; let (lower_bound, is_negative) = match result { - IntValue::I128(num) => (lower_bound_of_int(num), num <= 0), + IntValue::I128(num) => (lower_bound_of_int(num), num < 0), IntValue::U128(_) => (IntWidth::U128, false), }; diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index e72010899a..7867a06cd9 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -11,38 +11,31 @@ use roc_types::types::Category; use roc_types::types::Reason; use roc_types::types::Type::{self, *}; +#[must_use] pub fn add_numeric_bound_constr( constrs: &mut Vec, num_type: Type, bound: impl TypedNumericBound, region: Region, category: Category, -) { - if let Some(typ) = bound.concrete_num_type() { - constrs.push(Eq( - num_type, - Expected::ForReason(Reason::NumericLiteralSuffix, typ, region), - category, - region, - )); - } -} - -pub fn add_numeric_range_constr( - constrs: &mut Vec, - num_type: Type, - bound: impl TypedNumericBound, - region: Region, - category: Category, -) { +) -> Type { let range = bound.bounded_range(); - if !range.is_empty() { - constrs.push(EqBoundedRange( - num_type, - Expected::ForReason(Reason::NumericLiteralSuffix, range, region), - category, - region, - )); + + let total_num_type = num_type; + + match range.len() { + 0 => total_num_type, + 1 => { + let actual_type = Variable(range[0]); + constrs.push(Eq( + total_num_type.clone(), + Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region), + category, + region, + )); + total_num_type + } + _ => RangedNumber(Box::new(total_num_type.clone()), range), } } @@ -54,13 +47,18 @@ pub fn int_literal( region: Region, bound: IntBound, ) -> Constraint { - let num_type = Variable(num_var); let reason = Reason::IntLiteral; let mut constrs = Vec::with_capacity(3); // Always add the bound first; this improves the resolved type quality in case it's an alias // like "U8". - add_numeric_bound_constr(&mut constrs, num_type.clone(), bound, region, Category::Num); + let num_type = add_numeric_bound_constr( + &mut constrs, + Variable(num_var), + bound, + region, + Category::Num, + ); constrs.extend(vec![ Eq( num_type.clone(), @@ -70,13 +68,6 @@ pub fn int_literal( ), Eq(num_type, expected.clone(), Category::Int, region), ]); - add_numeric_range_constr( - &mut constrs, - expected.get_type(), - bound, - region, - Category::Int, - ); exists(vec![num_var], And(constrs)) } @@ -89,13 +80,12 @@ pub fn float_literal( region: Region, bound: FloatBound, ) -> Constraint { - let num_type = Variable(num_var); let reason = Reason::FloatLiteral; let mut constrs = Vec::with_capacity(3); - add_numeric_bound_constr( + let num_type = add_numeric_bound_constr( &mut constrs, - num_type.clone(), + Variable(num_var), bound, region, Category::Float, @@ -120,10 +110,11 @@ pub fn num_literal( region: Region, bound: NumericBound, ) -> Constraint { - let num_type = crate::builtins::num_num(Type::Variable(num_var)); + let open_number_type = crate::builtins::num_num(Type::Variable(num_var)); let mut constrs = Vec::with_capacity(3); - add_numeric_bound_constr(&mut constrs, num_type.clone(), bound, region, Category::Num); + let num_type = + add_numeric_bound_constr(&mut constrs, open_number_type, bound, region, Category::Num); constrs.extend(vec![Eq(num_type, expected, Category::Num, region)]); exists(vec![num_var], And(constrs)) @@ -219,56 +210,56 @@ pub fn num_int(range: Type) -> Type { ) } -macro_rules! num_types { - // Represent - // num_u8 ~ U8 : Num Integer Unsigned8 = @Num (@Integer (@Unsigned8)) - // int_u8 ~ Integer Unsigned8 = @Integer (@Unsigned8) - // - // num_f32 ~ F32 : Num FloaingPoint Binary32 = @Num (@FloaingPoint (@Binary32)) - // float_f32 ~ FloatingPoint Binary32 = @FloatingPoint (@Binary32) - // and so on, for all numeric types. - ($($num_fn:ident, $sub_fn:ident, $num_type:ident, $alias:path, $inner_alias:path, $inner_private_tag:path)*) => { - $( - #[inline(always)] - fn $sub_fn() -> Type { - builtin_alias( - $inner_alias, - vec![], - Box::new(Type::TagUnion( - vec![(TagName::Private($inner_private_tag), vec![])], - Box::new(Type::EmptyTagUnion) - )), - ) - } - - #[inline(always)] - fn $num_fn() -> Type { - builtin_alias( - $alias, - vec![], - Box::new($num_type($sub_fn())) - ) - } - )* - } -} - -num_types! { - num_u8, int_u8, num_int, Symbol::NUM_U8, Symbol::NUM_UNSIGNED8, Symbol::NUM_AT_UNSIGNED8 - num_u16, int_u16, num_int, Symbol::NUM_U16, Symbol::NUM_UNSIGNED16, Symbol::NUM_AT_UNSIGNED16 - num_u32, int_u32, num_int, Symbol::NUM_U32, Symbol::NUM_UNSIGNED32, Symbol::NUM_AT_UNSIGNED32 - num_u64, int_u64, num_int, Symbol::NUM_U64, Symbol::NUM_UNSIGNED64, Symbol::NUM_AT_UNSIGNED64 - num_u128, int_u128, num_int, Symbol::NUM_U128, Symbol::NUM_UNSIGNED128, Symbol::NUM_AT_UNSIGNED128 - num_i8, int_i8, num_int, Symbol::NUM_I8, Symbol::NUM_SIGNED8, Symbol::NUM_AT_SIGNED8 - num_i16, int_i16, num_int, Symbol::NUM_I16, Symbol::NUM_SIGNED16, Symbol::NUM_AT_SIGNED16 - num_i32, int_i32, num_int, Symbol::NUM_I32, Symbol::NUM_SIGNED32, Symbol::NUM_AT_SIGNED32 - num_i64, int_i64, num_int, Symbol::NUM_I64, Symbol::NUM_SIGNED64, Symbol::NUM_AT_SIGNED64 - num_i128, int_i128, num_int, Symbol::NUM_I128, Symbol::NUM_SIGNED128, Symbol::NUM_AT_SIGNED128 - num_nat, int_nat, num_int, Symbol::NUM_NAT, Symbol::NUM_NATURAL, Symbol::NUM_AT_NATURAL - num_dec, float_dec, num_float, Symbol::NUM_DEC, Symbol::NUM_DECIMAL, Symbol::NUM_AT_DECIMAL - num_f32, float_f32, num_float, Symbol::NUM_F32, Symbol::NUM_BINARY32, Symbol::NUM_AT_BINARY32 - num_f64, float_f64, num_float, Symbol::NUM_F64, Symbol::NUM_BINARY64, Symbol::NUM_AT_BINARY64 -} +// macro_rules! num_types { +// // Represent +// // num_u8 ~ U8 : Num Integer Unsigned8 = @Num (@Integer (@Unsigned8)) +// // int_u8 ~ Integer Unsigned8 = @Integer (@Unsigned8) +// // +// // num_f32 ~ F32 : Num FloaingPoint Binary32 = @Num (@FloaingPoint (@Binary32)) +// // float_f32 ~ FloatingPoint Binary32 = @FloatingPoint (@Binary32) +// // and so on, for all numeric types. +// ($($num_fn:ident, $sub_fn:ident, $num_type:ident, $alias:path, $inner_alias:path, $inner_private_tag:path)*) => { +// $( +// #[inline(always)] +// fn $sub_fn() -> Type { +// builtin_alias( +// $inner_alias, +// vec![], +// Box::new(Type::TagUnion( +// vec![(TagName::Private($inner_private_tag), vec![])], +// Box::new(Type::EmptyTagUnion) +// )), +// ) +// } +// +// #[inline(always)] +// fn $num_fn() -> Type { +// builtin_alias( +// $alias, +// vec![], +// Box::new($num_type($sub_fn())) +// ) +// } +// )* +// } +// } +// +// num_types! { +// num_u8, int_u8, num_int, Symbol::NUM_U8, Symbol::NUM_UNSIGNED8, Symbol::NUM_AT_UNSIGNED8 +// num_u16, int_u16, num_int, Symbol::NUM_U16, Symbol::NUM_UNSIGNED16, Symbol::NUM_AT_UNSIGNED16 +// num_u32, int_u32, num_int, Symbol::NUM_U32, Symbol::NUM_UNSIGNED32, Symbol::NUM_AT_UNSIGNED32 +// num_u64, int_u64, num_int, Symbol::NUM_U64, Symbol::NUM_UNSIGNED64, Symbol::NUM_AT_UNSIGNED64 +// num_u128, int_u128, num_int, Symbol::NUM_U128, Symbol::NUM_UNSIGNED128, Symbol::NUM_AT_UNSIGNED128 +// num_i8, int_i8, num_int, Symbol::NUM_I8, Symbol::NUM_SIGNED8, Symbol::NUM_AT_SIGNED8 +// num_i16, int_i16, num_int, Symbol::NUM_I16, Symbol::NUM_SIGNED16, Symbol::NUM_AT_SIGNED16 +// num_i32, int_i32, num_int, Symbol::NUM_I32, Symbol::NUM_SIGNED32, Symbol::NUM_AT_SIGNED32 +// num_i64, int_i64, num_int, Symbol::NUM_I64, Symbol::NUM_SIGNED64, Symbol::NUM_AT_SIGNED64 +// num_i128, int_i128, num_int, Symbol::NUM_I128, Symbol::NUM_SIGNED128, Symbol::NUM_AT_SIGNED128 +// num_nat, int_nat, num_int, Symbol::NUM_NAT, Symbol::NUM_NATURAL, Symbol::NUM_AT_NATURAL +// num_dec, float_dec, num_float, Symbol::NUM_DEC, Symbol::NUM_DECIMAL, Symbol::NUM_AT_DECIMAL +// num_f32, float_f32, num_float, Symbol::NUM_F32, Symbol::NUM_BINARY32, Symbol::NUM_AT_BINARY32 +// num_f64, float_f64, num_float, Symbol::NUM_F64, Symbol::NUM_BINARY64, Symbol::NUM_AT_BINARY64 +// } #[inline(always)] pub fn num_signed64() -> Type { @@ -312,37 +303,26 @@ pub fn num_num(typ: Type) -> Type { } pub trait TypedNumericBound { - /// Get a concrete type for this number, if one exists. - /// Returns `None` e.g. if the bound is open, like `Int *`. - fn concrete_num_type(&self) -> Option; - - fn bounded_range(&self) -> Vec; + fn bounded_range(&self) -> Vec; } impl TypedNumericBound for IntBound { - fn concrete_num_type(&self) -> Option { - match self { - IntBound::None | IntBound::AtLeast { .. } => None, - IntBound::Exact(w) => Some(match w { - IntWidth::U8 => num_u8(), - IntWidth::U16 => num_u16(), - IntWidth::U32 => num_u32(), - IntWidth::U64 => num_u64(), - IntWidth::U128 => num_u128(), - IntWidth::I8 => num_i8(), - IntWidth::I16 => num_i16(), - IntWidth::I32 => num_i32(), - IntWidth::I64 => num_i64(), - IntWidth::I128 => num_i128(), - IntWidth::Nat => num_nat(), - }), - } - } - - fn bounded_range(&self) -> Vec { + fn bounded_range(&self) -> Vec { match self { IntBound::None => vec![], - IntBound::Exact(_) => vec![], + IntBound::Exact(w) => vec![match w { + IntWidth::U8 => Variable::U8, + IntWidth::U16 => Variable::U16, + IntWidth::U32 => Variable::U32, + IntWidth::U64 => Variable::U64, + IntWidth::U128 => Variable::U128, + IntWidth::I8 => Variable::I8, + IntWidth::I16 => Variable::I16, + IntWidth::I32 => Variable::I32, + IntWidth::I64 => Variable::I64, + IntWidth::I128 => Variable::I128, + IntWidth::Nat => Variable::NAT, + }], IntBound::AtLeast { sign, width } => { let whole_range: &[(IntWidth, Variable)] = match sign { SignDemand::NoDemand => { @@ -371,7 +351,7 @@ impl TypedNumericBound for IntBound { whole_range .iter() .skip_while(|(lower_bound, _)| *lower_bound != *width) - .map(|(_, var)| Type::Variable(*var)) + .map(|(_, var)| *var) .collect() } } @@ -379,35 +359,20 @@ impl TypedNumericBound for IntBound { } impl TypedNumericBound for FloatBound { - fn concrete_num_type(&self) -> Option { - match self { - FloatBound::None => None, - FloatBound::Exact(w) => Some(match w { - FloatWidth::Dec => num_dec(), - FloatWidth::F32 => num_f32(), - FloatWidth::F64 => num_f64(), - }), - } - } - - fn bounded_range(&self) -> Vec { + fn bounded_range(&self) -> Vec { match self { FloatBound::None => vec![], - FloatBound::Exact(_) => vec![], + FloatBound::Exact(w) => vec![match w { + FloatWidth::Dec => Variable::DEC, + FloatWidth::F32 => Variable::F32, + FloatWidth::F64 => Variable::F64, + }], } } } impl TypedNumericBound for NumericBound { - fn concrete_num_type(&self) -> Option { - match self { - NumericBound::None => None, - NumericBound::Int(ib) => ib.concrete_num_type(), - NumericBound::Float(fb) => fb.concrete_num_type(), - } - } - - fn bounded_range(&self) -> Vec { + fn bounded_range(&self) -> Vec { match self { NumericBound::None => vec![], NumericBound::Int(ib) => ib.bounded_range(), diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 242ff11335..bdd11dfe27 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -183,7 +183,7 @@ pub fn constrain_pattern( let num_type = builtins::num_num(Type::Variable(var)); - builtins::add_numeric_bound_constr( + let num_type = builtins::add_numeric_bound_constr( &mut state.constraints, num_type.clone(), bound, @@ -202,7 +202,7 @@ pub fn constrain_pattern( &IntLiteral(num_var, precision_var, _, _, bound) => { // First constraint on the free num var; this improves the resolved type quality in // case the bound is an alias. - builtins::add_numeric_bound_constr( + let num_type = builtins::add_numeric_bound_constr( &mut state.constraints, Type::Variable(num_var), bound, @@ -214,7 +214,7 @@ pub fn constrain_pattern( let int_type = builtins::num_int(Type::Variable(precision_var)); state.constraints.push(Constraint::Eq( - Type::Variable(num_var), + num_type, // TODO check me if something breaks! Expected::NoExpectation(int_type), Category::Int, region, @@ -232,7 +232,7 @@ pub fn constrain_pattern( &FloatLiteral(num_var, precision_var, _, _, bound) => { // First constraint on the free num var; this improves the resolved type quality in // case the bound is an alias. - builtins::add_numeric_bound_constr( + let num_type = builtins::add_numeric_bound_constr( &mut state.constraints, Type::Variable(num_var), bound, @@ -244,7 +244,7 @@ pub fn constrain_pattern( let float_type = builtins::num_float(Type::Variable(precision_var)); state.constraints.push(Constraint::Eq( - Type::Variable(num_var), + num_type.clone(), // TODO check me if something breaks! Expected::NoExpectation(float_type), Category::Float, region, @@ -254,7 +254,7 @@ pub fn constrain_pattern( state.constraints.push(Constraint::Pattern( region, PatternCategory::Float, - Type::Variable(num_var), + num_type, // TODO check me if something breaks! expected, )); } diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 265ac14d68..edcd676c00 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -66,6 +66,7 @@ impl<'a> RawFunctionLayout<'a> { Self::new_help(env, structure, structure_content.clone()) } Structure(flat_type) => Self::layout_from_flat_type(env, flat_type), + RangedNumber(typ, _) => Self::from_var(env, typ), // Ints Alias(Symbol::NUM_I128, args, _) => { @@ -902,6 +903,8 @@ impl<'a> Layout<'a> { } } + RangedNumber(typ, _) => Self::from_var(env, typ), + Error => Err(LayoutProblem::Erroneous), } } @@ -2562,7 +2565,7 @@ fn layout_from_num_content<'a>( Alias(_, _, _) => { todo!("TODO recursively resolve type aliases in num_from_content"); } - Structure(_) => { + Structure(_) | RangedNumber(..) => { panic!("Invalid Num.Num type application: {:?}", content); } Error => Err(LayoutProblem::Erroneous), diff --git a/compiler/mono/src/layout_soa.rs b/compiler/mono/src/layout_soa.rs index fe37cc385d..295ee07cf1 100644 --- a/compiler/mono/src/layout_soa.rs +++ b/compiler/mono/src/layout_soa.rs @@ -142,6 +142,7 @@ impl FunctionLayout { Content::RecursionVar { .. } => Err(TypeError(())), Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), Content::Alias(_, _, actual) => Self::from_var_help(layouts, subs, *actual), + Content::RangedNumber(actual, _) => Self::from_var_help(layouts, subs, *actual), Content::Error => Err(TypeError(())), } } @@ -249,6 +250,7 @@ impl LambdaSet { } Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), Content::Alias(_, _, actual) => Self::from_var_help(layouts, subs, *actual), + Content::RangedNumber(actual, _) => Self::from_var_help(layouts, subs, *actual), Content::Error => Err(TypeError(())), } } @@ -682,6 +684,7 @@ impl Layout { } } } + Content::RangedNumber(typ, _) => Self::from_var_help(layouts, subs, *typ), Content::Error => Err(TypeError(())), } } diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index c1e9bc7e19..c831b776c1 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -72,6 +72,7 @@ pub enum TypeError { CircularType(Region, Symbol, ErrorType), BadType(roc_types::types::Problem), UnexposedLookup(Symbol), + NotInRange(Region, ErrorType, Expected>), } #[derive(Clone, Debug, Default)] @@ -231,6 +232,17 @@ fn solve( problems.push(TypeError::BadType(problem)); + state + } + NotInRange(vars, typ, range) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::NotInRange( + *region, + typ, + expectation.clone().replace(range), + )); + state } } @@ -254,7 +266,7 @@ fn solve( state } - BadType(vars, _problem) => { + BadType(vars, _) | NotInRange(vars, _, _) => { introduce(subs, rank, pools, &vars); // ERROR NOT REPORTED @@ -321,6 +333,17 @@ fn solve( problems.push(TypeError::BadType(problem)); + state + } + NotInRange(vars, typ, range) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::NotInRange( + *region, + typ, + expectation.clone().replace(range), + )); + state } } @@ -391,6 +414,18 @@ fn solve( problems.push(TypeError::BadType(problem)); + state + } + NotInRange(vars, typ, range) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::NotInRange( + *region, + typ, + // TODO expectation.clone().replace(range), + Expected::NoExpectation(range), + )); + state } } @@ -687,52 +722,18 @@ fn solve( state } - } - } - EqBoundedRange(typ, expect_one_of, category, region) => { - let actual = type_to_var(subs, rank, pools, cached_aliases, typ); + NotInRange(vars, typ, range) => { + introduce(subs, rank, pools, &vars); - let mut it = expect_one_of.get_type_ref().iter().peekable(); + problems.push(TypeError::NotInRange( + Region::zero(), + typ, + Expected::NoExpectation(range), + )); - while let Some(expected) = it.next() { - let expected = type_to_var(subs, rank, pools, cached_aliases, expected); - let snapshot = subs.snapshot(); - match unify(subs, actual, expected, Mode::Eq) { - Success(vars) => { - introduce(subs, rank, pools, &vars); - - return state; - } - Failure(..) if it.peek().is_some() => { - subs.rollback_to(snapshot); - - continue; - } - Failure(vars, actual_type, expected_type) => { - // This is the last type we could have tried and failed; record the error. - introduce(subs, rank, pools, &vars); - - let problem = TypeError::BadExpr( - *region, - category.clone(), - actual_type, - expect_one_of.clone().replace(expected_type), - ); - - problems.push(problem); - - return state; - } - BadType(vars, problem) => { - introduce(subs, rank, pools, &vars); - - problems.push(TypeError::BadType(problem)); - - return state; - } + state } } - unreachable!() } } } @@ -816,6 +817,13 @@ fn type_to_variable<'a>( match typ { Variable(var) => *var, + RangedNumber(typ, vars) => { + let ty_var = type_to_variable(subs, rank, pools, arena, typ); + let vars = VariableSubsSlice::insert_into_subs(subs, vars.iter().copied()); + let content = Content::RangedNumber(ty_var, vars); + + register(subs, rank, pools, content) + } Apply(symbol, arguments, _) => { let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { @@ -1636,6 +1644,8 @@ fn adjust_rank_content( rank } + + RangedNumber(typ, _) => adjust_rank(subs, young_mark, visit_mark, group_rank, *typ), } } @@ -1771,6 +1781,11 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) { stack.push(var); } + &RangedNumber(typ, vars) => { + stack.push(typ); + + stack.extend(var_slice!(vars)); + } } } @@ -2027,6 +2042,23 @@ fn deep_copy_var_help( copy } + + RangedNumber(typ, range_vars) => { + let new_type_var = deep_copy_var_help(subs, max_rank, pools, visited, typ); + + let new_vars = SubsSlice::reserve_into_subs(subs, range_vars.len()); + for (target_index, var_index) in (new_vars.indices()).zip(range_vars) { + let var = subs[var_index]; + let copy_var = deep_copy_var_help(subs, max_rank, pools, visited, var); + subs.variables[target_index] = copy_var; + } + + let new_content = RangedNumber(new_type_var, new_vars); + + subs.set(copy, make_descriptor(new_content)); + + copy + } } } diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index d8c5a38d6e..fee93d0253 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -164,7 +164,7 @@ mod solve_expr { #[test] fn int_literal() { - infer_eq("5", "Num *"); + infer_eq("5", "Int *"); } #[test] @@ -334,7 +334,7 @@ mod solve_expr { [42] "# ), - "List (Num *)", + "List (Int *)", ); } @@ -346,7 +346,7 @@ mod solve_expr { [[[ 5 ]]] "# ), - "List (List (List (Num *)))", + "List (List (List (Int *)))", ); } @@ -358,7 +358,7 @@ mod solve_expr { [ 1, 2, 3 ] "# ), - "List (Num *)", + "List (Int *)", ); } @@ -370,7 +370,7 @@ mod solve_expr { [ [ 1 ], [ 2, 3 ] ] "# ), - "List (List (Num *))", + "List (List (Int *))", ); } @@ -518,7 +518,7 @@ mod solve_expr { \_, _ -> 42 "# ), - "*, * -> Num *", + "*, * -> Int *", ); } @@ -689,7 +689,7 @@ mod solve_expr { func "# ), - "*, * -> Num *", + "*, * -> Int *", ); } @@ -753,7 +753,7 @@ mod solve_expr { c "# ), - "Num *", + "Int *", ); } @@ -788,7 +788,7 @@ mod solve_expr { alwaysFive "stuff" "# ), - "Num *", + "Int *", ); } @@ -835,7 +835,7 @@ mod solve_expr { x "# ), - "Num *", + "Int *", ); } @@ -849,7 +849,7 @@ mod solve_expr { enlist 5 "# ), - "List (Num *)", + "List (Int *)", ); } @@ -876,7 +876,7 @@ mod solve_expr { 1 |> (\a -> a) "# ), - "Num *", + "Int *", ); } @@ -890,7 +890,7 @@ mod solve_expr { 1 |> always2 "foo" "# ), - "Num *", + "Int *", ); } @@ -955,7 +955,7 @@ mod solve_expr { apply identity 5 "# ), - "Num *", + "Int *", ); } @@ -984,7 +984,7 @@ mod solve_expr { // flip neverendingInt // "# // ), - // "(Num *, (a -> a)) -> Num *", + // "(Int *, (a -> a)) -> Int *", // ); // } @@ -1058,7 +1058,7 @@ mod solve_expr { // 1 // 2 // "# // ), - // "Num *", + // "Int *", // ); // } @@ -1070,7 +1070,7 @@ mod solve_expr { // 1 + 2 // "# // ), - // "Num *", + // "Int *", // ); // } @@ -1119,7 +1119,7 @@ mod solve_expr { [ alwaysFive "foo", alwaysFive [] ] "# ), - "List (Num *)", + "List (Int *)", ); } @@ -1134,7 +1134,7 @@ mod solve_expr { 24 "# ), - "Num *", + "Int *", ); } @@ -1148,7 +1148,7 @@ mod solve_expr { 3 -> 4 "# ), - "Num *", + "Int *", ); } @@ -1161,17 +1161,17 @@ mod solve_expr { #[test] fn one_field_record() { - infer_eq("{ x: 5 }", "{ x : Num * }"); + infer_eq("{ x: 5 }", "{ x : Int * }"); } #[test] fn two_field_record() { - infer_eq("{ x: 5, y : 3.14 }", "{ x : Num *, y : Float * }"); + infer_eq("{ x: 5, y : 3.14 }", "{ x : Int *, y : Float * }"); } #[test] fn record_literal_accessor() { - infer_eq("{ x: 5, y : 3.14 }.x", "Num *"); + infer_eq("{ x: 5, y : 3.14 }.x", "Int *"); } #[test] @@ -1230,7 +1230,7 @@ mod solve_expr { infer_eq( indoc!( r#" - foo : Num * -> custom + foo : Int * -> custom foo 2 "# @@ -1327,7 +1327,7 @@ mod solve_expr { \Foo -> 42 "# ), - "[ Foo ] -> Num *", + "[ Foo ] -> Int *", ); } @@ -1339,7 +1339,7 @@ mod solve_expr { \@Foo -> 42 "# ), - "[ @Foo ] -> Num *", + "[ @Foo ] -> Int *", ); } @@ -1354,7 +1354,7 @@ mod solve_expr { False -> 0 "# ), - "[ False, True ] -> Num *", + "[ False, True ] -> Int *", ); } @@ -1366,7 +1366,7 @@ mod solve_expr { Foo "happy" 2020 "# ), - "[ Foo Str (Num *) ]*", + "[ Foo Str (Int *) ]*", ); } @@ -1378,7 +1378,7 @@ mod solve_expr { @Foo "happy" 2020 "# ), - "[ @Foo Str (Num *) ]*", + "[ @Foo Str (Int *) ]*", ); } @@ -1407,7 +1407,7 @@ mod solve_expr { { x: 4 } -> 4 "# ), - "Num *", + "Int *", ); } @@ -2347,7 +2347,7 @@ mod solve_expr { { numIdentity, x : numIdentity 42, y } "# ), - "{ numIdentity : Num a -> Num a, x : Num a, y : F64 }", + "{ numIdentity : Num a -> Num a, x : Int *, y : F64 }", ); } @@ -2383,7 +2383,7 @@ mod solve_expr { f "# ), - "Num * -> Num *", + "Int * -> Int *", ); } @@ -2416,7 +2416,7 @@ mod solve_expr { toBit "# ), - "[ False, True ] -> Num *", + "[ False, True ] -> Int *", ); } @@ -2453,7 +2453,7 @@ mod solve_expr { fromBit "# ), - "Num * -> [ False, True ]*", + "Int * -> [ False, True ]*", ); } @@ -2505,7 +2505,7 @@ mod solve_expr { foo { x: 5 } "# ), - "Num *", + "Int *", ); } @@ -2774,7 +2774,7 @@ mod solve_expr { // infer_eq_without_problem( // indoc!( // r#" - // s : Num * + // s : Int * // s = 3.1 // s @@ -3214,7 +3214,7 @@ mod solve_expr { List.get [ 10, 9, 8, 7 ] 1 "# ), - "Result (Num *) [ OutOfBounds ]*", + "Result (Int *) [ OutOfBounds ]*", ); infer_eq_without_problem( @@ -3497,7 +3497,7 @@ mod solve_expr { f "# ), - "{ p : *, q : * }* -> Num *", + "{ p : *, q : * }* -> Int *", ); } @@ -3552,7 +3552,7 @@ mod solve_expr { _ -> 3 "# ), - "Num * -> Num *", + "Int * -> Int *", ); } @@ -3724,7 +3724,8 @@ mod solve_expr { negatePoint { x: 1, y: 2.1, z: 0x3 } "# ), - "{ x : Num a, y : F64, z : Int * }", + // TODO this should be "Int a", FIXME + "{ x : Int *, y : F64, z : Int * }", ); } @@ -3741,7 +3742,8 @@ mod solve_expr { { a, b } "# ), - "{ a : { x : Num a, y : F64, z : c }, b : { blah : Str, x : Num a, y : F64, z : c } }", + // TODO this should be "Int a", FIXME + "{ a : { x : Int *, y : F64, z : c }, b : { blah : Str, x : Int *, y : F64, z : c } }", ); } @@ -3753,7 +3755,7 @@ mod solve_expr { \{ x, y ? 0 } -> x + y "# ), - "{ x : Num a, y ? Num a }* -> Num a", + "{ x : Int a, y ? Int a }* -> Int a", ); } @@ -3767,7 +3769,7 @@ mod solve_expr { x + y "# ), - "Num *", + "Int *", ); } @@ -3781,7 +3783,7 @@ mod solve_expr { { x, y ? 0 } -> x + y "# ), - "{ x : Num a, y ? Num a }* -> Num a", + "{ x : Int a, y ? Int a }* -> Int a", ); } @@ -3946,7 +3948,7 @@ mod solve_expr { g "# ), - "Num a -> Num a", + "Int a -> Int a", ); } @@ -3985,10 +3987,10 @@ mod solve_expr { Foo Bar 1 "# ), - "[ Foo [ Bar ]* (Num *) ]*", + "[ Foo [ Bar ]* (Int *) ]*", ); - infer_eq_without_problem("Foo Bar 1", "[ Foo [ Bar ]* (Num *) ]*"); + infer_eq_without_problem("Foo Bar 1", "[ Foo [ Bar ]* (Int *) ]*"); } #[test] @@ -4680,7 +4682,7 @@ mod solve_expr { x "# ), - "Num *", + "Int *", ); } @@ -4981,7 +4983,7 @@ mod solve_expr { None -> 0 "# ), - "[ None, Some { tag : [ A, B ] }* ] -> Num *", + "[ None, Some { tag : [ A, B ] }* ] -> Int *", ) } @@ -5014,7 +5016,7 @@ mod solve_expr { { x: Red, y ? 5 } -> y "# ), - "{ x : [ Blue, Red ], y ? Num a }* -> Num a", + "{ x : [ Blue, Red ], y ? Int a }* -> Int a", ) } @@ -5027,7 +5029,8 @@ mod solve_expr { \UserId id -> id + 1 "# ), - "[ UserId (Num a) ] -> Num a", + // TODO needs parantheses + "[ UserId Int a ] -> Int a", ) } diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index ead9c32afd..a84eb8b43b 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -206,6 +206,13 @@ fn find_names_needed( // TODO should we also look in the actual variable? // find_names_needed(_actual, subs, roots, root_appearances, names_taken); } + &RangedNumber(typ, vars) => { + find_names_needed(typ, subs, roots, root_appearances, names_taken); + for var_index in vars { + let var = subs[var_index]; + find_names_needed(var, subs, roots, root_appearances, names_taken); + } + } Error | Structure(Erroneous(_)) | Structure(EmptyRecord) | Structure(EmptyTagUnion) => { // Errors and empty records don't need names. } @@ -397,6 +404,13 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa }), } } + RangedNumber(typ, _range_vars) => write_content( + env, + subs.get_content_without_compacting(*typ), + subs, + buf, + parens, + ), Error => buf.push_str(""), } } diff --git a/compiler/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index 2174ace2f0..453e5207c1 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -231,6 +231,7 @@ impl SolvedType { } } Variable(var) => Self::from_var(solved_subs.inner(), *var), + RangedNumber(typ, _) => Self::from_type(solved_subs, &typ), } } @@ -284,6 +285,7 @@ impl SolvedType { SolvedType::Alias(*symbol, new_args, solved_lambda_sets, Box::new(aliased_to)) } + RangedNumber(typ, _range_vars) => Self::from_var_help(subs, recursion_vars, *typ), Error => SolvedType::Error, } } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 26e84320cd..e24503e899 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -371,6 +371,10 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt: write!(f, "Alias({:?}, {:?}, {:?})", name, slice, actual) } + Content::RangedNumber(typ, range) => { + let slice = subs.get_subs_slice(*range); + write!(f, "RangedNumber({:?}, {:?})", typ, slice) + } Content::Error => write!(f, "Error"), } } @@ -588,11 +592,6 @@ define_const_var! { AT_NATURAL, - AT_BINARY32, - AT_BINARY64, - - AT_DECIMAL, - // Signed8 : [ @Signed8 ] :pub SIGNED8, :pub SIGNED16, @@ -608,11 +607,6 @@ define_const_var! { :pub NATURAL, - :pub BINARY32, - :pub BINARY64, - - :pub DECIMAL, - // [ @Integer Signed8 ] AT_INTEGER_SIGNED8, AT_INTEGER_SIGNED16, @@ -688,6 +682,36 @@ define_const_var! { :pub NAT, + // [ @Binary32 ] + AT_BINARY32, + AT_BINARY64, + AT_DECIMAL, + + // Binary32 : [ @Binary32 ] + BINARY32, + BINARY64, + DECIMAL, + + // [ @Float Binary32 ] + AT_FLOAT_BINARY32, + AT_FLOAT_BINARY64, + AT_FLOAT_DECIMAL, + + // Float Binary32 : [ @Float Binary32 ] + FLOAT_BINARY32, + FLOAT_BINARY64, + FLOAT_DECIMAL, + + // [ @Num (Float Binary32) ] + AT_NUM_FLOAT_BINARY32, + AT_NUM_FLOAT_BINARY64, + AT_NUM_FLOAT_DECIMAL, + + // Num (Float Binary32) + NUM_FLOAT_BINARY32, + NUM_FLOAT_BINARY64, + NUM_FLOAT_DECIMAL, + :pub F32, :pub F64, @@ -1034,6 +1058,118 @@ fn define_integer_types(subs: &mut Subs) { ); } +fn float_type( + subs: &mut Subs, + + num_at_binary64: Symbol, + num_binary64: Symbol, + num_f64: Symbol, + + at_binary64: Variable, + binary64: Variable, + + at_float_binary64: Variable, + float_binary64: Variable, + + at_num_float_binary64: Variable, + num_float_binary64: Variable, + + var_f64: Variable, +) { + // define the type Binary64 (which is an alias for [ @Binary64 ]) + { + let tags = UnionTags::insert_into_subs(subs, [(TagName::Private(num_at_binary64), [])]); + + subs.set_content(at_binary64, { + Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) + }); + + subs.set_content(binary64, { + Content::Alias(num_binary64, AliasVariables::default(), at_binary64) + }); + } + + // define the type `Num.Float Num.Binary64` + { + let tags = UnionTags::insert_into_subs( + subs, + [(TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), [binary64])], + ); + subs.set_content(at_float_binary64, { + Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) + }); + + let vars = AliasVariables::insert_into_subs(subs, [binary64], []); + subs.set_content(float_binary64, { + Content::Alias(Symbol::NUM_FLOATINGPOINT, vars, at_binary64) + }); + } + + // define the type `F64: Num.Num (Num.Float Num.Binary64)` + { + let tags = UnionTags::insert_into_subs( + subs, + [(TagName::Private(Symbol::NUM_AT_NUM), [float_binary64])], + ); + subs.set_content(at_num_float_binary64, { + Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) + }); + + let vars = AliasVariables::insert_into_subs(subs, [float_binary64], []); + subs.set_content(num_float_binary64, { + Content::Alias(Symbol::NUM_NUM, vars, at_num_float_binary64) + }); + + subs.set_content(var_f64, { + Content::Alias(num_f64, AliasVariables::default(), num_float_binary64) + }); + } +} + +fn define_float_types(subs: &mut Subs) { + float_type( + subs, + Symbol::NUM_AT_BINARY32, + Symbol::NUM_BINARY32, + Symbol::NUM_F32, + Variable::AT_BINARY32, + Variable::BINARY32, + Variable::AT_FLOAT_BINARY32, + Variable::FLOAT_BINARY32, + Variable::AT_NUM_FLOAT_BINARY32, + Variable::NUM_FLOAT_BINARY32, + Variable::F32, + ); + + float_type( + subs, + Symbol::NUM_AT_BINARY64, + Symbol::NUM_BINARY64, + Symbol::NUM_F64, + Variable::AT_BINARY64, + Variable::BINARY64, + Variable::AT_FLOAT_BINARY64, + Variable::FLOAT_BINARY64, + Variable::AT_NUM_FLOAT_BINARY64, + Variable::NUM_FLOAT_BINARY64, + Variable::F64, + ); + + float_type( + subs, + Symbol::NUM_AT_DECIMAL, + Symbol::NUM_DECIMAL, + Symbol::NUM_DEC, + Variable::AT_DECIMAL, + Variable::DECIMAL, + Variable::AT_FLOAT_DECIMAL, + Variable::FLOAT_DECIMAL, + Variable::AT_NUM_FLOAT_DECIMAL, + Variable::NUM_FLOAT_DECIMAL, + Variable::DEC, + ); +} + impl Subs { pub const RESULT_TAG_NAMES: SubsSlice = SubsSlice::new(0, 2); @@ -1072,6 +1208,7 @@ impl Subs { } define_integer_types(&mut subs); + define_float_types(&mut subs); subs.set_content( Variable::EMPTY_RECORD, @@ -1492,6 +1629,7 @@ pub enum Content { }, Structure(FlatType), Alias(Symbol, AliasVariables, Variable), + RangedNumber(Variable, VariableSubsSlice), Error, } @@ -2244,6 +2382,15 @@ fn occurs( short_circuit_help(subs, root_var, &new_seen, var)?; } + Ok(()) + } + RangedNumber(typ, _range_vars) => { + let mut new_seen = seen.clone(); + new_seen.insert(root_var); + + short_circuit_help(subs, root_var, &new_seen, *typ)?; + // _range_vars excluded because they are not explicitly part of the type. + Ok(()) } } @@ -2433,6 +2580,19 @@ fn explicit_substitute( subs.set_content(in_var, Alias(symbol, args, new_actual)); + in_var + } + RangedNumber(typ, vars) => { + for index in vars.into_iter() { + let var = subs[index]; + let new_var = explicit_substitute(subs, from, to, var, seen); + subs[index] = new_var; + } + + let new_typ = explicit_substitute(subs, from, to, typ, seen); + + subs.set_content(in_var, RangedNumber(new_typ, vars)); + in_var } } @@ -2484,6 +2644,13 @@ fn get_var_names( get_var_names(subs, subs[arg_var], answer) }), + RangedNumber(typ, vars) => { + let taken_names = get_var_names(subs, typ, taken_names); + vars.into_iter().fold(taken_names, |answer, var| { + get_var_names(subs, subs[var], answer) + }) + } + Structure(flat_type) => match flat_type { FlatType::Apply(_, args) => { args.into_iter().fold(taken_names, |answer, arg_var| { @@ -2696,6 +2863,8 @@ fn content_to_err_type( ErrorType::Alias(symbol, err_args, Box::new(err_type)) } + RangedNumber(typ, _) => var_to_err_type(subs, state, typ), + Error => ErrorType::Error, } } @@ -2974,6 +3143,11 @@ fn restore_help(subs: &mut Subs, initial: Variable) { stack.push(*var); } + + RangedNumber(typ, vars) => { + stack.push(*typ); + stack.extend(var_slice(*vars)); + } } } } @@ -3133,6 +3307,10 @@ impl StorageSubs { Self::offset_alias_variables(offsets, *alias_variables), Self::offset_variable(offsets, *actual), ), + RangedNumber(typ, vars) => RangedNumber( + Self::offset_variable(offsets, *typ), + Self::offset_variable_slice(offsets, *vars), + ), Error => Content::Error, } } @@ -3525,6 +3703,23 @@ fn deep_copy_var_to_help<'a>( copy } + + RangedNumber(typ, vars) => { + let new_typ = deep_copy_var_to_help(arena, visited, source, target, max_rank, typ); + + let new_vars = SubsSlice::reserve_into_subs(target, vars.len()); + + for (target_index, var_index) in (new_vars.indices()).zip(vars) { + let var = source[var_index]; + let copy_var = deep_copy_var_to_help(arena, visited, source, target, max_rank, var); + target.variables[target_index] = copy_var; + } + + let new_content = RangedNumber(new_typ, new_vars); + + target.set(copy, make_descriptor(new_content)); + copy + } } } @@ -3598,6 +3793,10 @@ where push_var_slice!(arguments.variables()); stack.push(*real_type_var); } + RangedNumber(typ, vars) => { + stack.push(*typ); + push_var_slice!(*vars); + } Error => {} } } diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index f8e1296bdf..fdf00f17ab 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -196,6 +196,7 @@ pub enum Type { /// Applying a type to some arguments (e.g. Dict.Dict String Int) Apply(Symbol, Vec, Region), Variable(Variable), + RangedNumber(Box, Vec), /// A type error, which will code gen to a runtime error Erroneous(Problem), } @@ -439,6 +440,9 @@ impl fmt::Debug for Type { write!(f, " as <{:?}>", rec) } + Type::RangedNumber(typ, range_vars) => { + write!(f, "Ranged({:?}, {:?})", typ, range_vars) + } } } } @@ -549,6 +553,9 @@ impl Type { arg.substitute(substitutions); } } + RangedNumber(typ, _) => { + typ.substitute(substitutions); + } EmptyRec | EmptyTagUnion | Erroneous(_) => {} } @@ -616,6 +623,7 @@ impl Type { } Ok(()) } + RangedNumber(typ, _) => typ.substitute_alias(rep_symbol, rep_args, actual), EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => Ok(()), } } @@ -653,6 +661,7 @@ impl Type { } Apply(symbol, _, _) if *symbol == rep_symbol => true, Apply(_, args, _) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)), + RangedNumber(typ, _) => typ.contains_symbol(rep_symbol), EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => false, } } @@ -689,6 +698,9 @@ impl Type { } => actual_type.contains_variable(rep_variable), HostExposedAlias { actual, .. } => actual.contains_variable(rep_variable), Apply(_, args, _) => args.iter().any(|arg| arg.contains_variable(rep_variable)), + RangedNumber(typ, vars) => { + typ.contains_variable(rep_variable) || vars.iter().any(|&v| v == rep_variable) + } EmptyRec | EmptyTagUnion | Erroneous(_) => false, } } @@ -845,6 +857,9 @@ impl Type { } } } + RangedNumber(typ, _) => { + typ.instantiate_aliases(region, aliases, var_store, introduced); + } EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {} } } @@ -901,6 +916,9 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet) { Erroneous(Problem::CyclicAlias(alias, _, _)) => { accum.insert(*alias); } + RangedNumber(typ, _) => { + symbols_help(typ, accum); + } EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {} } } @@ -979,6 +997,10 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { } variables_help(actual, accum); } + RangedNumber(typ, vars) => { + variables_help(typ, accum); + accum.extend(vars.iter().copied()); + } Apply(_, args, _) => { for x in args { variables_help(x, accum); @@ -1083,6 +1105,10 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { } variables_help_detailed(actual, accum); } + RangedNumber(typ, vars) => { + variables_help_detailed(typ, accum); + accum.type_variables.extend(vars); + } Apply(_, args, _) => { for x in args { variables_help_detailed(x, accum); @@ -1288,6 +1314,7 @@ pub enum Mismatch { InconsistentIfElse, InconsistentWhenBranches, CanonicalizationProblem, + TypeNotInRange(Variable, Vec), } #[derive(PartialEq, Eq, Clone, Hash)] diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 34693d704d..fc6c313336 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -83,6 +83,7 @@ pub enum Unified { Success(Pool), Failure(Pool, ErrorType, ErrorType), BadType(Pool, roc_types::types::Problem), + NotInRange(Pool, ErrorType, Vec), } type Outcome = Vec; @@ -94,6 +95,13 @@ pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable, mode: Mode) -> Uni if mismatches.is_empty() { Unified::Success(vars) + } else if let Some((typ, range)) = mismatches.iter().find_map(|mis| match mis { + Mismatch::TypeNotInRange(typ, range) => Some((typ, range)), + _ => None, + }) { + let (target_type, _) = subs.var_to_error_type(*typ); + let range_types = range.iter().map(|&v| subs.var_to_error_type(v).0).collect(); + Unified::NotInRange(vars, target_type, range_types) } else { let (type1, mut problems) = subs.var_to_error_type(var1); let (type2, problems2) = subs.var_to_error_type(var2); @@ -175,6 +183,7 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content) } Alias(symbol, args, real_var) => unify_alias(subs, pool, &ctx, *symbol, *args, *real_var), + &RangedNumber(typ, range_vars) => unify_ranged_number(subs, pool, &ctx, typ, range_vars), Error => { // Error propagates. Whatever we're comparing it to doesn't matter! merge(subs, &ctx, Error) @@ -182,6 +191,73 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { } } +#[inline(always)] +fn unify_ranged_number( + subs: &mut Subs, + pool: &mut Pool, + ctx: &Context, + real_var: Variable, + range_vars: VariableSubsSlice, +) -> Outcome { + let other_content = &ctx.second_desc.content; + + let outcome = match other_content { + FlexVar(_) => { + // Ranged number wins + merge(subs, ctx, RangedNumber(real_var, range_vars)) + } + RecursionVar { .. } | RigidVar(..) | Alias(..) | Structure(..) => { + unify_pool(subs, pool, real_var, ctx.second, ctx.mode) + } + &RangedNumber(other_real_var, _other_range_vars) => { + unify_pool(subs, pool, real_var, other_real_var, ctx.mode) + // TODO: check and intersect "other_range_vars" + } + Error => merge(subs, ctx, Error), + }; + + if !outcome.is_empty() { + return outcome; + } + + check_valid_range(subs, pool, ctx.second, range_vars, ctx.mode) +} + +fn check_valid_range( + subs: &mut Subs, + pool: &mut Pool, + var: Variable, + range: VariableSubsSlice, + mode: Mode, +) -> Outcome { + let slice = subs + .get_subs_slice(range) + .iter() + .copied() + .collect::>(); + + let mut it = slice.iter().peekable(); + while let Some(&possible_var) = it.next() { + let snapshot = subs.snapshot(); + let old_pool = pool.clone(); + let outcome = unify_pool(subs, pool, var, possible_var, mode); + if outcome.is_empty() { + // Okay, we matched some type in the range. + subs.rollback_to(snapshot); + *pool = old_pool; + return vec![]; + } else if it.peek().is_some() { + // We failed to match something in the range, but there are still things we can try. + subs.rollback_to(snapshot); + *pool = old_pool; + } else { + subs.commit_snapshot(snapshot); + } + } + + return vec![Mismatch::TypeNotInRange(var, slice)]; +} + #[inline(always)] fn unify_alias( subs: &mut Subs, @@ -231,6 +307,9 @@ fn unify_alias( } } Structure(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), + RangedNumber(other_real_var, _) => { + unify_pool(subs, pool, real_var, *other_real_var, ctx.mode) + } Error => merge(subs, ctx, Error), } } @@ -302,6 +381,7 @@ fn unify_structure( // can't quite figure out why, but it doesn't seem to impact other types. unify_pool(subs, pool, ctx.first, *real_var, Mode::Eq) } + RangedNumber(real_var, _) => unify_pool(subs, pool, ctx.first, *real_var, ctx.mode), Error => merge(subs, ctx, Error), } } @@ -829,6 +909,7 @@ fn unify_tag_union_new( } } +#[derive(Debug)] enum OtherTags2 { Empty, Union( @@ -1222,7 +1303,7 @@ fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content // If the other is flex, rigid wins! merge(subs, ctx, RigidVar(name.clone())) } - RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _) => { + RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _) | RangedNumber(..) => { // Type mismatch! Rigid can only unify with flex, even if the // rigid names are the same. mismatch!("Rigid {:?} with {:?}", ctx.first, &other) @@ -1247,7 +1328,12 @@ fn unify_flex( merge(subs, ctx, FlexVar(opt_name.clone())) } - FlexVar(Some(_)) | RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _) => { + FlexVar(Some(_)) + | RigidVar(_) + | RecursionVar { .. } + | Structure(_) + | Alias(_, _, _) + | RangedNumber(..) => { // TODO special-case boolean here // In all other cases, if left is flex, defer to right. // (This includes using right's name if both are flex and named.) @@ -1306,6 +1392,12 @@ fn unify_recursion( unify_pool(subs, pool, ctx.first, *actual, ctx.mode) } + RangedNumber(..) => mismatch!( + "RecursionVar {:?} with ranged number {:?}", + ctx.first, + &other + ), + Error => merge(subs, ctx, Error), } } diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index 1b147ebb84..68dfa687e0 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -14,6 +14,7 @@ use ven_pretty::DocAllocator; const DUPLICATE_NAME: &str = "DUPLICATE NAME"; const ADD_ANNOTATIONS: &str = r#"Can more type annotations be added? Type annotations always help me give more specific messages, and I think they could help a lot in this case"#; +const TYPE_NOT_IN_RANGE: &str = r#"TYPE NOT IN RANGE"#; pub fn type_problem<'b>( alloc: &'b RocDocAllocator<'b>, @@ -59,6 +60,31 @@ pub fn type_problem<'b>( report(title, doc, filename) } + NotInRange(region, found, expected_range) => { + let mut range_choices = vec![alloc.reflow("It can only be used as a ")]; + let range = expected_range.get_type(); + let last = range.len() - 1; + for (i, choice) in range.into_iter().enumerate() { + if i == last && i == 1 { + range_choices.push(alloc.text(" or ")); + } else if i == last && i > 1 { + range_choices.push(alloc.text(", or ")); + } else if i > 1 { + range_choices.push(alloc.text(", ")); + } + + range_choices.push(to_doc(alloc, Parens::Unnecessary, choice)); + } + let doc = alloc.stack(vec![ + alloc.reflow("This expression is used in an unexpected way:"), + alloc.region(lines.convert_region(region)), + alloc.concat(range_choices), + alloc.text("But it is being used as:"), + to_doc(alloc, Parens::Unnecessary, found), + ]); + + report(TYPE_NOT_IN_RANGE.into(), doc, filename) + } BadType(type_problem) => { use roc_types::types::Problem::*; match type_problem { diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 3a7ad4dd87..95f31c5cad 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -1221,6 +1221,7 @@ mod test_reporting { x "# ), + // TODO FIXME the second error message is incomplete, should be removed indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── @@ -1241,6 +1242,20 @@ mod test_reporting { Tip: You can convert between Int and Float using functions like `Num.toFloat` and `Num.round`. + + ── TYPE NOT IN RANGE ─────────────────────────────────────────────────────────── + + This expression is used in an unexpected way: + + 2│ x = if True then 3.14 else 4 + ^ + + It can only be used as a + `I8``U8`, `I16`, `U16`, `I32`, `U32`, `I64`, `Nat`, `U64`, `I128`, or `U128` + + But it is being used as: + + `Int` `*` "# ), ) @@ -3386,7 +3401,9 @@ mod test_reporting { minlit = -170_141_183_460_469_231_731_687_303_715_884_105_728 maxlit = 340_282_366_920_938_463_463_374_607_431_768_211_455 - x + y + h + l + minlit + maxlit + getI128 = \_ -> 1i128 + + x + y + h + l + minlit + (getI128 maxlit) "# ), indoc!( @@ -7882,18 +7899,26 @@ I need all branches in an `if` to have the same type! #[test] fn list_get_negative_number() { report_problem_as( - "List.get [1, 2, 3] -1", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + a = -9_223_372_036_854 + List.get [1,2,3] a + "# + ), + indoc!( + r#" + ── TYPE NOT IN RANGE ─────────────────────────────────────────────────────────── - This integer literal overflows the type indicated by its suffix: + This expression is used in an unexpected way: - 1│ 170_141_183_460_469_231_731_687_303_715_884_105_728i128 - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 2│ List.get [1,2,3] a + ^ - Tip: The suffix indicates this integer is a I128, whose maximum value - is 170_141_183_460_469_231_731_687_303_715_884_105_727. + It can only be used as a `I64` or `I128` + + But it is being used as: + + `Nat` "# ), ) From c80c842a93aa738e8104486dd8d37a6b48a8c07a Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Feb 2022 00:24:39 -0500 Subject: [PATCH 497/541] Clippy --- ast/src/solve_type.rs | 29 +++++++++++++++++++++++++++++ compiler/constrain/src/builtins.rs | 4 ++-- compiler/constrain/src/pattern.rs | 2 +- compiler/types/src/solved_types.rs | 2 +- compiler/types/src/subs.rs | 1 + 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs index 2c391ce637..c437b78949 100644 --- a/ast/src/solve_type.rs +++ b/ast/src/solve_type.rs @@ -252,6 +252,7 @@ fn solve<'a>( state } + NotInRange(_vars, _typ, _range) => todo!(), } } // Store(source, target, _filename, _linenr) => { @@ -346,6 +347,7 @@ fn solve<'a>( state } + NotInRange(_vars, _typ, _range) => todo!(), } } None => { @@ -416,6 +418,7 @@ fn solve<'a>( state } + NotInRange(_vars, _typ, _range) => todo!(), } } Let(let_con) => { @@ -725,6 +728,7 @@ fn solve<'a>( state } + NotInRange(_vars, _typ, _range) => todo!(), } } } @@ -1400,6 +1404,8 @@ fn adjust_rank_content( rank } + + RangedNumber(typ, _vars) => adjust_rank(subs, young_mark, visit_mark, group_rank, *typ), } } @@ -1550,6 +1556,10 @@ fn instantiate_rigids_help( instantiate_rigids_help(subs, max_rank, pools, real_type_var); } + + RangedNumber(typ, _vars) => { + instantiate_rigids_help(subs, max_rank, pools, typ); + } } var @@ -1806,6 +1816,25 @@ fn deep_copy_var_help( copy } + + RangedNumber(typ, vars) => { + let mut new_vars = Vec::with_capacity(vars.len()); + + for var_index in vars { + let var = subs[var_index]; + let new_var = deep_copy_var_help(subs, max_rank, pools, var); + new_vars.push(new_var); + } + + let new_slice = VariableSubsSlice::insert_into_subs(subs, new_vars.drain(..)); + + let new_real_type = deep_copy_var_help(subs, max_rank, pools, typ); + let new_content = RangedNumber(new_real_type, new_slice); + + subs.set(copy, make_descriptor(new_content)); + + copy + } } } diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 7867a06cd9..87ba062388 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -35,7 +35,7 @@ pub fn add_numeric_bound_constr( )); total_num_type } - _ => RangedNumber(Box::new(total_num_type.clone()), range), + _ => RangedNumber(Box::new(total_num_type), range), } } @@ -66,7 +66,7 @@ pub fn int_literal( Category::Int, region, ), - Eq(num_type, expected.clone(), Category::Int, region), + Eq(num_type, expected, Category::Int, region), ]); exists(vec![num_var], And(constrs)) diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index bdd11dfe27..bf77a86f36 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -185,7 +185,7 @@ pub fn constrain_pattern( let num_type = builtins::add_numeric_bound_constr( &mut state.constraints, - num_type.clone(), + num_type, bound, region, Category::Num, diff --git a/compiler/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index 453e5207c1..fb5f20e7f0 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -231,7 +231,7 @@ impl SolvedType { } } Variable(var) => Self::from_var(solved_subs.inner(), *var), - RangedNumber(typ, _) => Self::from_type(solved_subs, &typ), + RangedNumber(typ, _) => Self::from_type(solved_subs, typ), } } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index e24503e899..dcd6767b2f 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -1058,6 +1058,7 @@ fn define_integer_types(subs: &mut Subs) { ); } +#[allow(clippy::too_many_arguments)] fn float_type( subs: &mut Subs, From 3fffca48bb0662bd64133c36289a45f70326faed Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Feb 2022 00:24:57 -0500 Subject: [PATCH 498/541] Bye bye dead code! --- compiler/constrain/src/builtins.rs | 51 ------------------------------ 1 file changed, 51 deletions(-) diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 87ba062388..1081d0c1fa 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -210,57 +210,6 @@ pub fn num_int(range: Type) -> Type { ) } -// macro_rules! num_types { -// // Represent -// // num_u8 ~ U8 : Num Integer Unsigned8 = @Num (@Integer (@Unsigned8)) -// // int_u8 ~ Integer Unsigned8 = @Integer (@Unsigned8) -// // -// // num_f32 ~ F32 : Num FloaingPoint Binary32 = @Num (@FloaingPoint (@Binary32)) -// // float_f32 ~ FloatingPoint Binary32 = @FloatingPoint (@Binary32) -// // and so on, for all numeric types. -// ($($num_fn:ident, $sub_fn:ident, $num_type:ident, $alias:path, $inner_alias:path, $inner_private_tag:path)*) => { -// $( -// #[inline(always)] -// fn $sub_fn() -> Type { -// builtin_alias( -// $inner_alias, -// vec![], -// Box::new(Type::TagUnion( -// vec![(TagName::Private($inner_private_tag), vec![])], -// Box::new(Type::EmptyTagUnion) -// )), -// ) -// } -// -// #[inline(always)] -// fn $num_fn() -> Type { -// builtin_alias( -// $alias, -// vec![], -// Box::new($num_type($sub_fn())) -// ) -// } -// )* -// } -// } -// -// num_types! { -// num_u8, int_u8, num_int, Symbol::NUM_U8, Symbol::NUM_UNSIGNED8, Symbol::NUM_AT_UNSIGNED8 -// num_u16, int_u16, num_int, Symbol::NUM_U16, Symbol::NUM_UNSIGNED16, Symbol::NUM_AT_UNSIGNED16 -// num_u32, int_u32, num_int, Symbol::NUM_U32, Symbol::NUM_UNSIGNED32, Symbol::NUM_AT_UNSIGNED32 -// num_u64, int_u64, num_int, Symbol::NUM_U64, Symbol::NUM_UNSIGNED64, Symbol::NUM_AT_UNSIGNED64 -// num_u128, int_u128, num_int, Symbol::NUM_U128, Symbol::NUM_UNSIGNED128, Symbol::NUM_AT_UNSIGNED128 -// num_i8, int_i8, num_int, Symbol::NUM_I8, Symbol::NUM_SIGNED8, Symbol::NUM_AT_SIGNED8 -// num_i16, int_i16, num_int, Symbol::NUM_I16, Symbol::NUM_SIGNED16, Symbol::NUM_AT_SIGNED16 -// num_i32, int_i32, num_int, Symbol::NUM_I32, Symbol::NUM_SIGNED32, Symbol::NUM_AT_SIGNED32 -// num_i64, int_i64, num_int, Symbol::NUM_I64, Symbol::NUM_SIGNED64, Symbol::NUM_AT_SIGNED64 -// num_i128, int_i128, num_int, Symbol::NUM_I128, Symbol::NUM_SIGNED128, Symbol::NUM_AT_SIGNED128 -// num_nat, int_nat, num_int, Symbol::NUM_NAT, Symbol::NUM_NATURAL, Symbol::NUM_AT_NATURAL -// num_dec, float_dec, num_float, Symbol::NUM_DEC, Symbol::NUM_DECIMAL, Symbol::NUM_AT_DECIMAL -// num_f32, float_f32, num_float, Symbol::NUM_F32, Symbol::NUM_BINARY32, Symbol::NUM_AT_BINARY32 -// num_f64, float_f64, num_float, Symbol::NUM_F64, Symbol::NUM_BINARY64, Symbol::NUM_AT_BINARY64 -// } - #[inline(always)] pub fn num_signed64() -> Type { let alias_content = Type::TagUnion( From c5d918e68cc4e332a0cdc56bfa09cb5388e6d0ce Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Feb 2022 12:31:37 -0500 Subject: [PATCH 499/541] Include floats in bounds for unspecified numbers --- ast/src/lang/core/expr/expr_to_expr2.rs | 2 +- ast/src/lang/core/pattern.rs | 2 +- compiler/can/src/num.rs | 75 +++++++---------- compiler/can/src/pattern.rs | 4 +- compiler/constrain/src/builtins.rs | 7 +- compiler/solve/tests/solve_expr.rs | 105 ++++++++++++----------- reporting/src/error/type.rs | 8 +- reporting/tests/test_reporting.rs | 106 +++++++++--------------- 8 files changed, 129 insertions(+), 180 deletions(-) diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index 5bcd4edd19..6295763f22 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -76,7 +76,7 @@ pub fn expr_to_expr2<'a>( } Num(string) => { match finish_parsing_num(string) { - Ok(ParsedNumResult::UnknownNum(int) | ParsedNumResult::Int(int, _)) => { + Ok(ParsedNumResult::UnknownNum(int, _) | ParsedNumResult::Int(int, _)) => { let expr = Expr2::SmallInt { number: IntVal::I64(match int { IntValue::U128(_) => todo!(), diff --git a/ast/src/lang/core/pattern.rs b/ast/src/lang/core/pattern.rs index aabb06254f..3bbefb7d83 100644 --- a/ast/src/lang/core/pattern.rs +++ b/ast/src/lang/core/pattern.rs @@ -196,7 +196,7 @@ pub fn to_pattern2<'a>( let problem = MalformedPatternProblem::MalformedInt; malformed_pattern(env, problem, region) } - Ok(ParsedNumResult::UnknownNum(int)) => { + Ok(ParsedNumResult::UnknownNum(int, _bound)) => { Pattern2::NumLiteral( env.var_store.fresh(), match int { diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index c1d8511eb4..d568a4f221 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -17,8 +17,8 @@ pub fn num_expr_from_result( env: &mut Env, ) -> Expr { match result { - Ok((str, ParsedNumResult::UnknownNum(num))) => { - Expr::Num(var_store.fresh(), (*str).into(), num, NumericBound::None) + Ok((str, ParsedNumResult::UnknownNum(num, bound))) => { + Expr::Num(var_store.fresh(), (*str).into(), num, bound) } Ok((str, ParsedNumResult::Int(num, bound))) => Expr::Int( var_store.fresh(), @@ -103,27 +103,14 @@ pub fn float_expr_from_result( pub enum ParsedNumResult { Int(IntValue, IntBound), Float(f64, FloatBound), - UnknownNum(IntValue), + UnknownNum(IntValue, NumericBound), } #[inline(always)] pub fn finish_parsing_num(raw: &str) -> Result { // Ignore underscores. let radix = 10; - let (num, bound) = - from_str_radix(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e))?; - // Let's try to specialize the number - Ok(match bound { - NumericBound::None => ParsedNumResult::UnknownNum(num), - NumericBound::Int(ib) => ParsedNumResult::Int(num, ib), - NumericBound::Float(fb) => { - let num = match num { - IntValue::I128(n) => n as f64, - IntValue::U128(n) => n as f64, - }; - ParsedNumResult::Float(num, fb) - } - }) + from_str_radix(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e)) } #[inline(always)] @@ -145,13 +132,13 @@ pub fn finish_parsing_base( } else { from_str_radix(raw.replace("_", "").as_str(), radix) }) - .and_then(|(n, bound)| { - let bound = match bound { - NumericBound::None => IntBound::None, - NumericBound::Int(ib) => ib, - NumericBound::Float(_) => return Err(IntErrorKind::FloatSuffix), - }; - Ok((n, bound)) + .and_then(|parsed| match parsed { + ParsedNumResult::Float(..) => return Err(IntErrorKind::FloatSuffix), + ParsedNumResult::Int(val, bound) => Ok((val, bound)), + ParsedNumResult::UnknownNum(val, NumericBound::None) => Ok((val, IntBound::None)), + ParsedNumResult::UnknownNum(val, NumericBound::AtLeastIntOrFloat { sign, width }) => { + Ok((val, IntBound::AtLeast { sign, width })) + } }) .map_err(|e| (raw, e)) } @@ -223,7 +210,7 @@ fn parse_literal_suffix(num_str: &str) -> (Option, &str) { /// the LEGAL_DETAILS file in the root directory of this distribution. /// /// Thanks to the Rust project and its contributors! -fn from_str_radix(src: &str, radix: u32) -> Result<(IntValue, NumericBound), IntErrorKind> { +fn from_str_radix(src: &str, radix: u32) -> Result { use self::IntErrorKind::*; assert!( @@ -273,25 +260,30 @@ fn from_str_radix(src: &str, radix: u32) -> Result<(IntValue, NumericBound), Int } else { SignDemand::NoDemand }; - Ok(( + Ok(ParsedNumResult::UnknownNum( result, - IntBound::AtLeast { + NumericBound::AtLeastIntOrFloat { sign: sign_demand, width: lower_bound, - } - .into(), + }, )) } Some(ParsedWidth::Float(fw)) => { // For now, assume floats can represent all integers // TODO: this is somewhat incorrect, revisit - Ok((result, FloatBound::Exact(fw).into())) + Ok(ParsedNumResult::Float( + match result { + IntValue::I128(n) => n as f64, + IntValue::U128(n) => n as f64, + }, + FloatBound::Exact(fw), + )) } Some(ParsedWidth::Int(exact_width)) => { // We need to check if the exact bound >= lower bound. if exact_width.is_superset(&lower_bound, is_negative) { // Great! Use the exact bound. - Ok((result, IntBound::Exact(exact_width).into())) + Ok(ParsedNumResult::Int(result, IntBound::Exact(exact_width))) } else { // This is something like 200i8; the lower bound is u8, which holds strictly more // ints on the positive side than i8 does. Report an error depending on which side @@ -512,20 +504,9 @@ pub enum FloatBound { #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum NumericBound { None, - Int(IntBound), - Float(FloatBound), -} - -impl From for NumericBound { - #[inline(always)] - fn from(ib: IntBound) -> Self { - Self::Int(ib) - } -} - -impl From for NumericBound { - #[inline(always)] - fn from(fb: FloatBound) -> Self { - Self::Float(fb) - } + /// Must be an integer of a certain size, or any float. + AtLeastIntOrFloat { + sign: SignDemand, + width: IntWidth, + }, } diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 2e574796c2..41aa49ca9a 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -216,8 +216,8 @@ pub fn canonicalize_pattern<'a>( let problem = MalformedPatternProblem::MalformedInt; malformed_pattern(env, problem, region) } - Ok(ParsedNumResult::UnknownNum(int)) => { - Pattern::NumLiteral(var_store.fresh(), (str).into(), int, NumericBound::None) + Ok(ParsedNumResult::UnknownNum(int, bound)) => { + Pattern::NumLiteral(var_store.fresh(), (str).into(), int, bound) } Ok(ParsedNumResult::Int(int, bound)) => Pattern::IntLiteral( var_store.fresh(), diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 1081d0c1fa..b5ac1cf14f 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -324,8 +324,11 @@ impl TypedNumericBound for NumericBound { fn bounded_range(&self) -> Vec { match self { NumericBound::None => vec![], - NumericBound::Int(ib) => ib.bounded_range(), - NumericBound::Float(fb) => fb.bounded_range(), + &NumericBound::AtLeastIntOrFloat { sign, width } => { + let mut range = IntBound::AtLeast { sign, width }.bounded_range(); + range.extend_from_slice(&[Variable::F32, Variable::F64, Variable::DEC]); + range + } } } } diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index fee93d0253..d8c5a38d6e 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -164,7 +164,7 @@ mod solve_expr { #[test] fn int_literal() { - infer_eq("5", "Int *"); + infer_eq("5", "Num *"); } #[test] @@ -334,7 +334,7 @@ mod solve_expr { [42] "# ), - "List (Int *)", + "List (Num *)", ); } @@ -346,7 +346,7 @@ mod solve_expr { [[[ 5 ]]] "# ), - "List (List (List (Int *)))", + "List (List (List (Num *)))", ); } @@ -358,7 +358,7 @@ mod solve_expr { [ 1, 2, 3 ] "# ), - "List (Int *)", + "List (Num *)", ); } @@ -370,7 +370,7 @@ mod solve_expr { [ [ 1 ], [ 2, 3 ] ] "# ), - "List (List (Int *))", + "List (List (Num *))", ); } @@ -518,7 +518,7 @@ mod solve_expr { \_, _ -> 42 "# ), - "*, * -> Int *", + "*, * -> Num *", ); } @@ -689,7 +689,7 @@ mod solve_expr { func "# ), - "*, * -> Int *", + "*, * -> Num *", ); } @@ -753,7 +753,7 @@ mod solve_expr { c "# ), - "Int *", + "Num *", ); } @@ -788,7 +788,7 @@ mod solve_expr { alwaysFive "stuff" "# ), - "Int *", + "Num *", ); } @@ -835,7 +835,7 @@ mod solve_expr { x "# ), - "Int *", + "Num *", ); } @@ -849,7 +849,7 @@ mod solve_expr { enlist 5 "# ), - "List (Int *)", + "List (Num *)", ); } @@ -876,7 +876,7 @@ mod solve_expr { 1 |> (\a -> a) "# ), - "Int *", + "Num *", ); } @@ -890,7 +890,7 @@ mod solve_expr { 1 |> always2 "foo" "# ), - "Int *", + "Num *", ); } @@ -955,7 +955,7 @@ mod solve_expr { apply identity 5 "# ), - "Int *", + "Num *", ); } @@ -984,7 +984,7 @@ mod solve_expr { // flip neverendingInt // "# // ), - // "(Int *, (a -> a)) -> Int *", + // "(Num *, (a -> a)) -> Num *", // ); // } @@ -1058,7 +1058,7 @@ mod solve_expr { // 1 // 2 // "# // ), - // "Int *", + // "Num *", // ); // } @@ -1070,7 +1070,7 @@ mod solve_expr { // 1 + 2 // "# // ), - // "Int *", + // "Num *", // ); // } @@ -1119,7 +1119,7 @@ mod solve_expr { [ alwaysFive "foo", alwaysFive [] ] "# ), - "List (Int *)", + "List (Num *)", ); } @@ -1134,7 +1134,7 @@ mod solve_expr { 24 "# ), - "Int *", + "Num *", ); } @@ -1148,7 +1148,7 @@ mod solve_expr { 3 -> 4 "# ), - "Int *", + "Num *", ); } @@ -1161,17 +1161,17 @@ mod solve_expr { #[test] fn one_field_record() { - infer_eq("{ x: 5 }", "{ x : Int * }"); + infer_eq("{ x: 5 }", "{ x : Num * }"); } #[test] fn two_field_record() { - infer_eq("{ x: 5, y : 3.14 }", "{ x : Int *, y : Float * }"); + infer_eq("{ x: 5, y : 3.14 }", "{ x : Num *, y : Float * }"); } #[test] fn record_literal_accessor() { - infer_eq("{ x: 5, y : 3.14 }.x", "Int *"); + infer_eq("{ x: 5, y : 3.14 }.x", "Num *"); } #[test] @@ -1230,7 +1230,7 @@ mod solve_expr { infer_eq( indoc!( r#" - foo : Int * -> custom + foo : Num * -> custom foo 2 "# @@ -1327,7 +1327,7 @@ mod solve_expr { \Foo -> 42 "# ), - "[ Foo ] -> Int *", + "[ Foo ] -> Num *", ); } @@ -1339,7 +1339,7 @@ mod solve_expr { \@Foo -> 42 "# ), - "[ @Foo ] -> Int *", + "[ @Foo ] -> Num *", ); } @@ -1354,7 +1354,7 @@ mod solve_expr { False -> 0 "# ), - "[ False, True ] -> Int *", + "[ False, True ] -> Num *", ); } @@ -1366,7 +1366,7 @@ mod solve_expr { Foo "happy" 2020 "# ), - "[ Foo Str (Int *) ]*", + "[ Foo Str (Num *) ]*", ); } @@ -1378,7 +1378,7 @@ mod solve_expr { @Foo "happy" 2020 "# ), - "[ @Foo Str (Int *) ]*", + "[ @Foo Str (Num *) ]*", ); } @@ -1407,7 +1407,7 @@ mod solve_expr { { x: 4 } -> 4 "# ), - "Int *", + "Num *", ); } @@ -2347,7 +2347,7 @@ mod solve_expr { { numIdentity, x : numIdentity 42, y } "# ), - "{ numIdentity : Num a -> Num a, x : Int *, y : F64 }", + "{ numIdentity : Num a -> Num a, x : Num a, y : F64 }", ); } @@ -2383,7 +2383,7 @@ mod solve_expr { f "# ), - "Int * -> Int *", + "Num * -> Num *", ); } @@ -2416,7 +2416,7 @@ mod solve_expr { toBit "# ), - "[ False, True ] -> Int *", + "[ False, True ] -> Num *", ); } @@ -2453,7 +2453,7 @@ mod solve_expr { fromBit "# ), - "Int * -> [ False, True ]*", + "Num * -> [ False, True ]*", ); } @@ -2505,7 +2505,7 @@ mod solve_expr { foo { x: 5 } "# ), - "Int *", + "Num *", ); } @@ -2774,7 +2774,7 @@ mod solve_expr { // infer_eq_without_problem( // indoc!( // r#" - // s : Int * + // s : Num * // s = 3.1 // s @@ -3214,7 +3214,7 @@ mod solve_expr { List.get [ 10, 9, 8, 7 ] 1 "# ), - "Result (Int *) [ OutOfBounds ]*", + "Result (Num *) [ OutOfBounds ]*", ); infer_eq_without_problem( @@ -3497,7 +3497,7 @@ mod solve_expr { f "# ), - "{ p : *, q : * }* -> Int *", + "{ p : *, q : * }* -> Num *", ); } @@ -3552,7 +3552,7 @@ mod solve_expr { _ -> 3 "# ), - "Int * -> Int *", + "Num * -> Num *", ); } @@ -3724,8 +3724,7 @@ mod solve_expr { negatePoint { x: 1, y: 2.1, z: 0x3 } "# ), - // TODO this should be "Int a", FIXME - "{ x : Int *, y : F64, z : Int * }", + "{ x : Num a, y : F64, z : Int * }", ); } @@ -3742,8 +3741,7 @@ mod solve_expr { { a, b } "# ), - // TODO this should be "Int a", FIXME - "{ a : { x : Int *, y : F64, z : c }, b : { blah : Str, x : Int *, y : F64, z : c } }", + "{ a : { x : Num a, y : F64, z : c }, b : { blah : Str, x : Num a, y : F64, z : c } }", ); } @@ -3755,7 +3753,7 @@ mod solve_expr { \{ x, y ? 0 } -> x + y "# ), - "{ x : Int a, y ? Int a }* -> Int a", + "{ x : Num a, y ? Num a }* -> Num a", ); } @@ -3769,7 +3767,7 @@ mod solve_expr { x + y "# ), - "Int *", + "Num *", ); } @@ -3783,7 +3781,7 @@ mod solve_expr { { x, y ? 0 } -> x + y "# ), - "{ x : Int a, y ? Int a }* -> Int a", + "{ x : Num a, y ? Num a }* -> Num a", ); } @@ -3948,7 +3946,7 @@ mod solve_expr { g "# ), - "Int a -> Int a", + "Num a -> Num a", ); } @@ -3987,10 +3985,10 @@ mod solve_expr { Foo Bar 1 "# ), - "[ Foo [ Bar ]* (Int *) ]*", + "[ Foo [ Bar ]* (Num *) ]*", ); - infer_eq_without_problem("Foo Bar 1", "[ Foo [ Bar ]* (Int *) ]*"); + infer_eq_without_problem("Foo Bar 1", "[ Foo [ Bar ]* (Num *) ]*"); } #[test] @@ -4682,7 +4680,7 @@ mod solve_expr { x "# ), - "Int *", + "Num *", ); } @@ -4983,7 +4981,7 @@ mod solve_expr { None -> 0 "# ), - "[ None, Some { tag : [ A, B ] }* ] -> Int *", + "[ None, Some { tag : [ A, B ] }* ] -> Num *", ) } @@ -5016,7 +5014,7 @@ mod solve_expr { { x: Red, y ? 5 } -> y "# ), - "{ x : [ Blue, Red ], y ? Int a }* -> Int a", + "{ x : [ Blue, Red ], y ? Num a }* -> Num a", ) } @@ -5029,8 +5027,7 @@ mod solve_expr { \UserId id -> id + 1 "# ), - // TODO needs parantheses - "[ UserId Int a ] -> Int a", + "[ UserId (Num a) ] -> Num a", ) } diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index 68dfa687e0..2092640e39 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -66,11 +66,11 @@ pub fn type_problem<'b>( let last = range.len() - 1; for (i, choice) in range.into_iter().enumerate() { if i == last && i == 1 { - range_choices.push(alloc.text(" or ")); + range_choices.push(alloc.reflow(" or ")); } else if i == last && i > 1 { - range_choices.push(alloc.text(", or ")); - } else if i > 1 { - range_choices.push(alloc.text(", ")); + range_choices.push(alloc.reflow(", or ")); + } else if i > 0 { + range_choices.push(alloc.reflow(", ")); } range_choices.push(to_doc(alloc, Parens::Unnecessary, choice)); diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 95f31c5cad..7da7f48487 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -862,9 +862,9 @@ mod test_reporting { 2│> 2 if 1 -> 0x0 3│ _ -> 0x1 - Right now it’s an integer of type: + Right now it’s a number of type: - Int a + Num a But I need every `if` guard condition to evaluate to a Bool—either `True` or `False`. @@ -896,7 +896,7 @@ mod test_reporting { but the `then` branch has the type: - Int a + Num a I need all branches in an `if` to have the same type! "# @@ -927,7 +927,7 @@ mod test_reporting { But all the previous branches have type: - Int a + Num a I need all branches in an `if` to have the same type! "# @@ -993,7 +993,7 @@ mod test_reporting { However, the preceding elements in the list all have the type: - Int a + Num a I need every element in a list to have the same type! "# @@ -1250,8 +1250,8 @@ mod test_reporting { 2│ x = if True then 3.14 else 4 ^ - It can only be used as a - `I8``U8`, `I16`, `U16`, `I32`, `U32`, `I64`, `Nat`, `U64`, `I128`, or `U128` + It can only be used as a `I8`, `U8`, `I16`, `U16`, `I32`, `U32`, `I64`, `Nat`, `U64`, + `I128`, `U128`, `F32`, `F64`, or `Dec` But it is being used as: @@ -1439,7 +1439,7 @@ mod test_reporting { But the expression between `when` and `is` has the type: - Int a + Num a "# ), ) @@ -1470,7 +1470,7 @@ mod test_reporting { But all the previous branches match: - Int a + Num a "# ), ) @@ -1500,7 +1500,7 @@ mod test_reporting { But the expression between `when` and `is` has the type: - { foo : Int a } + { foo : Num a } "# ), ) @@ -1620,13 +1620,13 @@ mod test_reporting { 2│ {} | 1 -> 3 ^^^^^^ - The first pattern is trying to match integers: + The first pattern is trying to match numbers: - Int a + Num a But the expression between `when` and `is` has the type: - { foo : Int a } + { foo : Num a } "# ), ) @@ -1652,9 +1652,9 @@ mod test_reporting { 1│ (Foo x) = 42 ^^ - It is an integer of type: + It is a number of type: - Int a + Num a But you are trying to use it as: @@ -2177,8 +2177,8 @@ mod test_reporting { This is usually a typo. Here are the `x` fields that are most similar: - { fo : Int b - , bar : Int a + { fo : Num b + , bar : Num a } So maybe `.foo` should be `.fo`? @@ -2244,10 +2244,10 @@ mod test_reporting { This is usually a typo. Here are the `x` fields that are most similar: - { fo : Int c - , foobar : Int d - , bar : Int a - , baz : Int b + { fo : Num c + , foobar : Num d + , bar : Num a + , baz : Num b , ... } @@ -2342,7 +2342,7 @@ mod test_reporting { But `add` needs the 2nd argument to be: - Num (Integer a) + Num a "# ), ) @@ -3375,8 +3375,8 @@ mod test_reporting { This `ACons` global tag application has the type: - [ ACons Int Signed64 [ BCons (Int a) [ ACons Str [ BNil ]b ]c ]d, - ANil ] + [ ACons Num (Integer Signed64) [ BCons (Num a) [ ACons Str [ BNil + ]b ]c ]d, ANil ] But the type annotation on `x` says it should be: @@ -3401,9 +3401,7 @@ mod test_reporting { minlit = -170_141_183_460_469_231_731_687_303_715_884_105_728 maxlit = 340_282_366_920_938_463_463_374_607_431_768_211_455 - getI128 = \_ -> 1i128 - - x + y + h + l + minlit + (getI128 maxlit) + x + y + h + l + minlit + maxlit "# ), indoc!( @@ -4990,7 +4988,7 @@ mod test_reporting { This `insert` call produces: - Dict Str (Int a) + Dict Str (Num a) But the type annotation on `myDict` says it should be: @@ -5652,7 +5650,7 @@ mod test_reporting { but the `then` branch has the type: - Int a + Num a I need all branches in an `if` to have the same type! "# @@ -6293,7 +6291,7 @@ I need all branches in an `if` to have the same type! This `map` call produces: - List [ Foo Int a ] + List [ Foo Num a ] But the type annotation on `x` says it should be: @@ -6543,11 +6541,11 @@ I need all branches in an `if` to have the same type! This argument is an anonymous function of type: - Num (Integer a) -> Num (Integer a) + Num a -> Num a But `map` needs the 2nd argument to be: - Str -> Num (Integer a) + Str -> Num a "# ), ) @@ -6649,21 +6647,6 @@ I need all branches in an `if` to have the same type! But the type annotation on `mult` says it should be: F64 - - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── - - The 2nd argument to `mult` is not what I expect: - - 4│ mult 0 0 - ^ - - This argument is an integer of type: - - Int a - - But `mult` needs the 2nd argument to be: - - F64 "# ), ) @@ -6712,21 +6695,6 @@ I need all branches in an `if` to have the same type! But the type annotation on `mult` says it should be: F64 - - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── - - The 2nd argument to `mult` is not what I expect: - - 4│ mult 0 0 - ^ - - This argument is an integer of type: - - Int a - - But `mult` needs the 2nd argument to be: - - F64 "# ), ) @@ -7078,9 +7046,9 @@ I need all branches in an `if` to have the same type! 5│ f = \c -> c 6 ^ - This argument is an integer of type: + This argument is a number of type: - Int a + Num a But `c` needs the 1st argument to be: @@ -7088,7 +7056,7 @@ I need all branches in an `if` to have the same type! Tip: The type annotation uses the type variable `a` to say that this definition can produce any type of value. But in the body I see that - it will only produce a `Int` value of a single specific type. Maybe + it will only produce a `Num` value of a single specific type. Maybe change the type annotation to be more specific? Maybe change the code to be more general? "# @@ -7901,9 +7869,9 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - a = -9_223_372_036_854 - List.get [1,2,3] a - "# + a = -9_223_372_036_854 + List.get [1,2,3] a + "# ), indoc!( r#" @@ -7914,7 +7882,7 @@ I need all branches in an `if` to have the same type! 2│ List.get [1,2,3] a ^ - It can only be used as a `I64` or `I128` + It can only be used as a `I64`, `I128`, `F32`, `F64`, or `Dec` But it is being used as: From 364d2585dfb3e585e98f523a2275fc3aa38d601b Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Feb 2022 12:44:31 -0500 Subject: [PATCH 500/541] Fix repl tests --- compiler/unify/src/unify.rs | 6 +++--- repl_eval/src/eval.rs | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index fc6c313336..392db8bfb7 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -9,7 +9,7 @@ use roc_types::types::{ErrorType, Mismatch, RecordField}; macro_rules! mismatch { () => {{ - if cfg!(debug_assertions) { + if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_some() { println!( "Mismatch in {} Line {} Column {}", file!(), @@ -21,7 +21,7 @@ macro_rules! mismatch { vec![Mismatch::TypeMismatch] }}; ($msg:expr) => {{ - if cfg!(debug_assertions) { + if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() { println!( "Mismatch in {} Line {} Column {}", file!(), @@ -39,7 +39,7 @@ macro_rules! mismatch { mismatch!($msg) }}; ($msg:expr, $($arg:tt)*) => {{ - if cfg!(debug_assertions) { + if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() { println!( "Mismatch in {} Line {} Column {}", file!(), diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index 0b946a73a1..4cb651bdda 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -1166,6 +1166,9 @@ fn num_to_ast<'a, A: ReplApp>( num_to_ast(env, num_expr, content) } + RangedNumber(typ, _) => { + num_to_ast(env, num_expr, env.subs.get_content_without_compacting(*typ)) + } other => { panic!("Unexpected FlatType {:?} in num_to_ast", other); } From 6d72971d6b2228ccb18551de6b26341e7f1057ad Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Feb 2022 12:48:52 -0500 Subject: [PATCH 501/541] Clippy and a ranged number unification bugfix --- compiler/can/src/num.rs | 2 +- compiler/unify/src/unify.rs | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index d568a4f221..33c6d9bb6a 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -133,7 +133,7 @@ pub fn finish_parsing_base( from_str_radix(raw.replace("_", "").as_str(), radix) }) .and_then(|parsed| match parsed { - ParsedNumResult::Float(..) => return Err(IntErrorKind::FloatSuffix), + ParsedNumResult::Float(..) => Err(IntErrorKind::FloatSuffix), ParsedNumResult::Int(val, bound) => Ok((val, bound)), ParsedNumResult::UnknownNum(val, NumericBound::None) => Ok((val, IntBound::None)), ParsedNumResult::UnknownNum(val, NumericBound::AtLeastIntOrFloat { sign, width }) => { diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 392db8bfb7..c8e02b2025 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -209,9 +209,14 @@ fn unify_ranged_number( RecursionVar { .. } | RigidVar(..) | Alias(..) | Structure(..) => { unify_pool(subs, pool, real_var, ctx.second, ctx.mode) } - &RangedNumber(other_real_var, _other_range_vars) => { - unify_pool(subs, pool, real_var, other_real_var, ctx.mode) - // TODO: check and intersect "other_range_vars" + &RangedNumber(other_real_var, other_range_vars) => { + let outcome = unify_pool(subs, pool, real_var, other_real_var, ctx.mode); + if outcome.is_empty() { + check_valid_range(subs, pool, ctx.first, other_range_vars, ctx.mode) + } else { + outcome + } + // TODO: We should probably check that "range_vars" and "other_range_vars" intersect } Error => merge(subs, ctx, Error), }; From 680bf8e0b7fd9748ed1c1ae1867539fe205b6a87 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Feb 2022 13:42:28 -0500 Subject: [PATCH 502/541] Treat rigids as flex vars when checking number range types --- Cargo.lock | 1 + compiler/mono/src/ir.rs | 6 +-- compiler/solve/src/solve.rs | 12 ++--- compiler/unify/Cargo.toml | 19 +++++--- compiler/unify/src/unify.rs | 78 ++++++++++++++++++++----------- reporting/tests/test_reporting.rs | 14 ------ 6 files changed, 75 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5a95ee182..00cfbd1c30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3811,6 +3811,7 @@ dependencies = [ name = "roc_unify" version = "0.1.0" dependencies = [ + "bitflags", "roc_collections", "roc_module", "roc_types", diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 75f89ae6fa..436212ba2e 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -2292,7 +2292,7 @@ fn specialize_external<'a>( env.subs, partial_proc.annotation, fn_var, - roc_unify::unify::Mode::Eq, + roc_unify::unify::Mode::EQ, ); // This will not hold for programs with type errors @@ -4281,7 +4281,7 @@ pub fn with_hole<'a>( env.subs, fn_var, lambda_expr_var, - roc_unify::unify::Mode::Eq, + roc_unify::unify::Mode::EQ, ); result = with_hole( @@ -6691,7 +6691,7 @@ fn reuse_function_symbol<'a>( env.subs, arg_var.unwrap(), expr_var, - roc_unify::unify::Mode::Eq, + roc_unify::unify::Mode::EQ, ); let result = with_hole( diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index c831b776c1..7d22ca16b6 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -207,7 +207,7 @@ fn solve( expectation.get_type_ref(), ); - match unify(subs, actual, expected, Mode::Eq) { + match unify(subs, actual, expected, Mode::EQ) { Success(vars) => { introduce(subs, rank, pools, &vars); @@ -253,7 +253,7 @@ fn solve( let actual = type_to_var(subs, rank, pools, cached_aliases, source); let target = *target; - match unify(subs, actual, target, Mode::Eq) { + match unify(subs, actual, target, Mode::EQ) { Success(vars) => { introduce(subs, rank, pools, &vars); @@ -307,7 +307,7 @@ fn solve( cached_aliases, expectation.get_type_ref(), ); - match unify(subs, actual, expected, Mode::Eq) { + match unify(subs, actual, expected, Mode::EQ) { Success(vars) => { introduce(subs, rank, pools, &vars); @@ -385,8 +385,8 @@ fn solve( ); let mode = match constraint { - Present(_, _) => Mode::Present, - _ => Mode::Eq, + Present(_, _) => Mode::PRESENT, + _ => Mode::EQ, }; match unify(subs, actual, expected, mode) { @@ -694,7 +694,7 @@ fn solve( ); let includes = type_to_var(subs, rank, pools, cached_aliases, &tag_ty); - match unify(subs, actual, includes, Mode::Present) { + match unify(subs, actual, includes, Mode::PRESENT) { Success(vars) => { introduce(subs, rank, pools, &vars); diff --git a/compiler/unify/Cargo.toml b/compiler/unify/Cargo.toml index c4a2c783c1..e15626603c 100644 --- a/compiler/unify/Cargo.toml +++ b/compiler/unify/Cargo.toml @@ -1,11 +1,18 @@ [package] +authors = ["The Roc Contributors"] +edition = "2018" +license = "UPL-1.0" name = "roc_unify" version = "0.1.0" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2018" [dependencies] -roc_collections = { path = "../collections" } -roc_module = { path = "../module" } -roc_types = { path = "../types" } +bitflags = "1.3.2" + +[dependencies.roc_collections] +path = "../collections" + +[dependencies.roc_module] +path = "../module" + +[dependencies.roc_types] +path = "../types" diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index c8e02b2025..4193525ca7 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -1,3 +1,4 @@ +use bitflags::bitflags; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_types::subs::Content::{self, *}; @@ -56,17 +57,37 @@ macro_rules! mismatch { type Pool = Vec; -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum Mode { - /// Instructs the unifier to solve two types for equality. - /// - /// For example, { n : Str }a ~ { n: Str, m : Str } will solve "a" to "{ m : Str }". - Eq, - /// Instructs the unifier to treat the right-hand-side of a constraint as - /// present in the left-hand-side, rather than strictly equal. - /// - /// For example, t1 += [A Str] says we should "add" the tag "A Str" to the type of "t1". - Present, +bitflags! { + pub struct Mode : u8 { + /// Instructs the unifier to solve two types for equality. + /// + /// For example, { n : Str }a ~ { n: Str, m : Str } will solve "a" to "{ m : Str }". + const EQ = 1 << 0; + /// Instructs the unifier to treat the right-hand-side of a constraint as + /// present in the left-hand-side, rather than strictly equal. + /// + /// For example, t1 += [A Str] says we should "add" the tag "A Str" to the type of "t1". + const PRESENT = 1 << 1; + /// Instructs the unifier to treat rigids exactly like flex vars. + /// Usually rigids can only unify with flex vars, because rigids are named and bound + /// explicitly. + /// However, when checking type ranges, as we do for `RangedNumber` types, we must loosen + /// this restriction because otherwise an admissable range will appear inadmissable. + /// For example, Int * is in the range . + const RIGID_AS_FLEX = 1 << 2; + } +} + +impl Mode { + fn is_eq(&self) -> bool { + debug_assert!(!self.contains(Mode::EQ | Mode::PRESENT)); + self.contains(Mode::EQ) + } + + fn is_present(&self) -> bool { + debug_assert!(!self.contains(Mode::EQ | Mode::PRESENT)); + self.contains(Mode::PRESENT) + } } #[derive(Debug)] @@ -245,7 +266,7 @@ fn check_valid_range( while let Some(&possible_var) = it.next() { let snapshot = subs.snapshot(); let old_pool = pool.clone(); - let outcome = unify_pool(subs, pool, var, possible_var, mode); + let outcome = unify_pool(subs, pool, var, possible_var, mode | Mode::RIGID_AS_FLEX); if outcome.is_empty() { // Okay, we matched some type in the range. subs.rollback_to(snapshot); @@ -334,14 +355,14 @@ fn unify_structure( // And if we see a flex variable on the right hand side of a presence // constraint, we know we need to open up the structure we're trying to unify with. - match (ctx.mode, flat_type) { - (Mode::Present, FlatType::TagUnion(tags, _ext)) => { + match (ctx.mode.is_present(), flat_type) { + (true, FlatType::TagUnion(tags, _ext)) => { let new_ext = subs.fresh_unnamed_flex_var(); let mut new_desc = ctx.first_desc.clone(); new_desc.content = Structure(FlatType::TagUnion(*tags, new_ext)); subs.set(ctx.first, new_desc); } - (Mode::Present, FlatType::FunctionOrTagUnion(tn, sym, _ext)) => { + (true, FlatType::FunctionOrTagUnion(tn, sym, _ext)) => { let new_ext = subs.fresh_unnamed_flex_var(); let mut new_desc = ctx.first_desc.clone(); new_desc.content = Structure(FlatType::FunctionOrTagUnion(*tn, *sym, new_ext)); @@ -384,7 +405,7 @@ fn unify_structure( Alias(_, _, real_var) => { // NB: not treating this as a presence constraint seems pivotal! I // can't quite figure out why, but it doesn't seem to impact other types. - unify_pool(subs, pool, ctx.first, *real_var, Mode::Eq) + unify_pool(subs, pool, ctx.first, *real_var, Mode::EQ) } RangedNumber(real_var, _) => unify_pool(subs, pool, ctx.first, *real_var, ctx.mode), Error => merge(subs, ctx, Error), @@ -759,8 +780,8 @@ fn unify_tag_union_new( let shared_tags = separate.in_both; - if let (Mode::Present, Content::Structure(FlatType::EmptyTagUnion)) = - (ctx.mode, subs.get(ext1).content) + if let (true, Content::Structure(FlatType::EmptyTagUnion)) = + (ctx.mode.is_present(), subs.get(ext1).content) { if !separate.only_in_2.is_empty() { // Create a new extension variable that we'll fill in with the @@ -789,7 +810,7 @@ fn unify_tag_union_new( if separate.only_in_1.is_empty() { if separate.only_in_2.is_empty() { - let ext_problems = if ctx.mode == Mode::Eq { + let ext_problems = if ctx.mode.is_eq() { unify_pool(subs, pool, ext1, ext2, ctx.mode) } else { // In a presence context, we don't care about ext2 being equal to ext1 @@ -843,7 +864,7 @@ fn unify_tag_union_new( let sub_record = fresh(subs, pool, ctx, Structure(flat_type)); // In a presence context, we don't care about ext2 being equal to tags1 - if ctx.mode == Mode::Eq { + if ctx.mode.is_eq() { let ext_problems = unify_pool(subs, pool, sub_record, ext2, ctx.mode); if !ext_problems.is_empty() { @@ -866,7 +887,7 @@ fn unify_tag_union_new( let unique_tags1 = UnionTags::insert_slices_into_subs(subs, separate.only_in_1); let unique_tags2 = UnionTags::insert_slices_into_subs(subs, separate.only_in_2); - let ext_content = if ctx.mode == Mode::Present { + let ext_content = if ctx.mode.is_present() { Content::Structure(FlatType::EmptyTagUnion) } else { Content::FlexVar(None) @@ -900,7 +921,7 @@ fn unify_tag_union_new( return ext1_problems; } - if ctx.mode == Mode::Eq { + if ctx.mode.is_eq() { let ext2_problems = unify_pool(subs, pool, sub1, ext2, ctx.mode); if !ext2_problems.is_empty() { subs.rollback_to(snapshot); @@ -1295,7 +1316,7 @@ fn unify_zip_slices( let l_var = subs[l_index]; let r_var = subs[r_index]; - problems.extend(unify_pool(subs, pool, l_var, r_var, Mode::Eq)); + problems.extend(unify_pool(subs, pool, l_var, r_var, Mode::EQ)); } problems @@ -1309,9 +1330,14 @@ fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content merge(subs, ctx, RigidVar(name.clone())) } RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _) | RangedNumber(..) => { - // Type mismatch! Rigid can only unify with flex, even if the - // rigid names are the same. - mismatch!("Rigid {:?} with {:?}", ctx.first, &other) + if !ctx.mode.contains(Mode::RIGID_AS_FLEX) { + // Type mismatch! Rigid can only unify with flex, even if the + // rigid names are the same. + mismatch!("Rigid {:?} with {:?}", ctx.first, &other) + } else { + // We are treating rigid vars as flex vars; admit this + merge(subs, ctx, other.clone()) + } } Error => { // Error propagates. diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 7da7f48487..345bcb51ce 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -1242,20 +1242,6 @@ mod test_reporting { Tip: You can convert between Int and Float using functions like `Num.toFloat` and `Num.round`. - - ── TYPE NOT IN RANGE ─────────────────────────────────────────────────────────── - - This expression is used in an unexpected way: - - 2│ x = if True then 3.14 else 4 - ^ - - It can only be used as a `I8`, `U8`, `I16`, `U16`, `I32`, `U32`, `I64`, `Nat`, `U64`, - `I128`, `U128`, `F32`, `F64`, or `Dec` - - But it is being used as: - - `Int` `*` "# ), ) From ff3596505e108a4aac7845883725e6f111238c93 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Feb 2022 13:49:49 -0500 Subject: [PATCH 503/541] Fix gen tests --- compiler/test_gen/src/gen_str.rs | 4 ++-- compiler/unify/src/unify.rs | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/compiler/test_gen/src/gen_str.rs b/compiler/test_gen/src/gen_str.rs index 4e5a74ecfd..2792b86fd7 100644 --- a/compiler/test_gen/src/gen_str.rs +++ b/compiler/test_gen/src/gen_str.rs @@ -36,7 +36,7 @@ fn str_split_empty_delimiter() { Str.countGraphemes str _ -> - -1 + 1729 "# ), @@ -66,7 +66,7 @@ fn str_split_bigger_delimiter_small_str() { Str.countGraphemes str _ -> - -1 + 1729 "# ), diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 4193525ca7..e953dfe149 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -88,6 +88,10 @@ impl Mode { debug_assert!(!self.contains(Mode::EQ | Mode::PRESENT)); self.contains(Mode::PRESENT) } + + fn as_eq(self) -> Self { + (self - Mode::PRESENT) | Mode::EQ + } } #[derive(Debug)] @@ -405,7 +409,7 @@ fn unify_structure( Alias(_, _, real_var) => { // NB: not treating this as a presence constraint seems pivotal! I // can't quite figure out why, but it doesn't seem to impact other types. - unify_pool(subs, pool, ctx.first, *real_var, Mode::EQ) + unify_pool(subs, pool, ctx.first, *real_var, ctx.mode.as_eq()) } RangedNumber(real_var, _) => unify_pool(subs, pool, ctx.first, *real_var, ctx.mode), Error => merge(subs, ctx, Error), From aaac22f6c2058308a0a4660190c1efd381ffd3ad Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Feb 2022 13:51:13 -0500 Subject: [PATCH 504/541] Respell typo --- compiler/unify/src/unify.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index e953dfe149..421cc6f61d 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -72,7 +72,7 @@ bitflags! { /// Usually rigids can only unify with flex vars, because rigids are named and bound /// explicitly. /// However, when checking type ranges, as we do for `RangedNumber` types, we must loosen - /// this restriction because otherwise an admissable range will appear inadmissable. + /// this restriction because otherwise an admissible range will appear inadmissible. /// For example, Int * is in the range . const RIGID_AS_FLEX = 1 << 2; } From 0d241f3c3caa50cf1bd4197f99885eec22e55765 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Feb 2022 14:55:22 -0500 Subject: [PATCH 505/541] Only expand `ErrorType::Range`'s type range when range unification fails --- compiler/solve/src/solve.rs | 48 +------------- compiler/types/src/subs.rs | 35 +++++++++- compiler/types/src/types.rs | 24 ++++++- compiler/unify/src/unify.rs | 42 +++++++----- reporting/src/error/type.rs | 59 +++++++++-------- reporting/tests/test_reporting.rs | 105 ++++++++++++++++++++++++++++-- 6 files changed, 215 insertions(+), 98 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 7d22ca16b6..8c2baee3e5 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -72,7 +72,6 @@ pub enum TypeError { CircularType(Region, Symbol, ErrorType), BadType(roc_types::types::Problem), UnexposedLookup(Symbol), - NotInRange(Region, ErrorType, Expected>), } #[derive(Clone, Debug, Default)] @@ -232,17 +231,6 @@ fn solve( problems.push(TypeError::BadType(problem)); - state - } - NotInRange(vars, typ, range) => { - introduce(subs, rank, pools, &vars); - - problems.push(TypeError::NotInRange( - *region, - typ, - expectation.clone().replace(range), - )); - state } } @@ -266,7 +254,7 @@ fn solve( state } - BadType(vars, _) | NotInRange(vars, _, _) => { + BadType(vars, _) => { introduce(subs, rank, pools, &vars); // ERROR NOT REPORTED @@ -333,17 +321,6 @@ fn solve( problems.push(TypeError::BadType(problem)); - state - } - NotInRange(vars, typ, range) => { - introduce(subs, rank, pools, &vars); - - problems.push(TypeError::NotInRange( - *region, - typ, - expectation.clone().replace(range), - )); - state } } @@ -414,18 +391,6 @@ fn solve( problems.push(TypeError::BadType(problem)); - state - } - NotInRange(vars, typ, range) => { - introduce(subs, rank, pools, &vars); - - problems.push(TypeError::NotInRange( - *region, - typ, - // TODO expectation.clone().replace(range), - Expected::NoExpectation(range), - )); - state } } @@ -720,17 +685,6 @@ fn solve( problems.push(TypeError::BadType(problem)); - state - } - NotInRange(vars, typ, range) => { - introduce(subs, rank, pools, &vars); - - problems.push(TypeError::NotInRange( - Region::zero(), - typ, - Expected::NoExpectation(range), - )); - state } } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index dcd6767b2f..94f3737aeb 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -44,11 +44,17 @@ impl fmt::Debug for Mark { } } -#[derive(Default)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum ErrorTypeContext { + None, + ExpandRanges, +} + struct ErrorTypeState { taken: MutSet, normals: u32, problems: Vec, + context: ErrorTypeContext, } #[derive(Clone)] @@ -1458,7 +1464,15 @@ impl Subs { explicit_substitute(self, x, y, z, &mut seen) } - pub fn var_to_error_type(&mut self, var: Variable) -> (ErrorType, Vec) { + pub fn var_to_error_type(&mut self, var: Variable) -> (ErrorType, Vec) { + self.var_to_error_type_contextual(var, ErrorTypeContext::None) + } + + pub fn var_to_error_type_contextual( + &mut self, + var: Variable, + context: ErrorTypeContext, + ) -> (ErrorType, Vec) { let names = get_var_names(self, var, ImMap::default()); let mut taken = MutSet::default(); @@ -1470,6 +1484,7 @@ impl Subs { taken, normals: 0, problems: Vec::new(), + context, }; (var_to_err_type(self, &mut state, var), state.problems) @@ -2864,7 +2879,21 @@ fn content_to_err_type( ErrorType::Alias(symbol, err_args, Box::new(err_type)) } - RangedNumber(typ, _) => var_to_err_type(subs, state, typ), + RangedNumber(typ, range) => { + let err_type = var_to_err_type(subs, state, typ); + + if state.context == ErrorTypeContext::ExpandRanges { + let mut types = Vec::with_capacity(range.len()); + for var_index in range { + let var = subs[var_index]; + + types.push(var_to_err_type(subs, state, var)); + } + ErrorType::Range(Box::new(err_type), types) + } else { + err_type + } + } Error => ErrorType::Error, } diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index fdf00f17ab..15fa24a597 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -1314,7 +1314,7 @@ pub enum Mismatch { InconsistentIfElse, InconsistentWhenBranches, CanonicalizationProblem, - TypeNotInRange(Variable, Vec), + TypeNotInRange, } #[derive(PartialEq, Eq, Clone, Hash)] @@ -1328,6 +1328,7 @@ pub enum ErrorType { RecursiveTagUnion(Box, SendMap>, TypeExt), Function(Vec, Box, Box), Alias(Symbol, Vec, Box), + Range(Box, Vec), Error, } @@ -1386,6 +1387,12 @@ impl ErrorType { }); t.add_names(taken); } + Range(typ, ts) => { + typ.add_names(taken); + ts.iter().for_each(|t| { + t.add_names(taken); + }); + } Error => {} } } @@ -1697,6 +1704,21 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens: write_debug_error_type_help(*rec, buf, Parens::Unnecessary); } + Range(typ, types) => { + write_debug_error_type_help(*typ, buf, parens); + buf.push('<'); + + let mut it = types.into_iter().peekable(); + while let Some(typ) = it.next() { + write_debug_error_type_help(typ, buf, Parens::Unnecessary); + + if it.peek().is_some() { + buf.push_str(", "); + } + } + + buf.push('>'); + } } } diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 421cc6f61d..befcf26b1c 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -3,8 +3,8 @@ use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_types::subs::Content::{self, *}; use roc_types::subs::{ - AliasVariables, Descriptor, FlatType, GetSubsSlice, Mark, OptVariable, RecordFields, Subs, - SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice, + AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable, + RecordFields, Subs, SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice, }; use roc_types::types::{ErrorType, Mismatch, RecordField}; @@ -108,7 +108,6 @@ pub enum Unified { Success(Pool), Failure(Pool, ErrorType, ErrorType), BadType(Pool, roc_types::types::Problem), - NotInRange(Pool, ErrorType, Vec), } type Outcome = Vec; @@ -120,16 +119,15 @@ pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable, mode: Mode) -> Uni if mismatches.is_empty() { Unified::Success(vars) - } else if let Some((typ, range)) = mismatches.iter().find_map(|mis| match mis { - Mismatch::TypeNotInRange(typ, range) => Some((typ, range)), - _ => None, - }) { - let (target_type, _) = subs.var_to_error_type(*typ); - let range_types = range.iter().map(|&v| subs.var_to_error_type(v).0).collect(); - Unified::NotInRange(vars, target_type, range_types) } else { - let (type1, mut problems) = subs.var_to_error_type(var1); - let (type2, problems2) = subs.var_to_error_type(var2); + let error_context = if mismatches.contains(&Mismatch::TypeNotInRange) { + ErrorTypeContext::ExpandRanges + } else { + ErrorTypeContext::None + }; + + let (type1, mut problems) = subs.var_to_error_type_contextual(var1, error_context); + let (type2, problems2) = subs.var_to_error_type_contextual(var2, error_context); problems.extend(problems2); @@ -285,7 +283,7 @@ fn check_valid_range( } } - return vec![Mismatch::TypeNotInRange(var, slice)]; + return vec![Mismatch::TypeNotInRange]; } #[inline(always)] @@ -337,8 +335,13 @@ fn unify_alias( } } Structure(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), - RangedNumber(other_real_var, _) => { - unify_pool(subs, pool, real_var, *other_real_var, ctx.mode) + RangedNumber(other_real_var, other_range_vars) => { + let outcome = unify_pool(subs, pool, real_var, *other_real_var, ctx.mode); + if outcome.is_empty() { + check_valid_range(subs, pool, real_var, *other_range_vars, ctx.mode) + } else { + outcome + } } Error => merge(subs, ctx, Error), } @@ -411,7 +414,14 @@ fn unify_structure( // can't quite figure out why, but it doesn't seem to impact other types. unify_pool(subs, pool, ctx.first, *real_var, ctx.mode.as_eq()) } - RangedNumber(real_var, _) => unify_pool(subs, pool, ctx.first, *real_var, ctx.mode), + RangedNumber(other_real_var, other_range_vars) => { + let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode); + if outcome.is_empty() { + check_valid_range(subs, pool, ctx.first, *other_range_vars, ctx.mode) + } else { + outcome + } + } Error => merge(subs, ctx, Error), } } diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index 2092640e39..4ac42f5fec 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -14,7 +14,6 @@ use ven_pretty::DocAllocator; const DUPLICATE_NAME: &str = "DUPLICATE NAME"; const ADD_ANNOTATIONS: &str = r#"Can more type annotations be added? Type annotations always help me give more specific messages, and I think they could help a lot in this case"#; -const TYPE_NOT_IN_RANGE: &str = r#"TYPE NOT IN RANGE"#; pub fn type_problem<'b>( alloc: &'b RocDocAllocator<'b>, @@ -60,31 +59,6 @@ pub fn type_problem<'b>( report(title, doc, filename) } - NotInRange(region, found, expected_range) => { - let mut range_choices = vec![alloc.reflow("It can only be used as a ")]; - let range = expected_range.get_type(); - let last = range.len() - 1; - for (i, choice) in range.into_iter().enumerate() { - if i == last && i == 1 { - range_choices.push(alloc.reflow(" or ")); - } else if i == last && i > 1 { - range_choices.push(alloc.reflow(", or ")); - } else if i > 0 { - range_choices.push(alloc.reflow(", ")); - } - - range_choices.push(to_doc(alloc, Parens::Unnecessary, choice)); - } - let doc = alloc.stack(vec![ - alloc.reflow("This expression is used in an unexpected way:"), - alloc.region(lines.convert_region(region)), - alloc.concat(range_choices), - alloc.text("But it is being used as:"), - to_doc(alloc, Parens::Unnecessary, found), - ]); - - report(TYPE_NOT_IN_RANGE.into(), doc, filename) - } BadType(type_problem) => { use roc_types::types::Problem::*; match type_problem { @@ -1806,6 +1780,15 @@ pub fn to_doc<'b>( ext_to_doc(alloc, ext), ) } + + Range(typ, range_types) => { + let typ = to_doc(alloc, parens, *typ); + let range_types = range_types + .into_iter() + .map(|arg| to_doc(alloc, Parens::Unnecessary, arg)) + .collect(); + report_text::range(alloc, typ, range_types) + } } } @@ -2660,6 +2643,29 @@ mod report_text { .append(rec_var) } } + + pub fn range<'b>( + alloc: &'b RocDocAllocator<'b>, + _encompassing_type: RocDocBuilder<'b>, + ranged_types: Vec>, + ) -> RocDocBuilder<'b> { + let mut doc = Vec::with_capacity(ranged_types.len() * 2); + + let last = ranged_types.len() - 1; + for (i, choice) in ranged_types.into_iter().enumerate() { + if i == last && i == 1 { + doc.push(alloc.reflow(" or ")); + } else if i == last && i > 1 { + doc.push(alloc.reflow(", or ")); + } else if i > 0 { + doc.push(alloc.reflow(", ")); + } + + doc.push(choice); + } + + alloc.concat(doc) + } } fn type_problem_to_pretty<'b>( @@ -2827,6 +2833,7 @@ fn type_problem_to_pretty<'b>( alloc.reflow(" value"), ]), ), + Range(..) => bad_rigid_var(x, alloc.reflow("a range")), } } diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 345bcb51ce..9af058362e 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -7852,6 +7852,37 @@ I need all branches in an `if` to have the same type! #[test] fn list_get_negative_number() { + report_problem_as( + indoc!( + r#" + List.get [1,2,3] -1 + "# + ), + // TODO: this error message could be improved, e.g. something like "This argument can + // be used as ... because of its literal value" + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 2nd argument to `get` is not what I expect: + + 1│ List.get [1,2,3] -1 + ^^ + + This argument is a number of type: + + I8, I16, I32, I64, I128, F32, F64, or Dec + + But `get` needs the 2nd argument to be: + + Nat + "# + ), + ) + } + + #[test] + fn list_get_negative_number_indirect() { report_problem_as( indoc!( r#" @@ -7861,18 +7892,82 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE NOT IN RANGE ─────────────────────────────────────────────────────────── + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── - This expression is used in an unexpected way: + The 2nd argument to `get` is not what I expect: 2│ List.get [1,2,3] a ^ - It can only be used as a `I64`, `I128`, `F32`, `F64`, or `Dec` + This `a` value is a: - But it is being used as: + I64, I128, F32, F64, or Dec - `Nat` + But `get` needs the 2nd argument to be: + + Nat + "# + ), + ) + } + + #[test] + fn list_get_negative_number_double_indirect() { + report_problem_as( + indoc!( + r#" + a = -9_223_372_036_854 + b = a + List.get [1,2,3] b + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 2nd argument to `get` is not what I expect: + + 3│ List.get [1,2,3] b + ^ + + This `b` value is a: + + I64, I128, F32, F64, or Dec + + But `get` needs the 2nd argument to be: + + Nat + "# + ), + ) + } + + #[test] + fn compare_unsigned_to_signed() { + report_problem_as( + indoc!( + r#" + when -1 is + 1u8 -> 1 + _ -> 1 + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 1st pattern in this `when` is causing a mismatch: + + 2│ 1u8 -> 1 + ^^^ + + The first pattern is trying to match integers: + + U8 + + But the expression between `when` and `is` has the type: + + I8, I16, I32, I64, I128, F32, F64, or Dec "# ), ) From a863e3cf1b2f0e7e5e8513bcd47ac1210a5959d7 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Feb 2022 15:01:29 -0500 Subject: [PATCH 506/541] Restore regressions --- reporting/tests/test_reporting.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 9af058362e..7a7abd9be2 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -1221,7 +1221,6 @@ mod test_reporting { x "# ), - // TODO FIXME the second error message is incomplete, should be removed indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── @@ -6103,6 +6102,12 @@ I need all branches in an `if` to have the same type! packages {} imports [Task] provides [ mainForHost ] + effects fx.Effect + { + putChar : I64 -> Effect {}, + putLine : Str -> Effect {}, + getLine : Effect Str + } "# ), indoc!( From 94ff9c39f1548bc9dafbbe8a38354b272e231dc3 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Feb 2022 15:02:46 -0500 Subject: [PATCH 507/541] Fix roc_ast compile errors --- ast/src/solve_type.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs index c437b78949..6bff9cd93b 100644 --- a/ast/src/solve_type.rs +++ b/ast/src/solve_type.rs @@ -225,7 +225,7 @@ fn solve<'a>( expectation.get_type_ref(), ); - match unify(subs, actual, expected, Mode::Eq) { + match unify(subs, actual, expected, Mode::EQ) { Success(vars) => { introduce(subs, rank, pools, &vars); @@ -252,7 +252,6 @@ fn solve<'a>( state } - NotInRange(_vars, _typ, _range) => todo!(), } } // Store(source, target, _filename, _linenr) => { @@ -319,7 +318,7 @@ fn solve<'a>( expectation.get_type_ref(), ); - match unify(subs, actual, expected, Mode::Eq) { + match unify(subs, actual, expected, Mode::EQ) { Success(vars) => { introduce(subs, rank, pools, &vars); @@ -347,7 +346,6 @@ fn solve<'a>( state } - NotInRange(_vars, _typ, _range) => todo!(), } } None => { @@ -391,7 +389,7 @@ fn solve<'a>( ); // TODO(ayazhafiz): presence constraints for Expr2/Type2 - match unify(subs, actual, expected, Mode::Eq) { + match unify(subs, actual, expected, Mode::EQ) { Success(vars) => { introduce(subs, rank, pools, &vars); @@ -418,7 +416,6 @@ fn solve<'a>( state } - NotInRange(_vars, _typ, _range) => todo!(), } } Let(let_con) => { @@ -700,7 +697,7 @@ fn solve<'a>( ); let includes = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, &tag_ty); - match unify(subs, actual, includes, Mode::Present) { + match unify(subs, actual, includes, Mode::PRESENT) { Success(vars) => { introduce(subs, rank, pools, &vars); @@ -728,7 +725,6 @@ fn solve<'a>( state } - NotInRange(_vars, _typ, _range) => todo!(), } } } From 40196185a0388b9e3c0a3faaed9f41412e681796 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Feb 2022 20:58:13 -0500 Subject: [PATCH 508/541] Specialize zero-argument thunks with the correct variable Previously we would pass the annotation down as the type-to-be-monomorphized for, but that would just mean the annotation would unify with itself. We instead want to use the variable the thunk is being used as to be the one unified with the thunk's annotation. Closes #2445 Closes #2446 --- compiler/mono/src/ir.rs | 3 +-- compiler/test_gen/src/gen_tags.rs | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 436212ba2e..3c736435d9 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -2941,8 +2941,7 @@ fn specialize_naked_symbol<'a>( symbol: Symbol, ) -> Stmt<'a> { if procs.is_module_thunk(symbol) { - let partial_proc = procs.get_partial_proc(symbol).unwrap(); - let fn_var = partial_proc.annotation; + let fn_var = variable; // This is a top-level declaration, which will code gen to a 0-arity thunk. let result = call_by_name( diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index b4abaf711f..89251ac981 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -1449,3 +1449,28 @@ fn issue_2365_monomorphize_tag_with_non_empty_ext_var_wrapped_nested() { u8 ) } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn issue_2445() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + none : [ None, Update a ] + none = None + + press : [ None, Update U8 ] + press = none + + main = + when press is + None -> 15 + Update _ -> 25 + "# + ), + 15, + i64 + ); +} From 1a1eaf7f68be8475be3e511fd59f531a15f03908 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Feb 2022 21:57:14 -0500 Subject: [PATCH 509/541] Normalize c/Roc callconv parameters when C returns by pointer Closes #2413 --- compiler/gen_llvm/src/llvm/build.rs | 13 ++++++---- compiler/load/tests/test_load.rs | 38 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 7fb646d4f4..9ca24e1739 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -3600,11 +3600,14 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>( let param_types = Vec::from_iter_in(roc_function.get_type().get_param_types(), env.arena); - // drop the "return pointer" if it exists on the roc function - // and the c function does not return via pointer - let param_types = match (&roc_return, &cc_return) { - (RocReturn::ByPointer, CCReturn::Return) => ¶m_types[1..], - _ => ¶m_types, + let (params, param_types) = match (&roc_return, &cc_return) { + // Drop the "return pointer" if it exists on the roc function + // and the c function does not return via pointer + (RocReturn::ByPointer, CCReturn::Return) => (¶ms[..], ¶m_types[1..]), + // Drop the return pointer the other way, if the C function returns by pointer but Roc + // doesn't + (RocReturn::Return, CCReturn::ByPointer) => (¶ms[1..], ¶m_types[..]), + _ => (¶ms[..], ¶m_types[..]), }; debug_assert_eq!(params.len(), param_types.len()); diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index 4634f65d75..8b4abdd7ca 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -648,4 +648,42 @@ mod test_load { Ok(_) => unreachable!("we expect failure here"), } } + + #[test] + // See https://github.com/rtfeldman/roc/issues/2413 + fn platform_exposes_main_return_by_pointer_issue() { + let modules = vec![ + ( + "platform/Package-Config.roc", + indoc!( + r#" + platform "examples/hello-world" + requires {} { main : { content: Str, other: Str } } + exposes [] + packages {} + imports [] + provides [ mainForHost ] + + mainForHost : { content: Str, other: Str } + mainForHost = main + "# + ), + ), + ( + "Main", + indoc!( + r#" + app "hello-world" + packages { pf: "platform" } + imports [] + provides [ main ] to pf + + main = { content: "Hello, World!\n", other: "" } + "# + ), + ), + ]; + + assert!(multiple_modules(modules).is_ok()); + } } From 4298cb7eabf00df0eeb80510655e530fb36c9778 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Tue, 8 Feb 2022 09:35:55 +0100 Subject: [PATCH 510/541] Added `cargo build` to BUILDING_FROM_SOURCE --- BUILDING_FROM_SOURCE.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index dc2740021f..7bae697da1 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -79,6 +79,12 @@ There are also alternative installation options at http://releases.llvm.org/down [Troubleshooting](#troubleshooting) +### Building + +Use `cargo build` to build the whole project. +Use `cargo run help` to see all subcommands. +To use the `repl` subcommand, execute `cargo run repl`. + ## Using Nix ### Install @@ -105,11 +111,10 @@ Now with nix installed, you just need to run one command: > Also, if you're on NixOS you'll need to enable opengl at the system-wide level. You can do this in configuration.nix with `hardware.opengl.enable = true;`. If you don't do this, nix-shell will fail! -You should be in a shell with everything needed to build already installed. Next run: - -`cargo run repl` - -You should be in a repl now. Have fun! +You should be in a shell with everything needed to build already installed. +Use `cargo run help` to see all subcommands. +To use the `repl` subcommand, execute `cargo run repl`. +Use `cargo build` to build the whole project. ### Extra tips From e93e0bfbbd9668cef32b3cebc6bdd9cdd9a5e319 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 3 Feb 2022 12:27:19 +0000 Subject: [PATCH 511/541] repl: copy mock wasm repl from external repo git@github.com:brian-carroll/mock-repl.git --- repl_wasm/mock-repl/.gitignore | 13 ++ repl_wasm/mock-repl/Cargo.toml | 15 ++ repl_wasm/mock-repl/README.md | 15 ++ repl_wasm/mock-repl/build.sh | 23 ++++ repl_wasm/mock-repl/deploy.sh | 12 ++ .../mock-repl/print_bytes_as_rust_code.js | 17 +++ repl_wasm/mock-repl/src/app.c | 22 +++ repl_wasm/mock-repl/src/byte_array.h | 5 + repl_wasm/mock-repl/src/index.html | 122 ++++++++++++++++ repl_wasm/mock-repl/src/lib.rs | 90 ++++++++++++ repl_wasm/mock-repl/src/repl.js | 130 ++++++++++++++++++ 11 files changed, 464 insertions(+) create mode 100644 repl_wasm/mock-repl/.gitignore create mode 100644 repl_wasm/mock-repl/Cargo.toml create mode 100644 repl_wasm/mock-repl/README.md create mode 100755 repl_wasm/mock-repl/build.sh create mode 100755 repl_wasm/mock-repl/deploy.sh create mode 100755 repl_wasm/mock-repl/print_bytes_as_rust_code.js create mode 100644 repl_wasm/mock-repl/src/app.c create mode 100644 repl_wasm/mock-repl/src/byte_array.h create mode 100644 repl_wasm/mock-repl/src/index.html create mode 100644 repl_wasm/mock-repl/src/lib.rs create mode 100644 repl_wasm/mock-repl/src/repl.js diff --git a/repl_wasm/mock-repl/.gitignore b/repl_wasm/mock-repl/.gitignore new file mode 100644 index 0000000000..3e44871a22 --- /dev/null +++ b/repl_wasm/mock-repl/.gitignore @@ -0,0 +1,13 @@ +/*.txt +/generated +/dist +/notes.md +src/generated_app_bytes.rs + +# wasm-pack +/pkg + +# Added by cargo + +/target +Cargo.lock diff --git a/repl_wasm/mock-repl/Cargo.toml b/repl_wasm/mock-repl/Cargo.toml new file mode 100644 index 0000000000..cd64da5624 --- /dev/null +++ b/repl_wasm/mock-repl/Cargo.toml @@ -0,0 +1,15 @@ +[package] +edition = "2021" +name = "mock-repl" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib"] + +[dependencies] +bumpalo = {version = "3.8.0", features = ["collections"]} +js-sys = "0.3.56" +wasm-bindgen = "0.2.79" +wasm-bindgen-futures = "0.4.29" diff --git a/repl_wasm/mock-repl/README.md b/repl_wasm/mock-repl/README.md new file mode 100644 index 0000000000..1a26b561ce --- /dev/null +++ b/repl_wasm/mock-repl/README.md @@ -0,0 +1,15 @@ +# A mockin' Roc REPL! + +This is a mock-up Web REPL for a fake compiler. The only valid inputs are the numbers 0-255! The output program is a Wasm module that counts backwards from that number to 1. + +The same web page should work with minimal adjustments whenever we manage to get a WebAssembly build of the Roc compiler working. But this way, I was able to build up the functionality more gradually. + +How it works + +- There are two Wasm modules: a compiler, and an app +- The compiler simply modifies a single byte in an otherwise fixed Wasm binary, using your input. +- The compiler sends the Wasm binary to JS, which runs it +- JS calls back into another function in the compiler that stringifies the result from the app +- JS reads the string and displays it under the input + +See it live on [GitHub Pages](https://brian-carroll.github.io/mock-repl/) diff --git a/repl_wasm/mock-repl/build.sh b/repl_wasm/mock-repl/build.sh new file mode 100755 index 0000000000..ab33809385 --- /dev/null +++ b/repl_wasm/mock-repl/build.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -ex + +app_wasm="generated/app.wasm" +app_include="generated/app_bytes.c" +app_rust="src/generated_app_bytes.rs" +comp_wasm="dist/compiler.wasm" + +mkdir -p generated dist +rm -f generated/* dist/* + +zig9 build-lib -target wasm32-freestanding-musl -dynamic src/app.c -femit-bin=$app_wasm + +./print_bytes_as_rust_code.js $app_wasm > $app_rust + +# wasm-pack build --target web --dev +wasm-pack build --target web --release + +cp src/index.html dist +cp src/repl.js dist +cp pkg/mock_repl_bg.wasm dist +cp pkg/mock_repl.js dist diff --git a/repl_wasm/mock-repl/deploy.sh b/repl_wasm/mock-repl/deploy.sh new file mode 100755 index 0000000000..925090dceb --- /dev/null +++ b/repl_wasm/mock-repl/deploy.sh @@ -0,0 +1,12 @@ +DEST=../mock-repl-deploy + +rm -rf $DEST/* + +cp dist/* $DEST/ + +VERSION_SHA=$(git rev-parse HEAD) + +cd $DEST +git add . +git commit -m "Deployed from $VERSION_SHA" +git push diff --git a/repl_wasm/mock-repl/print_bytes_as_rust_code.js b/repl_wasm/mock-repl/print_bytes_as_rust_code.js new file mode 100755 index 0000000000..563eb79bbb --- /dev/null +++ b/repl_wasm/mock-repl/print_bytes_as_rust_code.js @@ -0,0 +1,17 @@ +#!/usr/bin/env node + +const fs = require("fs"); + +const infile = process.argv[2]; +if (!infile) { + throw new Error("Need an input file argument") +} + +const buffer = fs.readFileSync(infile); + +console.log("pub const APP: &[u8] = &[") +buffer.forEach((byte) => { + const hex = byte.toString(16).padStart(2, '0'); + console.log(` 0x${hex},`); +}); +console.log(`];`) diff --git a/repl_wasm/mock-repl/src/app.c b/repl_wasm/mock-repl/src/app.c new file mode 100644 index 0000000000..89705c65b3 --- /dev/null +++ b/repl_wasm/mock-repl/src/app.c @@ -0,0 +1,22 @@ +#include +#include "byte_array.h" + +// The starting value of the countdown. We will modify this with the "compiler". +// It's not a very good compiler, all it can do is change this number. +int start_value = 22; + +char result_buffer[260]; + +ByteArray *run() +{ + ByteArray *result = (ByteArray*) result_buffer; + + size_t i = 0; + for (char x = start_value; x; --x) + { + result->bytes[i++] = x; + } + result->length = start_value; + + return result; +} diff --git a/repl_wasm/mock-repl/src/byte_array.h b/repl_wasm/mock-repl/src/byte_array.h new file mode 100644 index 0000000000..0222f20688 --- /dev/null +++ b/repl_wasm/mock-repl/src/byte_array.h @@ -0,0 +1,5 @@ +typedef struct +{ + size_t length; + char bytes[]; +} ByteArray; diff --git a/repl_wasm/mock-repl/src/index.html b/repl_wasm/mock-repl/src/index.html new file mode 100644 index 0000000000..bd068bdf53 --- /dev/null +++ b/repl_wasm/mock-repl/src/index.html @@ -0,0 +1,122 @@ + + + + Mock REPL + + +

+
+

A mockin' Roc REPL!

+

+ This is a mock-up Web REPL for a fake compiler. The + only valid inputs are the numbers 0-255! The output program is a Wasm + module that counts backwards from that number to 1. +

+

+ The same web page should work with minimal adjustments whenever we + manage to get a WebAssembly build of the Roc compiler working. But + this way, I was able to build up the functionality more gradually. +

+

How it works

+
    +
  • There are two Wasm modules: a compiler, and an app
  • +
  • + The compiler simply modifies a single byte in an otherwise fixed + Wasm binary, using your input. +
  • +
  • The compiler sends the Wasm binary to JS, which runs it
  • +
  • + JS calls back into another function in the compiler that stringifies + the result from the app +
  • +
  • JS takes the output string and displays it
  • +
+
+ +
+
+
+
+
+ +
+ +
+
+ + + diff --git a/repl_wasm/mock-repl/src/lib.rs b/repl_wasm/mock-repl/src/lib.rs new file mode 100644 index 0000000000..fba5027b57 --- /dev/null +++ b/repl_wasm/mock-repl/src/lib.rs @@ -0,0 +1,90 @@ +mod generated_app_bytes; + +use bumpalo::Bump; +use generated_app_bytes::APP; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; + +// Manually update these constants whenever app.c or the C compilation options change! +const COUNTDOWN_START_BYTE_OFFSET: usize = 0x172; +const DEFAULT_START_VALUE: u8 = 22; + +// Make sure that heap-allocated compiler data stays alive while the app is executing +struct AppIdentifiers { + dummy: String, +} +const MOCK_HEAP_DATA: &str = "This is just some dummy string data"; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(catch)] + pub async fn webrepl_execute( + app_bytes: &[u8], + app_memory_size_ptr: *mut usize, + ) -> Result<(), JsValue>; + pub fn webrepl_read_result(buffer_alloc_addr: *mut u8) -> usize; +} + +#[wasm_bindgen] +pub async fn webrepl_run(input_text: String) -> Result { + let arena = &Bump::new(); + + // Compile the app + let (app_bytes, identifiers) = compile(arena, input_text)?; + + // Execute the app (asynchronously in JS) + let mut app_final_memory_size: usize = 0; + let size_mut_ptr = (&mut app_final_memory_size) as *mut usize; + webrepl_execute(app_bytes, size_mut_ptr) + .await + .map_err(|js| format!("{:?}", js))?; + + // Get the address of the result in the app's memory, and a copy of its memory buffer + let app_memory_copy: &mut [u8] = arena.alloc_slice_fill_default(app_final_memory_size); + let app_result_addr = webrepl_read_result(app_memory_copy.as_mut_ptr()); + + // Create a String representation of the result value + let output_text = stringify(app_memory_copy, app_result_addr, identifiers); + + Ok(output_text) +} + +/// Compile the user's input code to a Wasm binary and some metadata +/// This is fake and will be replaced in the final version +fn compile(arena: &Bump, input_text: String) -> Result<(&[u8], AppIdentifiers), String> { + if APP[COUNTDOWN_START_BYTE_OFFSET] != DEFAULT_START_VALUE { + panic!( + "Template app.wasm changed! Did not find start value {} at offset 0x{:x}\n", + DEFAULT_START_VALUE, COUNTDOWN_START_BYTE_OFFSET + ); + } + + let countdown_start = input_text.parse::().map_err(|e| format!("{:?}", e))?; + + let app_copy = arena.alloc_slice_copy(APP); + app_copy[COUNTDOWN_START_BYTE_OFFSET] = countdown_start; + + let fake_types_and_names = AppIdentifiers { + dummy: MOCK_HEAP_DATA.to_string(), + }; + + Ok((app_copy, fake_types_and_names)) +} + +/// Create a String representation of the result value from the app +/// This is fake and will be replaced in the final version +fn stringify(app_memory_copy: &[u8], app_result_addr: usize, idents: AppIdentifiers) -> String { + // Get the bytes of the app's result struct (C ByteArray) + let result = &app_memory_copy[app_result_addr..]; + + // Parse the length of the C ByteArray + let mut length_bytes: [u8; 4] = Default::default(); + length_bytes.copy_from_slice(&result[0..4]); + let length = u32::from_le_bytes(length_bytes) as usize; + + assert_eq!(idents.dummy, MOCK_HEAP_DATA); + + // Stringify the numbers + let numbers = &result[4..][0..length]; + format!("{:?}", numbers) +} diff --git a/repl_wasm/mock-repl/src/repl.js b/repl_wasm/mock-repl/src/repl.js new file mode 100644 index 0000000000..44f98301d7 --- /dev/null +++ b/repl_wasm/mock-repl/src/repl.js @@ -0,0 +1,130 @@ +// wasm_bindgen's JS code expects our imported functions to be global +window.webrepl_execute = webrepl_execute; +window.webrepl_read_result = webrepl_read_result; +import * as mock_repl from "./mock_repl.js"; + +// ---------------------------------------------------------------------------- +// REPL state +// ---------------------------------------------------------------------------- + +const repl = { + elemHistory: document.getElementById("history-text"), + elemSourceInput: document.getElementById("source-input"), + + inputQueue: [], + inputHistory: [], + + textDecoder: new TextDecoder(), + textEncoder: new TextEncoder(), + + compiler: null, + + // Temporary storage for values passing back and forth between JS and Wasm + result: { addr: 0, buffer: new ArrayBuffer() }, +}; + +// Initialise +repl.elemSourceInput.addEventListener("change", onInputChange); +mock_repl.default().then((instance) => { + repl.compiler = instance; +}); + +// ---------------------------------------------------------------------------- +// Handle inputs +// ---------------------------------------------------------------------------- + +function onInputChange(event) { + const inputText = event.target.value; + event.target.value = ""; + + repl.inputQueue.push(inputText); + if (repl.inputQueue.length === 1) { + processInputQueue(); + } +} + +// Use a queue just in case we somehow get inputs very fast +// This is definitely an edge case, but maybe could happen from copy/paste? +// It allows us to rely on having only one input at a time +async function processInputQueue() { + while (repl.inputQueue.length) { + const inputText = repl.inputQueue[0]; + const historyIndex = createHistoryEntry(inputText); + + let outputText; + let ok = true; + try { + outputText = await mock_repl.webrepl_run(inputText); + } catch (e) { + outputText = `${e}`; + ok = false; + } + + updateHistoryEntry(historyIndex, ok, outputText); + repl.inputQueue.shift(); + } +} + +// ---------------------------------------------------------------------------- +// Callbacks to JS from Rust +// ---------------------------------------------------------------------------- + +// Transform the app bytes into a Wasm module and execute it +// Cache the result but don't send it to Rust yet, as it needs to allocate space first. +// Then it will immediately call webrepl_read_result. +async function webrepl_execute(app_bytes, app_memory_size_ptr) { + const { instance: app } = await WebAssembly.instantiate(app_bytes); + + const addr = app.exports.run(); + const { buffer } = app.exports.memory; + repl.result = { addr, buffer }; + + // Tell Rust how much space to reserve for its copy of the app's memory buffer. + // The app might have grown its heap while running, so we couldn't predict it beforehand. + // Write the result to memory instead of returning, since wasm_bindgen only allows + // imported async functions to return JsValue, which has some conversion overhead. + const compilerMemory32 = new Uint32Array(repl.compiler.memory.buffer); + compilerMemory32[app_memory_size_ptr >> 2] = buffer.byteLength; +} + +// Now that the Rust app has allocated space for the app's memory buffer, we can copy it +function webrepl_read_result(buffer_alloc_addr) { + const { addr, buffer } = repl.result; + const appMemory = new Uint8Array(buffer); + const compilerMemory = new Uint8Array(repl.compiler.memory.buffer); + compilerMemory.set(appMemory, buffer_alloc_addr); + return addr; +} + +// ---------------------------------------------------------------------------- +// Rendering +// ---------------------------------------------------------------------------- + +function createHistoryEntry(inputText) { + const historyIndex = repl.inputHistory.length; + repl.inputHistory.push(inputText); + + const inputElem = document.createElement("div"); + inputElem.textContent = "> " + inputText; + inputElem.classList.add("input"); + + const historyItem = document.createElement("div"); + historyItem.appendChild(inputElem); + + repl.elemHistory.appendChild(historyItem); + repl.elemHistory.scrollTop = repl.elemHistory.scrollHeight; + + return historyIndex; +} + +function updateHistoryEntry(index, ok, outputText) { + const outputElem = document.createElement("div"); + outputElem.textContent = outputText; + outputElem.classList.add("output"); + outputElem.classList.add(ok ? "output-ok" : "output-error"); + + const historyItem = repl.elemHistory.childNodes[index]; + historyItem.appendChild(outputElem); + + repl.elemHistory.scrollTop = repl.elemHistory.scrollHeight; +} From 4e855f1094c571cc700ba3313f6749afcc7881d5 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 3 Feb 2022 12:35:15 +0000 Subject: [PATCH 512/541] repl: delete and move files in repl_wasm --- Cargo.lock | 32 +++++++++++-------- repl_wasm/.gitignore | 6 ++++ repl_wasm/Cargo.toml | 8 ++++- repl_wasm/{mock-repl => }/build.sh | 0 repl_wasm/mock-repl/.gitignore | 13 -------- repl_wasm/mock-repl/Cargo.toml | 15 --------- repl_wasm/mock-repl/README.md | 15 --------- repl_wasm/mock-repl/deploy.sh | 12 ------- .../mock-repl/print_bytes_as_rust_code.js | 17 ---------- repl_wasm/mock-repl/src/app.c | 22 ------------- repl_wasm/mock-repl/src/byte_array.h | 5 --- repl_wasm/{mock-repl => }/src/index.html | 0 repl_wasm/{mock-repl => }/src/repl.js | 0 .../{mock-repl/src/lib.rs => src/repl.rs} | 0 14 files changed, 31 insertions(+), 114 deletions(-) create mode 100644 repl_wasm/.gitignore rename repl_wasm/{mock-repl => }/build.sh (100%) delete mode 100644 repl_wasm/mock-repl/.gitignore delete mode 100644 repl_wasm/mock-repl/Cargo.toml delete mode 100644 repl_wasm/mock-repl/README.md delete mode 100755 repl_wasm/mock-repl/deploy.sh delete mode 100755 repl_wasm/mock-repl/print_bytes_as_rust_code.js delete mode 100644 repl_wasm/mock-repl/src/app.c delete mode 100644 repl_wasm/mock-repl/src/byte_array.h rename repl_wasm/{mock-repl => }/src/index.html (100%) rename repl_wasm/{mock-repl => }/src/repl.js (100%) rename repl_wasm/{mock-repl/src/lib.rs => src/repl.rs} (100%) diff --git a/Cargo.lock b/Cargo.lock index 00cfbd1c30..c6e0266c34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1865,9 +1865,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.55" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" dependencies = [ "wasm-bindgen", ] @@ -3720,8 +3720,12 @@ dependencies = [ name = "roc_repl_wasm" version = "0.1.0" dependencies = [ + "bumpalo", + "js-sys", "roc_parse", "roc_repl_eval", + "wasm-bindgen", + "wasm-bindgen-futures", ] [[package]] @@ -4684,9 +4688,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -4694,9 +4698,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" dependencies = [ "bumpalo", "lazy_static", @@ -4709,9 +4713,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -4721,9 +4725,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4731,9 +4735,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" dependencies = [ "proc-macro2", "quote", @@ -4744,9 +4748,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" [[package]] name = "wasmer" diff --git a/repl_wasm/.gitignore b/repl_wasm/.gitignore new file mode 100644 index 0000000000..43ad75e279 --- /dev/null +++ b/repl_wasm/.gitignore @@ -0,0 +1,6 @@ +/*.txt +/dist +/notes.md + +# wasm-pack +/pkg diff --git a/repl_wasm/Cargo.toml b/repl_wasm/Cargo.toml index e3e6ceae95..1cb38a3ee1 100644 --- a/repl_wasm/Cargo.toml +++ b/repl_wasm/Cargo.toml @@ -3,8 +3,14 @@ edition = "2021" name = "roc_repl_wasm" version = "0.1.0" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib"] [dependencies] +bumpalo = {version = "3.8.0", features = ["collections"]} +js-sys = "0.3.56" +wasm-bindgen = "0.2.79" +wasm-bindgen-futures = "0.4.29" + roc_parse = {path = "../compiler/parse"} roc_repl_eval = {path = "../repl_eval"} diff --git a/repl_wasm/mock-repl/build.sh b/repl_wasm/build.sh similarity index 100% rename from repl_wasm/mock-repl/build.sh rename to repl_wasm/build.sh diff --git a/repl_wasm/mock-repl/.gitignore b/repl_wasm/mock-repl/.gitignore deleted file mode 100644 index 3e44871a22..0000000000 --- a/repl_wasm/mock-repl/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -/*.txt -/generated -/dist -/notes.md -src/generated_app_bytes.rs - -# wasm-pack -/pkg - -# Added by cargo - -/target -Cargo.lock diff --git a/repl_wasm/mock-repl/Cargo.toml b/repl_wasm/mock-repl/Cargo.toml deleted file mode 100644 index cd64da5624..0000000000 --- a/repl_wasm/mock-repl/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -edition = "2021" -name = "mock-repl" -version = "0.1.0" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib"] - -[dependencies] -bumpalo = {version = "3.8.0", features = ["collections"]} -js-sys = "0.3.56" -wasm-bindgen = "0.2.79" -wasm-bindgen-futures = "0.4.29" diff --git a/repl_wasm/mock-repl/README.md b/repl_wasm/mock-repl/README.md deleted file mode 100644 index 1a26b561ce..0000000000 --- a/repl_wasm/mock-repl/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# A mockin' Roc REPL! - -This is a mock-up Web REPL for a fake compiler. The only valid inputs are the numbers 0-255! The output program is a Wasm module that counts backwards from that number to 1. - -The same web page should work with minimal adjustments whenever we manage to get a WebAssembly build of the Roc compiler working. But this way, I was able to build up the functionality more gradually. - -How it works - -- There are two Wasm modules: a compiler, and an app -- The compiler simply modifies a single byte in an otherwise fixed Wasm binary, using your input. -- The compiler sends the Wasm binary to JS, which runs it -- JS calls back into another function in the compiler that stringifies the result from the app -- JS reads the string and displays it under the input - -See it live on [GitHub Pages](https://brian-carroll.github.io/mock-repl/) diff --git a/repl_wasm/mock-repl/deploy.sh b/repl_wasm/mock-repl/deploy.sh deleted file mode 100755 index 925090dceb..0000000000 --- a/repl_wasm/mock-repl/deploy.sh +++ /dev/null @@ -1,12 +0,0 @@ -DEST=../mock-repl-deploy - -rm -rf $DEST/* - -cp dist/* $DEST/ - -VERSION_SHA=$(git rev-parse HEAD) - -cd $DEST -git add . -git commit -m "Deployed from $VERSION_SHA" -git push diff --git a/repl_wasm/mock-repl/print_bytes_as_rust_code.js b/repl_wasm/mock-repl/print_bytes_as_rust_code.js deleted file mode 100755 index 563eb79bbb..0000000000 --- a/repl_wasm/mock-repl/print_bytes_as_rust_code.js +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env node - -const fs = require("fs"); - -const infile = process.argv[2]; -if (!infile) { - throw new Error("Need an input file argument") -} - -const buffer = fs.readFileSync(infile); - -console.log("pub const APP: &[u8] = &[") -buffer.forEach((byte) => { - const hex = byte.toString(16).padStart(2, '0'); - console.log(` 0x${hex},`); -}); -console.log(`];`) diff --git a/repl_wasm/mock-repl/src/app.c b/repl_wasm/mock-repl/src/app.c deleted file mode 100644 index 89705c65b3..0000000000 --- a/repl_wasm/mock-repl/src/app.c +++ /dev/null @@ -1,22 +0,0 @@ -#include -#include "byte_array.h" - -// The starting value of the countdown. We will modify this with the "compiler". -// It's not a very good compiler, all it can do is change this number. -int start_value = 22; - -char result_buffer[260]; - -ByteArray *run() -{ - ByteArray *result = (ByteArray*) result_buffer; - - size_t i = 0; - for (char x = start_value; x; --x) - { - result->bytes[i++] = x; - } - result->length = start_value; - - return result; -} diff --git a/repl_wasm/mock-repl/src/byte_array.h b/repl_wasm/mock-repl/src/byte_array.h deleted file mode 100644 index 0222f20688..0000000000 --- a/repl_wasm/mock-repl/src/byte_array.h +++ /dev/null @@ -1,5 +0,0 @@ -typedef struct -{ - size_t length; - char bytes[]; -} ByteArray; diff --git a/repl_wasm/mock-repl/src/index.html b/repl_wasm/src/index.html similarity index 100% rename from repl_wasm/mock-repl/src/index.html rename to repl_wasm/src/index.html diff --git a/repl_wasm/mock-repl/src/repl.js b/repl_wasm/src/repl.js similarity index 100% rename from repl_wasm/mock-repl/src/repl.js rename to repl_wasm/src/repl.js diff --git a/repl_wasm/mock-repl/src/lib.rs b/repl_wasm/src/repl.rs similarity index 100% rename from repl_wasm/mock-repl/src/lib.rs rename to repl_wasm/src/repl.rs From 8c3d272a789d9d36fac0e6605a130bbbcb7b6f6c Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 3 Feb 2022 15:31:10 +0000 Subject: [PATCH 513/541] repl: delete mock code and rename Wasm/Js interface functions --- repl_wasm/src/lib.rs | 9 ++++--- repl_wasm/src/repl.js | 10 ++++---- repl_wasm/src/repl.rs | 59 +++---------------------------------------- 3 files changed, 14 insertions(+), 64 deletions(-) diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index d5e0c58a11..35ff90468d 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -4,7 +4,8 @@ use roc_parse::ast::Expr; use roc_repl_eval::ReplApp; pub struct WasmReplApp<'a> { - bytes: &'a [u8], + module: &'a [u8], + copied_memory: &'a [u8], } macro_rules! deref_number { @@ -12,7 +13,7 @@ macro_rules! deref_number { fn $name(&self, address: usize) -> $t { const N: usize = size_of::<$t>(); let mut array = [0; N]; - array.copy_from_slice(&self.bytes[address..][..N]); + array.copy_from_slice(&self.copied_memory[address..][..N]); <$t>::from_le_bytes(array) } }; @@ -20,7 +21,7 @@ macro_rules! deref_number { impl<'a> ReplApp for WasmReplApp<'a> { fn deref_bool(&self, address: usize) -> bool { - self.bytes[address] != 0 + self.copied_memory[address] != 0 } deref_number!(deref_u8, u8); @@ -43,7 +44,7 @@ impl<'a> ReplApp for WasmReplApp<'a> { fn deref_str(&self, addr: usize) -> &str { let elems_addr = self.deref_usize(addr); let len = self.deref_usize(addr + size_of::()); - let bytes = &self.bytes[elems_addr..][..len]; + let bytes = &self.copied_memory[elems_addr..][..len]; std::str::from_utf8(bytes).unwrap() } diff --git a/repl_wasm/src/repl.js b/repl_wasm/src/repl.js index 44f98301d7..2cbc6a54c8 100644 --- a/repl_wasm/src/repl.js +++ b/repl_wasm/src/repl.js @@ -1,6 +1,6 @@ // wasm_bindgen's JS code expects our imported functions to be global -window.webrepl_execute = webrepl_execute; -window.webrepl_read_result = webrepl_read_result; +window.js_create_and_run_app = js_create_and_run_app; +window.js_copy_app_memory = js_copy_app_memory; import * as mock_repl from "./mock_repl.js"; // ---------------------------------------------------------------------------- @@ -71,8 +71,8 @@ async function processInputQueue() { // Transform the app bytes into a Wasm module and execute it // Cache the result but don't send it to Rust yet, as it needs to allocate space first. -// Then it will immediately call webrepl_read_result. -async function webrepl_execute(app_bytes, app_memory_size_ptr) { +// Then it will immediately call js_copy_app_memory. +async function js_create_and_run_app(app_bytes, app_memory_size_ptr) { const { instance: app } = await WebAssembly.instantiate(app_bytes); const addr = app.exports.run(); @@ -88,7 +88,7 @@ async function webrepl_execute(app_bytes, app_memory_size_ptr) { } // Now that the Rust app has allocated space for the app's memory buffer, we can copy it -function webrepl_read_result(buffer_alloc_addr) { +function js_copy_app_memory(buffer_alloc_addr) { const { addr, buffer } = repl.result; const appMemory = new Uint8Array(buffer); const compilerMemory = new Uint8Array(repl.compiler.memory.buffer); diff --git a/repl_wasm/src/repl.rs b/repl_wasm/src/repl.rs index fba5027b57..94265a37d6 100644 --- a/repl_wasm/src/repl.rs +++ b/repl_wasm/src/repl.rs @@ -1,28 +1,17 @@ mod generated_app_bytes; use bumpalo::Bump; -use generated_app_bytes::APP; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; -// Manually update these constants whenever app.c or the C compilation options change! -const COUNTDOWN_START_BYTE_OFFSET: usize = 0x172; -const DEFAULT_START_VALUE: u8 = 22; - -// Make sure that heap-allocated compiler data stays alive while the app is executing -struct AppIdentifiers { - dummy: String, -} -const MOCK_HEAP_DATA: &str = "This is just some dummy string data"; - #[wasm_bindgen] extern "C" { #[wasm_bindgen(catch)] - pub async fn webrepl_execute( + pub async fn js_create_and_run_app( app_bytes: &[u8], app_memory_size_ptr: *mut usize, ) -> Result<(), JsValue>; - pub fn webrepl_read_result(buffer_alloc_addr: *mut u8) -> usize; + pub fn js_copy_app_memory(buffer_alloc_addr: *mut u8) -> usize; } #[wasm_bindgen] @@ -35,56 +24,16 @@ pub async fn webrepl_run(input_text: String) -> Result { // Execute the app (asynchronously in JS) let mut app_final_memory_size: usize = 0; let size_mut_ptr = (&mut app_final_memory_size) as *mut usize; - webrepl_execute(app_bytes, size_mut_ptr) + js_create_and_run_app(app_bytes, size_mut_ptr) .await .map_err(|js| format!("{:?}", js))?; // Get the address of the result in the app's memory, and a copy of its memory buffer let app_memory_copy: &mut [u8] = arena.alloc_slice_fill_default(app_final_memory_size); - let app_result_addr = webrepl_read_result(app_memory_copy.as_mut_ptr()); + let app_result_addr = js_copy_app_memory(app_memory_copy.as_mut_ptr()); // Create a String representation of the result value let output_text = stringify(app_memory_copy, app_result_addr, identifiers); Ok(output_text) } - -/// Compile the user's input code to a Wasm binary and some metadata -/// This is fake and will be replaced in the final version -fn compile(arena: &Bump, input_text: String) -> Result<(&[u8], AppIdentifiers), String> { - if APP[COUNTDOWN_START_BYTE_OFFSET] != DEFAULT_START_VALUE { - panic!( - "Template app.wasm changed! Did not find start value {} at offset 0x{:x}\n", - DEFAULT_START_VALUE, COUNTDOWN_START_BYTE_OFFSET - ); - } - - let countdown_start = input_text.parse::().map_err(|e| format!("{:?}", e))?; - - let app_copy = arena.alloc_slice_copy(APP); - app_copy[COUNTDOWN_START_BYTE_OFFSET] = countdown_start; - - let fake_types_and_names = AppIdentifiers { - dummy: MOCK_HEAP_DATA.to_string(), - }; - - Ok((app_copy, fake_types_and_names)) -} - -/// Create a String representation of the result value from the app -/// This is fake and will be replaced in the final version -fn stringify(app_memory_copy: &[u8], app_result_addr: usize, idents: AppIdentifiers) -> String { - // Get the bytes of the app's result struct (C ByteArray) - let result = &app_memory_copy[app_result_addr..]; - - // Parse the length of the C ByteArray - let mut length_bytes: [u8; 4] = Default::default(); - length_bytes.copy_from_slice(&result[0..4]); - let length = u32::from_le_bytes(length_bytes) as usize; - - assert_eq!(idents.dummy, MOCK_HEAP_DATA); - - // Stringify the numbers - let numbers = &result[4..][0..length]; - format!("{:?}", numbers) -} From fdf8363b7ee86cca686afc021caa1cb7d07b36a4 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 4 Feb 2022 09:56:32 +0000 Subject: [PATCH 514/541] repl: Separate app from env --- repl_cli/src/lib.rs | 2 +- repl_eval/src/eval.rs | 212 +++++++++++++++++++++++------------------- 2 files changed, 117 insertions(+), 97 deletions(-) diff --git a/repl_cli/src/lib.rs b/repl_cli/src/lib.rs index 8c64ee17b1..8b96e2affe 100644 --- a/repl_cli/src/lib.rs +++ b/repl_cli/src/lib.rs @@ -292,7 +292,7 @@ fn gen_and_eval_llvm<'a>( let res_answer = jit_to_ast( &arena, - &app, + app, main_fn_name, main_fn_layout, content, diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index 4cb651bdda..e87ab1e7d0 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -18,10 +18,9 @@ use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, Union use crate::ReplApp; -struct Env<'a, 'env, A> { +struct Env<'a, 'env> { arena: &'a Bump, subs: &'env Subs, - app: &'a A, target_info: TargetInfo, interns: &'env Interns, home: ModuleId, @@ -42,7 +41,7 @@ pub enum ToAstProblem { #[allow(clippy::too_many_arguments)] pub fn jit_to_ast<'a, A: ReplApp>( arena: &'a Bump, - app: &'a A, + app: A, main_fn_name: &str, layout: ProcLayout<'a>, content: &'a Content, @@ -54,7 +53,6 @@ pub fn jit_to_ast<'a, A: ReplApp>( let env = Env { arena, subs, - app, target_info, interns, home, @@ -66,7 +64,7 @@ pub fn jit_to_ast<'a, A: ReplApp>( result, } => { // this is a thunk - jit_to_ast_help(&env, main_fn_name, &result, content) + jit_to_ast_help(&env, app, main_fn_name, &result, content) } _ => Err(ToAstProblem::FunctionLayout), } @@ -86,8 +84,8 @@ enum NewtypeKind<'a> { /// /// The returned list of newtype containers is ordered by increasing depth. As an example, /// `A ({b : C 123})` will have the unrolled list `[Tag(A), RecordField(b), Tag(C)]`. -fn unroll_newtypes<'a, A: ReplApp>( - env: &Env<'a, 'a, A>, +fn unroll_newtypes<'a>( + env: &Env<'a, 'a>, mut content: &'a Content, ) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) { let mut newtype_containers = Vec::with_capacity_in(1, env.arena); @@ -120,8 +118,8 @@ fn unroll_newtypes<'a, A: ReplApp>( } } -fn apply_newtypes<'a, A: ReplApp>( - env: &Env<'a, '_, A>, +fn apply_newtypes<'a>( + env: &Env<'a, '_>, newtype_containers: Vec<'a, NewtypeKind<'a>>, mut expr: Expr<'a>, ) -> Expr<'a> { @@ -148,25 +146,22 @@ fn apply_newtypes<'a, A: ReplApp>( expr } -fn unroll_aliases<'a, A: ReplApp>(env: &Env<'a, 'a, A>, mut content: &'a Content) -> &'a Content { +fn unroll_aliases<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content { while let Content::Alias(_, _, real) = content { content = env.subs.get_content_without_compacting(*real); } content } -fn unroll_recursion_var<'a, A: ReplApp>( - env: &Env<'a, 'a, A>, - mut content: &'a Content, -) -> &'a Content { +fn unroll_recursion_var<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content { while let Content::RecursionVar { structure, .. } = content { content = env.subs.get_content_without_compacting(*structure); } content } -fn get_tags_vars_and_variant<'a, A: ReplApp>( - env: &Env<'a, '_, A>, +fn get_tags_vars_and_variant<'a>( + env: &Env<'a, '_>, tags: &UnionTags, opt_rec_var: Option, ) -> (MutMap>, UnionVariant<'a>) { @@ -184,7 +179,8 @@ fn get_tags_vars_and_variant<'a, A: ReplApp>( } fn expr_of_tag<'a, A: ReplApp>( - env: &Env<'a, 'a, A>, + env: &Env<'a, 'a>, + app: &A, data_addr: usize, tag_name: &TagName, arg_layouts: &'a [Layout<'a>], @@ -198,7 +194,7 @@ fn expr_of_tag<'a, A: ReplApp>( // NOTE assumes the data bytes are the first bytes let it = arg_vars.iter().copied().zip(arg_layouts.iter()); - let output = sequence_of_expr(env, data_addr, it, when_recursive); + let output = sequence_of_expr(env, app, data_addr, it, when_recursive); let output = output.into_bump_slice(); Expr::Apply(loc_tag_expr, output, CalledVia::Space) @@ -207,7 +203,8 @@ fn expr_of_tag<'a, A: ReplApp>( /// Gets the tag ID of a union variant, assuming that the tag ID is stored alongside (after) the /// tag data. The caller is expected to check that the tag ID is indeed stored this way. fn tag_id_from_data<'a, A: ReplApp>( - env: &Env<'a, 'a, A>, + env: &Env<'a, 'a>, + app: &A, union_layout: UnionLayout, data_addr: usize, ) -> i64 { @@ -217,13 +214,13 @@ fn tag_id_from_data<'a, A: ReplApp>( let tag_id_addr = data_addr + offset as usize; match union_layout.tag_id_builtin() { - Builtin::Bool => env.app.deref_bool(tag_id_addr) as i64, - Builtin::Int(IntWidth::U8) => env.app.deref_u8(tag_id_addr) as i64, - Builtin::Int(IntWidth::U16) => env.app.deref_u16(tag_id_addr) as i64, + Builtin::Bool => app.deref_bool(tag_id_addr) as i64, + Builtin::Int(IntWidth::U8) => app.deref_u8(tag_id_addr) as i64, + Builtin::Int(IntWidth::U16) => app.deref_u16(tag_id_addr) as i64, Builtin::Int(IntWidth::U64) => { // used by non-recursive unions at the // moment, remove if that is no longer the case - env.app.deref_i64(tag_id_addr) + app.deref_i64(tag_id_addr) } _ => unreachable!("invalid tag id layout"), } @@ -234,12 +231,13 @@ fn tag_id_from_data<'a, A: ReplApp>( /// - the tag ID /// - the address of the data of the union variant, unmasked if the pointer held the tag ID fn tag_id_from_recursive_ptr<'a, A: ReplApp>( - env: &Env<'a, 'a, A>, + env: &Env<'a, 'a>, + app: &A, union_layout: UnionLayout, rec_addr: usize, ) -> (i64, usize) { let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.target_info); - let addr_with_id = env.app.deref_usize(rec_addr); + let addr_with_id = app.deref_usize(rec_addr); if tag_in_ptr { let (_, tag_id_mask) = UnionLayout::tag_id_pointer_bits_and_mask(env.target_info); @@ -247,7 +245,7 @@ fn tag_id_from_recursive_ptr<'a, A: ReplApp>( let data_addr = addr_with_id & !tag_id_mask; (tag_id as i64, data_addr) } else { - let tag_id = tag_id_from_data(env, union_layout, addr_with_id); + let tag_id = tag_id_from_data(env, app, union_layout, addr_with_id); (tag_id, addr_with_id) } } @@ -258,7 +256,8 @@ const OPAQUE_FUNCTION: Expr = Expr::Var { }; fn jit_to_ast_help<'a, A: ReplApp>( - env: &Env<'a, 'a, A>, + env: &Env<'a, 'a>, + mut app: A, main_fn_name: &str, layout: &Layout<'a>, content: &'a Content, @@ -266,15 +265,15 @@ fn jit_to_ast_help<'a, A: ReplApp>( let (newtype_containers, content) = unroll_newtypes(env, content); let content = unroll_aliases(env, content); let result = match layout { - Layout::Builtin(Builtin::Bool) => Ok(env - .app - .call_function(main_fn_name, |num: bool| bool_to_ast(env, num, content))), + Layout::Builtin(Builtin::Bool) => Ok(app.call_function(main_fn_name, |num: bool| { + bool_to_ast(env, &app, num, content) + })), Layout::Builtin(Builtin::Int(int_width)) => { use IntWidth::*; macro_rules! helper { ($ty:ty) => { - env.app.call_function(main_fn_name, |num: $ty| { + app.call_function(main_fn_name, |num: $ty| { num_to_ast(env, number_literal_to_ast(env.arena, num), content) }) }; @@ -283,8 +282,7 @@ fn jit_to_ast_help<'a, A: ReplApp>( let result = match int_width { U8 | I8 => { // NOTE: `helper!` does not handle 8-bit numbers yet - env.app - .call_function(main_fn_name, |num: u8| byte_to_ast(env, num, content)) + app.call_function(main_fn_name, |num: u8| byte_to_ast(env, &app, num, content)) } U16 => helper!(u16), U32 => helper!(u32), @@ -303,7 +301,7 @@ fn jit_to_ast_help<'a, A: ReplApp>( macro_rules! helper { ($ty:ty) => { - env.app.call_function(main_fn_name, |num: $ty| { + app.call_function(main_fn_name, |num: $ty| { num_to_ast(env, number_literal_to_ast(env.arena, num), content) }) }; @@ -317,15 +315,13 @@ fn jit_to_ast_help<'a, A: ReplApp>( Ok(result) } - Layout::Builtin(Builtin::Str) => { - Ok(env.app.call_function(main_fn_name, |string: &'static str| { + Layout::Builtin(Builtin::Str) => Ok(app + .call_function(main_fn_name, |string: &'static str| { str_to_ast(env.arena, env.arena.alloc(string)) - })) - } - Layout::Builtin(Builtin::List(elem_layout)) => Ok(env - .app + })), + Layout::Builtin(Builtin::List(elem_layout)) => Ok(app .call_function(main_fn_name, |(addr, len): (usize, usize)| { - list_to_ast(env, addr, len, elem_layout, content) + list_to_ast(env, &app, addr, len, elem_layout, content) })), Layout::Builtin(other) => { todo!("add support for rendering builtin {:?} to the REPL", other) @@ -333,10 +329,11 @@ fn jit_to_ast_help<'a, A: ReplApp>( Layout::Struct(field_layouts) => { let struct_addr_to_ast = |addr: usize| match content { Content::Structure(FlatType::Record(fields, _)) => { - Ok(struct_to_ast(env, addr, field_layouts, *fields)) + Ok(struct_to_ast(env, &app, addr, field_layouts, *fields)) } Content::Structure(FlatType::EmptyRecord) => Ok(struct_to_ast( env, + &app, addr, field_layouts, RecordFields::empty(), @@ -348,6 +345,7 @@ fn jit_to_ast_help<'a, A: ReplApp>( Ok(single_tag_union_to_ast( env, + &app, addr, field_layouts, tag_name, @@ -359,6 +357,7 @@ fn jit_to_ast_help<'a, A: ReplApp>( Ok(single_tag_union_to_ast( env, + &app, addr, field_layouts, tag_name, @@ -382,7 +381,7 @@ fn jit_to_ast_help<'a, A: ReplApp>( let result_stack_size = layout.stack_size(env.target_info); - env.app.call_function_dynamic_size( + app.call_function_dynamic_size( main_fn_name, result_stack_size as usize, struct_addr_to_ast, @@ -390,22 +389,29 @@ fn jit_to_ast_help<'a, A: ReplApp>( } Layout::Union(UnionLayout::NonRecursive(_)) => { let size = layout.stack_size(env.target_info); - Ok(env - .app - .call_function_dynamic_size(main_fn_name, size as usize, |addr: usize| { - addr_to_ast(env, addr, layout, WhenRecursive::Unreachable, content) - })) + Ok( + app.call_function_dynamic_size(main_fn_name, size as usize, |addr: usize| { + addr_to_ast(env, &app, addr, layout, WhenRecursive::Unreachable, content) + }), + ) } Layout::Union(UnionLayout::Recursive(_)) | Layout::Union(UnionLayout::NonNullableUnwrapped(_)) | Layout::Union(UnionLayout::NullableUnwrapped { .. }) | Layout::Union(UnionLayout::NullableWrapped { .. }) => { let size = layout.stack_size(env.target_info); - Ok(env - .app - .call_function_dynamic_size(main_fn_name, size as usize, |addr: usize| { - addr_to_ast(env, addr, layout, WhenRecursive::Loop(*layout), content) - })) + Ok( + app.call_function_dynamic_size(main_fn_name, size as usize, |addr: usize| { + addr_to_ast( + env, + &app, + addr, + layout, + WhenRecursive::Loop(*layout), + content, + ) + }), + ) } Layout::RecursivePointer => { unreachable!("RecursivePointers can only be inside structures") @@ -415,7 +421,7 @@ fn jit_to_ast_help<'a, A: ReplApp>( result.map(|e| apply_newtypes(env, newtype_containers, e)) } -fn tag_name_to_expr<'a, A: ReplApp>(env: &Env<'a, '_, A>, tag_name: &TagName) -> Expr<'a> { +fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> { match tag_name { TagName::Global(_) => Expr::GlobalTag( env.arena @@ -438,7 +444,8 @@ enum WhenRecursive<'a> { } fn addr_to_ast<'a, A: ReplApp>( - env: &Env<'a, 'a, A>, + env: &Env<'a, 'a>, + app: &A, addr: usize, layout: &Layout<'a>, when_recursive: WhenRecursive<'a>, @@ -446,7 +453,7 @@ fn addr_to_ast<'a, A: ReplApp>( ) -> Expr<'a> { macro_rules! helper { ($method: ident, $ty: ty) => {{ - let num: $ty = env.app.$method(addr); + let num: $ty = app.$method(addr); num_to_ast(env, number_literal_to_ast(env.arena, num), content) }}; @@ -460,9 +467,9 @@ fn addr_to_ast<'a, A: ReplApp>( (_, Layout::Builtin(Builtin::Bool)) => { // TODO: bits are not as expected here. // num is always false at the moment. - let num: bool = env.app.deref_bool(addr); + let num: bool = app.deref_bool(addr); - bool_to_ast(env, num, content) + bool_to_ast(env, app, num, content) } (_, Layout::Builtin(Builtin::Int(int_width))) => { use IntWidth::*; @@ -490,32 +497,32 @@ fn addr_to_ast<'a, A: ReplApp>( } } (_, Layout::Builtin(Builtin::List(elem_layout))) => { - let elem_addr = env.app.deref_usize(addr); - let len = env.app.deref_usize(addr + env.target_info.ptr_width() as usize); + let elem_addr = app.deref_usize(addr); + let len = app.deref_usize(addr + env.target_info.ptr_width() as usize); - list_to_ast(env, elem_addr, len, elem_layout, content) + list_to_ast(env, app, elem_addr, len, elem_layout, content) } (_, Layout::Builtin(Builtin::Str)) => { - let arena_str = env.app.deref_str(addr); + let arena_str = app.deref_str(addr); str_to_ast(env.arena, arena_str) } (_, Layout::Struct(field_layouts)) => match content { Content::Structure(FlatType::Record(fields, _)) => { - struct_to_ast(env, addr, field_layouts, *fields) + struct_to_ast(env, app, addr, field_layouts, *fields) } Content::Structure(FlatType::TagUnion(tags, _)) => { debug_assert_eq!(tags.len(), 1); let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); - single_tag_union_to_ast(env, addr, field_layouts, tag_name, payload_vars) + single_tag_union_to_ast(env, app, addr, field_layouts, tag_name, payload_vars) } Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => { let tag_name = &env.subs[*tag_name]; - single_tag_union_to_ast(env, addr, field_layouts, tag_name, &[]) + single_tag_union_to_ast(env, app, addr, field_layouts, tag_name, &[]) } Content::Structure(FlatType::EmptyRecord) => { - struct_to_ast(env, addr, &[], RecordFields::empty()) + struct_to_ast(env, app, addr, &[], RecordFields::empty()) } other => { unreachable!( @@ -531,7 +538,7 @@ fn addr_to_ast<'a, A: ReplApp>( opt_name: _, }, WhenRecursive::Loop(union_layout)) => { let content = env.subs.get_content_without_compacting(*structure); - addr_to_ast(env, addr, &union_layout, when_recursive, content) + addr_to_ast(env, app, addr, &union_layout, when_recursive, content) } other => unreachable!("Something had a RecursivePointer layout, but instead of being a RecursionVar and having a known recursive layout, I found {:?}", other), } @@ -556,14 +563,14 @@ fn addr_to_ast<'a, A: ReplApp>( }; // Because this is a `NonRecursive`, the tag ID is definitely after the data. - let tag_id = tag_id_from_data(env, union_layout, addr); + let tag_id = tag_id_from_data(env, app, union_layout, addr); // use the tag ID as an index, to get its name and layout of any arguments let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; expr_of_tag( - env, + env, app, addr, tag_name, arg_layouts, @@ -588,11 +595,11 @@ fn addr_to_ast<'a, A: ReplApp>( _ => unreachable!("any other variant would have a different layout"), }; - let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(env, *union_layout, addr); + let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(env, app, *union_layout, addr); let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; expr_of_tag( - env, + env, app, ptr_to_data, tag_name, arg_layouts, @@ -616,10 +623,10 @@ fn addr_to_ast<'a, A: ReplApp>( _ => unreachable!("any other variant would have a different layout"), }; - let data_addr = env.app.deref_usize(addr); + let data_addr = app.deref_usize(addr); expr_of_tag( - env, + env, app, data_addr, &tag_name, arg_layouts, @@ -646,12 +653,12 @@ fn addr_to_ast<'a, A: ReplApp>( _ => unreachable!("any other variant would have a different layout"), }; - let data_addr = env.app.deref_usize(addr); + let data_addr = app.deref_usize(addr); if data_addr == 0 { tag_name_to_expr(env, &nullable_name) } else { expr_of_tag( - env, + env, app, data_addr, &other_name, other_arg_layouts, @@ -677,17 +684,17 @@ fn addr_to_ast<'a, A: ReplApp>( _ => unreachable!("any other variant would have a different layout"), }; - let data_addr = env.app.deref_usize(addr); + let data_addr = app.deref_usize(addr); if data_addr == 0 { tag_name_to_expr(env, &nullable_name) } else { - let (tag_id, data_addr) = tag_id_from_recursive_ptr(env, *union_layout, addr); + let (tag_id, data_addr) = tag_id_from_recursive_ptr(env, app, *union_layout, addr); let tag_id = if tag_id > nullable_id.into() { tag_id - 1 } else { tag_id }; let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; expr_of_tag( - env, + env, app, data_addr, tag_name, arg_layouts, @@ -707,7 +714,8 @@ fn addr_to_ast<'a, A: ReplApp>( } fn list_to_ast<'a, A: ReplApp>( - env: &Env<'a, 'a, A>, + env: &Env<'a, 'a>, + app: &A, addr: usize, len: usize, elem_layout: &Layout<'a>, @@ -740,6 +748,7 @@ fn list_to_ast<'a, A: ReplApp>( let (newtype_containers, elem_content) = unroll_newtypes(env, elem_content); let expr = addr_to_ast( env, + app, elem_addr, elem_layout, WhenRecursive::Unreachable, @@ -756,7 +765,8 @@ fn list_to_ast<'a, A: ReplApp>( } fn single_tag_union_to_ast<'a, A: ReplApp>( - env: &Env<'a, 'a, A>, + env: &Env<'a, 'a>, + app: &A, addr: usize, field_layouts: &'a [Layout<'a>], tag_name: &TagName, @@ -769,11 +779,11 @@ fn single_tag_union_to_ast<'a, A: ReplApp>( let output = if field_layouts.len() == payload_vars.len() { let it = payload_vars.iter().copied().zip(field_layouts); - sequence_of_expr(env, addr, it, WhenRecursive::Unreachable).into_bump_slice() + sequence_of_expr(env, app, addr, it, WhenRecursive::Unreachable).into_bump_slice() } else if field_layouts.is_empty() && !payload_vars.is_empty() { // happens for e.g. `Foo Bar` where unit structures are nested and the inner one is dropped let it = payload_vars.iter().copied().zip([&Layout::Struct(&[])]); - sequence_of_expr(env, addr, it, WhenRecursive::Unreachable).into_bump_slice() + sequence_of_expr(env, app, addr, it, WhenRecursive::Unreachable).into_bump_slice() } else { unreachable!() }; @@ -782,7 +792,8 @@ fn single_tag_union_to_ast<'a, A: ReplApp>( } fn sequence_of_expr<'a, I, A: ReplApp>( - env: &Env<'a, 'a, A>, + env: &Env<'a, 'a>, + app: &A, addr: usize, sequence: I, when_recursive: WhenRecursive<'a>, @@ -800,7 +811,7 @@ where for (var, layout) in sequence { let content = subs.get_content_without_compacting(var); - let expr = addr_to_ast(env, field_addr, layout, when_recursive, content); + let expr = addr_to_ast(env, app, field_addr, layout, when_recursive, content); let loc_expr = Loc::at_zero(expr); output.push(&*arena.alloc(loc_expr)); @@ -813,7 +824,8 @@ where } fn struct_to_ast<'a, A: ReplApp>( - env: &Env<'a, 'a, A>, + env: &Env<'a, 'a>, + app: &A, addr: usize, field_layouts: &'a [Layout<'a>], record_fields: RecordFields, @@ -836,6 +848,7 @@ fn struct_to_ast<'a, A: ReplApp>( let loc_expr = &*arena.alloc(Loc { value: addr_to_ast( env, + app, addr, &Layout::Struct(field_layouts), WhenRecursive::Unreachable, @@ -870,6 +883,7 @@ fn struct_to_ast<'a, A: ReplApp>( let loc_expr = &*arena.alloc(Loc { value: addr_to_ast( env, + app, field_addr, field_layout, WhenRecursive::Unreachable, @@ -929,7 +943,12 @@ fn unpack_two_element_tag_union( (tag_name1, payload_vars1, tag_name2, payload_vars2) } -fn bool_to_ast<'a, A: ReplApp>(env: &Env<'a, '_, A>, value: bool, content: &Content) -> Expr<'a> { +fn bool_to_ast<'a, A: ReplApp>( + env: &Env<'a, '_>, + app: &A, + value: bool, + content: &Content, +) -> Expr<'a> { use Content::*; let arena = env.arena; @@ -963,7 +982,7 @@ fn bool_to_ast<'a, A: ReplApp>(env: &Env<'a, '_, A>, value: bool, content: &Cont let content = env.subs.get_content_without_compacting(var); let loc_payload = &*arena.alloc(Loc { - value: bool_to_ast(env, value, content), + value: bool_to_ast(env, app, value, content), region: Region::zero(), }); @@ -999,7 +1018,7 @@ fn bool_to_ast<'a, A: ReplApp>(env: &Env<'a, '_, A>, value: bool, content: &Cont Alias(_, _, var) => { let content = env.subs.get_content_without_compacting(*var); - bool_to_ast(env, value, content) + bool_to_ast(env, app, value, content) } other => { unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); @@ -1007,7 +1026,12 @@ fn bool_to_ast<'a, A: ReplApp>(env: &Env<'a, '_, A>, value: bool, content: &Cont } } -fn byte_to_ast<'a, A: ReplApp>(env: &Env<'a, '_, A>, value: u8, content: &Content) -> Expr<'a> { +fn byte_to_ast<'a, A: ReplApp>( + env: &Env<'a, '_>, + app: &A, + value: u8, + content: &Content, +) -> Expr<'a> { use Content::*; let arena = env.arena; @@ -1047,7 +1071,7 @@ fn byte_to_ast<'a, A: ReplApp>(env: &Env<'a, '_, A>, value: u8, content: &Conten let content = env.subs.get_content_without_compacting(var); let loc_payload = &*arena.alloc(Loc { - value: byte_to_ast(env, value, content), + value: byte_to_ast(env, app, value, content), region: Region::zero(), }); @@ -1091,7 +1115,7 @@ fn byte_to_ast<'a, A: ReplApp>(env: &Env<'a, '_, A>, value: u8, content: &Conten Alias(_, _, var) => { let content = env.subs.get_content_without_compacting(*var); - byte_to_ast(env, value, content) + byte_to_ast(env, app, value, content) } other => { unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); @@ -1099,11 +1123,7 @@ fn byte_to_ast<'a, A: ReplApp>(env: &Env<'a, '_, A>, value: u8, content: &Conten } } -fn num_to_ast<'a, A: ReplApp>( - env: &Env<'a, '_, A>, - num_expr: Expr<'a>, - content: &Content, -) -> Expr<'a> { +fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> Expr<'a> { use Content::*; let arena = env.arena; From 1528651a5a361c507e756c8123c3b5dfffe59a7c Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 4 Feb 2022 09:58:39 +0000 Subject: [PATCH 515/541] repl: explicit lifetimes for app --- repl_cli/src/lib.rs | 6 ++++-- repl_eval/src/eval.rs | 34 +++++++++++++++++----------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/repl_cli/src/lib.rs b/repl_cli/src/lib.rs index 8b96e2affe..c1356c07a8 100644 --- a/repl_cli/src/lib.rs +++ b/repl_cli/src/lib.rs @@ -288,11 +288,13 @@ fn gen_and_eval_llvm<'a>( let lib = module_to_dylib(env.module, &target, opt_level) .expect("Error loading compiled dylib for test"); - let app = CliReplApp { lib }; + // The app is `mut` only because Wasm needs it. + // It has no public fields, and its "mutating" methods don't actually mutate. + let mut app = CliReplApp { lib }; let res_answer = jit_to_ast( &arena, - app, + &mut app, main_fn_name, main_fn_layout, content, diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index e87ab1e7d0..4825a39897 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -41,7 +41,7 @@ pub enum ToAstProblem { #[allow(clippy::too_many_arguments)] pub fn jit_to_ast<'a, A: ReplApp>( arena: &'a Bump, - app: A, + app: &'a mut A, main_fn_name: &str, layout: ProcLayout<'a>, content: &'a Content, @@ -180,7 +180,7 @@ fn get_tags_vars_and_variant<'a>( fn expr_of_tag<'a, A: ReplApp>( env: &Env<'a, 'a>, - app: &A, + app: &'a A, data_addr: usize, tag_name: &TagName, arg_layouts: &'a [Layout<'a>], @@ -257,7 +257,7 @@ const OPAQUE_FUNCTION: Expr = Expr::Var { fn jit_to_ast_help<'a, A: ReplApp>( env: &Env<'a, 'a>, - mut app: A, + app: &'a mut A, main_fn_name: &str, layout: &Layout<'a>, content: &'a Content, @@ -266,7 +266,7 @@ fn jit_to_ast_help<'a, A: ReplApp>( let content = unroll_aliases(env, content); let result = match layout { Layout::Builtin(Builtin::Bool) => Ok(app.call_function(main_fn_name, |num: bool| { - bool_to_ast(env, &app, num, content) + bool_to_ast(env, app, num, content) })), Layout::Builtin(Builtin::Int(int_width)) => { use IntWidth::*; @@ -282,7 +282,7 @@ fn jit_to_ast_help<'a, A: ReplApp>( let result = match int_width { U8 | I8 => { // NOTE: `helper!` does not handle 8-bit numbers yet - app.call_function(main_fn_name, |num: u8| byte_to_ast(env, &app, num, content)) + app.call_function(main_fn_name, |num: u8| byte_to_ast(env, app, num, content)) } U16 => helper!(u16), U32 => helper!(u32), @@ -321,7 +321,7 @@ fn jit_to_ast_help<'a, A: ReplApp>( })), Layout::Builtin(Builtin::List(elem_layout)) => Ok(app .call_function(main_fn_name, |(addr, len): (usize, usize)| { - list_to_ast(env, &app, addr, len, elem_layout, content) + list_to_ast(env, app, addr, len, elem_layout, content) })), Layout::Builtin(other) => { todo!("add support for rendering builtin {:?} to the REPL", other) @@ -329,11 +329,11 @@ fn jit_to_ast_help<'a, A: ReplApp>( Layout::Struct(field_layouts) => { let struct_addr_to_ast = |addr: usize| match content { Content::Structure(FlatType::Record(fields, _)) => { - Ok(struct_to_ast(env, &app, addr, field_layouts, *fields)) + Ok(struct_to_ast(env, app, addr, field_layouts, *fields)) } Content::Structure(FlatType::EmptyRecord) => Ok(struct_to_ast( env, - &app, + app, addr, field_layouts, RecordFields::empty(), @@ -345,7 +345,7 @@ fn jit_to_ast_help<'a, A: ReplApp>( Ok(single_tag_union_to_ast( env, - &app, + app, addr, field_layouts, tag_name, @@ -357,7 +357,7 @@ fn jit_to_ast_help<'a, A: ReplApp>( Ok(single_tag_union_to_ast( env, - &app, + app, addr, field_layouts, tag_name, @@ -391,7 +391,7 @@ fn jit_to_ast_help<'a, A: ReplApp>( let size = layout.stack_size(env.target_info); Ok( app.call_function_dynamic_size(main_fn_name, size as usize, |addr: usize| { - addr_to_ast(env, &app, addr, layout, WhenRecursive::Unreachable, content) + addr_to_ast(env, app, addr, layout, WhenRecursive::Unreachable, content) }), ) } @@ -404,7 +404,7 @@ fn jit_to_ast_help<'a, A: ReplApp>( app.call_function_dynamic_size(main_fn_name, size as usize, |addr: usize| { addr_to_ast( env, - &app, + app, addr, layout, WhenRecursive::Loop(*layout), @@ -445,7 +445,7 @@ enum WhenRecursive<'a> { fn addr_to_ast<'a, A: ReplApp>( env: &Env<'a, 'a>, - app: &A, + app: &'a A, addr: usize, layout: &Layout<'a>, when_recursive: WhenRecursive<'a>, @@ -715,7 +715,7 @@ fn addr_to_ast<'a, A: ReplApp>( fn list_to_ast<'a, A: ReplApp>( env: &Env<'a, 'a>, - app: &A, + app: &'a A, addr: usize, len: usize, elem_layout: &Layout<'a>, @@ -766,7 +766,7 @@ fn list_to_ast<'a, A: ReplApp>( fn single_tag_union_to_ast<'a, A: ReplApp>( env: &Env<'a, 'a>, - app: &A, + app: &'a A, addr: usize, field_layouts: &'a [Layout<'a>], tag_name: &TagName, @@ -793,7 +793,7 @@ fn single_tag_union_to_ast<'a, A: ReplApp>( fn sequence_of_expr<'a, I, A: ReplApp>( env: &Env<'a, 'a>, - app: &A, + app: &'a A, addr: usize, sequence: I, when_recursive: WhenRecursive<'a>, @@ -825,7 +825,7 @@ where fn struct_to_ast<'a, A: ReplApp>( env: &Env<'a, 'a>, - app: &A, + app: &'a A, addr: usize, field_layouts: &'a [Layout<'a>], record_fields: RecordFields, From 3c6cb5bc19b17975452f428966a6693f789272f4 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 5 Feb 2022 08:41:39 +0000 Subject: [PATCH 516/541] repl: Separate traits for the app and its memory --- repl_cli/src/lib.rs | 60 +++++++------ repl_eval/src/eval.rs | 196 ++++++++++++++++++++++-------------------- repl_eval/src/lib.rs | 39 ++++++--- repl_wasm/src/lib.rs | 64 ++++++++------ 4 files changed, 206 insertions(+), 153 deletions(-) diff --git a/repl_cli/src/lib.rs b/repl_cli/src/lib.rs index c1356c07a8..9ab1910804 100644 --- a/repl_cli/src/lib.rs +++ b/repl_cli/src/lib.rs @@ -19,7 +19,7 @@ use roc_parse::ast::Expr; use roc_parse::parser::{EExpr, ELambda, SyntaxError}; use roc_repl_eval::eval::jit_to_ast; use roc_repl_eval::gen::{compile_to_mono, format_answer, ReplOutput}; -use roc_repl_eval::ReplApp; +use roc_repl_eval::{ReplApp, ReplAppMemory}; use roc_target::TargetInfo; use roc_types::pretty_print::{content_to_string, name_all_type_vars}; @@ -117,10 +117,42 @@ impl Validator for InputValidator { } } -struct CliReplApp { +struct CliApp { lib: Library, } +struct CliMemory; + +impl<'a> ReplApp<'a> for CliApp { + type Memory = CliMemory; + + /// Run user code that returns a type with a `Builtin` layout + /// Size of the return value is statically determined from its Rust type + fn call_function(&self, main_fn_name: &str, transform: F) -> Expr<'a> + where + F: Fn(&'a Self::Memory, Return) -> Expr<'a>, + Self::Memory: 'a, + { + run_jit_function!(self.lib, main_fn_name, Return, |v| transform(&CliMemory, v)) + } + + /// Run user code that returns a struct or union, whose size is provided as an argument + fn call_function_dynamic_size( + &self, + main_fn_name: &str, + ret_bytes: usize, + transform: F, + ) -> T + where + F: Fn(&'a Self::Memory, usize) -> T, + Self::Memory: 'a, + { + run_jit_function_dynamic_type!(self.lib, main_fn_name, ret_bytes, |v| transform( + &CliMemory, v + )) + } +} + macro_rules! deref_number { ($name: ident, $t: ty) => { fn $name(&self, addr: usize) -> $t { @@ -130,7 +162,7 @@ macro_rules! deref_number { }; } -impl ReplApp for CliReplApp { +impl ReplAppMemory for CliMemory { deref_number!(deref_bool, bool); deref_number!(deref_u8, u8); @@ -153,26 +185,6 @@ impl ReplApp for CliReplApp { fn deref_str(&self, addr: usize) -> &str { unsafe { *(addr as *const &'static str) } } - - /// Run user code that returns a type with a `Builtin` layout - /// Size of the return value is statically determined from its Rust type - fn call_function<'a, T: Sized, F: Fn(T) -> Expr<'a>>( - &self, - main_fn_name: &str, - transform: F, - ) -> Expr<'a> { - run_jit_function!(self.lib, main_fn_name, T, transform) - } - - /// Run user code that returns a struct or union, whose size is provided as an argument - fn call_function_dynamic_size T>( - &self, - main_fn_name: &str, - bytes: usize, - transform: F, - ) -> T { - run_jit_function_dynamic_type!(self.lib, main_fn_name, bytes, transform) - } } fn gen_and_eval_llvm<'a>( @@ -290,7 +302,7 @@ fn gen_and_eval_llvm<'a>( // The app is `mut` only because Wasm needs it. // It has no public fields, and its "mutating" methods don't actually mutate. - let mut app = CliReplApp { lib }; + let mut app = CliApp { lib }; let res_answer = jit_to_ast( &arena, diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index 4825a39897..f97ce94494 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -16,7 +16,7 @@ use roc_region::all::{Loc, Region}; use roc_target::TargetInfo; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; -use crate::ReplApp; +use crate::{ReplApp, ReplAppMemory}; struct Env<'a, 'env> { arena: &'a Bump, @@ -39,9 +39,9 @@ pub enum ToAstProblem { /// we get to a struct or tag, we know what the labels are and can turn them /// back into the appropriate user-facing literals. #[allow(clippy::too_many_arguments)] -pub fn jit_to_ast<'a, A: ReplApp>( +pub fn jit_to_ast<'a, A: ReplApp<'a>>( arena: &'a Bump, - app: &'a mut A, + app: &'a A, main_fn_name: &str, layout: ProcLayout<'a>, content: &'a Content, @@ -178,9 +178,9 @@ fn get_tags_vars_and_variant<'a>( (vars_of_tag, union_variant) } -fn expr_of_tag<'a, A: ReplApp>( +fn expr_of_tag<'a, M: ReplAppMemory>( env: &Env<'a, 'a>, - app: &'a A, + mem: &'a M, data_addr: usize, tag_name: &TagName, arg_layouts: &'a [Layout<'a>], @@ -194,7 +194,7 @@ fn expr_of_tag<'a, A: ReplApp>( // NOTE assumes the data bytes are the first bytes let it = arg_vars.iter().copied().zip(arg_layouts.iter()); - let output = sequence_of_expr(env, app, data_addr, it, when_recursive); + let output = sequence_of_expr(env, mem, data_addr, it, when_recursive); let output = output.into_bump_slice(); Expr::Apply(loc_tag_expr, output, CalledVia::Space) @@ -202,9 +202,9 @@ fn expr_of_tag<'a, A: ReplApp>( /// Gets the tag ID of a union variant, assuming that the tag ID is stored alongside (after) the /// tag data. The caller is expected to check that the tag ID is indeed stored this way. -fn tag_id_from_data<'a, A: ReplApp>( +fn tag_id_from_data<'a, M: ReplAppMemory>( env: &Env<'a, 'a>, - app: &A, + mem: &M, union_layout: UnionLayout, data_addr: usize, ) -> i64 { @@ -214,13 +214,13 @@ fn tag_id_from_data<'a, A: ReplApp>( let tag_id_addr = data_addr + offset as usize; match union_layout.tag_id_builtin() { - Builtin::Bool => app.deref_bool(tag_id_addr) as i64, - Builtin::Int(IntWidth::U8) => app.deref_u8(tag_id_addr) as i64, - Builtin::Int(IntWidth::U16) => app.deref_u16(tag_id_addr) as i64, + Builtin::Bool => mem.deref_bool(tag_id_addr) as i64, + Builtin::Int(IntWidth::U8) => mem.deref_u8(tag_id_addr) as i64, + Builtin::Int(IntWidth::U16) => mem.deref_u16(tag_id_addr) as i64, Builtin::Int(IntWidth::U64) => { // used by non-recursive unions at the // moment, remove if that is no longer the case - app.deref_i64(tag_id_addr) + mem.deref_i64(tag_id_addr) } _ => unreachable!("invalid tag id layout"), } @@ -230,14 +230,14 @@ fn tag_id_from_data<'a, A: ReplApp>( /// pointer to the data of the union variant). Returns /// - the tag ID /// - the address of the data of the union variant, unmasked if the pointer held the tag ID -fn tag_id_from_recursive_ptr<'a, A: ReplApp>( +fn tag_id_from_recursive_ptr<'a, M: ReplAppMemory>( env: &Env<'a, 'a>, - app: &A, + mem: &M, union_layout: UnionLayout, rec_addr: usize, ) -> (i64, usize) { let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.target_info); - let addr_with_id = app.deref_usize(rec_addr); + let addr_with_id = mem.deref_usize(rec_addr); if tag_in_ptr { let (_, tag_id_mask) = UnionLayout::tag_id_pointer_bits_and_mask(env.target_info); @@ -245,7 +245,7 @@ fn tag_id_from_recursive_ptr<'a, A: ReplApp>( let data_addr = addr_with_id & !tag_id_mask; (tag_id as i64, data_addr) } else { - let tag_id = tag_id_from_data(env, app, union_layout, addr_with_id); + let tag_id = tag_id_from_data(env, mem, union_layout, addr_with_id); (tag_id, addr_with_id) } } @@ -255,9 +255,9 @@ const OPAQUE_FUNCTION: Expr = Expr::Var { ident: "", }; -fn jit_to_ast_help<'a, A: ReplApp>( +fn jit_to_ast_help<'a, A: ReplApp<'a>>( env: &Env<'a, 'a>, - app: &'a mut A, + app: &'a A, main_fn_name: &str, layout: &Layout<'a>, content: &'a Content, @@ -265,15 +265,16 @@ fn jit_to_ast_help<'a, A: ReplApp>( let (newtype_containers, content) = unroll_newtypes(env, content); let content = unroll_aliases(env, content); let result = match layout { - Layout::Builtin(Builtin::Bool) => Ok(app.call_function(main_fn_name, |num: bool| { - bool_to_ast(env, app, num, content) - })), + Layout::Builtin(Builtin::Bool) => Ok(app + .call_function(main_fn_name, |mem: &A::Memory, num: bool| { + bool_to_ast(env, mem, num, content) + })), Layout::Builtin(Builtin::Int(int_width)) => { use IntWidth::*; macro_rules! helper { ($ty:ty) => { - app.call_function(main_fn_name, |num: $ty| { + app.call_function(main_fn_name, |_, num: $ty| { num_to_ast(env, number_literal_to_ast(env.arena, num), content) }) }; @@ -282,7 +283,9 @@ fn jit_to_ast_help<'a, A: ReplApp>( let result = match int_width { U8 | I8 => { // NOTE: `helper!` does not handle 8-bit numbers yet - app.call_function(main_fn_name, |num: u8| byte_to_ast(env, app, num, content)) + app.call_function(main_fn_name, |mem: &A::Memory, num: u8| { + byte_to_ast(env, mem, num, content) + }) } U16 => helper!(u16), U32 => helper!(u32), @@ -301,7 +304,7 @@ fn jit_to_ast_help<'a, A: ReplApp>( macro_rules! helper { ($ty:ty) => { - app.call_function(main_fn_name, |num: $ty| { + app.call_function(main_fn_name, |_, num: $ty| { num_to_ast(env, number_literal_to_ast(env.arena, num), content) }) }; @@ -316,24 +319,26 @@ fn jit_to_ast_help<'a, A: ReplApp>( Ok(result) } Layout::Builtin(Builtin::Str) => Ok(app - .call_function(main_fn_name, |string: &'static str| { + .call_function(main_fn_name, |_, string: &'static str| { str_to_ast(env.arena, env.arena.alloc(string)) })), - Layout::Builtin(Builtin::List(elem_layout)) => Ok(app - .call_function(main_fn_name, |(addr, len): (usize, usize)| { - list_to_ast(env, app, addr, len, elem_layout, content) - })), + Layout::Builtin(Builtin::List(elem_layout)) => Ok(app.call_function( + main_fn_name, + |mem: &A::Memory, (addr, len): (usize, usize)| { + list_to_ast(env, mem, addr, len, elem_layout, content) + }, + )), Layout::Builtin(other) => { todo!("add support for rendering builtin {:?} to the REPL", other) } Layout::Struct(field_layouts) => { - let struct_addr_to_ast = |addr: usize| match content { + let struct_addr_to_ast = |mem: &'a A::Memory, addr: usize| match content { Content::Structure(FlatType::Record(fields, _)) => { - Ok(struct_to_ast(env, app, addr, field_layouts, *fields)) + Ok(struct_to_ast(env, mem, addr, field_layouts, *fields)) } Content::Structure(FlatType::EmptyRecord) => Ok(struct_to_ast( env, - app, + mem, addr, field_layouts, RecordFields::empty(), @@ -345,7 +350,7 @@ fn jit_to_ast_help<'a, A: ReplApp>( Ok(single_tag_union_to_ast( env, - app, + mem, addr, field_layouts, tag_name, @@ -357,7 +362,7 @@ fn jit_to_ast_help<'a, A: ReplApp>( Ok(single_tag_union_to_ast( env, - app, + mem, addr, field_layouts, tag_name, @@ -389,29 +394,33 @@ fn jit_to_ast_help<'a, A: ReplApp>( } Layout::Union(UnionLayout::NonRecursive(_)) => { let size = layout.stack_size(env.target_info); - Ok( - app.call_function_dynamic_size(main_fn_name, size as usize, |addr: usize| { - addr_to_ast(env, app, addr, layout, WhenRecursive::Unreachable, content) - }), - ) + Ok(app.call_function_dynamic_size( + main_fn_name, + size as usize, + |mem: &'a A::Memory, addr: usize| { + addr_to_ast(env, mem, addr, layout, WhenRecursive::Unreachable, content) + }, + )) } Layout::Union(UnionLayout::Recursive(_)) | Layout::Union(UnionLayout::NonNullableUnwrapped(_)) | Layout::Union(UnionLayout::NullableUnwrapped { .. }) | Layout::Union(UnionLayout::NullableWrapped { .. }) => { let size = layout.stack_size(env.target_info); - Ok( - app.call_function_dynamic_size(main_fn_name, size as usize, |addr: usize| { + Ok(app.call_function_dynamic_size( + main_fn_name, + size as usize, + |mem: &'a A::Memory, addr: usize| { addr_to_ast( env, - app, + mem, addr, layout, WhenRecursive::Loop(*layout), content, ) - }), - ) + }, + )) } Layout::RecursivePointer => { unreachable!("RecursivePointers can only be inside structures") @@ -443,9 +452,9 @@ enum WhenRecursive<'a> { Loop(Layout<'a>), } -fn addr_to_ast<'a, A: ReplApp>( +fn addr_to_ast<'a, M: ReplAppMemory>( env: &Env<'a, 'a>, - app: &'a A, + mem: &'a M, addr: usize, layout: &Layout<'a>, when_recursive: WhenRecursive<'a>, @@ -453,7 +462,7 @@ fn addr_to_ast<'a, A: ReplApp>( ) -> Expr<'a> { macro_rules! helper { ($method: ident, $ty: ty) => {{ - let num: $ty = app.$method(addr); + let num: $ty = mem.$method(addr); num_to_ast(env, number_literal_to_ast(env.arena, num), content) }}; @@ -467,9 +476,9 @@ fn addr_to_ast<'a, A: ReplApp>( (_, Layout::Builtin(Builtin::Bool)) => { // TODO: bits are not as expected here. // num is always false at the moment. - let num: bool = app.deref_bool(addr); + let num: bool = mem.deref_bool(addr); - bool_to_ast(env, app, num, content) + bool_to_ast(env, mem, num, content) } (_, Layout::Builtin(Builtin::Int(int_width))) => { use IntWidth::*; @@ -497,32 +506,32 @@ fn addr_to_ast<'a, A: ReplApp>( } } (_, Layout::Builtin(Builtin::List(elem_layout))) => { - let elem_addr = app.deref_usize(addr); - let len = app.deref_usize(addr + env.target_info.ptr_width() as usize); + let elem_addr = mem.deref_usize(addr); + let len = mem.deref_usize(addr + env.target_info.ptr_width() as usize); - list_to_ast(env, app, elem_addr, len, elem_layout, content) + list_to_ast(env, mem, elem_addr, len, elem_layout, content) } (_, Layout::Builtin(Builtin::Str)) => { - let arena_str = app.deref_str(addr); + let arena_str = mem.deref_str(addr); str_to_ast(env.arena, arena_str) } (_, Layout::Struct(field_layouts)) => match content { Content::Structure(FlatType::Record(fields, _)) => { - struct_to_ast(env, app, addr, field_layouts, *fields) + struct_to_ast(env, mem, addr, field_layouts, *fields) } Content::Structure(FlatType::TagUnion(tags, _)) => { debug_assert_eq!(tags.len(), 1); let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); - single_tag_union_to_ast(env, app, addr, field_layouts, tag_name, payload_vars) + single_tag_union_to_ast(env, mem, addr, field_layouts, tag_name, payload_vars) } Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => { let tag_name = &env.subs[*tag_name]; - single_tag_union_to_ast(env, app, addr, field_layouts, tag_name, &[]) + single_tag_union_to_ast(env, mem, addr, field_layouts, tag_name, &[]) } Content::Structure(FlatType::EmptyRecord) => { - struct_to_ast(env, app, addr, &[], RecordFields::empty()) + struct_to_ast(env, mem, addr, &[], RecordFields::empty()) } other => { unreachable!( @@ -538,7 +547,7 @@ fn addr_to_ast<'a, A: ReplApp>( opt_name: _, }, WhenRecursive::Loop(union_layout)) => { let content = env.subs.get_content_without_compacting(*structure); - addr_to_ast(env, app, addr, &union_layout, when_recursive, content) + addr_to_ast(env, mem, addr, &union_layout, when_recursive, content) } other => unreachable!("Something had a RecursivePointer layout, but instead of being a RecursionVar and having a known recursive layout, I found {:?}", other), } @@ -563,14 +572,15 @@ fn addr_to_ast<'a, A: ReplApp>( }; // Because this is a `NonRecursive`, the tag ID is definitely after the data. - let tag_id = tag_id_from_data(env, app, union_layout, addr); + let tag_id = tag_id_from_data(env, mem, union_layout, addr); // use the tag ID as an index, to get its name and layout of any arguments let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; expr_of_tag( - env, app, + env, + mem, addr, tag_name, arg_layouts, @@ -595,11 +605,12 @@ fn addr_to_ast<'a, A: ReplApp>( _ => unreachable!("any other variant would have a different layout"), }; - let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(env, app, *union_layout, addr); + let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(env, mem, *union_layout, addr); let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; expr_of_tag( - env, app, + env, + mem, ptr_to_data, tag_name, arg_layouts, @@ -623,10 +634,11 @@ fn addr_to_ast<'a, A: ReplApp>( _ => unreachable!("any other variant would have a different layout"), }; - let data_addr = app.deref_usize(addr); + let data_addr = mem.deref_usize(addr); expr_of_tag( - env, app, + env, + mem, data_addr, &tag_name, arg_layouts, @@ -653,12 +665,13 @@ fn addr_to_ast<'a, A: ReplApp>( _ => unreachable!("any other variant would have a different layout"), }; - let data_addr = app.deref_usize(addr); + let data_addr = mem.deref_usize(addr); if data_addr == 0 { tag_name_to_expr(env, &nullable_name) } else { expr_of_tag( - env, app, + env, + mem, data_addr, &other_name, other_arg_layouts, @@ -684,17 +697,18 @@ fn addr_to_ast<'a, A: ReplApp>( _ => unreachable!("any other variant would have a different layout"), }; - let data_addr = app.deref_usize(addr); + let data_addr = mem.deref_usize(addr); if data_addr == 0 { tag_name_to_expr(env, &nullable_name) } else { - let (tag_id, data_addr) = tag_id_from_recursive_ptr(env, app, *union_layout, addr); + let (tag_id, data_addr) = tag_id_from_recursive_ptr(env, mem, *union_layout, addr); let tag_id = if tag_id > nullable_id.into() { tag_id - 1 } else { tag_id }; let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; expr_of_tag( - env, app, + env, + mem, data_addr, tag_name, arg_layouts, @@ -713,9 +727,9 @@ fn addr_to_ast<'a, A: ReplApp>( apply_newtypes(env, newtype_containers, expr) } -fn list_to_ast<'a, A: ReplApp>( +fn list_to_ast<'a, M: ReplAppMemory>( env: &Env<'a, 'a>, - app: &'a A, + mem: &'a M, addr: usize, len: usize, elem_layout: &Layout<'a>, @@ -748,7 +762,7 @@ fn list_to_ast<'a, A: ReplApp>( let (newtype_containers, elem_content) = unroll_newtypes(env, elem_content); let expr = addr_to_ast( env, - app, + mem, elem_addr, elem_layout, WhenRecursive::Unreachable, @@ -764,9 +778,9 @@ fn list_to_ast<'a, A: ReplApp>( Expr::List(Collection::with_items(output)) } -fn single_tag_union_to_ast<'a, A: ReplApp>( +fn single_tag_union_to_ast<'a, M: ReplAppMemory>( env: &Env<'a, 'a>, - app: &'a A, + mem: &'a M, addr: usize, field_layouts: &'a [Layout<'a>], tag_name: &TagName, @@ -779,11 +793,11 @@ fn single_tag_union_to_ast<'a, A: ReplApp>( let output = if field_layouts.len() == payload_vars.len() { let it = payload_vars.iter().copied().zip(field_layouts); - sequence_of_expr(env, app, addr, it, WhenRecursive::Unreachable).into_bump_slice() + sequence_of_expr(env, mem, addr, it, WhenRecursive::Unreachable).into_bump_slice() } else if field_layouts.is_empty() && !payload_vars.is_empty() { // happens for e.g. `Foo Bar` where unit structures are nested and the inner one is dropped let it = payload_vars.iter().copied().zip([&Layout::Struct(&[])]); - sequence_of_expr(env, app, addr, it, WhenRecursive::Unreachable).into_bump_slice() + sequence_of_expr(env, mem, addr, it, WhenRecursive::Unreachable).into_bump_slice() } else { unreachable!() }; @@ -791,9 +805,9 @@ fn single_tag_union_to_ast<'a, A: ReplApp>( Expr::Apply(loc_tag_expr, output, CalledVia::Space) } -fn sequence_of_expr<'a, I, A: ReplApp>( +fn sequence_of_expr<'a, I, M: ReplAppMemory>( env: &Env<'a, 'a>, - app: &'a A, + mem: &'a M, addr: usize, sequence: I, when_recursive: WhenRecursive<'a>, @@ -811,7 +825,7 @@ where for (var, layout) in sequence { let content = subs.get_content_without_compacting(var); - let expr = addr_to_ast(env, app, field_addr, layout, when_recursive, content); + let expr = addr_to_ast(env, mem, field_addr, layout, when_recursive, content); let loc_expr = Loc::at_zero(expr); output.push(&*arena.alloc(loc_expr)); @@ -823,9 +837,9 @@ where output } -fn struct_to_ast<'a, A: ReplApp>( +fn struct_to_ast<'a, M: ReplAppMemory>( env: &Env<'a, 'a>, - app: &'a A, + mem: &'a M, addr: usize, field_layouts: &'a [Layout<'a>], record_fields: RecordFields, @@ -848,7 +862,7 @@ fn struct_to_ast<'a, A: ReplApp>( let loc_expr = &*arena.alloc(Loc { value: addr_to_ast( env, - app, + mem, addr, &Layout::Struct(field_layouts), WhenRecursive::Unreachable, @@ -883,7 +897,7 @@ fn struct_to_ast<'a, A: ReplApp>( let loc_expr = &*arena.alloc(Loc { value: addr_to_ast( env, - app, + mem, field_addr, field_layout, WhenRecursive::Unreachable, @@ -943,9 +957,9 @@ fn unpack_two_element_tag_union( (tag_name1, payload_vars1, tag_name2, payload_vars2) } -fn bool_to_ast<'a, A: ReplApp>( +fn bool_to_ast<'a, M: ReplAppMemory>( env: &Env<'a, '_>, - app: &A, + mem: &M, value: bool, content: &Content, ) -> Expr<'a> { @@ -982,7 +996,7 @@ fn bool_to_ast<'a, A: ReplApp>( let content = env.subs.get_content_without_compacting(var); let loc_payload = &*arena.alloc(Loc { - value: bool_to_ast(env, app, value, content), + value: bool_to_ast(env, mem, value, content), region: Region::zero(), }); @@ -1018,7 +1032,7 @@ fn bool_to_ast<'a, A: ReplApp>( Alias(_, _, var) => { let content = env.subs.get_content_without_compacting(*var); - bool_to_ast(env, app, value, content) + bool_to_ast(env, mem, value, content) } other => { unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); @@ -1026,9 +1040,9 @@ fn bool_to_ast<'a, A: ReplApp>( } } -fn byte_to_ast<'a, A: ReplApp>( +fn byte_to_ast<'a, M: ReplAppMemory>( env: &Env<'a, '_>, - app: &A, + mem: &M, value: u8, content: &Content, ) -> Expr<'a> { @@ -1071,7 +1085,7 @@ fn byte_to_ast<'a, A: ReplApp>( let content = env.subs.get_content_without_compacting(var); let loc_payload = &*arena.alloc(Loc { - value: byte_to_ast(env, app, value, content), + value: byte_to_ast(env, mem, value, content), region: Region::zero(), }); @@ -1115,7 +1129,7 @@ fn byte_to_ast<'a, A: ReplApp>( Alias(_, _, var) => { let content = env.subs.get_content_without_compacting(*var); - byte_to_ast(env, app, value, content) + byte_to_ast(env, mem, value, content) } other => { unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); diff --git a/repl_eval/src/lib.rs b/repl_eval/src/lib.rs index 53dba07a58..0aebe53507 100644 --- a/repl_eval/src/lib.rs +++ b/repl_eval/src/lib.rs @@ -3,7 +3,31 @@ use roc_parse::ast::Expr; pub mod eval; pub mod gen; -pub trait ReplApp { +pub trait ReplApp<'a> { + type Memory: 'a + ReplAppMemory; + + /// Run user code that returns a type with a `Builtin` layout + /// Size of the return value is statically determined from its Rust type + /// The `transform` callback takes the app's memory and the returned value + fn call_function(&self, main_fn_name: &str, transform: F) -> Expr<'a> + where + F: Fn(&'a Self::Memory, Return) -> Expr<'a>, + Self::Memory: 'a; + + /// Run user code that returns a struct or union, whose size is provided as an argument + /// The `transform` callback takes the app's memory and the address of the returned value + fn call_function_dynamic_size( + &self, + main_fn_name: &str, + ret_bytes: usize, + transform: F, + ) -> T + where + F: Fn(&'a Self::Memory, usize) -> T, + Self::Memory: 'a; +} + +pub trait ReplAppMemory { fn deref_bool(&self, addr: usize) -> bool; fn deref_u8(&self, addr: usize) -> u8; @@ -24,17 +48,4 @@ pub trait ReplApp { fn deref_f64(&self, addr: usize) -> f64; fn deref_str(&self, addr: usize) -> &str; - - fn call_function<'a, Return: Sized, F: Fn(Return) -> Expr<'a>>( - &self, - main_fn_name: &str, - transform: F, - ) -> Expr<'a>; - - fn call_function_dynamic_size T>( - &self, - main_fn_name: &str, - ret_bytes: usize, - transform: F, - ) -> T; } diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index 35ff90468d..952f5da5b8 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -1,11 +1,44 @@ use std::mem::size_of; use roc_parse::ast::Expr; -use roc_repl_eval::ReplApp; +use roc_repl_eval::{ReplApp, ReplAppMemory}; pub struct WasmReplApp<'a> { - module: &'a [u8], - copied_memory: &'a [u8], + _module: &'a [u8], +} + +pub struct WasmMemory<'a> { + copied_bytes: &'a [u8], +} + +impl<'a> ReplApp<'a> for WasmReplApp<'a> { + type Memory = WasmMemory<'a>; + + /// Run user code that returns a type with a `Builtin` layout + /// Size of the return value is statically determined from its Rust type + /// The `transform` callback takes the app's memory and the returned value + fn call_function(&self, _main_fn_name: &str, _transform: F) -> Expr<'a> + where + F: Fn(&'a Self::Memory, Return) -> Expr<'a>, + Self::Memory: 'a, + { + todo!() + } + + /// Run user code that returns a struct or union, whose size is provided as an argument + /// The `transform` callback takes the app's memory and the address of the returned value + fn call_function_dynamic_size( + &self, + _main_fn_name: &str, + _ret_bytes: usize, + _transform: F, + ) -> T + where + F: Fn(&'a Self::Memory, usize) -> T, + Self::Memory: 'a, + { + todo!() + } } macro_rules! deref_number { @@ -13,15 +46,15 @@ macro_rules! deref_number { fn $name(&self, address: usize) -> $t { const N: usize = size_of::<$t>(); let mut array = [0; N]; - array.copy_from_slice(&self.copied_memory[address..][..N]); + array.copy_from_slice(&self.copied_bytes[address..][..N]); <$t>::from_le_bytes(array) } }; } -impl<'a> ReplApp for WasmReplApp<'a> { +impl<'a> ReplAppMemory for WasmMemory<'a> { fn deref_bool(&self, address: usize) -> bool { - self.copied_memory[address] != 0 + self.copied_bytes[address] != 0 } deref_number!(deref_u8, u8); @@ -44,24 +77,7 @@ impl<'a> ReplApp for WasmReplApp<'a> { fn deref_str(&self, addr: usize) -> &str { let elems_addr = self.deref_usize(addr); let len = self.deref_usize(addr + size_of::()); - let bytes = &self.copied_memory[elems_addr..][..len]; + let bytes = &self.copied_bytes[elems_addr..][..len]; std::str::from_utf8(bytes).unwrap() } - - fn call_function<'e, Return: Sized, F: Fn(Return) -> Expr<'e>>( - &self, - _main_fn_name: &str, - _transform: F, - ) -> Expr<'e> { - todo!() - } - - fn call_function_dynamic_size T>( - &self, - _main_fn_name: &str, - _ret_bytes: usize, - _transform: F, - ) -> T { - todo!() - } } From aaa5fa04cdb170c09c3fd86298f308664fb648ef Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 5 Feb 2022 20:23:32 +0000 Subject: [PATCH 517/541] wasm: delete obsolete code --- .../test_gen/src/helpers/dummy_libc_program.c | 7 -- .../src/helpers/wasm32_test_result.rs | 108 ------------------ 2 files changed, 115 deletions(-) delete mode 100644 compiler/test_gen/src/helpers/dummy_libc_program.c diff --git a/compiler/test_gen/src/helpers/dummy_libc_program.c b/compiler/test_gen/src/helpers/dummy_libc_program.c deleted file mode 100644 index 0d358ef2a7..0000000000 --- a/compiler/test_gen/src/helpers/dummy_libc_program.c +++ /dev/null @@ -1,7 +0,0 @@ -#include - -void main() { - printf("Hello, I am a C program and I use libc.\n"); - printf("If you compile me, you'll compile libc too. That's handy for cross-compilation including Wasm.\n"); - printf("Use `zig build-exe` with `--global-cache-dir my/build/directory` to put libc.a where you want it.\n"); -} diff --git a/compiler/test_gen/src/helpers/wasm32_test_result.rs b/compiler/test_gen/src/helpers/wasm32_test_result.rs index b96f3e03fa..a57dae737a 100644 --- a/compiler/test_gen/src/helpers/wasm32_test_result.rs +++ b/compiler/test_gen/src/helpers/wasm32_test_result.rs @@ -181,111 +181,3 @@ where ) } } - -impl Wasm32TestResult for (T, U, V, W) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, - V: Wasm32TestResult + FromWasm32Memory, - W: Wasm32TestResult + FromWasm32Memory, -{ - fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { - build_wrapper_body_stack_memory( - code_builder, - main_function_index, - T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH, - ) - } -} - -impl Wasm32TestResult for (T, U, V, W, X) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, - V: Wasm32TestResult + FromWasm32Memory, - W: Wasm32TestResult + FromWasm32Memory, - X: Wasm32TestResult + FromWasm32Memory, -{ - fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { - build_wrapper_body_stack_memory( - code_builder, - main_function_index, - T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH + X::ACTUAL_WIDTH, - ) - } -} - -impl Wasm32TestResult for (T, U, V, W, X, Y) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, - V: Wasm32TestResult + FromWasm32Memory, - W: Wasm32TestResult + FromWasm32Memory, - X: Wasm32TestResult + FromWasm32Memory, - Y: Wasm32TestResult + FromWasm32Memory, -{ - fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { - build_wrapper_body_stack_memory( - code_builder, - main_function_index, - T::ACTUAL_WIDTH - + U::ACTUAL_WIDTH - + V::ACTUAL_WIDTH - + W::ACTUAL_WIDTH - + X::ACTUAL_WIDTH - + Y::ACTUAL_WIDTH, - ) - } -} - -impl Wasm32TestResult for (T, U, V, W, X, Y, Z) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, - V: Wasm32TestResult + FromWasm32Memory, - W: Wasm32TestResult + FromWasm32Memory, - X: Wasm32TestResult + FromWasm32Memory, - Y: Wasm32TestResult + FromWasm32Memory, - Z: Wasm32TestResult + FromWasm32Memory, -{ - fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { - build_wrapper_body_stack_memory( - code_builder, - main_function_index, - T::ACTUAL_WIDTH - + U::ACTUAL_WIDTH - + V::ACTUAL_WIDTH - + W::ACTUAL_WIDTH - + X::ACTUAL_WIDTH - + Y::ACTUAL_WIDTH - + Z::ACTUAL_WIDTH, - ) - } -} - -impl Wasm32TestResult for (T, U, V, W, X, Y, Z, A) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, - V: Wasm32TestResult + FromWasm32Memory, - W: Wasm32TestResult + FromWasm32Memory, - X: Wasm32TestResult + FromWasm32Memory, - Y: Wasm32TestResult + FromWasm32Memory, - Z: Wasm32TestResult + FromWasm32Memory, - A: Wasm32TestResult + FromWasm32Memory, -{ - fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { - build_wrapper_body_stack_memory( - code_builder, - main_function_index, - T::ACTUAL_WIDTH - + U::ACTUAL_WIDTH - + V::ACTUAL_WIDTH - + W::ACTUAL_WIDTH - + X::ACTUAL_WIDTH - + Y::ACTUAL_WIDTH - + Z::ACTUAL_WIDTH - + A::ACTUAL_WIDTH, - ) - } -} From 70e19053f0b28b68dd0c22817edb0b4c53cc0bd3 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 6 Feb 2022 08:13:29 +0000 Subject: [PATCH 518/541] repl: implement more of the JS/Wasm interface --- Cargo.lock | 2 + repl_wasm/Cargo.toml | 2 + repl_wasm/src/lib.rs | 164 ++++++++++++++++++++++++++++++++++-------- repl_wasm/src/repl.js | 37 +++++----- repl_wasm/src/repl.rs | 39 ---------- 5 files changed, 158 insertions(+), 86 deletions(-) delete mode 100644 repl_wasm/src/repl.rs diff --git a/Cargo.lock b/Cargo.lock index c6e0266c34..d8beff51f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3722,8 +3722,10 @@ version = "0.1.0" dependencies = [ "bumpalo", "js-sys", + "roc_load", "roc_parse", "roc_repl_eval", + "roc_target", "wasm-bindgen", "wasm-bindgen-futures", ] diff --git a/repl_wasm/Cargo.toml b/repl_wasm/Cargo.toml index 1cb38a3ee1..8cb4113fbb 100644 --- a/repl_wasm/Cargo.toml +++ b/repl_wasm/Cargo.toml @@ -12,5 +12,7 @@ js-sys = "0.3.56" wasm-bindgen = "0.2.79" wasm-bindgen-futures = "0.4.29" +roc_load = {path = "../compiler/load"} roc_parse = {path = "../compiler/parse"} roc_repl_eval = {path = "../repl_eval"} +roc_target = {path = "../compiler/roc_target"} diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index 952f5da5b8..67b0aa8274 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -1,46 +1,37 @@ +use bumpalo::Bump; use std::mem::size_of; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; +use roc_load::file::MonomorphizedModule; use roc_parse::ast::Expr; -use roc_repl_eval::{ReplApp, ReplAppMemory}; +use roc_repl_eval::{gen::compile_to_mono, ReplApp, ReplAppMemory}; +use roc_target::TargetInfo; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(catch)] + pub async fn js_create_app(wasm_module_bytes: &[u8]) -> Result<(), JsValue>; + + pub fn js_run_app() -> usize; + + pub fn js_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize; +} pub struct WasmReplApp<'a> { + arena: &'a Bump, _module: &'a [u8], } +/// A copy of the app's memory, made after running the main function +/// The Wasm app ran in a separate address space from the compiler and the eval code. +/// This means we can't simply dereference its pointers as if they were local, because +/// an unrelated value may exist at the same-numbered address in our own address space! +/// Instead we have dereferencing methods that index into the copied bytes. pub struct WasmMemory<'a> { copied_bytes: &'a [u8], } -impl<'a> ReplApp<'a> for WasmReplApp<'a> { - type Memory = WasmMemory<'a>; - - /// Run user code that returns a type with a `Builtin` layout - /// Size of the return value is statically determined from its Rust type - /// The `transform` callback takes the app's memory and the returned value - fn call_function(&self, _main_fn_name: &str, _transform: F) -> Expr<'a> - where - F: Fn(&'a Self::Memory, Return) -> Expr<'a>, - Self::Memory: 'a, - { - todo!() - } - - /// Run user code that returns a struct or union, whose size is provided as an argument - /// The `transform` callback takes the app's memory and the address of the returned value - fn call_function_dynamic_size( - &self, - _main_fn_name: &str, - _ret_bytes: usize, - _transform: F, - ) -> T - where - F: Fn(&'a Self::Memory, usize) -> T, - Self::Memory: 'a, - { - todo!() - } -} - macro_rules! deref_number { ($name: ident, $t: ty) => { fn $name(&self, address: usize) -> $t { @@ -81,3 +72,114 @@ impl<'a> ReplAppMemory for WasmMemory<'a> { std::str::from_utf8(bytes).unwrap() } } + +impl<'a> ReplApp<'a> for WasmReplApp<'a> { + type Memory = WasmMemory<'a>; + + /// Run user code that returns a type with a `Builtin` layout + /// Size of the return value is statically determined from its Rust type + /// The `transform` callback takes the app's memory and the returned value + /// _main_fn_name is always the same and we don't use it here + fn call_function(&self, _main_fn_name: &str, transform: F) -> Expr<'a> + where + F: Fn(&'a Self::Memory, Return) -> Expr<'a>, + Self::Memory: 'a, + { + let app_final_memory_size: usize = js_run_app(); + + // Allocate a buffer to copy the app memory into + // Aligning it to 64 bits will preserve the original alignment of all Wasm numbers + let copy_buffer_aligned: &mut [u64] = self + .arena + .alloc_slice_fill_default((app_final_memory_size / size_of::()) + 1); + let copied_bytes: &mut [u8] = unsafe { std::mem::transmute(copy_buffer_aligned) }; + + let app_result_addr = js_get_result_and_memory(copied_bytes.as_mut_ptr()); + + let result: Return = unsafe { + let ptr: *const Return = std::mem::transmute(&copied_bytes[app_result_addr]); + ptr.read() + }; + let mem = self.arena.alloc(WasmMemory { copied_bytes }); + + transform(mem, result) + } + + /// Run user code that returns a struct or union, whose size is provided as an argument + /// The `transform` callback takes the app's memory and the address of the returned value + /// _main_fn_name and _ret_bytes are only used for the CLI REPL. For Wasm they are compiled-in + /// to the test_wrapper function of the app itself + fn call_function_dynamic_size( + &self, + _main_fn_name: &str, + _ret_bytes: usize, + transform: F, + ) -> T + where + F: Fn(&'a Self::Memory, usize) -> T, + Self::Memory: 'a, + { + let app_final_memory_size: usize = js_run_app(); + + // Allocate a buffer to copy the app memory into + // Aligning it to 64 bits will preserve the original alignment of all Wasm numbers + let copy_buffer_aligned: &mut [u64] = self + .arena + .alloc_slice_fill_default((app_final_memory_size / size_of::()) + 1); + let copied_bytes: &mut [u8] = unsafe { std::mem::transmute(copy_buffer_aligned) }; + + let app_result_addr = js_get_result_and_memory(copied_bytes.as_mut_ptr()); + let mem = self.arena.alloc(WasmMemory { copied_bytes }); + + transform(mem, app_result_addr) + } +} + +#[wasm_bindgen] +pub async fn repl_wasm_entrypoint_from_js(src: String) -> Result { + let arena = &Bump::new(); + + // Compile the app + let mono = match compile_to_mono(arena, &src, TargetInfo::default_wasm32()) { + Ok(m) => m, + Err(messages) => return Err(messages.join("\n\n")), + }; + + let MonomorphizedModule { + module_id, + procedures, + mut interns, + exposed_to_host, + .. + } = mono; + + /* + TODO + - reuse code from test_gen/src/wasm.rs + - use return type to create test_wrapper + + */ + + let app_module_bytes: &[u8] = &[]; + + js_create_app(app_module_bytes) + .await + .map_err(|js| format!("{:?}", js))?; + + let app_final_memory_size: usize = js_run_app(); + + // Copy the app's memory and get the result address + let app_memory_copy: &mut [u8] = arena.alloc_slice_fill_default(app_final_memory_size); + let app_result_addr = js_get_result_and_memory(app_memory_copy.as_mut_ptr()); + + /* + TODO + - gen_and_eval_wasm + + */ + + // Create a String representation of the result value + let output_text = format!("{}", app_result_addr); + + Ok(output_text) +} diff --git a/repl_wasm/src/repl.js b/repl_wasm/src/repl.js index 2cbc6a54c8..e3f9c374eb 100644 --- a/repl_wasm/src/repl.js +++ b/repl_wasm/src/repl.js @@ -1,6 +1,7 @@ // wasm_bindgen's JS code expects our imported functions to be global -window.js_create_and_run_app = js_create_and_run_app; -window.js_copy_app_memory = js_copy_app_memory; +window.js_create_app = js_create_app; +window.js_run_app = js_run_app; +window.js_get_result_and_memory = js_get_result_and_memory; import * as mock_repl from "./mock_repl.js"; // ---------------------------------------------------------------------------- @@ -18,6 +19,7 @@ const repl = { textEncoder: new TextEncoder(), compiler: null, + app: null, // Temporary storage for values passing back and forth between JS and Wasm result: { addr: 0, buffer: new ArrayBuffer() }, @@ -69,26 +71,29 @@ async function processInputQueue() { // Callbacks to JS from Rust // ---------------------------------------------------------------------------- -// Transform the app bytes into a Wasm module and execute it -// Cache the result but don't send it to Rust yet, as it needs to allocate space first. -// Then it will immediately call js_copy_app_memory. -async function js_create_and_run_app(app_bytes, app_memory_size_ptr) { - const { instance: app } = await WebAssembly.instantiate(app_bytes); +// Create an executable Wasm instance from an array of bytes +// (Browser validates the module and does the final compilation to the host's machine code.) +async function js_create_app(wasm_module_bytes) { + const { instance } = await WebAssembly.instantiate(wasm_module_bytes); + repl.app = instance; +} - const addr = app.exports.run(); - const { buffer } = app.exports.memory; +// Call the main function of the app, via the test wrapper +// Cache the result and return the size of the app's memory +function js_run_app() { + const { run, memory } = repl.app.exports; + const addr = run(); + const { buffer } = memory; repl.result = { addr, buffer }; // Tell Rust how much space to reserve for its copy of the app's memory buffer. - // The app might have grown its heap while running, so we couldn't predict it beforehand. - // Write the result to memory instead of returning, since wasm_bindgen only allows - // imported async functions to return JsValue, which has some conversion overhead. - const compilerMemory32 = new Uint32Array(repl.compiler.memory.buffer); - compilerMemory32[app_memory_size_ptr >> 2] = buffer.byteLength; + // This is not predictable, since the app can resize its own memory via malloc. + return buffer.byteLength; } -// Now that the Rust app has allocated space for the app's memory buffer, we can copy it -function js_copy_app_memory(buffer_alloc_addr) { +// After the Rust app has allocated space for the app's memory buffer, +// it calls this function and we copy it, and return the result too +function js_get_result_and_memory(buffer_alloc_addr) { const { addr, buffer } = repl.result; const appMemory = new Uint8Array(buffer); const compilerMemory = new Uint8Array(repl.compiler.memory.buffer); diff --git a/repl_wasm/src/repl.rs b/repl_wasm/src/repl.rs deleted file mode 100644 index 94265a37d6..0000000000 --- a/repl_wasm/src/repl.rs +++ /dev/null @@ -1,39 +0,0 @@ -mod generated_app_bytes; - -use bumpalo::Bump; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::JsValue; - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(catch)] - pub async fn js_create_and_run_app( - app_bytes: &[u8], - app_memory_size_ptr: *mut usize, - ) -> Result<(), JsValue>; - pub fn js_copy_app_memory(buffer_alloc_addr: *mut u8) -> usize; -} - -#[wasm_bindgen] -pub async fn webrepl_run(input_text: String) -> Result { - let arena = &Bump::new(); - - // Compile the app - let (app_bytes, identifiers) = compile(arena, input_text)?; - - // Execute the app (asynchronously in JS) - let mut app_final_memory_size: usize = 0; - let size_mut_ptr = (&mut app_final_memory_size) as *mut usize; - js_create_and_run_app(app_bytes, size_mut_ptr) - .await - .map_err(|js| format!("{:?}", js))?; - - // Get the address of the result in the app's memory, and a copy of its memory buffer - let app_memory_copy: &mut [u8] = arena.alloc_slice_fill_default(app_final_memory_size); - let app_result_addr = js_copy_app_memory(app_memory_copy.as_mut_ptr()); - - // Create a String representation of the result value - let output_text = stringify(app_memory_copy, app_result_addr, identifiers); - - Ok(output_text) -} From e9871947d32e94e30676490908faf2c1224f46fb Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 6 Feb 2022 08:48:09 +0000 Subject: [PATCH 519/541] repl: move wasm32_test_result to gen_wasm, and extract Wasm32Sized from FromWasm32Memory --- compiler/gen_wasm/src/lib.rs | 4 + compiler/gen_wasm/src/wasm32_sized.rs | 78 +++++++++++++++++++ .../src}/wasm32_test_result.rs | 16 ++-- .../src/helpers/from_wasm32_memory.rs | 54 +------------ compiler/test_gen/src/helpers/mod.rs | 2 - compiler/test_gen/src/helpers/wasm.rs | 2 +- repl_wasm/src/lib.rs | 3 +- 7 files changed, 96 insertions(+), 63 deletions(-) create mode 100644 compiler/gen_wasm/src/wasm32_sized.rs rename compiler/{test_gen/src/helpers => gen_wasm/src}/wasm32_test_result.rs (94%) diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 9163e592e5..c5dd2e83ce 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -4,6 +4,10 @@ mod low_level; mod storage; pub mod wasm_module; +// Helpers for interfacing to a Wasm module from outside +pub mod wasm32_sized; +pub mod wasm32_test_result; + use bumpalo::{self, collections::Vec, Bump}; use roc_collections::all::{MutMap, MutSet}; diff --git a/compiler/gen_wasm/src/wasm32_sized.rs b/compiler/gen_wasm/src/wasm32_sized.rs new file mode 100644 index 0000000000..2eea997b6d --- /dev/null +++ b/compiler/gen_wasm/src/wasm32_sized.rs @@ -0,0 +1,78 @@ +use roc_std::{RocDec, RocList, RocOrder, RocStr}; + +pub trait Wasm32Sized: 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)) + }; +} + +macro_rules! wasm32_sized_primitive { + ($($type_name:ident ,)+) => { + $( + impl Wasm32Sized for $type_name { + const SIZE_OF_WASM: usize = core::mem::size_of::<$type_name>(); + const ALIGN_OF_WASM: usize = core::mem::align_of::<$type_name>(); + } + )* + } +} + +wasm32_sized_primitive!( + u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, f32, f64, bool, RocDec, RocOrder, +); + +impl Wasm32Sized for () { + const SIZE_OF_WASM: usize = 0; + const ALIGN_OF_WASM: usize = 0; +} + +impl Wasm32Sized for RocStr { + const SIZE_OF_WASM: usize = 8; + const ALIGN_OF_WASM: usize = 4; +} + +impl Wasm32Sized for RocList { + const SIZE_OF_WASM: usize = 8; + const ALIGN_OF_WASM: usize = 4; +} + +impl Wasm32Sized for &'_ T { + const SIZE_OF_WASM: usize = 4; + const ALIGN_OF_WASM: usize = 4; +} + +impl Wasm32Sized for [T; N] { + const SIZE_OF_WASM: usize = N * T::SIZE_OF_WASM; + const ALIGN_OF_WASM: usize = T::ALIGN_OF_WASM; +} + +impl Wasm32Sized for usize { + const SIZE_OF_WASM: usize = 4; + const ALIGN_OF_WASM: usize = 4; +} + +impl Wasm32Sized 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); +} + +impl Wasm32Sized 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); +} + +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) +} diff --git a/compiler/test_gen/src/helpers/wasm32_test_result.rs b/compiler/gen_wasm/src/wasm32_test_result.rs similarity index 94% rename from compiler/test_gen/src/helpers/wasm32_test_result.rs rename to compiler/gen_wasm/src/wasm32_test_result.rs index a57dae737a..2f525149d8 100644 --- a/compiler/test_gen/src/helpers/wasm32_test_result.rs +++ b/compiler/gen_wasm/src/wasm32_test_result.rs @@ -1,7 +1,7 @@ use bumpalo::collections::Vec; -use crate::helpers::from_wasm32_memory::FromWasm32Memory; -use roc_gen_wasm::wasm_module::{ +use crate::wasm32_sized::Wasm32Sized; +use crate::wasm_module::{ linking::SymInfo, linking::WasmObjectSymbol, Align, CodeBuilder, Export, ExportType, LocalId, Signature, ValueType, WasmModule, }; @@ -136,7 +136,7 @@ impl Wasm32TestResult for &'_ T { impl Wasm32TestResult for [T; N] where - T: Wasm32TestResult + FromWasm32Memory, + T: Wasm32TestResult + Wasm32Sized, { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { build_wrapper_body_stack_memory(code_builder, main_function_index, N * T::ACTUAL_WIDTH) @@ -155,8 +155,8 @@ impl Wasm32TestResult for () { impl Wasm32TestResult for (T, U) where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, + T: Wasm32TestResult + Wasm32Sized, + U: Wasm32TestResult + Wasm32Sized, { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { build_wrapper_body_stack_memory( @@ -169,9 +169,9 @@ where impl Wasm32TestResult for (T, U, V) where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, - V: Wasm32TestResult + FromWasm32Memory, + T: Wasm32TestResult + Wasm32Sized, + U: Wasm32TestResult + Wasm32Sized, + V: Wasm32TestResult + Wasm32Sized, { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { build_wrapper_body_stack_memory( diff --git a/compiler/test_gen/src/helpers/from_wasm32_memory.rs b/compiler/test_gen/src/helpers/from_wasm32_memory.rs index b082e0f28e..b4bd972813 100644 --- a/compiler/test_gen/src/helpers/from_wasm32_memory.rs +++ b/compiler/test_gen/src/helpers/from_wasm32_memory.rs @@ -1,22 +1,12 @@ +use roc_gen_wasm::wasm32_sized::Wasm32Sized; use roc_std::{RocDec, RocList, RocOrder, RocStr}; -pub trait FromWasm32Memory: 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)) - }; - +pub trait FromWasm32Memory: Wasm32Sized { 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; @@ -53,16 +43,10 @@ from_wasm_memory_primitive!( ); impl FromWasm32Memory for () { - const SIZE_OF_WASM: usize = 0; - const ALIGN_OF_WASM: usize = 0; - fn decode(_: &wasmer::Memory, _: u32) -> Self {} } impl FromWasm32Memory 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); @@ -90,9 +74,6 @@ impl FromWasm32Memory for RocStr { } impl FromWasm32Memory 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); @@ -104,7 +85,7 @@ impl FromWasm32Memory for RocList { for i in 0..length { let item = ::decode( memory, - elements + i * ::SIZE_OF_WASM as u32, + elements + i * ::SIZE_OF_WASM as u32, ); items.push(item); } @@ -114,9 +95,6 @@ impl FromWasm32Memory for RocList { } impl FromWasm32Memory 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); @@ -129,12 +107,9 @@ impl FromWasm32Memory for &'_ T { } impl FromWasm32Memory 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 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]) }; @@ -143,18 +118,12 @@ impl FromWasm32Memory for [T; N] { } impl FromWasm32Memory 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 FromWasm32Memory 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 { debug_assert!( T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM, @@ -169,22 +138,7 @@ impl FromWasm32Memory for (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 FromWasm32Memory 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 { debug_assert!( T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM, diff --git a/compiler/test_gen/src/helpers/mod.rs b/compiler/test_gen/src/helpers/mod.rs index 5eac4eb5cd..faf3c9afee 100644 --- a/compiler/test_gen/src/helpers/mod.rs +++ b/compiler/test_gen/src/helpers/mod.rs @@ -7,8 +7,6 @@ pub mod from_wasm32_memory; pub mod llvm; #[cfg(feature = "gen-wasm")] pub mod wasm; -#[cfg(feature = "gen-wasm")] -pub mod wasm32_test_result; #[allow(dead_code)] pub fn zig_executable() -> String { diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 5d756fe921..c18d83ddb6 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -7,9 +7,9 @@ use std::path::{Path, PathBuf}; use wasmer::{Memory, WasmPtr}; use crate::helpers::from_wasm32_memory::FromWasm32Memory; -use crate::helpers::wasm32_test_result::Wasm32TestResult; use roc_can::builtins::builtin_defs_map; use roc_collections::all::{MutMap, MutSet}; +use roc_gen_wasm::wasm32_test_result::Wasm32TestResult; use roc_gen_wasm::{DEBUG_LOG_SETTINGS, MEMORY_NAME}; // Should manually match build.rs diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index 67b0aa8274..27e9d9d714 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -157,7 +157,7 @@ pub async fn repl_wasm_entrypoint_from_js(src: String) -> Result TODO - reuse code from test_gen/src/wasm.rs - use return type to create test_wrapper - + - preload builtins and libc platform */ let app_module_bytes: &[u8] = &[]; @@ -175,7 +175,6 @@ pub async fn repl_wasm_entrypoint_from_js(src: String) -> Result /* TODO - gen_and_eval_wasm - */ // Create a String representation of the result value From 1783fa8009fc31d071b4fe562b99dfb4aad556f2 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 6 Feb 2022 08:50:41 +0000 Subject: [PATCH 520/541] repl: rename FromWasm32Memory -> FromWasmerMemory --- ...wasm32_memory.rs => from_wasmer_memory.rs} | 42 +++++++++---------- compiler/test_gen/src/helpers/mod.rs | 2 +- compiler/test_gen/src/helpers/wasm.rs | 8 ++-- 3 files changed, 26 insertions(+), 26 deletions(-) rename compiler/test_gen/src/helpers/{from_wasm32_memory.rs => from_wasmer_memory.rs} (76%) diff --git a/compiler/test_gen/src/helpers/from_wasm32_memory.rs b/compiler/test_gen/src/helpers/from_wasmer_memory.rs similarity index 76% rename from compiler/test_gen/src/helpers/from_wasm32_memory.rs rename to compiler/test_gen/src/helpers/from_wasmer_memory.rs index b4bd972813..99f368f87f 100644 --- a/compiler/test_gen/src/helpers/from_wasm32_memory.rs +++ b/compiler/test_gen/src/helpers/from_wasmer_memory.rs @@ -1,7 +1,7 @@ use roc_gen_wasm::wasm32_sized::Wasm32Sized; use roc_std::{RocDec, RocList, RocOrder, RocStr}; -pub trait FromWasm32Memory: Wasm32Sized { +pub trait FromWasmerMemory: Wasm32Sized { fn decode(memory: &wasmer::Memory, offset: u32) -> Self; } @@ -31,7 +31,7 @@ macro_rules! from_wasm_memory_primitive_decode { macro_rules! from_wasm_memory_primitive { ($($type_name:ident ,)+) => { $( - impl FromWasm32Memory for $type_name { + impl FromWasmerMemory for $type_name { from_wasm_memory_primitive_decode!($type_name); } )* @@ -42,13 +42,13 @@ from_wasm_memory_primitive!( u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, f32, f64, bool, RocDec, RocOrder, ); -impl FromWasm32Memory for () { +impl FromWasmerMemory for () { fn decode(_: &wasmer::Memory, _: u32) -> Self {} } -impl FromWasm32Memory for RocStr { +impl FromWasmerMemory for RocStr { fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - let bytes = ::decode(memory, offset); + let bytes = ::decode(memory, offset); let length = (bytes >> 32) as u32; let elements = bytes as u32; @@ -73,9 +73,9 @@ impl FromWasm32Memory for RocStr { } } -impl FromWasm32Memory for RocList { +impl FromWasmerMemory for RocList { fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - let bytes = ::decode(memory, offset); + let bytes = ::decode(memory, offset); let length = (bytes >> 32) as u32; let elements = bytes as u32; @@ -83,7 +83,7 @@ impl FromWasm32Memory for RocList { let mut items = Vec::with_capacity(length as usize); for i in 0..length { - let item = ::decode( + let item = ::decode( memory, elements + i * ::SIZE_OF_WASM as u32, ); @@ -94,11 +94,11 @@ impl FromWasm32Memory for RocList { } } -impl FromWasm32Memory for &'_ T { +impl FromWasmerMemory for &'_ T { fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - let elements = ::decode(memory, offset); + let elements = ::decode(memory, offset); - let actual = ::decode(memory, elements); + let actual = ::decode(memory, elements); let b = Box::new(actual); @@ -106,7 +106,7 @@ impl FromWasm32Memory for &'_ T { } } -impl FromWasm32Memory for [T; N] { +impl FromWasmerMemory for [T; N] { 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; @@ -117,28 +117,28 @@ impl FromWasm32Memory for [T; N] { } } -impl FromWasm32Memory for usize { +impl FromWasmerMemory for usize { fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - ::decode(memory, offset) as usize + ::decode(memory, offset) as usize } } -impl FromWasm32Memory for (T, U) { +impl FromWasmerMemory for (T, U) { fn decode(memory: &wasmer::Memory, offset: u32) -> Self { debug_assert!( T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM, "this function does not handle alignment" ); - let t = ::decode(memory, offset); + let t = ::decode(memory, offset); - let u = ::decode(memory, offset + T::ACTUAL_WIDTH as u32); + let u = ::decode(memory, offset + T::ACTUAL_WIDTH as u32); (t, u) } } -impl FromWasm32Memory for (T, U, V) { +impl FromWasmerMemory for (T, U, V) { fn decode(memory: &wasmer::Memory, offset: u32) -> Self { debug_assert!( T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM, @@ -150,11 +150,11 @@ impl FromWasm32Me "this function does not handle alignment" ); - let t = ::decode(memory, offset); + let t = ::decode(memory, offset); - let u = ::decode(memory, offset + T::ACTUAL_WIDTH as u32); + let u = ::decode(memory, offset + T::ACTUAL_WIDTH as u32); - let v = ::decode( + let v = ::decode( memory, offset + T::ACTUAL_WIDTH as u32 + U::ACTUAL_WIDTH as u32, ); diff --git a/compiler/test_gen/src/helpers/mod.rs b/compiler/test_gen/src/helpers/mod.rs index faf3c9afee..10e3c70e8b 100644 --- a/compiler/test_gen/src/helpers/mod.rs +++ b/compiler/test_gen/src/helpers/mod.rs @@ -2,7 +2,7 @@ extern crate bumpalo; #[cfg(feature = "gen-dev")] pub mod dev; -pub mod from_wasm32_memory; +pub mod from_wasmer_memory; #[cfg(feature = "gen-llvm")] pub mod llvm; #[cfg(feature = "gen-wasm")] diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index c18d83ddb6..f16bd5f819 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -6,7 +6,7 @@ use std::marker::PhantomData; use std::path::{Path, PathBuf}; use wasmer::{Memory, WasmPtr}; -use crate::helpers::from_wasm32_memory::FromWasm32Memory; +use crate::helpers::from_wasmer_memory::FromWasmerMemory; use roc_can::builtins::builtin_defs_map; use roc_collections::all::{MutMap, MutSet}; use roc_gen_wasm::wasm32_test_result::Wasm32TestResult; @@ -193,7 +193,7 @@ fn load_bytes_into_runtime(bytes: Vec) -> wasmer::Instance { #[allow(dead_code)] pub fn assert_wasm_evals_to_help(src: &str, phantom: PhantomData) -> Result where - T: FromWasm32Memory + Wasm32TestResult, + T: FromWasmerMemory + Wasm32TestResult, { let arena = bumpalo::Bump::new(); @@ -225,7 +225,7 @@ where // Manually provide address and size based on printf in wasm_test_platform.c crate::helpers::wasm::debug_memory_hex(memory, 0x11440, 24); } - let output = ::decode(memory, address as u32); + let output = ::decode(memory, address as u32); Ok(output) } @@ -239,7 +239,7 @@ pub fn assert_wasm_refcounts_help( num_refcounts: usize, ) -> Result, String> where - T: FromWasm32Memory + Wasm32TestResult, + T: FromWasmerMemory + Wasm32TestResult, { let arena = bumpalo::Bump::new(); From c5ccc99e20dbb55b5a568a5b2087fe4a76484a32 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 6 Feb 2022 09:15:35 +0000 Subject: [PATCH 521/541] repl: rename Wasm32TestResult -> Wasm32Result --- compiler/gen_wasm/src/lib.rs | 2 +- ...wasm32_test_result.rs => wasm32_result.rs} | 70 +++++++++---------- compiler/test_gen/src/helpers/wasm.rs | 12 ++-- 3 files changed, 42 insertions(+), 42 deletions(-) rename compiler/gen_wasm/src/{wasm32_test_result.rs => wasm32_result.rs} (74%) diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index c5dd2e83ce..b1d72458c0 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -5,8 +5,8 @@ mod storage; pub mod wasm_module; // Helpers for interfacing to a Wasm module from outside +pub mod wasm32_result; pub mod wasm32_sized; -pub mod wasm32_test_result; use bumpalo::{self, collections::Vec, Bump}; diff --git a/compiler/gen_wasm/src/wasm32_test_result.rs b/compiler/gen_wasm/src/wasm32_result.rs similarity index 74% rename from compiler/gen_wasm/src/wasm32_test_result.rs rename to compiler/gen_wasm/src/wasm32_result.rs index 2f525149d8..f4548c8f75 100644 --- a/compiler/gen_wasm/src/wasm32_test_result.rs +++ b/compiler/gen_wasm/src/wasm32_result.rs @@ -7,8 +7,8 @@ use crate::wasm_module::{ }; use roc_std::{RocDec, RocList, RocOrder, RocStr}; -pub trait Wasm32TestResult { - fn insert_test_wrapper<'a>( +pub trait Wasm32Result { + fn insert_wrapper<'a>( arena: &'a bumpalo::Bump, module: &mut WasmModule<'a>, wrapper_name: &str, @@ -64,9 +64,9 @@ macro_rules! build_wrapper_body_primitive { }; } -macro_rules! wasm_test_result_primitive { +macro_rules! wasm_result_primitive { ($type_name: ident, $store_instruction: ident, $align: expr) => { - impl Wasm32TestResult for $type_name { + impl Wasm32Result for $type_name { build_wrapper_body_primitive!($store_instruction, $align); } }; @@ -89,9 +89,9 @@ fn build_wrapper_body_stack_memory( code_builder.build_fn_header_and_footer(local_types, size as i32, frame_pointer); } -macro_rules! wasm_test_result_stack_memory { +macro_rules! wasm_result_stack_memory { ($type_name: ident) => { - impl Wasm32TestResult for $type_name { + impl Wasm32Result for $type_name { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { build_wrapper_body_stack_memory( code_builder, @@ -103,47 +103,47 @@ macro_rules! wasm_test_result_stack_memory { }; } -wasm_test_result_primitive!(bool, i32_store8, Align::Bytes1); -wasm_test_result_primitive!(RocOrder, i32_store8, Align::Bytes1); +wasm_result_primitive!(bool, i32_store8, Align::Bytes1); +wasm_result_primitive!(RocOrder, i32_store8, Align::Bytes1); -wasm_test_result_primitive!(u8, i32_store8, Align::Bytes1); -wasm_test_result_primitive!(i8, i32_store8, Align::Bytes1); -wasm_test_result_primitive!(u16, i32_store16, Align::Bytes2); -wasm_test_result_primitive!(i16, i32_store16, Align::Bytes2); -wasm_test_result_primitive!(u32, i32_store, Align::Bytes4); -wasm_test_result_primitive!(i32, i32_store, Align::Bytes4); -wasm_test_result_primitive!(u64, i64_store, Align::Bytes8); -wasm_test_result_primitive!(i64, i64_store, Align::Bytes8); -wasm_test_result_primitive!(usize, i32_store, Align::Bytes4); +wasm_result_primitive!(u8, i32_store8, Align::Bytes1); +wasm_result_primitive!(i8, i32_store8, Align::Bytes1); +wasm_result_primitive!(u16, i32_store16, Align::Bytes2); +wasm_result_primitive!(i16, i32_store16, Align::Bytes2); +wasm_result_primitive!(u32, i32_store, Align::Bytes4); +wasm_result_primitive!(i32, i32_store, Align::Bytes4); +wasm_result_primitive!(u64, i64_store, Align::Bytes8); +wasm_result_primitive!(i64, i64_store, Align::Bytes8); +wasm_result_primitive!(usize, i32_store, Align::Bytes4); -wasm_test_result_primitive!(f32, f32_store, Align::Bytes4); -wasm_test_result_primitive!(f64, f64_store, Align::Bytes8); +wasm_result_primitive!(f32, f32_store, Align::Bytes4); +wasm_result_primitive!(f64, f64_store, Align::Bytes8); -wasm_test_result_stack_memory!(u128); -wasm_test_result_stack_memory!(i128); -wasm_test_result_stack_memory!(RocDec); -wasm_test_result_stack_memory!(RocStr); +wasm_result_stack_memory!(u128); +wasm_result_stack_memory!(i128); +wasm_result_stack_memory!(RocDec); +wasm_result_stack_memory!(RocStr); -impl Wasm32TestResult for RocList { +impl Wasm32Result for RocList { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { build_wrapper_body_stack_memory(code_builder, main_function_index, 12) } } -impl Wasm32TestResult for &'_ T { +impl Wasm32Result for &'_ T { build_wrapper_body_primitive!(i32_store, Align::Bytes4); } -impl Wasm32TestResult for [T; N] +impl Wasm32Result for [T; N] where - T: Wasm32TestResult + Wasm32Sized, + T: Wasm32Result + Wasm32Sized, { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { build_wrapper_body_stack_memory(code_builder, main_function_index, N * T::ACTUAL_WIDTH) } } -impl Wasm32TestResult for () { +impl Wasm32Result for () { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { // Main's symbol index is the same as its function index, since the first symbols we created were for procs let main_symbol_index = main_function_index; @@ -153,10 +153,10 @@ impl Wasm32TestResult for () { } } -impl Wasm32TestResult for (T, U) +impl Wasm32Result for (T, U) where - T: Wasm32TestResult + Wasm32Sized, - U: Wasm32TestResult + Wasm32Sized, + T: Wasm32Result + Wasm32Sized, + U: Wasm32Result + Wasm32Sized, { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { build_wrapper_body_stack_memory( @@ -167,11 +167,11 @@ where } } -impl Wasm32TestResult for (T, U, V) +impl Wasm32Result for (T, U, V) where - T: Wasm32TestResult + Wasm32Sized, - U: Wasm32TestResult + Wasm32Sized, - V: Wasm32TestResult + Wasm32Sized, + T: Wasm32Result + Wasm32Sized, + U: Wasm32Result + Wasm32Sized, + V: Wasm32Result + Wasm32Sized, { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { build_wrapper_body_stack_memory( diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index f16bd5f819..d2bd39ef81 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -9,7 +9,7 @@ use wasmer::{Memory, WasmPtr}; use crate::helpers::from_wasmer_memory::FromWasmerMemory; use roc_can::builtins::builtin_defs_map; use roc_collections::all::{MutMap, MutSet}; -use roc_gen_wasm::wasm32_test_result::Wasm32TestResult; +use roc_gen_wasm::wasm32_result::Wasm32Result; use roc_gen_wasm::{DEBUG_LOG_SETTINGS, MEMORY_NAME}; // Should manually match build.rs @@ -40,7 +40,7 @@ pub enum TestType { } #[allow(dead_code)] -pub fn compile_and_load<'a, T: Wasm32TestResult>( +pub fn compile_and_load<'a, T: Wasm32Result>( arena: &'a bumpalo::Bump, src: &str, stdlib: &'a roc_builtins::std::StdLib, @@ -72,7 +72,7 @@ fn src_hash(src: &str) -> u64 { hash_state.finish() } -fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( +fn compile_roc_to_wasm_bytes<'a, T: Wasm32Result>( arena: &'a bumpalo::Bump, stdlib: &'a roc_builtins::std::StdLib, preload_bytes: &[u8], @@ -138,7 +138,7 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32TestResult>( procedures, ); - T::insert_test_wrapper(arena, &mut module, TEST_WRAPPER_NAME, main_fn_index); + T::insert_wrapper(arena, &mut module, TEST_WRAPPER_NAME, main_fn_index); // Export the initialiser function for refcount tests let init_refcount_bytes = INIT_REFCOUNT_NAME.as_bytes(); @@ -193,7 +193,7 @@ fn load_bytes_into_runtime(bytes: Vec) -> wasmer::Instance { #[allow(dead_code)] pub fn assert_wasm_evals_to_help(src: &str, phantom: PhantomData) -> Result where - T: FromWasmerMemory + Wasm32TestResult, + T: FromWasmerMemory + Wasm32Result, { let arena = bumpalo::Bump::new(); @@ -239,7 +239,7 @@ pub fn assert_wasm_refcounts_help( num_refcounts: usize, ) -> Result, String> where - T: FromWasmerMemory + Wasm32TestResult, + T: FromWasmerMemory + Wasm32Result, { let arena = bumpalo::Bump::new(); From b969f207c40ea3fa58ada583f0bfee6c318e11ba Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 6 Feb 2022 10:06:38 +0000 Subject: [PATCH 522/541] repl: Cargo build script for repl_wasm, generating the pre-linked binary --- Cargo.lock | 1 + repl_wasm/.gitignore | 3 ++ repl_wasm/Cargo.toml | 3 ++ repl_wasm/build.rs | 67 +++++++++++++++++++++++++++++++ repl_wasm/build.sh | 23 ----------- repl_wasm/src/lib.rs | 2 + repl_wasm/src/repl_platform.c | 74 +++++++++++++++++++++++++++++++++++ 7 files changed, 150 insertions(+), 23 deletions(-) create mode 100644 repl_wasm/build.rs delete mode 100755 repl_wasm/build.sh create mode 100644 repl_wasm/src/repl_platform.c diff --git a/Cargo.lock b/Cargo.lock index d8beff51f8..220f0fe1ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3722,6 +3722,7 @@ version = "0.1.0" dependencies = [ "bumpalo", "js-sys", + "roc_builtins", "roc_load", "roc_parse", "roc_repl_eval", diff --git a/repl_wasm/.gitignore b/repl_wasm/.gitignore index 43ad75e279..dfccf1ba05 100644 --- a/repl_wasm/.gitignore +++ b/repl_wasm/.gitignore @@ -2,5 +2,8 @@ /dist /notes.md +# Binary data folder +/data + # wasm-pack /pkg diff --git a/repl_wasm/Cargo.toml b/repl_wasm/Cargo.toml index 8cb4113fbb..2ac29e6e77 100644 --- a/repl_wasm/Cargo.toml +++ b/repl_wasm/Cargo.toml @@ -6,6 +6,9 @@ version = "0.1.0" [lib] crate-type = ["cdylib"] +[build-dependencies] +roc_builtins = {path = "../compiler/builtins"} + [dependencies] bumpalo = {version = "3.8.0", features = ["collections"]} js-sys = "0.3.56" diff --git a/repl_wasm/build.rs b/repl_wasm/build.rs new file mode 100644 index 0000000000..3a1ffed494 --- /dev/null +++ b/repl_wasm/build.rs @@ -0,0 +1,67 @@ +use std::ffi::OsStr; +use std::path::Path; +use std::process::Command; + +use roc_builtins::bitcode; + +const PLATFORM_FILENAME: &str = "repl_platform"; +const PRE_LINKED_BINARY: &str = "data/pre_linked_binary.o"; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=src/{}.c", PLATFORM_FILENAME); + + std::fs::create_dir_all("./data").unwrap(); + + // Build a pre-linked binary with platform, builtins and all their libc dependencies + // This builds a library file that exports all symbols. It has no linker data but we don't need it. + // See discussion with Luuk de Gram (Zig contributor) + // https://github.com/rtfeldman/roc/pull/2181#pullrequestreview-839608063 + let args = [ + "build-lib", + "-target", + "wasm32-wasi", + "-lc", + "-dynamic", // -dynamic ensures libc code goes into the binary + bitcode::BUILTINS_WASM32_OBJ_PATH, + &format!("src/{}.c", PLATFORM_FILENAME), + &format!("-femit-bin={}", PRE_LINKED_BINARY), + ]; + + let zig = zig_executable(); + + // println!("{} {}", zig, args.join(" ")); + + run_command(Path::new("."), &zig, args); +} + +fn zig_executable() -> String { + match std::env::var("ROC_ZIG") { + Ok(path) => path, + Err(_) => "zig".into(), + } +} + +fn run_command>(path: P, command_str: &str, args: I) -> String +where + I: IntoIterator, + S: AsRef, +{ + let output_result = Command::new(OsStr::new(&command_str)) + .current_dir(path) + .args(args) + .output(); + match output_result { + Ok(output) => match output.status.success() { + true => std::str::from_utf8(&output.stdout).unwrap().to_string(), + false => { + let error_str = match std::str::from_utf8(&output.stderr) { + Ok(stderr) => stderr.to_string(), + Err(_) => format!("Failed to run \"{}\"", command_str), + }; + panic!("{} failed: {}", command_str, error_str); + } + }, + Err(reason) => panic!("{} failed: {}", command_str, reason), + } +} diff --git a/repl_wasm/build.sh b/repl_wasm/build.sh deleted file mode 100755 index ab33809385..0000000000 --- a/repl_wasm/build.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -set -ex - -app_wasm="generated/app.wasm" -app_include="generated/app_bytes.c" -app_rust="src/generated_app_bytes.rs" -comp_wasm="dist/compiler.wasm" - -mkdir -p generated dist -rm -f generated/* dist/* - -zig9 build-lib -target wasm32-freestanding-musl -dynamic src/app.c -femit-bin=$app_wasm - -./print_bytes_as_rust_code.js $app_wasm > $app_rust - -# wasm-pack build --target web --dev -wasm-pack build --target web --release - -cp src/index.html dist -cp src/repl.js dist -cp pkg/mock_repl_bg.wasm dist -cp pkg/mock_repl.js dist diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index 27e9d9d714..957539a865 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -153,6 +153,8 @@ pub async fn repl_wasm_entrypoint_from_js(src: String) -> Result .. } = mono; + let pre_linked_binary: &'static [u8] = include_bytes!("../data/pre_linked_binary.o"); + /* TODO - reuse code from test_gen/src/wasm.rs diff --git a/repl_wasm/src/repl_platform.c b/repl_wasm/src/repl_platform.c new file mode 100644 index 0000000000..a7fecad410 --- /dev/null +++ b/repl_wasm/src/repl_platform.c @@ -0,0 +1,74 @@ +#include + +// printf debugging slows down automated tests a lot so make sure to disable it afterwards! +// (Wasmer has a much larger binary to validate and compile. Chrome handles it easily though.) +#define ENABLE_PRINTF 0 + +//-------------------------- + +void *roc_alloc(size_t size, unsigned int alignment) +{ + void *allocated = malloc(size); + +#if ENABLE_PRINTF + if (!allocated) + { + fprintf(stderr, "roc_alloc failed\n"); + exit(1); + } + else + { + printf("roc_alloc allocated %d bytes with alignment %d at %p\n", size, alignment, allocated); + } +#endif + return allocated; +} + +//-------------------------- + +void *roc_realloc(void *ptr, size_t new_size, size_t old_size, + unsigned int alignment) +{ +#if ENABLE_PRINTF + printf("roc_realloc reallocated %p from %d to %d with alignment %zd\n", + ptr, old_size, new_size, alignment); +#endif + return realloc(ptr, new_size); +} + +//-------------------------- + +void roc_dealloc(void *ptr, unsigned int alignment) +{ + +#if ENABLE_PRINTF + printf("roc_dealloc deallocated %p with alignment %zd\n", ptr, alignment); +#endif + free(ptr); +} + +//-------------------------- + +void roc_panic(void *ptr, unsigned int alignment) +{ +#if ENABLE_PRINTF + char *msg = (char *)ptr; + fprintf(stderr, + "Application crashed with message\n\n %s\n\nShutting down\n", msg); +#endif + abort(); +} + +//-------------------------- + +void *roc_memcpy(void *dest, const void *src, size_t n) +{ + return memcpy(dest, src, n); +} + +//-------------------------- + +void *roc_memset(void *str, int c, size_t n) +{ + return memset(str, c, n); +} From 2fd2a85887d9b5b2d77c5cd446928100ee6244be Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 6 Feb 2022 10:29:46 +0000 Subject: [PATCH 523/541] wasm: delete redundant enum TestType --- compiler/test_gen/src/helpers/wasm.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index d2bd39ef81..0b52a9a64f 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -32,20 +32,12 @@ fn promote_expr_to_module(src: &str) -> String { buffer } -pub enum TestType { - /// Test that some Roc code evaluates to the right result - Evaluate, - /// Test that some Roc values have the right refcount - Refcount, -} - #[allow(dead_code)] pub fn compile_and_load<'a, T: Wasm32Result>( arena: &'a bumpalo::Bump, src: &str, stdlib: &'a roc_builtins::std::StdLib, _test_wrapper_type_info: PhantomData, - _test_type: TestType, ) -> wasmer::Instance { let platform_bytes = load_platform_and_builtins(); @@ -200,8 +192,7 @@ where // NOTE the stdlib must be in the arena; just taking a reference will segfault let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); - let instance = - crate::helpers::wasm::compile_and_load(&arena, src, stdlib, phantom, TestType::Evaluate); + let instance = crate::helpers::wasm::compile_and_load(&arena, src, stdlib, phantom); let memory = instance.exports.get_memory(MEMORY_NAME).unwrap(); @@ -246,8 +237,7 @@ where // NOTE the stdlib must be in the arena; just taking a reference will segfault let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); - let instance = - crate::helpers::wasm::compile_and_load(&arena, src, stdlib, phantom, TestType::Refcount); + let instance = crate::helpers::wasm::compile_and_load(&arena, src, stdlib, phantom); let memory = instance.exports.get_memory(MEMORY_NAME).unwrap(); From 85b418ebdff187ced57f9a8c10e19705b5e80186 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 6 Feb 2022 11:31:49 +0000 Subject: [PATCH 524/541] wasm: get rid of Result from gen_wasm, rename a function, improve comments --- compiler/build/src/program.rs | 3 +-- compiler/gen_wasm/src/lib.rs | 12 ++++++------ compiler/test_gen/src/helpers/wasm.rs | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 24b2cb9f0e..252a75c050 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -517,8 +517,7 @@ fn gen_from_mono_module_dev_wasm32( &mut interns, platform_and_builtins_object_file_bytes, procedures, - ) - .unwrap(); + ); std::fs::write(&app_o_file, &bytes).expect("failed to write object to file"); diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index b1d72458c0..7030419e49 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -47,25 +47,25 @@ pub struct Env<'a> { pub exposed_to_host: MutSet, } -/// Entry point for production +/// Entry point for Roc CLI pub fn build_module<'a>( env: &'a Env<'a>, interns: &'a mut Interns, preload_bytes: &[u8], procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, -) -> Result, String> { +) -> std::vec::Vec { let (mut wasm_module, called_preload_fns, _) = - build_module_without_test_wrapper(env, interns, preload_bytes, procedures); + build_module_without_wrapper(env, interns, preload_bytes, procedures); wasm_module.remove_dead_preloads(env.arena, called_preload_fns); let mut buffer = std::vec::Vec::with_capacity(wasm_module.size()); wasm_module.serialize(&mut buffer); - Ok(buffer) + buffer } -/// Entry point for integration tests (test_gen) -pub fn build_module_without_test_wrapper<'a>( +/// Entry point for REPL (repl_wasm) and integration tests (test_gen) +pub fn build_module_without_wrapper<'a>( env: &'a Env<'a>, interns: &'a mut Interns, preload_bytes: &[u8], diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 0b52a9a64f..4418faf815 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -123,7 +123,7 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32Result>( }; let (mut module, called_preload_fns, main_fn_index) = - roc_gen_wasm::build_module_without_test_wrapper( + roc_gen_wasm::build_module_without_wrapper( &env, &mut interns, preload_bytes, From 0af4361f2ad3edfe11db7160b3220cfb5462b0c4 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 6 Feb 2022 14:32:38 +0000 Subject: [PATCH 525/541] repl: continue fleshing out the Wasm repl code. Generate wrapper function. --- Cargo.lock | 3 + compiler/gen_wasm/src/wasm32_result.rs | 99 +++++++++++++++++++------- repl_wasm/Cargo.toml | 3 + repl_wasm/src/lib.rs | 64 ++++++++++++++--- 4 files changed, 134 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 220f0fe1ac..1d68c7e26d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3723,10 +3723,13 @@ dependencies = [ "bumpalo", "js-sys", "roc_builtins", + "roc_collections", + "roc_gen_wasm", "roc_load", "roc_parse", "roc_repl_eval", "roc_target", + "roc_types", "wasm-bindgen", "wasm-bindgen-futures", ] diff --git a/compiler/gen_wasm/src/wasm32_result.rs b/compiler/gen_wasm/src/wasm32_result.rs index f4548c8f75..ce21c83683 100644 --- a/compiler/gen_wasm/src/wasm32_result.rs +++ b/compiler/gen_wasm/src/wasm32_result.rs @@ -1,4 +1,13 @@ -use bumpalo::collections::Vec; +/* +Generate a wrapper function to expose a generic interface from a Wasm module for any result type. +The wrapper function ensures the value is written to memory and returns its address as i32. +The user needs to analyse the Wasm module's memory to decode the result. +*/ + +use bumpalo::{collections::Vec, Bump}; +use roc_builtins::bitcode::{FloatWidth, IntWidth}; +use roc_mono::layout::{Builtin, Layout}; +use roc_target::TargetInfo; use crate::wasm32_sized::Wasm32Sized; use crate::wasm_module::{ @@ -7,35 +16,15 @@ use crate::wasm_module::{ }; use roc_std::{RocDec, RocList, RocOrder, RocStr}; +/// Type-driven wrapper generation pub trait Wasm32Result { fn insert_wrapper<'a>( - arena: &'a bumpalo::Bump, + arena: &'a Bump, module: &mut WasmModule<'a>, wrapper_name: &str, main_function_index: u32, ) { - let index = module.import.function_count - + module.code.preloaded_count - + module.code.code_builders.len() as u32; - - module.add_function_signature(Signature { - param_types: Vec::with_capacity_in(0, arena), - ret_type: Some(ValueType::I32), - }); - - module.export.append(Export { - name: arena.alloc_slice_copy(wrapper_name.as_bytes()), - ty: ExportType::Func, - index, - }); - - let linker_symbol = SymInfo::Function(WasmObjectSymbol::Defined { - flags: 0, - index, - name: wrapper_name.to_string(), - }); - module.linking.symbol_table.push(linker_symbol); - + insert_wrapper_metadata(arena, module, wrapper_name); let mut code_builder = CodeBuilder::new(arena); Self::build_wrapper_body(&mut code_builder, main_function_index); module.code.code_builders.push(code_builder); @@ -44,6 +33,68 @@ pub trait Wasm32Result { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32); } +/// Layout-driven wrapper generation +pub fn insert_wrapper_for_layout<'a>( + arena: &'a Bump, + module: &mut WasmModule<'a>, + wrapper_name: &str, + main_fn_index: u32, + layout: &Layout<'a>, +) { + match layout { + Layout::Builtin(Builtin::Int(IntWidth::U8 | IntWidth::I8)) => { + i8::insert_wrapper(arena, module, wrapper_name, main_fn_index); + } + Layout::Builtin(Builtin::Int(IntWidth::U16 | IntWidth::I16)) => { + i16::insert_wrapper(arena, module, wrapper_name, main_fn_index); + } + Layout::Builtin(Builtin::Int(IntWidth::U32 | IntWidth::I32)) => { + i32::insert_wrapper(arena, module, wrapper_name, main_fn_index); + } + Layout::Builtin(Builtin::Int(IntWidth::U64 | IntWidth::I64)) => { + i64::insert_wrapper(arena, module, wrapper_name, main_fn_index); + } + Layout::Builtin(Builtin::Float(FloatWidth::F32)) => { + f32::insert_wrapper(arena, module, wrapper_name, main_fn_index); + } + Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { + f64::insert_wrapper(arena, module, wrapper_name, main_fn_index); + } + _ => { + // The result is not a Wasm primitive, it's an array of bytes in stack memory. + let size = layout.stack_size(TargetInfo::default_wasm32()); + insert_wrapper_metadata(arena, module, wrapper_name); + let mut code_builder = CodeBuilder::new(arena); + build_wrapper_body_stack_memory(&mut code_builder, main_fn_index, size as usize); + module.code.code_builders.push(code_builder); + } + } +} + +fn insert_wrapper_metadata<'a>(arena: &'a Bump, module: &mut WasmModule<'a>, wrapper_name: &str) { + let index = module.import.function_count + + module.code.preloaded_count + + module.code.code_builders.len() as u32; + + module.add_function_signature(Signature { + param_types: Vec::with_capacity_in(0, arena), + ret_type: Some(ValueType::I32), + }); + + module.export.append(Export { + name: arena.alloc_slice_copy(wrapper_name.as_bytes()), + ty: ExportType::Func, + index, + }); + + let linker_symbol = SymInfo::Function(WasmObjectSymbol::Defined { + flags: 0, + index, + name: wrapper_name.to_string(), + }); + module.linking.symbol_table.push(linker_symbol); +} + macro_rules! build_wrapper_body_primitive { ($store_instruction: ident, $align: expr) => { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { diff --git a/repl_wasm/Cargo.toml b/repl_wasm/Cargo.toml index 2ac29e6e77..03dd9536a2 100644 --- a/repl_wasm/Cargo.toml +++ b/repl_wasm/Cargo.toml @@ -15,7 +15,10 @@ js-sys = "0.3.56" wasm-bindgen = "0.2.79" wasm-bindgen-futures = "0.4.29" +roc_collections = {path = "../compiler/collections"} +roc_gen_wasm = {path = "../compiler/gen_wasm"} roc_load = {path = "../compiler/load"} roc_parse = {path = "../compiler/parse"} roc_repl_eval = {path = "../repl_eval"} roc_target = {path = "../compiler/roc_target"} +roc_types = {path = "../compiler/types"} diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index 957539a865..593df45363 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -1,12 +1,17 @@ -use bumpalo::Bump; +use bumpalo::{collections::vec::Vec, Bump}; use std::mem::size_of; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; +use roc_collections::all::MutSet; +use roc_gen_wasm::wasm32_result::insert_wrapper_for_layout; use roc_load::file::MonomorphizedModule; use roc_parse::ast::Expr; use roc_repl_eval::{gen::compile_to_mono, ReplApp, ReplAppMemory}; use roc_target::TargetInfo; +use roc_types::pretty_print::{content_to_string, name_all_type_vars}; + +const WRAPPER_NAME: &str = "wrapper"; #[wasm_bindgen] extern "C" { @@ -135,9 +140,10 @@ impl<'a> ReplApp<'a> for WasmReplApp<'a> { } } -#[wasm_bindgen] +// #[wasm_bindgen] pub async fn repl_wasm_entrypoint_from_js(src: String) -> Result { let arena = &Bump::new(); + let pre_linked_binary: &'static [u8] = include_bytes!("../data/pre_linked_binary.o"); // Compile the app let mono = match compile_to_mono(arena, &src, TargetInfo::default_wasm32()) { @@ -149,22 +155,58 @@ pub async fn repl_wasm_entrypoint_from_js(src: String) -> Result module_id, procedures, mut interns, + mut subs, exposed_to_host, .. } = mono; - let pre_linked_binary: &'static [u8] = include_bytes!("../data/pre_linked_binary.o"); + debug_assert_eq!(exposed_to_host.values.len(), 1); + let (main_fn_symbol, main_fn_var) = exposed_to_host.values.iter().next().unwrap(); + let main_fn_symbol = *main_fn_symbol; + let main_fn_var = *main_fn_var; - /* - TODO - - reuse code from test_gen/src/wasm.rs - - use return type to create test_wrapper - - preload builtins and libc platform - */ + // pretty-print the expr type string for later. + name_all_type_vars(main_fn_var, &mut subs); + let content = subs.get_content_without_compacting(main_fn_var); + let expr_type_str = content_to_string(content, &subs, module_id, &interns); - let app_module_bytes: &[u8] = &[]; + let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { + Some(layout) => *layout, + None => return Ok(format!(" : {}", expr_type_str)), + }; - js_create_app(app_module_bytes) + let env = roc_gen_wasm::Env { + arena, + module_id, + exposed_to_host: exposed_to_host + .values + .keys() + .copied() + .collect::>(), + }; + + let (mut module, called_preload_fns, main_fn_index) = + roc_gen_wasm::build_module_without_wrapper( + &env, + &mut interns, + pre_linked_binary, + procedures, + ); + + insert_wrapper_for_layout( + arena, + &mut module, + WRAPPER_NAME, + main_fn_index, + &main_fn_layout.result, + ); + + module.remove_dead_preloads(env.arena, called_preload_fns); + + let mut app_module_bytes = Vec::with_capacity_in(module.size(), arena); + module.serialize(&mut app_module_bytes); + + js_create_app(&app_module_bytes) .await .map_err(|js| format!("{:?}", js))?; From ff6b29dfe781aeeda341c3469c3c51c6569e18c0 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 6 Feb 2022 15:00:05 +0000 Subject: [PATCH 526/541] repl: first compiling version (untested) --- repl_wasm/src/lib.rs | 101 +++++++++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 42 deletions(-) diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index 593df45363..09a9e61cf0 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -4,10 +4,14 @@ use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; use roc_collections::all::MutSet; -use roc_gen_wasm::wasm32_result::insert_wrapper_for_layout; +use roc_gen_wasm::wasm32_result; use roc_load::file::MonomorphizedModule; use roc_parse::ast::Expr; -use roc_repl_eval::{gen::compile_to_mono, ReplApp, ReplAppMemory}; +use roc_repl_eval::{ + eval::jit_to_ast, + gen::{compile_to_mono, format_answer, ReplOutput}, + ReplApp, ReplAppMemory, +}; use roc_target::TargetInfo; use roc_types::pretty_print::{content_to_string, name_all_type_vars}; @@ -25,7 +29,6 @@ extern "C" { pub struct WasmReplApp<'a> { arena: &'a Bump, - _module: &'a [u8], } /// A copy of the app's memory, made after running the main function @@ -146,7 +149,8 @@ pub async fn repl_wasm_entrypoint_from_js(src: String) -> Result let pre_linked_binary: &'static [u8] = include_bytes!("../data/pre_linked_binary.o"); // Compile the app - let mono = match compile_to_mono(arena, &src, TargetInfo::default_wasm32()) { + let target_info = TargetInfo::default_wasm32(); + let mono = match compile_to_mono(arena, &src, target_info) { Ok(m) => m, Err(messages) => return Err(messages.join("\n\n")), }; @@ -175,54 +179,67 @@ pub async fn repl_wasm_entrypoint_from_js(src: String) -> Result None => return Ok(format!(" : {}", expr_type_str)), }; - let env = roc_gen_wasm::Env { - arena, - module_id, - exposed_to_host: exposed_to_host - .values - .keys() - .copied() - .collect::>(), - }; + let app_module_bytes = { + let env = roc_gen_wasm::Env { + arena, + module_id, + exposed_to_host: exposed_to_host + .values + .keys() + .copied() + .collect::>(), + }; - let (mut module, called_preload_fns, main_fn_index) = - roc_gen_wasm::build_module_without_wrapper( - &env, - &mut interns, - pre_linked_binary, - procedures, + let (mut module, called_preload_fns, main_fn_index) = { + roc_gen_wasm::build_module_without_wrapper( + &env, + &mut interns, // NOTE: must drop this mutable ref before jit_to_ast + pre_linked_binary, + procedures, + ) + }; + + wasm32_result::insert_wrapper_for_layout( + arena, + &mut module, + WRAPPER_NAME, + main_fn_index, + &main_fn_layout.result, ); - insert_wrapper_for_layout( - arena, - &mut module, - WRAPPER_NAME, - main_fn_index, - &main_fn_layout.result, - ); + module.remove_dead_preloads(env.arena, called_preload_fns); - module.remove_dead_preloads(env.arena, called_preload_fns); + let mut buffer = Vec::with_capacity_in(module.size(), arena); + module.serialize(&mut buffer); - let mut app_module_bytes = Vec::with_capacity_in(module.size(), arena); - module.serialize(&mut app_module_bytes); + buffer + }; + // Send the compiled binary out to JS and create an executable instance from it js_create_app(&app_module_bytes) .await .map_err(|js| format!("{:?}", js))?; - let app_final_memory_size: usize = js_run_app(); + let mut app = WasmReplApp { arena }; - // Copy the app's memory and get the result address - let app_memory_copy: &mut [u8] = arena.alloc_slice_fill_default(app_final_memory_size); - let app_result_addr = js_get_result_and_memory(app_memory_copy.as_mut_ptr()); + // Run the app and transform the result value to an AST `Expr` + // Restore type constructor names, and other user-facing info that was erased during compilation. + let res_answer = jit_to_ast( + arena, + &mut app, + "", // main_fn_name is ignored (only passed to WasmReplApp methods) + main_fn_layout, + content, + &interns, + module_id, + &subs, + target_info, + ); - /* - TODO - - gen_and_eval_wasm - */ - - // Create a String representation of the result value - let output_text = format!("{}", app_result_addr); - - Ok(output_text) + // Transform the Expr to a string + // `Result::Err` becomes a JS exception that will be caught and displayed + match format_answer(&arena, res_answer, expr_type_str) { + ReplOutput::NoProblems { expr, expr_type } => Ok(format!("\n{}: {}", expr, expr_type)), + ReplOutput::Problems(lines) => Err(format!("\n{}\n", lines.join("\n\n"))), + } } From c21b2bd0da1eda6d56b2378106b2d50ee3ffa226 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 6 Feb 2022 15:22:47 +0000 Subject: [PATCH 527/541] Formatting and Clippy --- compiler/test_gen/src/helpers/wasm.rs | 7 +------ repl_cli/src/lib.rs | 4 ++-- repl_wasm/src/lib.rs | 6 +++--- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 4418faf815..a0fb311283 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -123,12 +123,7 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32Result>( }; let (mut module, called_preload_fns, main_fn_index) = - roc_gen_wasm::build_module_without_wrapper( - &env, - &mut interns, - preload_bytes, - procedures, - ); + roc_gen_wasm::build_module_without_wrapper(&env, &mut interns, preload_bytes, procedures); T::insert_wrapper(arena, &mut module, TEST_WRAPPER_NAME, main_fn_index); diff --git a/repl_cli/src/lib.rs b/repl_cli/src/lib.rs index 9ab1910804..999f09de7a 100644 --- a/repl_cli/src/lib.rs +++ b/repl_cli/src/lib.rs @@ -302,11 +302,11 @@ fn gen_and_eval_llvm<'a>( // The app is `mut` only because Wasm needs it. // It has no public fields, and its "mutating" methods don't actually mutate. - let mut app = CliApp { lib }; + let app = CliApp { lib }; let res_answer = jit_to_ast( &arena, - &mut app, + &app, main_fn_name, main_fn_layout, content, diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index 09a9e61cf0..c71991bd84 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -220,13 +220,13 @@ pub async fn repl_wasm_entrypoint_from_js(src: String) -> Result .await .map_err(|js| format!("{:?}", js))?; - let mut app = WasmReplApp { arena }; + let app = WasmReplApp { arena }; // Run the app and transform the result value to an AST `Expr` // Restore type constructor names, and other user-facing info that was erased during compilation. let res_answer = jit_to_ast( arena, - &mut app, + &app, "", // main_fn_name is ignored (only passed to WasmReplApp methods) main_fn_layout, content, @@ -238,7 +238,7 @@ pub async fn repl_wasm_entrypoint_from_js(src: String) -> Result // Transform the Expr to a string // `Result::Err` becomes a JS exception that will be caught and displayed - match format_answer(&arena, res_answer, expr_type_str) { + match format_answer(arena, res_answer, expr_type_str) { ReplOutput::NoProblems { expr, expr_type } => Ok(format!("\n{}: {}", expr, expr_type)), ReplOutput::Problems(lines) => Err(format!("\n{}\n", lines.join("\n\n"))), } From cff762afe5aa1b12624293697aec0544668dd6fa Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 6 Feb 2022 22:08:36 +0000 Subject: [PATCH 528/541] Fix test_gen llvm --- compiler/test_gen/src/helpers/llvm.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index 2868cb7094..d9af1a821d 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -1,4 +1,4 @@ -use crate::helpers::from_wasm32_memory::FromWasm32Memory; +use crate::helpers::from_wasmer_memory::FromWasmerMemory; use inkwell::module::Module; use libloading::Library; use roc_build::link::module_to_dylib; @@ -468,7 +468,7 @@ fn fake_wasm_main_function(_: u32, _: u32) -> u32 { #[allow(dead_code)] pub fn assert_wasm_evals_to_help(src: &str, ignore_problems: bool) -> Result where - T: FromWasm32Memory, + T: FromWasmerMemory, { let arena = bumpalo::Bump::new(); let context = inkwell::context::Context::create(); @@ -502,7 +502,7 @@ where _ => panic!(), }; - let output = ::decode( + let output = ::decode( memory, // skip the RocCallResult tag id address as u32 + 8, From 858a3c3527b6340ba86b4d83c3c7cbe311a01cf1 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 7 Feb 2022 11:43:53 +0000 Subject: [PATCH 529/541] roc_load: Remove unused deadlock_detection feature of parking_lot crate, to enable Wasm REPL build --- Cargo.lock | 30 ------------------------------ compiler/load/Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1d68c7e26d..7500ac6277 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1306,12 +1306,6 @@ dependencies = [ "toml", ] -[[package]] -name = "fixedbitset" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" - [[package]] name = "flate2" version = "1.0.22" @@ -2676,14 +2670,11 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ - "backtrace", "cfg-if 1.0.0", "instant", "libc", - "petgraph", "redox_syscall", "smallvec", - "thread-id", "winapi", ] @@ -2742,16 +2733,6 @@ dependencies = [ "sha-1", ] -[[package]] -name = "petgraph" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" -dependencies = [ - "fixedbitset", - "indexmap", -] - [[package]] name = "phf" version = "0.9.0" @@ -4415,17 +4396,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thread-id" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fdfe0627923f7411a43ec9ec9c39c3a9b4151be313e0922042581fb6c9b717f" -dependencies = [ - "libc", - "redox_syscall", - "winapi", -] - [[package]] name = "threadpool" version = "1.8.1" diff --git a/compiler/load/Cargo.toml b/compiler/load/Cargo.toml index 329a3f5386..57f4ffd530 100644 --- a/compiler/load/Cargo.toml +++ b/compiler/load/Cargo.toml @@ -23,7 +23,7 @@ roc_reporting = { path = "../../reporting" } morphic_lib = { path = "../../vendor/morphic_lib" } ven_pretty = { path = "../../vendor/pretty" } bumpalo = { version = "3.8.0", features = ["collections"] } -parking_lot = { version = "0.11.2", features = ["deadlock_detection"] } +parking_lot = { version = "0.11.2" } crossbeam = "0.8.1" num_cpus = "1.13.0" From 2c8b957763ab253e0ebe5c3f551c3b18fdd2aceb Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 7 Feb 2022 14:54:07 +0000 Subject: [PATCH 530/541] repl: Split repl_www from repl_wasm --- repl_wasm/.gitignore | 2 +- repl_wasm/src/lib.rs | 2 +- repl_www/.gitignore | 1 + repl_www/build.sh | 22 ++++++++++++++ {repl_wasm/src => repl_www/public}/index.html | 29 ++----------------- {repl_wasm/src => repl_www/public}/repl.js | 8 ++--- 6 files changed, 31 insertions(+), 33 deletions(-) create mode 100644 repl_www/.gitignore create mode 100755 repl_www/build.sh rename {repl_wasm/src => repl_www/public}/index.html (61%) rename {repl_wasm/src => repl_www/public}/repl.js (94%) diff --git a/repl_wasm/.gitignore b/repl_wasm/.gitignore index dfccf1ba05..0613f22c9d 100644 --- a/repl_wasm/.gitignore +++ b/repl_wasm/.gitignore @@ -2,7 +2,7 @@ /dist /notes.md -# Binary data folder +# Binary data (pre-linked Wasm object file) /data # wasm-pack diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index c71991bd84..51950d9fe9 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -143,7 +143,7 @@ impl<'a> ReplApp<'a> for WasmReplApp<'a> { } } -// #[wasm_bindgen] +#[wasm_bindgen] pub async fn repl_wasm_entrypoint_from_js(src: String) -> Result { let arena = &Bump::new(); let pre_linked_binary: &'static [u8] = include_bytes!("../data/pre_linked_binary.o"); diff --git a/repl_www/.gitignore b/repl_www/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/repl_www/.gitignore @@ -0,0 +1 @@ +/build diff --git a/repl_www/build.sh b/repl_www/build.sh new file mode 100755 index 0000000000..30f416d475 --- /dev/null +++ b/repl_www/build.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +if [[ ! -d repl_www ]] +then + echo "This script should be run from the project root" + exit 1 +fi + +if ! which wasm-pack +then + cargo install wasm-pack +fi + +WWW_DIR="repl_www/build" +mkdir -p $WWW_DIR +cp repl_www/public/* $WWW_DIR + +# Pass all script arguments through to wasm-pack (such as --release) +wasm-pack build --target web "$@" repl_wasm + +cp repl_wasm/pkg/*.wasm $WWW_DIR +cp repl_wasm/pkg/*.js $WWW_DIR diff --git a/repl_wasm/src/index.html b/repl_www/public/index.html similarity index 61% rename from repl_wasm/src/index.html rename to repl_www/public/index.html index bd068bdf53..20e4ed3ba2 100644 --- a/repl_wasm/src/index.html +++ b/repl_www/public/index.html @@ -75,31 +75,7 @@
-

A mockin' Roc REPL!

-

- This is a mock-up Web REPL for a fake compiler. The - only valid inputs are the numbers 0-255! The output program is a Wasm - module that counts backwards from that number to 1. -

-

- The same web page should work with minimal adjustments whenever we - manage to get a WebAssembly build of the Roc compiler working. But - this way, I was able to build up the functionality more gradually. -

-

How it works

-
    -
  • There are two Wasm modules: a compiler, and an app
  • -
  • - The compiler simply modifies a single byte in an otherwise fixed - Wasm binary, using your input. -
  • -
  • The compiler sends the Wasm binary to JS, which runs it
  • -
  • - JS calls back into another function in the compiler that stringifies - the result from the app -
  • -
  • JS takes the output string and displays it
  • -
+

The rockin' Roc REPL

@@ -112,8 +88,7 @@
diff --git a/repl_wasm/src/repl.js b/repl_www/public/repl.js similarity index 94% rename from repl_wasm/src/repl.js rename to repl_www/public/repl.js index e3f9c374eb..6f4c7597da 100644 --- a/repl_wasm/src/repl.js +++ b/repl_www/public/repl.js @@ -1,8 +1,8 @@ -// wasm_bindgen's JS code expects our imported functions to be global +// wasm_bindgen treats our `extern` declarations as JS globals, so let's keep it happy window.js_create_app = js_create_app; window.js_run_app = js_run_app; window.js_get_result_and_memory = js_get_result_and_memory; -import * as mock_repl from "./mock_repl.js"; +import * as roc_repl_wasm from "./roc_repl_wasm.js"; // ---------------------------------------------------------------------------- // REPL state @@ -27,7 +27,7 @@ const repl = { // Initialise repl.elemSourceInput.addEventListener("change", onInputChange); -mock_repl.default().then((instance) => { +roc_repl_wasm.default().then((instance) => { repl.compiler = instance; }); @@ -56,7 +56,7 @@ async function processInputQueue() { let outputText; let ok = true; try { - outputText = await mock_repl.webrepl_run(inputText); + outputText = await roc_repl_wasm.repl_wasm_entrypoint_from_js(inputText); } catch (e) { outputText = `${e}`; ok = false; From bdff338319cb7f8b131eb50923732f05a7be7ea0 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 7 Feb 2022 15:05:38 +0000 Subject: [PATCH 531/541] repl: ignore Clippy rule broken by wasm-bindgen macro --- repl_wasm/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index 51950d9fe9..f09e4bafe5 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -1,3 +1,7 @@ +// wasm_bindgen procedural macro breaks this clippy rule +// https://github.com/rustwasm/wasm-bindgen/issues/2774 +#![allow(clippy::unused_unit)] + use bumpalo::{collections::vec::Vec, Bump}; use std::mem::size_of; use wasm_bindgen::prelude::wasm_bindgen; From 9a7ac6429bf3b1468363423b1f32d1f9c9b37770 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 7 Feb 2022 15:57:37 +0000 Subject: [PATCH 532/541] repl: Fix invalid JS import from wasm_bindgen caused by parking_lot --- repl_www/build.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/repl_www/build.sh b/repl_www/build.sh index 30f416d475..866aa0ffbd 100755 --- a/repl_www/build.sh +++ b/repl_www/build.sh @@ -19,4 +19,11 @@ cp repl_www/public/* $WWW_DIR wasm-pack build --target web "$@" repl_wasm cp repl_wasm/pkg/*.wasm $WWW_DIR -cp repl_wasm/pkg/*.js $WWW_DIR + +# Copy the JS from wasm_bindgen, replacing its invalid `import` statement with a `var`. +# The JS import from the invalid path 'env', seems to be generated when there are unresolved symbols. +# However there is no corresponding import declared in the .wasm file so we can leave the `var` undefined. +# Anyway, it works. ¯\_(ツ)_/¯ +BINDGEN_FILE="roc_repl_wasm.js" +echo 'var __wbg_star0;' > $WWW_DIR/$BINDGEN_FILE +grep -v '^import' repl_wasm/pkg/$BINDGEN_FILE >> $WWW_DIR/$BINDGEN_FILE From 9bfd0e44d68dca621a8f2685e1e2884769520a45 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 7 Feb 2022 16:26:07 +0000 Subject: [PATCH 533/541] repl: comments --- repl_wasm/src/repl_platform.c | 7 +++++-- repl_www/public/repl.js | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/repl_wasm/src/repl_platform.c b/repl_wasm/src/repl_platform.c index a7fecad410..1f288159e9 100644 --- a/repl_wasm/src/repl_platform.c +++ b/repl_wasm/src/repl_platform.c @@ -1,7 +1,10 @@ #include -// printf debugging slows down automated tests a lot so make sure to disable it afterwards! -// (Wasmer has a much larger binary to validate and compile. Chrome handles it easily though.) +/* + A bare-bones Roc "platform" for REPL code, providing heap allocation for builtins. +*/ + +// Enable/disable printf debugging. Leave disabled to avoid bloating .wasm files and slowing down Wasmer tests. #define ENABLE_PRINTF 0 //-------------------------- diff --git a/repl_www/public/repl.js b/repl_www/public/repl.js index 6f4c7597da..2d11a3f6d2 100644 --- a/repl_www/public/repl.js +++ b/repl_www/public/repl.js @@ -46,8 +46,8 @@ function onInputChange(event) { } // Use a queue just in case we somehow get inputs very fast -// This is definitely an edge case, but maybe could happen from copy/paste? -// It allows us to rely on having only one input at a time +// We want the REPL to only process one at a time, since we're using some global state. +// In normal usage we shouldn't see this edge case anyway. Maybe with copy/paste? async function processInputQueue() { while (repl.inputQueue.length) { const inputText = repl.inputQueue[0]; From f8ad09f6abc7a7cc726b9e0d841e6ea1cecf2ed8 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 8 Feb 2022 09:28:42 +0000 Subject: [PATCH 534/541] repl: rename entrypoint from JS and remove unnecessary `pub` in Rust --- repl_wasm/src/lib.rs | 8 ++++---- repl_www/public/repl.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index f09e4bafe5..8d018387ac 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -24,11 +24,11 @@ const WRAPPER_NAME: &str = "wrapper"; #[wasm_bindgen] extern "C" { #[wasm_bindgen(catch)] - pub async fn js_create_app(wasm_module_bytes: &[u8]) -> Result<(), JsValue>; + async fn js_create_app(wasm_module_bytes: &[u8]) -> Result<(), JsValue>; - pub fn js_run_app() -> usize; + fn js_run_app() -> usize; - pub fn js_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize; + fn js_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize; } pub struct WasmReplApp<'a> { @@ -148,7 +148,7 @@ impl<'a> ReplApp<'a> for WasmReplApp<'a> { } #[wasm_bindgen] -pub async fn repl_wasm_entrypoint_from_js(src: String) -> Result { +pub async fn entrypoint_from_js(src: String) -> Result { let arena = &Bump::new(); let pre_linked_binary: &'static [u8] = include_bytes!("../data/pre_linked_binary.o"); diff --git a/repl_www/public/repl.js b/repl_www/public/repl.js index 2d11a3f6d2..ecd07d8b5b 100644 --- a/repl_www/public/repl.js +++ b/repl_www/public/repl.js @@ -56,7 +56,7 @@ async function processInputQueue() { let outputText; let ok = true; try { - outputText = await roc_repl_wasm.repl_wasm_entrypoint_from_js(inputText); + outputText = await roc_repl_wasm.entrypoint_from_js(inputText); } catch (e) { outputText = `${e}`; ok = false; From 9244008d942143123709af769f226a14aaaf38e1 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 8 Feb 2022 09:30:49 +0000 Subject: [PATCH 535/541] repl: implement console.log from Rust for debugging --- repl_wasm/src/lib.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index 8d018387ac..48910defba 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -29,6 +29,15 @@ extern "C" { fn js_run_app() -> usize; fn js_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize; + + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); +} + +// In-browser debugging +#[allow(unused_macros)] +macro_rules! console_log { + ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) } pub struct WasmReplApp<'a> { From 6e307d2c1295ec02b75c9a75e97932e71228281a Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 8 Feb 2022 09:31:10 +0000 Subject: [PATCH 536/541] repl: build script updates --- repl_www/build.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/repl_www/build.sh b/repl_www/build.sh index 866aa0ffbd..2b9c6e4023 100755 --- a/repl_www/build.sh +++ b/repl_www/build.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -eux + if [[ ! -d repl_www ]] then echo "This script should be run from the project root" @@ -22,8 +24,6 @@ cp repl_wasm/pkg/*.wasm $WWW_DIR # Copy the JS from wasm_bindgen, replacing its invalid `import` statement with a `var`. # The JS import from the invalid path 'env', seems to be generated when there are unresolved symbols. -# However there is no corresponding import declared in the .wasm file so we can leave the `var` undefined. -# Anyway, it works. ¯\_(ツ)_/¯ BINDGEN_FILE="roc_repl_wasm.js" -echo 'var __wbg_star0;' > $WWW_DIR/$BINDGEN_FILE +echo 'var __wbg_star0 = { now: Date.now };' > $WWW_DIR/$BINDGEN_FILE grep -v '^import' repl_wasm/pkg/$BINDGEN_FILE >> $WWW_DIR/$BINDGEN_FILE From 49524731c384852aa66a2bb3d6aab1c9828e6a42 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 8 Feb 2022 11:01:11 +0000 Subject: [PATCH 537/541] repl: remove stale comment --- repl_cli/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/repl_cli/src/lib.rs b/repl_cli/src/lib.rs index 999f09de7a..08ccbfbdd1 100644 --- a/repl_cli/src/lib.rs +++ b/repl_cli/src/lib.rs @@ -300,8 +300,6 @@ fn gen_and_eval_llvm<'a>( let lib = module_to_dylib(env.module, &target, opt_level) .expect("Error loading compiled dylib for test"); - // The app is `mut` only because Wasm needs it. - // It has no public fields, and its "mutating" methods don't actually mutate. let app = CliApp { lib }; let res_answer = jit_to_ast( From 8e370a32b6ab6edd74b78d8ca5811bb8e6bbe17a Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 7 Feb 2022 00:41:40 +0000 Subject: [PATCH 538/541] repl: update some static_asserts for 32-bit compiler build --- compiler/module/src/ident.rs | 2 +- compiler/mono/src/ir.rs | 10 +++++----- compiler/mono/src/layout.rs | 8 ++++---- compiler/types/src/subs.rs | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/compiler/module/src/ident.rs b/compiler/module/src/ident.rs index 6dcf965313..4f4cb22196 100644 --- a/compiler/module/src/ident.rs +++ b/compiler/module/src/ident.rs @@ -59,7 +59,7 @@ pub enum TagName { Closure(Symbol), } -static_assertions::assert_eq_size!([u8; 24], TagName); +static_assertions::assert_eq_size!((usize, usize, u64), TagName); impl TagName { pub fn as_ident_str(&self, interns: &Interns, home: ModuleId) -> IdentStr { diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 3c736435d9..c866a16d97 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -38,14 +38,14 @@ pub fn pretty_print_ir_symbols() -> bool { static_assertions::assert_eq_size!([u8; 4 * 8], Literal); #[cfg(not(target_arch = "aarch64"))] static_assertions::assert_eq_size!([u8; 3 * 8], Literal); -static_assertions::assert_eq_size!([u8; 10 * 8], Expr); +static_assertions::assert_eq_size!(([u64; 4], [usize; 6]), Expr); #[cfg(not(target_arch = "aarch64"))] -static_assertions::assert_eq_size!([u8; 19 * 8], Stmt); +static_assertions::assert_eq_size!(([u64; 5], [usize; 14]), Stmt); #[cfg(target_arch = "aarch64")] static_assertions::assert_eq_size!([u8; 20 * 8], Stmt); -static_assertions::assert_eq_size!([u8; 6 * 8], ProcLayout); -static_assertions::assert_eq_size!([u8; 7 * 8], Call); -static_assertions::assert_eq_size!([u8; 5 * 8], CallType); +static_assertions::assert_eq_size!([usize; 6], ProcLayout); +static_assertions::assert_eq_size!(([u64; 3], [usize; 4]), Call); +static_assertions::assert_eq_size!(([u64; 3], [usize; 2]), CallType); macro_rules! return_on_layout_error { ($env:expr, $layout_result:expr) => { diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index edcd676c00..b04993c9d6 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -18,10 +18,10 @@ use ven_pretty::{DocAllocator, DocBuilder}; // if your changes cause this number to go down, great! // please change it to the lower number. // if it went up, maybe check that the change is really required -static_assertions::assert_eq_size!([u8; 3 * 8], Builtin); -static_assertions::assert_eq_size!([u8; 4 * 8], Layout); -static_assertions::assert_eq_size!([u8; 3 * 8], UnionLayout); -static_assertions::assert_eq_size!([u8; 3 * 8], LambdaSet); +static_assertions::assert_eq_size!([usize; 3], Builtin); +static_assertions::assert_eq_size!([usize; 4], Layout); +static_assertions::assert_eq_size!([usize; 3], UnionLayout); +static_assertions::assert_eq_size!([usize; 3], LambdaSet); pub type TagIdIntType = u16; pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::() * 8) as usize; diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 94f3737aeb..c2500d7780 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -12,7 +12,7 @@ use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey}; static_assertions::assert_eq_size!([u8; 6 * 8], Descriptor); static_assertions::assert_eq_size!([u8; 4 * 8], Content); static_assertions::assert_eq_size!([u8; 3 * 8], FlatType); -static_assertions::assert_eq_size!([u8; 6 * 8], Problem); +static_assertions::assert_eq_size!(([usize; 4], [u64; 2]), Problem); static_assertions::assert_eq_size!([u8; 12], UnionTags); static_assertions::assert_eq_size!([u8; 2 * 8], RecordFields); @@ -1624,7 +1624,7 @@ impl From for Descriptor { } static_assertions::assert_eq_size!([u8; 4 * 8], Content); -static_assertions::assert_eq_size!([u8; 4 * 8], (Variable, Option)); +static_assertions::assert_eq_size!([usize; 4], (Variable, Option)); static_assertions::assert_eq_size!([u8; 3 * 8], (Symbol, AliasVariables, Variable)); static_assertions::assert_eq_size!([u8; 8], AliasVariables); static_assertions::assert_eq_size!([u8; 3 * 8], FlatType); From a0ccae2865f7fbe0b28d42135ac3dd9b11f6fa74 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 9 Feb 2022 08:14:58 +0000 Subject: [PATCH 539/541] repl: Create dumy Wasm implementations of SystemTime and Duration --- compiler/load/src/file.rs | 6 +++- compiler/load/src/lib.rs | 3 ++ compiler/load/src/wasm_system_time.rs | 42 +++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 compiler/load/src/wasm_system_time.rs diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 676e55d2e9..1bc1c1a8de 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -43,9 +43,13 @@ use std::iter; use std::path::{Path, PathBuf}; use std::str::from_utf8_unchecked; use std::sync::Arc; -use std::time::{Duration, SystemTime}; use std::{env, fs}; +#[cfg(not(target_family = "wasm"))] +use std::time::{Duration, SystemTime}; +#[cfg(target_family = "wasm")] +use wasm_system_time::{Duration, SystemTime}; + /// Default name for the binary generated for an app, if an invalid one was specified. const DEFAULT_APP_OUTPUT_PATH: &str = "app"; diff --git a/compiler/load/src/lib.rs b/compiler/load/src/lib.rs index a40812dc89..90f9140930 100644 --- a/compiler/load/src/lib.rs +++ b/compiler/load/src/lib.rs @@ -3,3 +3,6 @@ #![allow(clippy::large_enum_variant)] pub mod docs; pub mod file; + +#[cfg(target_family = "wasm")] +mod wasm_system_time; diff --git a/compiler/load/src/wasm_system_time.rs b/compiler/load/src/wasm_system_time.rs new file mode 100644 index 0000000000..9709c1e22d --- /dev/null +++ b/compiler/load/src/wasm_system_time.rs @@ -0,0 +1,42 @@ +#![cfg(target_family = "wasm")] +/* +For the Web REPL (repl_www), we build the compiler as a Wasm module. +SystemTime is the only thing in the compiler that would need a special implementation for this. +There is a WASI implementation for it, but we are targeting the browser, not WASI! +It's possible to write browser versions of WASI's low-level ABI but we'd rather avoid it. +Instead we use these dummy implementations, which should just disappear at compile time. +*/ + +#[derive(Debug, Clone, Copy)] +pub struct SystemTime; + +impl SystemTime { + fn now() -> Self { + SystemTime + } + fn duration_since(&self, _: SystemTime) -> Result { + Ok(Duration) + } + fn elapsed(&self) -> Result { + Ok(Duration) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Duration; + +impl Duration { + fn checked_sub(&self, _: Duration) -> Option { + Some(Duration) + } +} + +impl Default for Duration { + fn default() -> Self { + Duration + } +} + +impl std::ops::AddAssign for Duration { + fn add_assign(&mut self, _: Duration) {} +} From d8b76b317ba2ab53538440e58b4970f3faf497c3 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 9 Feb 2022 08:34:54 +0000 Subject: [PATCH 540/541] repl: replace [u8; 8] with u64 in static assertions --- compiler/mono/src/ir.rs | 6 +++--- compiler/types/src/subs.rs | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index c866a16d97..01b0a78389 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -35,14 +35,14 @@ pub fn pretty_print_ir_symbols() -> bool { // i128 alignment is different on arm #[cfg(target_arch = "aarch64")] -static_assertions::assert_eq_size!([u8; 4 * 8], Literal); +static_assertions::assert_eq_size!([u64; 4], Literal); #[cfg(not(target_arch = "aarch64"))] -static_assertions::assert_eq_size!([u8; 3 * 8], Literal); +static_assertions::assert_eq_size!([u64; 3], Literal); static_assertions::assert_eq_size!(([u64; 4], [usize; 6]), Expr); #[cfg(not(target_arch = "aarch64"))] static_assertions::assert_eq_size!(([u64; 5], [usize; 14]), Stmt); #[cfg(target_arch = "aarch64")] -static_assertions::assert_eq_size!([u8; 20 * 8], Stmt); +static_assertions::assert_eq_size!([u64; 20], Stmt); static_assertions::assert_eq_size!([usize; 6], ProcLayout); static_assertions::assert_eq_size!(([u64; 3], [usize; 4]), Call); static_assertions::assert_eq_size!(([u64; 3], [usize; 2]), CallType); diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index c2500d7780..051845d03b 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -9,12 +9,12 @@ use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey}; // if your changes cause this number to go down, great! // please change it to the lower number. // if it went up, maybe check that the change is really required -static_assertions::assert_eq_size!([u8; 6 * 8], Descriptor); -static_assertions::assert_eq_size!([u8; 4 * 8], Content); -static_assertions::assert_eq_size!([u8; 3 * 8], FlatType); +static_assertions::assert_eq_size!([u64; 6], Descriptor); +static_assertions::assert_eq_size!([u64; 4], Content); +static_assertions::assert_eq_size!([u64; 3], FlatType); static_assertions::assert_eq_size!(([usize; 4], [u64; 2]), Problem); static_assertions::assert_eq_size!([u8; 12], UnionTags); -static_assertions::assert_eq_size!([u8; 2 * 8], RecordFields); +static_assertions::assert_eq_size!([u64; 2], RecordFields); #[derive(Clone, Copy, Hash, PartialEq, Eq)] pub struct Mark(i32); @@ -1623,11 +1623,11 @@ impl From for Descriptor { } } -static_assertions::assert_eq_size!([u8; 4 * 8], Content); +static_assertions::assert_eq_size!([u64; 4], Content); static_assertions::assert_eq_size!([usize; 4], (Variable, Option)); -static_assertions::assert_eq_size!([u8; 3 * 8], (Symbol, AliasVariables, Variable)); +static_assertions::assert_eq_size!([u64; 3], (Symbol, AliasVariables, Variable)); static_assertions::assert_eq_size!([u8; 8], AliasVariables); -static_assertions::assert_eq_size!([u8; 3 * 8], FlatType); +static_assertions::assert_eq_size!([u64; 3], FlatType); #[derive(Clone, Debug)] pub enum Content { @@ -1765,7 +1765,7 @@ impl Content { } } -static_assertions::assert_eq_size!([u8; 3 * 8], FlatType); +static_assertions::assert_eq_size!([u64; 3], FlatType); #[derive(Clone, Debug)] pub enum FlatType { From c61a18a2004047e9be546028a6b47b6385726412 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 9 Feb 2022 17:04:28 +0000 Subject: [PATCH 541/541] Use custom macros for static assertions --- Cargo.lock | 3 +++ compiler/module/Cargo.toml | 1 + compiler/module/src/ident.rs | 4 +++- compiler/mono/Cargo.toml | 1 + compiler/mono/src/ir.rs | 32 ++++++++++++++++++++------------ compiler/types/Cargo.toml | 1 + compiler/types/src/subs.rs | 30 +++++++++++++++++------------- error_macros/src/lib.rs | 36 ++++++++++++++++++++++++++++++++++++ 8 files changed, 82 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7500ac6277..b530153d8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3592,6 +3592,7 @@ dependencies = [ "bumpalo", "lazy_static", "roc_collections", + "roc_error_macros", "roc_ident", "roc_region", "snafu", @@ -3608,6 +3609,7 @@ dependencies = [ "roc_builtins", "roc_can", "roc_collections", + "roc_error_macros", "roc_module", "roc_problem", "roc_region", @@ -3792,6 +3794,7 @@ version = "0.1.0" dependencies = [ "bumpalo", "roc_collections", + "roc_error_macros", "roc_module", "roc_region", "static_assertions", diff --git a/compiler/module/Cargo.toml b/compiler/module/Cargo.toml index c05ca69ed1..9b8c272d54 100644 --- a/compiler/module/Cargo.toml +++ b/compiler/module/Cargo.toml @@ -9,6 +9,7 @@ license = "UPL-1.0" roc_region = { path = "../region" } roc_ident = { path = "../ident" } roc_collections = { path = "../collections" } +roc_error_macros = {path = "../../error_macros"} bumpalo = { version = "3.8.0", features = ["collections"] } lazy_static = "1.4.0" static_assertions = "1.1.0" diff --git a/compiler/module/src/ident.rs b/compiler/module/src/ident.rs index 4f4cb22196..73a5fc9737 100644 --- a/compiler/module/src/ident.rs +++ b/compiler/module/src/ident.rs @@ -59,7 +59,9 @@ pub enum TagName { Closure(Symbol), } -static_assertions::assert_eq_size!((usize, usize, u64), TagName); +roc_error_macros::assert_sizeof_aarch64!(TagName, 24); +roc_error_macros::assert_sizeof_wasm!(TagName, 16); +roc_error_macros::assert_sizeof_default!(TagName, 24); impl TagName { pub fn as_ident_str(&self, interns: &Interns, home: ModuleId) -> IdentStr { diff --git a/compiler/mono/Cargo.toml b/compiler/mono/Cargo.toml index 969bd4e256..5bdf3723fa 100644 --- a/compiler/mono/Cargo.toml +++ b/compiler/mono/Cargo.toml @@ -17,6 +17,7 @@ roc_std = { path = "../../roc_std" } roc_problem = { path = "../problem" } roc_builtins = { path = "../builtins" } roc_target = { path = "../roc_target" } +roc_error_macros = {path="../../error_macros"} ven_pretty = { path = "../../vendor/pretty" } morphic_lib = { path = "../../vendor/morphic_lib" } bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 01b0a78389..0e0c39019a 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -34,18 +34,26 @@ pub fn pretty_print_ir_symbols() -> bool { // if it went up, maybe check that the change is really required // i128 alignment is different on arm -#[cfg(target_arch = "aarch64")] -static_assertions::assert_eq_size!([u64; 4], Literal); -#[cfg(not(target_arch = "aarch64"))] -static_assertions::assert_eq_size!([u64; 3], Literal); -static_assertions::assert_eq_size!(([u64; 4], [usize; 6]), Expr); -#[cfg(not(target_arch = "aarch64"))] -static_assertions::assert_eq_size!(([u64; 5], [usize; 14]), Stmt); -#[cfg(target_arch = "aarch64")] -static_assertions::assert_eq_size!([u64; 20], Stmt); -static_assertions::assert_eq_size!([usize; 6], ProcLayout); -static_assertions::assert_eq_size!(([u64; 3], [usize; 4]), Call); -static_assertions::assert_eq_size!(([u64; 3], [usize; 2]), CallType); +roc_error_macros::assert_sizeof_aarch64!(Literal, 4 * 8); +roc_error_macros::assert_sizeof_aarch64!(Expr, 10 * 8); +roc_error_macros::assert_sizeof_aarch64!(Stmt, 20 * 8); +roc_error_macros::assert_sizeof_aarch64!(ProcLayout, 6 * 8); +roc_error_macros::assert_sizeof_aarch64!(Call, 7 * 8); +roc_error_macros::assert_sizeof_aarch64!(CallType, 5 * 8); + +roc_error_macros::assert_sizeof_wasm!(Literal, 24); +roc_error_macros::assert_sizeof_wasm!(Expr, 56); +roc_error_macros::assert_sizeof_wasm!(Stmt, 96); +roc_error_macros::assert_sizeof_wasm!(ProcLayout, 24); +roc_error_macros::assert_sizeof_wasm!(Call, 40); +roc_error_macros::assert_sizeof_wasm!(CallType, 32); + +roc_error_macros::assert_sizeof_default!(Literal, 3 * 8); +roc_error_macros::assert_sizeof_default!(Expr, 10 * 8); +roc_error_macros::assert_sizeof_default!(Stmt, 19 * 8); +roc_error_macros::assert_sizeof_default!(ProcLayout, 6 * 8); +roc_error_macros::assert_sizeof_default!(Call, 7 * 8); +roc_error_macros::assert_sizeof_default!(CallType, 5 * 8); macro_rules! return_on_layout_error { ($env:expr, $layout_result:expr) => { diff --git a/compiler/types/Cargo.toml b/compiler/types/Cargo.toml index 97cd23ac45..550cb5f333 100644 --- a/compiler/types/Cargo.toml +++ b/compiler/types/Cargo.toml @@ -9,6 +9,7 @@ edition = "2018" roc_collections = { path = "../collections" } roc_region = { path = "../region" } roc_module = { path = "../module" } +roc_error_macros = {path="../../error_macros"} ven_ena = { path = "../../vendor/ena" } bumpalo = { version = "3.8.0", features = ["collections"] } static_assertions = "1.1.0" diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 051845d03b..7dac3c50c7 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -9,12 +9,15 @@ use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey}; // if your changes cause this number to go down, great! // please change it to the lower number. // if it went up, maybe check that the change is really required -static_assertions::assert_eq_size!([u64; 6], Descriptor); -static_assertions::assert_eq_size!([u64; 4], Content); -static_assertions::assert_eq_size!([u64; 3], FlatType); -static_assertions::assert_eq_size!(([usize; 4], [u64; 2]), Problem); -static_assertions::assert_eq_size!([u8; 12], UnionTags); -static_assertions::assert_eq_size!([u64; 2], RecordFields); +roc_error_macros::assert_sizeof_all!(Descriptor, 6 * 8); +roc_error_macros::assert_sizeof_all!(Content, 4 * 8); +roc_error_macros::assert_sizeof_all!(FlatType, 3 * 8); +roc_error_macros::assert_sizeof_all!(UnionTags, 12); +roc_error_macros::assert_sizeof_all!(RecordFields, 2 * 8); + +roc_error_macros::assert_sizeof_aarch64!(Problem, 6 * 8); +roc_error_macros::assert_sizeof_wasm!(Problem, 32); +roc_error_macros::assert_sizeof_default!(Problem, 6 * 8); #[derive(Clone, Copy, Hash, PartialEq, Eq)] pub struct Mark(i32); @@ -1623,11 +1626,14 @@ impl From for Descriptor { } } -static_assertions::assert_eq_size!([u64; 4], Content); -static_assertions::assert_eq_size!([usize; 4], (Variable, Option)); -static_assertions::assert_eq_size!([u64; 3], (Symbol, AliasVariables, Variable)); -static_assertions::assert_eq_size!([u8; 8], AliasVariables); -static_assertions::assert_eq_size!([u64; 3], FlatType); +roc_error_macros::assert_sizeof_all!(Content, 4 * 8); +roc_error_macros::assert_sizeof_all!((Symbol, AliasVariables, Variable), 3 * 8); +roc_error_macros::assert_sizeof_all!(AliasVariables, 8); +roc_error_macros::assert_sizeof_all!(FlatType, 3 * 8); + +roc_error_macros::assert_sizeof_aarch64!((Variable, Option), 4 * 8); +roc_error_macros::assert_sizeof_wasm!((Variable, Option), 4 * 4); +roc_error_macros::assert_sizeof_all!((Variable, Option), 4 * 8); #[derive(Clone, Debug)] pub enum Content { @@ -1765,8 +1771,6 @@ impl Content { } } -static_assertions::assert_eq_size!([u64; 3], FlatType); - #[derive(Clone, Debug)] pub enum FlatType { Apply(Symbol, VariableSubsSlice), diff --git a/error_macros/src/lib.rs b/error_macros/src/lib.rs index d9b2e0e9a3..39aeca5aa0 100644 --- a/error_macros/src/lib.rs +++ b/error_macros/src/lib.rs @@ -29,3 +29,39 @@ macro_rules! user_error { std::process::exit(1); }) } + +/// Assert that a type has the expected size on ARM +#[macro_export] +macro_rules! assert_sizeof_aarch64 { + ($t: ty, $expected_size: expr) => { + #[cfg(target_arch = "aarch64")] + static_assertions::assert_eq_size!($t, [u8; $expected_size]); + }; +} + +/// Assert that a type has the expected size in Wasm +#[macro_export] +macro_rules! assert_sizeof_wasm { + ($t: ty, $expected_size: expr) => { + #[cfg(target_family = "wasm")] + static_assertions::assert_eq_size!($t, [u8; $expected_size]); + }; +} + +/// Assert that a type has the expected size on any target not covered above +/// In practice we use this for x86_64, and add specific macros for other platforms +#[macro_export] +macro_rules! assert_sizeof_default { + ($t: ty, $expected_size: expr) => { + #[cfg(not(any(target_family = "wasm", target_arch = "aarch64")))] + static_assertions::assert_eq_size!($t, [u8; $expected_size]); + }; +} + +/// Assert that a type has the expected size on all targets +#[macro_export] +macro_rules! assert_sizeof_all { + ($t: ty, $expected_size: expr) => { + static_assertions::assert_eq_size!($t, [u8; $expected_size]); + }; +}