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" diff --git a/Cargo.lock b/Cargo.lock index cfbd14e68d..a5a95ee182 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3204,6 +3204,17 @@ 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_cli", + "roc_repl_cli", + "roc_test_utils", + "strip-ansi-escapes", +] + [[package]] name = "rkyv" version = "0.6.7" @@ -3669,14 +3680,20 @@ version = "0.1.0" dependencies = [ "bumpalo", "const_format", - "indoc", + "inkwell 0.1.0", + "libloading 0.7.1", + "roc_build", + "roc_builtins", + "roc_collections", + "roc_gen_llvm", + "roc_load", "roc_mono", "roc_parse", "roc_repl_eval", - "roc_test_utils", + "roc_target", + "roc_types", "rustyline", "rustyline-derive", - "strip-ansi-escapes", "target-lexicon", ] @@ -3685,14 +3702,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", @@ -3701,7 +3714,14 @@ dependencies = [ "roc_reporting", "roc_target", "roc_types", - "target-lexicon", +] + +[[package]] +name = "roc_repl_wasm" +version = "0.1.0" +dependencies = [ + "roc_parse", + "roc_repl_eval", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f685a9058c..874eeb705d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,8 @@ members = [ "reporting", "repl_cli", "repl_eval", + "repl_test", + "repl_wasm", "roc_std", "test_utils", "utils", 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 diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index a83fd39ed1..5bcd4edd19 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::expr::{IntValue, Recursive}; +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; @@ -52,7 +54,7 @@ pub fn expr_to_expr2<'a>( match parse_expr { 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(), @@ -73,10 +75,13 @@ pub fn expr_to_expr2<'a>( } } Num(string) => { - match finish_parsing_int(string) { - Ok(int) => { + 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, @@ -85,6 +90,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( @@ -107,9 +121,12 @@ pub fn expr_to_expr2<'a>( is_negative, } => { match finish_parsing_base(string, *base, *is_negative) { - Ok(int) => { + 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 19a956bcd8..aabb06254f 100644 --- a/ast/src/lang/core/pattern.rs +++ b/ast/src/lang/core/pattern.rs @@ -3,8 +3,10 @@ #![allow(unused_imports)] 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::expr::{unescape_char, IntValue}; +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}; @@ -183,18 +185,35 @@ pub fn to_pattern2<'a>( 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), }, NumLiteral(string) => match pattern_type { - WhenBranch => match finish_parsing_int(string) { + 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), + Ok(ParsedNumResult::UnknownNum(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::Float(int, _bound)) => { + Pattern2::FloatLiteral(FloatVal::F64(int)) + } }, ptype => unsupported_pattern(env, ptype, region), }, @@ -209,7 +228,11 @@ pub fn to_pattern2<'a>( let problem = MalformedPatternProblem::MalformedBase(*base); malformed_pattern(env, problem, region) } - Ok(int) => { + 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/cli/src/format.rs b/cli/src/format.rs index 365bb0034e..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, 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::{ ast::{Def, Module}, @@ -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 ac9d12d39f..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.Effect {} 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..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.Effect {} mainForHost : Str mainForHost = main 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" 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/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 new file mode 100644 index 0000000000..3734b578ce --- /dev/null +++ b/compiler/builtins/bitcode/src/expect.zig @@ -0,0 +1,162 @@ +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(); + + 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(raw_clones, @ptrCast([*]u8, failures), num_bytes); + + 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 { + 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(); + + var fails = getExpectFailures(); + try std.testing.expectEqual(fails.len, 0); + + expectFailed(1, 2, 3, 4); + + 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 }; + + fails = getExpectFailures(); + try std.testing.expectEqual(fails[0], what_it_should_look_like); + utils.dealloc(@ptrCast([*]u8, fails.ptr), @alignOf([*]Failure)); +} 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/main.zig b/compiler/builtins/bitcode/src/main.zig index c925054a75..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"; @@ -141,12 +142,14 @@ comptime { } // Utils - comptime { exportUtilsFn(utils.test_panic, "test_panic"); exportUtilsFn(utils.increfC, "incref"); exportUtilsFn(utils.decrefC, "decref"); exportUtilsFn(utils.decrefCheckNullC, "decref_check_null"); + 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 }); } @@ -175,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 904f6badcc..810d75b27f 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -18,14 +18,18 @@ 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. + // 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 }); } } @@ -53,8 +57,16 @@ fn testing_roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { @panic("Roc panicked"); } -pub fn alloc(size: usize, alignment: u32) [*]u8 { - return @ptrCast([*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment })); +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 })); } pub fn realloc(c_ptr: [*]u8, new_size: usize, old_size: usize, alignment: u32) [*]u8 { @@ -70,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 { @@ -173,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; @@ -185,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); @@ -197,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); @@ -217,6 +236,11 @@ pub fn allocateWithRefcount( } } +pub const CSlice = extern struct { + pointer: *c_void, + len: usize, +}; + pub fn unsafeReallocate( source_ptr: [*]u8, alignment: u32, diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 0284e9e774..c52c30cf09 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -336,3 +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.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/can/src/builtins.rs b/compiler/can/src/builtins.rs index cd90b4d698..93f6b6947b 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1,6 +1,7 @@ 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; use roc_collections::all::SendMap; use roc_module::called_via::CalledVia; @@ -793,7 +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)), + (arg_var, num(unbound_zero_var, 0, num_no_bound())), ], ret_var: bool_var, }; @@ -816,7 +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)), + (arg_var, num(unbound_zero_var, 0, num_no_bound())), (arg_var, Var(Symbol::ARG_1)), ], ret_var: bool_var, @@ -841,7 +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)), + (arg_var, num(unbound_zero_var, 0, num_no_bound())), ], ret_var: bool_var, }; @@ -866,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), + int::(var_store.fresh(), var_store.fresh(), 1, num_no_bound()), ), ( arg_var, @@ -874,7 +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)), + (arg_var, num(unbound_two_var, 2, num_no_bound())), ], ret_var: arg_var, }, @@ -901,14 +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)), + (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)), + (arg_var, num(arg_num_var, 2, num_no_bound())), ], ret_var: arg_var, }, @@ -962,7 +963,10 @@ 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()), + ), ], ret_var: bool_var, }), @@ -1008,7 +1012,10 @@ 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()), + ), ], ret_var: bool_var, }), @@ -1246,92 +1253,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 @@ -1344,7 +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)), + (len_var, num(unbound_zero_var, 0, num_no_bound())), ( len_var, RunLowLevel { @@ -1458,7 +1555,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, @@ -2201,7 +2303,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, @@ -2227,7 +2334,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 { @@ -2337,7 +2449,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 { @@ -2417,7 +2534,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, @@ -2579,7 +2701,10 @@ 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()), + ), ], ret_var: list_var, }; @@ -2676,7 +2801,10 @@ 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()), + ), ], ret_var: len_var, }, @@ -2874,7 +3002,10 @@ 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()), + ), ( len_var, RunLowLevel { @@ -2905,7 +3036,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(), + ), + ), ], ret_var: list_elem_var, }, @@ -3004,7 +3143,10 @@ 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()), + ), ( len_var, RunLowLevel { @@ -3035,7 +3177,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(), + ), + ), ], ret_var: list_elem_var, }, @@ -3129,7 +3279,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())), + ), (closure_var, Loc::at_zero(Var(Symbol::NUM_ADD))), ], CalledVia::Space, @@ -3161,7 +3314,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())), + ), (closure_var, Loc::at_zero(Var(Symbol::NUM_MUL))), ], CalledVia::Space, @@ -3815,7 +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_var, num(unbound_zero_var, 0, num_no_bound())), ], ret_var: bool_var, }, @@ -3918,7 +4074,10 @@ 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()), + ), ], ret_var: bool_var, }, @@ -3983,7 +4142,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(), + ), ), ], ret_var: bool_var, @@ -4049,7 +4213,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(), + ), ), ], ret_var: bool_var, @@ -4119,7 +4288,10 @@ 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()), + ), ( len_var, RunLowLevel { @@ -4143,7 +4315,10 @@ 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()), + ), ], ret_var: list_elem_var, }, @@ -4200,7 +4375,10 @@ 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()), + ), ( len_var, RunLowLevel { @@ -4239,7 +4417,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(), + ), + ), ], ret_var: len_var, }, @@ -4867,7 +5053,10 @@ 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()), + )], op: LowLevel::NumIntCast, }, ), @@ -4955,13 +5144,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(); @@ -4984,21 +5178,53 @@ where } } +fn num_no_bound() -> NumericBound { + NumericBound::None +} + #[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, { 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(), + IntValue::I128(ii), + bound, + ) } #[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) +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, + bound, + ) } #[inline(always)] -fn num(num_var: Variable, i: i64) -> Expr { - Num(num_var, i.to_string().into_boxed_str(), i) +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/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/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/expr.rs b/compiler/can/src/expr.rs index 846a064d5d..3d4954e5ad 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_num, float_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; @@ -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,17 +46,38 @@ 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), + Num(Variable, Box, IntValue, 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, + IntValue, + NumericBound, + ), + Float(Variable, Variable, Box, f64, NumericBound), Str(Box), List { elem_var: Variable, @@ -208,20 +229,20 @@ pub fn canonicalize_expr<'a>( use Expr::*; let (expr, output) = match expr { - ast::Expr::Num(str) => { + &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, env, ); (answer, Output::default()) } - ast::Expr::Float(str) => { + &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, env, ); @@ -790,21 +811,21 @@ pub fn canonicalize_expr<'a>( (RuntimeError(problem), Output::default()) } - ast::Expr::NonBase10Int { + &ast::Expr::NonBase10Int { string, base, is_negative, } => { // 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) => { + let answer = match finish_parsing_base(string, base, is_negative) { + 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)), 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), + Err(e) => int_expr_from_result(var_store, Err(e), region, base, env), }; (answer, Output::default()) @@ -1226,9 +1247,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/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; diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index e164fb8680..ba4d5ce0b9 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::HeaderFor; 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 { @@ -44,6 +45,7 @@ pub struct ModuleOutput { pub fn canonicalize_module_defs<'a, F>( arena: &Bump, loc_defs: &'a [Loc>], + header_for: &roc_parse::header::HeaderFor, home: ModuleId, module_ids: &ModuleIds, exposed_ident_ids: IdentIds, @@ -59,12 +61,49 @@ 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 HeaderFor::Hosted { generates, .. } = header_for { + // TODO extract effect name from the header + let name: &str = generates.into(); + 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. // @@ -82,7 +121,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(); @@ -143,7 +181,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, @@ -208,7 +246,21 @@ where (Ok(mut declarations), output) => { use crate::def::Declaration::*; - for decl in declarations.iter() { + 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() { match decl { Declare(def) => { for (symbol, _) in def.pattern_vars.iter() { @@ -221,6 +273,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 { @@ -253,6 +358,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, @@ -397,9 +514,9 @@ fn fix_values_captured_in_closure_pattern( } } Identifier(_) - | NumLiteral(_, _, _) - | IntLiteral(_, _, _) - | FloatLiteral(_, _, _) + | NumLiteral(..) + | IntLiteral(..) + | FloatLiteral(..) | StrLiteral(_) | Underscore | Shadowed(..) @@ -453,9 +570,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..847d729466 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::*; @@ -7,21 +7,33 @@ use roc_problem::can::{FloatErrorKind, IntErrorKind}; use roc_region::all::Region; use roc_types::subs::VarStore; use std::i64; - -// 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. +use std::str; #[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, env: &mut Env, ) -> Expr { match result { - Ok((str, num)) => Expr::Num(var_store.fresh(), (*str).into(), 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, + bound, + ), + Ok((str, ParsedNumResult::Float(num, bound))) => Expr::Float( + var_store.fresh(), + var_store.fresh(), + (*str).into(), + num, + bound, + ), Err((raw, error)) => { // (Num *) compiles to Int if it doesn't // get specialized to something else first, @@ -38,14 +50,20 @@ 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, IntValue, NumericBound), (&str, IntErrorKind)>, region: Region, base: Base, 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, bound)) => Expr::Int( + var_store.fresh(), + var_store.fresh(), + (*str).into(), + int, + bound, + ), Err((raw, error)) => { let runtime_error = InvalidInt(error, base, region, raw.into()); @@ -59,13 +77,19 @@ 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, 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, bound)) => Expr::Float( + var_store.fresh(), + var_store.fresh(), + (*str).into(), + float, + bound, + ), Err((raw, error)) => { let runtime_error = InvalidFloat(error, region, raw.into()); @@ -76,11 +100,32 @@ pub fn float_expr_from_result( } } +pub enum ParsedNumResult { + Int(IntValue, NumericBound), + Float(f64, NumericBound), + UnknownNum(IntValue), +} + #[inline(always)] -pub fn finish_parsing_int(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))?; + // 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)) => { + let num = match num { + IntValue::I128(n) => n as f64, + IntValue::U128(n) => n as f64, + }; + ParsedNumResult::Float(num, NumericBound::Exact(fw)) + } + }) } #[inline(always)] @@ -88,7 +133,7 @@ pub fn finish_parsing_base( raw: &str, base: Base, is_negative: bool, -) -> Result { +) -> Result<(IntValue, NumericBound), (&str, IntErrorKind)> { let radix = match base { Base::Hex => 16, Base::Decimal => 10, @@ -98,18 +143,36 @@ 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| (raw, 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 (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)), + }; + // 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)) @@ -121,6 +184,35 @@ pub fn finish_parsing_float(raw: &str) -> Result { } } +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 (Some($width), num_str.get(0..num_str.len() - $suffix.len()).unwrap()); + } + )*} + } + + 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) + } + + (None, num_str) +} + /// Integer parsing code taken from the rust libcore, /// pulled in so we can give custom error messages /// @@ -129,44 +221,11 @@ pub fn finish_parsing_float(raw: &str) -> Result { /// 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(src: &str, radix: u32) -> Result { +fn from_str_radix( + src: &str, + radix: u32, +) -> Result<(IntValue, NumericBound), IntErrorKind> { use self::IntErrorKind::*; - use self::ParseIntError as PIE; assert!( (2..=36).contains(&radix), @@ -174,86 +233,260 @@ fn from_str_radix(src: &str, radix: u32) -> Result 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" + ), + }, }; - if digits.is_empty() { - return Err(PIE { kind: Empty }); - } + let (lower_bound, is_negative) = match result { + IntValue::I128(num) => (lower_bound_of_int(num), num <= 0), + IntValue::U128(_) => (IntWidth::U128, false), + }; - 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 }), - }; + 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) + } + } + } +} + +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 + } 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 }), - }; + // Negative + 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) } -/// 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, +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum IntSign { + Unsigned, + Signed, } -impl ParseIntError { - /// Outputs the detailed cause of parsing an integer failing. - pub fn kind(&self) -> &IntErrorKind { - &self.kind +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum IntWidth { + U8, + U16, + U32, + U64, + U128, + I8, + I16, + I32, + I64, + I128, + 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, + 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), +} 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..ac85b53306 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -1,6 +1,9 @@ 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, IntValue, Output}; +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::symbol::Symbol; @@ -9,6 +12,7 @@ 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,9 +29,15 @@ pub enum Pattern { ext_var: Variable, destructs: Vec>, }, - IntLiteral(Variable, Box, i64), - NumLiteral(Variable, Box, i64), - FloatLiteral(Variable, Box, f64), + NumLiteral(Variable, Box, IntValue, NumericBound), + IntLiteral( + Variable, + Variable, + Box, + IntValue, + NumericBound, + ), + FloatLiteral(Variable, Variable, Box, f64, NumericBound), StrLiteral(Box), Underscore, @@ -85,9 +95,9 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec) { } } - NumLiteral(_, _, _) - | IntLiteral(_, _, _) - | FloatLiteral(_, _, _) + NumLiteral(..) + | IntLiteral(..) + | FloatLiteral(..) | StrLiteral(_) | Underscore | MalformedPattern(_, _) @@ -184,13 +194,19 @@ pub fn canonicalize_pattern<'a>( } } - FloatLiteral(str) => 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), + Ok((float, bound)) => Pattern::FloatLiteral( + var_store.fresh(), + var_store.fresh(), + (str).into(), + float, + bound, + ), }, ptype => unsupported_pattern(env, ptype, region), }, @@ -200,32 +216,58 @@ pub fn canonicalize_pattern<'a>( TopLevelDef | DefExpr => bad_underscore(env, region), }, - NumLiteral(str) => 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), + Ok(ParsedNumResult::UnknownNum(int)) => { + Pattern::NumLiteral(var_store.fresh(), (str).into(), int, NumericBound::None) + } + 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), }, - NonBase10Literal { + &NonBase10Literal { string, base, is_negative, } => 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 { "" }; + 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 }; - Pattern::IntLiteral(var_store.fresh(), int_str, i) + 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) } }, ptype => unsupported_pattern(env, ptype, region), @@ -473,9 +515,9 @@ fn add_bindings_from_patterns( answer.push((*symbol, *region)); } } - NumLiteral(_, _, _) - | IntLiteral(_, _, _) - | FloatLiteral(_, _, _) + NumLiteral(..) + | IntLiteral(..) + | FloatLiteral(..) | StrLiteral(_) | Underscore | MalformedPattern(_, _) diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index 196be508c0..3489b0677f 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}; @@ -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,8 +46,8 @@ mod test_can { let actual_out = can_expr_with(&arena, test_home(), input); match actual_out.loc_expr.value { - Expr::Int(_, _, _, actual) => { - assert_eq!(expected, actual); + Expr::Int(_, _, _, 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); + Expr::Num(_, _, actual, _) => { + assert_eq!(IntValue::I128(expected), actual); } actual => { panic!("Expected a Num, but got: {:?}", actual); @@ -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(), @@ -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/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 8fe6bfeece..020d5b4564 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -1,6 +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_collections::all::SendMap; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; @@ -10,28 +11,49 @@ use roc_types::types::Category; use roc_types::types::Reason; use roc_types::types::Type::{self, *}; +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, + )); + } +} + #[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 +62,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 +194,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 +285,55 @@ pub fn num_num(typ: Type) -> Type { Box::new(alias_content), ) } + +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; +} + +impl TypedNumericBound for NumericBound { + fn concrete_num_type(&self) -> Option { + match self { + NumericBound::None => None, + NumericBound::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(), + }), + } + } +} + +impl TypedNumericBound for NumericBound { + fn concrete_num_type(&self) -> Option { + match self { + NumericBound::None => None, + NumericBound::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 { + 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() + } + } + } +} diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index c0cfffa07b..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,17 +98,11 @@ pub fn constrain_expr( expected: Expected, ) -> Constraint { match expr { - Int(var, precision, _, _) => int_literal(*var, *precision, expected, region), - Num(var, _, _) => exists( - vec![*var], - Eq( - crate::builtins::num_num(Type::Variable(*var)), - expected, - Category::Num, - region, - ), - ), - Float(var, precision, _, _) => 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/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 2a1ce6fa46..242ff11335 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,31 +178,83 @@ pub fn constrain_pattern( ); } - NumLiteral(var, _, _) => { - 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, _, _) => { + &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, _, _) => { + &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/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, diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index 994d9d9796..8487c19761 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -27,8 +27,8 @@ impl<'a> Formattable for Expr<'a> { } // These expressions never have newlines - Float(_) - | Num(_) + Float(..) + | Num(..) | NonBase10Int { .. } | Access(_, _) | AccessorFunction(_) @@ -196,17 +196,25 @@ impl<'a> Formattable for Expr<'a> { buf.push(')'); } } - Num(string) | Float(string) | GlobalTag(string) | PrivateTag(string) => { + &Num(string) => { + buf.indent(indent); + buf.push_str(string); + } + &Float(string) => { + buf.indent(indent); + buf.push_str(string); + } + GlobalTag(string) | PrivateTag(string) => { buf.indent(indent); buf.push_str(string) } - NonBase10Int { + &NonBase10Int { base, string, is_negative, } => { buf.indent(indent); - if *is_negative { + if is_negative { buf.push('-'); } diff --git a/compiler/fmt/src/module.rs b/compiler/fmt/src/module.rs index 28a4a26918..ba698dd2f8 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,29 +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 + INDENT, - '{', - '}', - effects.entries, - Newlines::No, - ) -} - impl<'a> Formattable for TypedIdent<'a> { fn is_multiline(&self) -> bool { false diff --git a/compiler/fmt/src/pattern.rs b/compiler/fmt/src/pattern.rs index 2aac5b3e91..e09f9e19d7 100644 --- a/compiler/fmt/src/pattern.rs +++ b/compiler/fmt/src/pattern.rs @@ -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,17 +116,17 @@ impl<'a> Formattable for Pattern<'a> { loc_pattern.format(buf, indent); } - NumLiteral(string) => { + &NumLiteral(string) => { buf.indent(indent); buf.push_str(string); } - NonBase10Literal { + &NonBase10Literal { base, string, is_negative, } => { buf.indent(indent); - if *is_negative { + if is_negative { buf.push('-'); } @@ -139,7 +139,7 @@ impl<'a> Formattable for Pattern<'a> { buf.push_str(string); } - FloatLiteral(string) => { + &FloatLiteral(string) => { buf.indent(indent); buf.push_str(string); } diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index 0ff058cc88..a49914abce 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -2675,13 +2675,7 @@ mod test_fmt { exposes [] \ packages {} \ imports [ Task.{ Task } ] \ - provides [ mainForHost ] \ - effects fx.Effect \ - { \ - putLine : Str -> Effect {}, \ - putInt : I64 -> Effect {}, \ - getInt : Effect { value : I64, errorCode : [ A, B ], isError : Bool } \ - }", + provides [ mainForHost ]", ); } diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 031ce7b1db..7fb646d4f4 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>, @@ -5284,11 +5299,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>, @@ -5299,7 +5309,6 @@ fn run_low_level<'a, 'ctx, 'env>( op: LowLevel, args: &[Symbol], update_mode: UpdateMode, - // expect_failed: *const (), ) -> BasicValueEnum<'ctx> { use LowLevel::*; @@ -6063,21 +6072,28 @@ fn run_low_level<'a, 'ctx, 'env>( match env.target_info.ptr_width() { roc_target::PtrWidth::Bytes8 => { - 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 func = 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); + 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); } diff --git a/compiler/gen_llvm/src/llvm/externs.rs b/compiler/gen_llvm/src/llvm/externs.rs index 91af05faf8..889a0edd3b 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 d6237ce629..812fbdede6 100644 --- a/compiler/gen_llvm/src/run_roc.rs +++ b/compiler/gen_llvm/src/run_roc.rs @@ -38,10 +38,23 @@ 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_builtins::bitcode; use roc_gen_llvm::run_roc::RocCallResult; use std::mem::MaybeUninit; + #[derive(Debug, Copy, Clone)] + #[repr(C)] + 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()) @@ -49,11 +62,50 @@ macro_rules! run_jit_function { .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) .expect("errored"); - let mut result = MaybeUninit::uninit(); + #[repr(C)] + struct Failures { + failures: *const Failure, + count: usize, + } - main(result.as_mut_ptr()); + impl Drop for Failures { + fn drop(&mut self) { + use std::alloc::{dealloc, Layout}; + use std::mem; - match result.assume_init().into() { + 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() + .ok_or(format!( + "Unable to JIT compile `{}`", + bitcode::UTILS_GET_EXPECT_FAILURES + )) + .expect("errored"); + let mut main_result = MaybeUninit::uninit(); + + main(main_result.as_mut_ptr()); + 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) }; + + panic!("Failed with {} failures. Failures: ", failures.len()); + } + + 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/load/src/file.rs b/compiler/load/src/file.rs index b73aaa4211..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,9 +23,9 @@ use roc_mono::ir::{ UpdateModeIds, }; use roc_mono::layout::{Layout, LayoutCache, LayoutProblem}; -use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation}; -use roc_parse::header::PackageName; +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; use roc_parse::module::module_defs; use roc_parse::parser::{FileError, Parser, SyntaxError}; @@ -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 { @@ -674,24 +643,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>, - }, - 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)] @@ -774,7 +726,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, @@ -783,6 +734,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, @@ -790,19 +743,13 @@ 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, 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, @@ -861,6 +808,7 @@ enum PlatformPath<'a> { NotSpecified, Valid(To<'a>), RootIsInterface, + RootIsHosted, RootIsPkgConfig, } @@ -1695,7 +1643,7 @@ fn update<'a>( Ok(state) } - Header(header, header_extra) => { + Header(header) => { use HeaderFor::*; log!("loaded header for {:?}", header.module_id); @@ -1712,13 +1660,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); @@ -1742,6 +1690,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 @@ -1863,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, @@ -2366,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!( @@ -2396,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!( @@ -2551,7 +2440,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 +2508,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( @@ -2661,37 +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::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, - )) + 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), @@ -2760,15 +2653,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>, @@ -2778,7 +2662,7 @@ struct HeaderInfo<'a> { packages: &'a [Loc>], exposes: &'a [Loc>], imports: &'a [Loc>], - to_platform: Option>, + extra: HeaderFor<'a>, } #[allow(clippy::too_many_arguments)] @@ -2799,7 +2683,7 @@ fn send_header<'a>( packages, exposes, imports, - to_platform, + extra, } = info; let declared_name: ModuleName = match &loc_name.value { @@ -2936,11 +2820,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 { @@ -2957,24 +2836,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, + }), ) } @@ -3023,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()), @@ -3198,24 +3074,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, + }), ) } @@ -3383,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<_> = crate::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 = crate::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 = crate::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. - crate::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>( @@ -3668,6 +3276,7 @@ where let ParsedModule { module_id, module_name, + header_for, exposed_ident_ids, parsed_defs, exposed_imports, @@ -3680,6 +3289,7 @@ where let canonicalized = canonicalize_module_defs( arena, parsed_defs, + &header_for, module_id, module_ids, exposed_ident_ids, @@ -3702,12 +3312,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) } }; @@ -3784,6 +3396,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result, Loadi exposed_ident_ids, exposed_imports, module_path, + header_for, .. } = header; @@ -3798,6 +3411,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)) @@ -4430,7 +4044,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."), 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; 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 ff51aab7fd..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)), @@ -2020,7 +2022,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 +3011,17 @@ 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)), + IntOrFloat::Int(_) => Some(match *int { + IntValue::I128(n) => Literal::Int(n), + IntValue::U128(n) => Literal::U128(n), + }), _ => 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,11 +3041,17 @@ 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())), - 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, @@ -3072,11 +3083,14 @@ 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, - 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, ), @@ -3084,7 +3098,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,18 +3129,24 @@ 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( 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), @@ -7662,9 +7684,15 @@ fn from_can_pattern_help<'a>( match can_pattern { Underscore => Ok(Pattern::Underscore), Identifier(symbol) => Ok(Pattern::Identifier(*symbol)), - IntLiteral(var, _, int) => { - match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { - IntOrFloat::Int(precision) => Ok(Pattern::IntLiteral(*int as i128, precision)), + IntLiteral(_, precision_var, _, int, _bound) => { + match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) { + 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 {:?}", @@ -7673,11 +7701,11 @@ fn from_can_pattern_help<'a>( } } } - FloatLiteral(var, float_str, float) => { + 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)) @@ -7704,10 +7732,20 @@ 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)), + 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/parse/src/expr.rs b/compiler/parse/src/expr.rs index c56e6bb5da..f45cbe5c9a 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -514,7 +514,7 @@ fn numeric_negate_expression<'a, T>( let start = state.pos(); let region = Region::new(start, expr.region.end()); - let new_expr = match &expr.value { + let new_expr = match expr.value { Expr::Num(string) => { let new_string = unsafe { std::str::from_utf8_unchecked(&state.bytes()[..string.len() + 1]) }; @@ -536,7 +536,7 @@ fn numeric_negate_expression<'a, T>( Expr::NonBase10Int { is_negative: !is_negative, string, - base: *base, + base, } } _ => Expr::UnaryOp(arena.alloc(expr), Loc::at(loc_op.region, UnaryOp::Negate)), @@ -1450,8 +1450,8 @@ 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) => Ok(Pattern::FloatLiteral(string)), + &Expr::Num(string) => Ok(Pattern::NumLiteral(string)), Expr::NonBase10Int { string, base, diff --git a/compiler/parse/src/header.rs b/compiler/parse/src/header.rs index 17aa05950d..6688b845fe 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), @@ -47,6 +69,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); @@ -162,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>], @@ -179,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/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 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/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/function_effect_types.header.roc b/compiler/parse/tests/snapshots/pass/function_effect_types.header.roc index 1135057391..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.Effect - { - getLine : Effect Str, - putLine : Str -> Effect {}, - twoArguments : Int, Int -> Effect {} - } 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/nonempty_platform_header.header.roc b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc index 97673c692a..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.Effect {} 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..8ee611e8f7 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.result-ast @@ -0,0 +1,451 @@ +Record( + Collection { + items: [ + @4-15 SpaceBefore( + RequiredValue( + @4-6 "u8", + [], + @10-15 Num( + "123u8", + ), + ), + [ + Newline, + ], + ), + @19-31 SpaceBefore( + RequiredValue( + @19-22 "u16", + [], + @25-31 Num( + "123u16", + ), + ), + [ + Newline, + ], + ), + @35-47 SpaceBefore( + RequiredValue( + @35-38 "u32", + [], + @41-47 Num( + "123u32", + ), + ), + [ + Newline, + ], + ), + @51-63 SpaceBefore( + RequiredValue( + @51-54 "u64", + [], + @57-63 Num( + "123u64", + ), + ), + [ + Newline, + ], + ), + @67-80 SpaceBefore( + RequiredValue( + @67-71 "u128", + [], + @73-80 Num( + "123u128", + ), + ), + [ + Newline, + ], + ), + @84-95 SpaceBefore( + RequiredValue( + @84-86 "i8", + [], + @90-95 Num( + "123i8", + ), + ), + [ + Newline, + ], + ), + @99-111 SpaceBefore( + RequiredValue( + @99-102 "i16", + [], + @105-111 Num( + "123i16", + ), + ), + [ + Newline, + ], + ), + @115-127 SpaceBefore( + RequiredValue( + @115-118 "i32", + [], + @121-127 Num( + "123i32", + ), + ), + [ + Newline, + ], + ), + @131-143 SpaceBefore( + RequiredValue( + @131-134 "i64", + [], + @137-143 Num( + "123i64", + ), + ), + [ + Newline, + ], + ), + @147-160 SpaceBefore( + RequiredValue( + @147-151 "i128", + [], + @153-160 Num( + "123i128", + ), + ), + [ + Newline, + ], + ), + @164-176 SpaceBefore( + RequiredValue( + @164-167 "nat", + [], + @170-176 Num( + "123nat", + ), + ), + [ + Newline, + ], + ), + @180-192 SpaceBefore( + RequiredValue( + @180-183 "dec", + [], + @186-192 Num( + "123dec", + ), + ), + [ + Newline, + ], + ), + @196-211 SpaceBefore( + RequiredValue( + @196-201 "u8Neg", + [], + @205-211 Num( + "-123u8", + ), + ), + [ + Newline, + ], + ), + @215-231 SpaceBefore( + RequiredValue( + @215-221 "u16Neg", + [], + @224-231 Num( + "-123u16", + ), + ), + [ + Newline, + ], + ), + @235-251 SpaceBefore( + RequiredValue( + @235-241 "u32Neg", + [], + @244-251 Num( + "-123u32", + ), + ), + [ + Newline, + ], + ), + @255-271 SpaceBefore( + RequiredValue( + @255-261 "u64Neg", + [], + @264-271 Num( + "-123u64", + ), + ), + [ + Newline, + ], + ), + @275-292 SpaceBefore( + RequiredValue( + @275-282 "u128Neg", + [], + @284-292 Num( + "-123u128", + ), + ), + [ + Newline, + ], + ), + @296-311 SpaceBefore( + RequiredValue( + @296-301 "i8Neg", + [], + @305-311 Num( + "-123i8", + ), + ), + [ + Newline, + ], + ), + @315-331 SpaceBefore( + RequiredValue( + @315-321 "i16Neg", + [], + @324-331 Num( + "-123i16", + ), + ), + [ + Newline, + ], + ), + @335-351 SpaceBefore( + RequiredValue( + @335-341 "i32Neg", + [], + @344-351 Num( + "-123i32", + ), + ), + [ + Newline, + ], + ), + @355-371 SpaceBefore( + RequiredValue( + @355-361 "i64Neg", + [], + @364-371 Num( + "-123i64", + ), + ), + [ + Newline, + ], + ), + @375-392 SpaceBefore( + RequiredValue( + @375-382 "i128Neg", + [], + @384-392 Num( + "-123i128", + ), + ), + [ + Newline, + ], + ), + @396-412 SpaceBefore( + RequiredValue( + @396-402 "natNeg", + [], + @405-412 Num( + "-123nat", + ), + ), + [ + Newline, + ], + ), + @416-432 SpaceBefore( + RequiredValue( + @416-422 "decNeg", + [], + @425-432 Num( + "-123dec", + ), + ), + [ + Newline, + ], + ), + @436-452 SpaceBefore( + RequiredValue( + @436-441 "u8Bin", + [], + @445-452 NonBase10Int { + string: "101u8", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @456-473 SpaceBefore( + RequiredValue( + @456-462 "u16Bin", + [], + @465-473 NonBase10Int { + string: "101u16", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @477-494 SpaceBefore( + RequiredValue( + @477-483 "u32Bin", + [], + @486-494 NonBase10Int { + string: "101u32", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @498-515 SpaceBefore( + RequiredValue( + @498-504 "u64Bin", + [], + @507-515 NonBase10Int { + string: "101u64", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @519-537 SpaceBefore( + RequiredValue( + @519-526 "u128Bin", + [], + @528-537 NonBase10Int { + string: "101u128", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @541-557 SpaceBefore( + RequiredValue( + @541-546 "i8Bin", + [], + @550-557 NonBase10Int { + string: "101i8", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @561-578 SpaceBefore( + RequiredValue( + @561-567 "i16Bin", + [], + @570-578 NonBase10Int { + string: "101i16", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @582-599 SpaceBefore( + RequiredValue( + @582-588 "i32Bin", + [], + @591-599 NonBase10Int { + string: "101i32", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @603-620 SpaceBefore( + RequiredValue( + @603-609 "i64Bin", + [], + @612-620 NonBase10Int { + string: "101i64", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @624-642 SpaceBefore( + RequiredValue( + @624-631 "i128Bin", + [], + @633-642 NonBase10Int { + string: "101i128", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @646-663 SpaceBefore( + RequiredValue( + @646-652 "natBin", + [], + @655-663 NonBase10Int { + string: "101nat", + base: Binary, + is_negative: false, + }, + ), + [ + 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..e76387bf3c --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.roc @@ -0,0 +1,37 @@ +{ + 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, +} 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: [ diff --git a/compiler/parse/tests/snapshots/pass/requires_type.header.roc b/compiler/parse/tests/snapshots/pass/requires_type.header.roc index 343674511a..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.Effect {} mainForHost : App Flags Model mainForHost = main diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 60c92b5b07..b42d3edca6 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -182,6 +182,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, diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index e2dce07a9a..f8d964e8fc 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -107,6 +107,18 @@ 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, + /// 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. @@ -118,6 +130,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/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index b86caf3c81..d8c5a38d6e 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -5044,4 +5044,156 @@ 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 }"#, + ) + } + + #[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/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index 24782ef1a5..2f97c23753 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; @@ -2485,9 +2491,9 @@ fn call_invalid_layout() { #[test] #[cfg(any(feature = "gen-llvm"))] -#[should_panic(expected = "An expectation failed!")] +#[should_panic(expected = "Failed with 1 failures. Failures: ")] 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 3a562cbf8c..d17c326161 100644 --- a/compiler/test_gen/src/helpers/dev.rs +++ b/compiler/test_gen/src/helpers/dev.rs @@ -257,5 +257,25 @@ 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); + }}; +} + #[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 1392b1ab13..2868cb7094 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.expect") { + function.set_linkage(Linkage::External); + } else { + function.set_linkage(Linkage::Internal); + } } if name.starts_with("roc_builtins.dict") { @@ -601,6 +605,46 @@ 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); + }; +} + macro_rules! expect_runtime_error_panic { ($src:expr) => {{ #[cfg(feature = "wasm-cli-run")] @@ -651,6 +695,8 @@ 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; diff --git a/compiler/test_gen/src/tests.rs b/compiler/test_gen/src/tests.rs index a6da79ca91..41923811a5 100644 --- a/compiler/test_gen/src/tests.rs +++ b/compiler/test_gen/src/tests.rs @@ -24,6 +24,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, diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index a861242bd5..ead9c32afd 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,33 @@ 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"), + Alias(Symbol::NUM_DECIMAL, _, _) => buf.push_str("Dec"), + _ => 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 +401,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)*) => { + 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 + "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 + } +} + 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/editor/src/editor/resources/strings.rs b/editor/src/editor/resources/strings.rs index 4175ffc1c9..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.Effect {} mainForHost : Str mainForHost = main 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 07583550da..fe634bbc7f 100644 --- a/examples/benchmarks/platform/Package-Config.roc +++ b/examples/benchmarks/platform/Package-Config.roc @@ -4,12 +4,6 @@ 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 }, - } 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/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/cli/platform/Package-Config.roc b/examples/cli/platform/Package-Config.roc index f82a01c43b..4f9c8bdf62 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.Effect { putLine : Str -> Effect {}, getLine : Effect Str } 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) 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 17d8a92f5d..b7377c6412 100644 --- a/examples/effect/thing/platform-dir/Package-Config.roc +++ b/examples/effect/thing/platform-dir/Package-Config.roc @@ -2,13 +2,8 @@ platform "roc-examples/cli" requires {} { main : Effect {} } exposes [] packages {} - imports [ fx.Effect ] + imports [ pf.Effect ] provides [ mainForHost ] - effects fx.Effect - { - putLine : Str -> Effect {}, - getLine : Effect Str, - } mainForHost : Effect.Effect {} as Fx mainForHost = main diff --git a/examples/false-interpreter/platform/Effect.roc b/examples/false-interpreter/platform/Effect.roc new file mode 100644 index 0000000000..b54ae3fd1d --- /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, getLine, 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 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/Package-Config.roc b/examples/false-interpreter/platform/Package-Config.roc index da5868f3b3..304d4d0fe5 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/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 {}) 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 2391724dda..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.Effect {} 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..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.Effect {} mainForHost : Str mainForHost = main diff --git a/examples/hello-swift/platform/Package-Config.roc b/examples/hello-swift/platform/Package-Config.roc index 9285a43b6b..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.Effect {} mainForHost : Str mainForHost = main diff --git a/examples/hello-web/platform/Package-Config.roc b/examples/hello-web/platform/Package-Config.roc index 5ef87710de..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.Effect {} mainForHost : Str mainForHost = main diff --git a/examples/hello-world/platform/Package-Config.roc b/examples/hello-world/platform/Package-Config.roc index 5ef87710de..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.Effect {} mainForHost : Str mainForHost = main diff --git a/examples/hello-zig/platform/Package-Config.roc b/examples/hello-zig/platform/Package-Config.roc index 5ef87710de..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.Effect {} mainForHost : Str mainForHost = main diff --git a/examples/quicksort/platform/Package-Config.roc b/examples/quicksort/platform/Package-Config.roc index a29fb7925d..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.Effect {} 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..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.Effect {} mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View } mainForHost = main diff --git a/repl_cli/Cargo.toml b/repl_cli/Cargo.toml index 00ab87922c..240d7113ba 100644 --- a/repl_cli/Cargo.toml +++ b/repl_cli/Cargo.toml @@ -8,18 +8,22 @@ 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"} - -[dev-dependencies] -indoc = "1.0.3" -roc_test_utils = {path = "../test_utils"} -strip-ansi-escapes = "0.1.1" +roc_target = {path = "../compiler/roc_target"} +roc_types = {path = "../compiler/types"} +roc_builtins = {path = "../compiler/builtins"} [lib] name = "roc_repl_cli" diff --git a/repl_cli/src/lib.rs b/repl_cli/src/lib.rs index 1d1f8b2901..8c64ee17b1 100644 --- a/repl_cli/src/lib.rs +++ b/repl_cli/src/lib.rs @@ -1,15 +1,27 @@ +use bumpalo::Bump; use const_format::concatcp; +use inkwell::context::Context; +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::io; +use target_lexicon::Triple; +use roc_build::link::module_to_dylib; +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::gen::{gen_and_eval, ReplOutput}; - -#[cfg(test)] -mod tests; +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}; const BLUE: &str = "\u{001b}[36m"; const PINK: &str = "\u{001b}[35m"; @@ -105,6 +117,210 @@ impl Validator for InputValidator { } } +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> { + 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>( + 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, "", + )); + + 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 app = CliReplApp { lib }; + + 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) +} + +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 +438,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.as_bytes(), 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/Cargo.toml b/repl_eval/Cargo.toml index 2f6ba54a65..562cb27c9e 100644 --- a/repl_eval/Cargo.toml +++ b/repl_eval/Cargo.toml @@ -5,22 +5,13 @@ 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"} 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/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/eval.rs b/repl_eval/src/eval.rs index d24823b26c..0b946a73a1 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -16,18 +16,12 @@ 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}; +use crate::ReplApp; -#[cfg(feature = "llvm")] -type AppExecutable = libloading::Library; - -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, @@ -46,9 +40,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 unsafe fn jit_to_ast<'a, M: AppMemory>( +pub 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, @@ -56,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, @@ -73,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), } @@ -93,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); @@ -127,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> { @@ -155,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 { @@ -172,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>) { @@ -190,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>], @@ -213,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 { @@ -224,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"), } @@ -240,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); @@ -264,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, @@ -274,26 +266,25 @@ 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| { - 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, $ty, |num| num_to_ast( - env, - number_literal_to_ast(env.arena, num), - content - )) + env.app.call_function(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)) + // NOTE: `helper!` does not handle 8-bit numbers yet + env.app + .call_function(main_fn_name, |num: u8| byte_to_ast(env, num, content)) } U16 => helper!(u16), U32 => helper!(u32), @@ -312,11 +303,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 - )) + env.app.call_function(main_fn_name, |num: $ty| { + num_to_ast(env, number_literal_to_ast(env.arena, num), content) + }) }; } @@ -328,18 +317,16 @@ fn jit_to_ast_help<'a, M: AppMemory>( Ok(result) } - 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)) } - )), - 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) } - )), + 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) } @@ -395,37 +382,30 @@ fn jit_to_ast_help<'a, M: AppMemory>( let result_stack_size = layout.stack_size(env.target_info); - run_jit_function_dynamic_type!( - app, + env.app.call_function_dynamic_size( main_fn_name, result_stack_size as usize, - |bytes_addr: usize| { struct_addr_to_ast(bytes_addr as usize) } + struct_addr_to_ast, ) } Layout::Union(UnionLayout::NonRecursive(_)) => { let size = layout.stack_size(env.target_info); - Ok(run_jit_function_dynamic_type!( - app, - main_fn_name, - size as usize, - |addr: usize| { + 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_type!( - app, - main_fn_name, - size as usize, - |addr: usize| { + 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") @@ -435,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 @@ -457,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>, @@ -466,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) }}; @@ -480,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) } @@ -510,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) } @@ -636,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, @@ -666,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 { @@ -697,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 { @@ -726,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>, @@ -775,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, @@ -801,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>, @@ -832,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, @@ -949,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; @@ -1027,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; @@ -1119,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> { @@ -1199,15 +1179,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) { diff --git a/repl_eval/src/gen.rs b/repl_eval/src/gen.rs index d7fa969862..0874c42b49 100644 --- a/repl_eval/src/gen.rs +++ b/repl_eval/src/gen.rs @@ -1,196 +1,30 @@ 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; -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_load::file::{LoadingProblem, MonomorphizedModule}; 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: &[u8], - 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( +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); @@ -207,23 +41,20 @@ fn format_answer( } } -fn compile_to_mono<'a>( +pub 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( diff --git a/repl_eval/src/lib.rs b/repl_eval/src/lib.rs index 358e05e325..53dba07a58 100644 --- a/repl_eval/src/lib.rs +++ b/repl_eval/src/lib.rs @@ -1,3 +1,40 @@ -mod app_memory; -mod eval; +use roc_parse::ast::Expr; + +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 T>( + &self, + main_fn_name: &str, + ret_bytes: usize, + transform: F, + ) -> T; +} diff --git a/repl_test/Cargo.toml b/repl_test/Cargo.toml new file mode 100644 index 0000000000..2d86ad19d4 --- /dev/null +++ b/repl_test/Cargo.toml @@ -0,0 +1,24 @@ +[package] +edition = "2021" +name = "repl_test" +version = "0.1.0" + +[[test]] +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"} +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..74775b88c5 --- /dev/null +++ b/repl_test/src/cli.rs @@ -0,0 +1,154 @@ +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)] +struct Out { + stdout: String, + stderr: String, + status: ExitStatus, +} + +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, + } +} + +pub 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()); +} + +pub 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 80% rename from repl_cli/src/tests.rs rename to repl_test/src/tests.rs index aa0022384f..49a166386b 100644 --- a/repl_cli/src/tests.rs +++ b/repl_test/src/tests.rs @@ -1,159 +1,9 @@ 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; +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 crate::cli::{expect_failure, expect_success}; #[test] fn literal_0() { @@ -192,7 +42,7 @@ fn num_addition() { #[test] fn int_addition() { - expect_success("0x1 + 2", "3 : I64"); + expect_success("0x1 + 2", "3 : Int *"); } #[test] @@ -425,7 +275,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 *))"#, ); } @@ -459,23 +309,17 @@ fn num_bitwise_xor() { #[test] fn num_add_wrap() { - expect_success( - "Num.addWrap Num.maxI64 1", - "-9223372036854775808 : Int Signed64", - ); + expect_success("Num.addWrap Num.maxI64 1", "-9223372036854775808 : I64"); } #[test] fn num_sub_wrap() { - expect_success( - "Num.subWrap Num.minI64 1", - "9223372036854775807 : Int Signed64", - ); + expect_success("Num.subWrap Num.minI64 1", "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] 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!() + } +} diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index ed0c9d97e1..3ad20cebf6 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -25,6 +25,9 @@ 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"; +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>, @@ -1055,13 +1058,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 +1129,7 @@ fn pretty_runtime_error<'b>( alloc.text(plurals), contains, alloc.text(charset), - alloc.text("."), + alloc.text(", or have an integer suffix."), ]), tip, ]); @@ -1125,10 +1138,28 @@ 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.concat(vec![ + alloc.reflow( + "The smallest number representable in Roc is the minimum I128 value, ", + ), + alloc.int_literal(i128::MIN), + alloc.text("."), + ]), + ) } else { - "big" + ( + "big", + alloc.concat(vec![ + alloc.reflow( + "The largest number representable in Roc is the maximum U128 value, ", + ), + alloc.int_literal(u128::MAX), + alloc.text("."), + ]), + ) }; let tip = alloc @@ -1142,12 +1173,72 @@ 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, ]); 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::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.int_literal(max_value), + alloc.reflow("."), + ])), + ]); + + 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.int_literal(min_value), + alloc.reflow("."), + ])), + ]); + + 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/src/error/parse.rs b/reporting/src/error/parse.rs index cb1660597f..1e767a38fd 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, @@ -2980,8 +3009,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 +3599,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/src/error/type.rs b/reporting/src/error/type.rs index 3bcde68b07..1b147ebb84 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 the value is used as a:"), + 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") } @@ -1451,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/src/report.rs b/reporting/src/report.rs index 07a7cbfca7..c74de2e0ff 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 f736655cea..c2b8699716 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 "# @@ -3546,7 +3546,8 @@ mod test_reporting { 1│ dec = 100A ^^^^ - Integer literals can only contain the digits 0-9. + Integer literals can only contain the digits + 0-9, or have an integer suffix. Tip: Learn more about number literals at TODO @@ -3558,7 +3559,7 @@ mod test_reporting { ^^^^^ Hexadecimal (base-16) integer literals can only contain the digits - 0-9, a-f and A-F. + 0-9, a-f and A-F, or have an integer suffix. Tip: Learn more about number literals at TODO @@ -3569,7 +3570,8 @@ mod test_reporting { 5│ oct = 0o9 ^^^ - Octal (base-8) integer literals can only contain the digits 0-7. + 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 @@ -3580,7 +3582,8 @@ mod test_reporting { 7│ bin = 0b2 ^^^ - Binary (base-2) integer literals can only contain the digits 0 and 1. + 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 "# @@ -3614,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 @@ -3626,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 @@ -3638,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 "# @@ -3666,7 +3669,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 "# @@ -5500,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 "# @@ -6132,12 +6135,6 @@ 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!( @@ -7304,4 +7301,584 @@ 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 + } + + 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( + indoc!( + r#" + 1u256 + "# + ), + // TODO: link to number suffixes + indoc!( + r#" + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + + 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 + "# + ), + ) + } + + #[test] + fn numer_literal_multi_suffix() { + report_problem_as( + indoc!( + r#" + 1u8u8 + "# + ), + // TODO: link to number suffixes + indoc!( + r#" + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + + 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 + "# + ), + ) + } + + #[test] + fn int_literal_has_float_suffix() { + report_problem_as( + indoc!( + r#" + 0b1f32 + "# + ), + indoc!( + r#" + ── CONFLICTING NUMBER SUFFIX ─────────────────────────────────────────────────── + + This number literal is an integer, but it has a float suffix: + + 1│ 0b1f32 + ^^^^^^ + "# + ), + ) + } + + #[test] + fn float_literal_has_int_suffix() { + report_problem_as( + indoc!( + r#" + 1.0u8 + "# + ), + indoc!( + r#" + ── CONFLICTING NUMBER SUFFIX ─────────────────────────────────────────────────── + + This number literal is a float, but it has an integer suffix: + + 1│ 1.0u8 + ^^^^^ + "# + ), + ) + } + + #[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 4_294_967_295. + "# + ), + ) + } + + #[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 18_446_744_073_709_551_615. + "# + ), + ) + } + + #[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 2_147_483_647. + "# + ), + ) + } + + #[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 -2_147_483_648. + "# + ), + ) + } + + #[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 9_223_372_036_854_775_807. + "# + ), + ) + } + + #[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 -9_223_372_036_854_775_808. + "# + ), + ) + } + + #[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 170_141_183_460_469_231_731_687_303_715_884_105_727. + "# + ), + ) + } } 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`.