Merge branch 'trunk' into fix-module-formatting

This commit is contained in:
Richard Feldman 2022-02-04 23:03:52 -05:00 committed by GitHub
commit 881bae7267
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
114 changed files with 4260 additions and 1986 deletions

View file

@ -1,4 +1,4 @@
[alias] [alias]
test-gen-llvm = "test -p test_gen" 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-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"

36
Cargo.lock generated
View file

@ -3204,6 +3204,17 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157"
[[package]]
name = "repl_test"
version = "0.1.0"
dependencies = [
"indoc",
"roc_cli",
"roc_repl_cli",
"roc_test_utils",
"strip-ansi-escapes",
]
[[package]] [[package]]
name = "rkyv" name = "rkyv"
version = "0.6.7" version = "0.6.7"
@ -3669,14 +3680,20 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"const_format", "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_mono",
"roc_parse", "roc_parse",
"roc_repl_eval", "roc_repl_eval",
"roc_test_utils", "roc_target",
"roc_types",
"rustyline", "rustyline",
"rustyline-derive", "rustyline-derive",
"strip-ansi-escapes",
"target-lexicon", "target-lexicon",
] ]
@ -3685,14 +3702,10 @@ name = "roc_repl_eval"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"inkwell 0.1.0",
"libloading 0.7.1",
"roc_build",
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_fmt", "roc_fmt",
"roc_gen_llvm",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
@ -3701,7 +3714,14 @@ dependencies = [
"roc_reporting", "roc_reporting",
"roc_target", "roc_target",
"roc_types", "roc_types",
"target-lexicon", ]
[[package]]
name = "roc_repl_wasm"
version = "0.1.0"
dependencies = [
"roc_parse",
"roc_repl_eval",
] ]
[[package]] [[package]]

View file

@ -35,6 +35,8 @@ members = [
"reporting", "reporting",
"repl_cli", "repl_cli",
"repl_eval", "repl_eval",
"repl_test",
"repl_wasm",
"roc_std", "roc_std",
"test_utils", "test_utils",
"utils", "utils",

View file

@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
copy-dirs: copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt 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: test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt FROM +install-zig-llvm-valgrind-clippy-rustfmt

View file

@ -1,6 +1,8 @@
use bumpalo::Bump; use bumpalo::Bump;
use roc_can::expr::Recursive; use roc_can::expr::{IntValue, Recursive};
use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use roc_can::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult,
};
use roc_can::operator::desugar_expr; use roc_can::operator::desugar_expr;
use roc_collections::all::MutSet; use roc_collections::all::MutSet;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -52,7 +54,7 @@ pub fn expr_to_expr2<'a>(
match parse_expr { match parse_expr {
Float(string) => { Float(string) => {
match finish_parsing_float(string) { match finish_parsing_float(string) {
Ok(float) => { Ok((float, _bound)) => {
let expr = Expr2::Float { let expr = Expr2::Float {
number: FloatVal::F64(float), number: FloatVal::F64(float),
var: env.var_store.fresh(), var: env.var_store.fresh(),
@ -73,10 +75,13 @@ pub fn expr_to_expr2<'a>(
} }
} }
Num(string) => { Num(string) => {
match finish_parsing_int(string) { match finish_parsing_num(string) {
Ok(int) => { Ok(ParsedNumResult::UnknownNum(int) | ParsedNumResult::Int(int, _)) => {
let expr = Expr2::SmallInt { 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(), var: env.var_store.fresh(),
// TODO non-hardcode // TODO non-hardcode
style: IntStyle::Decimal, style: IntStyle::Decimal,
@ -85,6 +90,15 @@ pub fn expr_to_expr2<'a>(
(expr, Output::default()) (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)) => { Err((raw, error)) => {
// emit runtime error // emit runtime error
let runtime_error = RuntimeError::InvalidInt( let runtime_error = RuntimeError::InvalidInt(
@ -107,9 +121,12 @@ pub fn expr_to_expr2<'a>(
is_negative, is_negative,
} => { } => {
match finish_parsing_base(string, *base, *is_negative) { match finish_parsing_base(string, *base, *is_negative) {
Ok(int) => { Ok((int, _bound)) => {
let expr = Expr2::SmallInt { 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(), var: env.var_store.fresh(),
// TODO non-hardcode // TODO non-hardcode
style: IntStyle::from_base(*base), style: IntStyle::from_base(*base),

View file

@ -3,8 +3,10 @@
#![allow(unused_imports)] #![allow(unused_imports)]
use bumpalo::collections::Vec as BumpVec; use bumpalo::collections::Vec as BumpVec;
use roc_can::expr::unescape_char; use roc_can::expr::{unescape_char, IntValue};
use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use roc_can::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult,
};
use roc_collections::all::BumpMap; use roc_collections::all::BumpMap;
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_parse::ast::{StrLiteral, StrSegment}; use roc_parse::ast::{StrLiteral, StrSegment};
@ -183,18 +185,35 @@ pub fn to_pattern2<'a>(
let problem = MalformedPatternProblem::MalformedFloat; let problem = MalformedPatternProblem::MalformedFloat;
malformed_pattern(env, problem, region) 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), ptype => unsupported_pattern(env, ptype, region),
}, },
NumLiteral(string) => match pattern_type { NumLiteral(string) => match pattern_type {
WhenBranch => match finish_parsing_int(string) { WhenBranch => match finish_parsing_num(string) {
Err(_error) => { Err(_error) => {
let problem = MalformedPatternProblem::MalformedInt; let problem = MalformedPatternProblem::MalformedInt;
malformed_pattern(env, problem, region) 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), ptype => unsupported_pattern(env, ptype, region),
}, },
@ -209,7 +228,11 @@ pub fn to_pattern2<'a>(
let problem = MalformedPatternProblem::MalformedBase(*base); let problem = MalformedPatternProblem::MalformedBase(*base);
malformed_pattern(env, problem, region) 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 { if *is_negative {
Pattern2::IntLiteral(IntVal::I64(-int)) Pattern2::IntLiteral(IntVal::I64(-int))
} else { } else {

View file

@ -12,8 +12,8 @@ use roc_parse::ast::{
TypeAnnotation, WhenBranch, TypeAnnotation, WhenBranch,
}; };
use roc_parse::header::{ use roc_parse::header::{
AppHeader, Effects, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageEntry, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
}; };
use roc_parse::{ use roc_parse::{
ast::{Def, Module}, ast::{Def, Module},
@ -199,14 +199,6 @@ impl<'a> RemoveSpaces<'a> for Module<'a> {
packages: header.packages.remove_spaces(arena), packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena), imports: header.imports.remove_spaces(arena),
provides: header.provides.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: &[], before_header: &[],
after_platform_keyword: &[], after_platform_keyword: &[],
before_requires: &[], before_requires: &[],

View file

@ -4,7 +4,6 @@ platform "examples/multi-module"
packages {} packages {}
imports [] imports []
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str mainForHost : Str
mainForHost = main mainForHost = main

View file

@ -4,7 +4,6 @@ platform "examples/multi-dep-thunk"
packages {} packages {}
imports [] imports []
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str mainForHost : Str
mainForHost = main mainForHost = main

79
cli_utils/Cargo.lock generated
View file

@ -2475,12 +2475,13 @@ dependencies = [
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_error_macros",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_parse", "roc_parse",
"roc_problem", "roc_problem",
"roc_region", "roc_region",
"roc_reporting", "roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"snafu", "snafu",
@ -2510,6 +2511,7 @@ dependencies = [
"roc_reporting", "roc_reporting",
"roc_solve", "roc_solve",
"roc_std", "roc_std",
"roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"serde_json", "serde_json",
@ -2524,6 +2526,7 @@ dependencies = [
"roc_collections", "roc_collections",
"roc_module", "roc_module",
"roc_region", "roc_region",
"roc_target",
"roc_types", "roc_types",
] ]
@ -2549,31 +2552,24 @@ dependencies = [
"bumpalo", "bumpalo",
"clap 3.0.0-beta.5", "clap 3.0.0-beta.5",
"const_format", "const_format",
"inkwell 0.1.0",
"libloading 0.7.1",
"mimalloc", "mimalloc",
"roc_build", "roc_build",
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_constrain",
"roc_docs", "roc_docs",
"roc_editor", "roc_editor",
"roc_error_macros",
"roc_fmt", "roc_fmt",
"roc_gen_llvm",
"roc_linker", "roc_linker",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_parse", "roc_parse",
"roc_problem",
"roc_region", "roc_region",
"roc_repl_cli",
"roc_reporting", "roc_reporting",
"roc_solve", "roc_target",
"roc_types",
"roc_unify",
"rustyline",
"rustyline-derive",
"target-lexicon", "target-lexicon",
"tempfile", "tempfile",
] ]
@ -2631,6 +2627,7 @@ dependencies = [
"roc_module", "roc_module",
"roc_parse", "roc_parse",
"roc_region", "roc_region",
"roc_target",
"roc_types", "roc_types",
"snafu", "snafu",
] ]
@ -2680,6 +2677,10 @@ dependencies = [
"winit", "winit",
] ]
[[package]]
name = "roc_error_macros"
version = "0.1.0"
[[package]] [[package]]
name = "roc_fmt" name = "roc_fmt"
version = "0.1.0" version = "0.1.0"
@ -2700,12 +2701,13 @@ dependencies = [
"packed_struct", "packed_struct",
"roc_builtins", "roc_builtins",
"roc_collections", "roc_collections",
"roc_error_macros",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_problem", "roc_problem",
"roc_region", "roc_region",
"roc_reporting",
"roc_solve", "roc_solve",
"roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"target-lexicon", "target-lexicon",
@ -2720,10 +2722,11 @@ dependencies = [
"morphic_lib", "morphic_lib",
"roc_builtins", "roc_builtins",
"roc_collections", "roc_collections",
"roc_error_macros",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_reporting",
"roc_std", "roc_std",
"roc_target",
"target-lexicon", "target-lexicon",
] ]
@ -2734,10 +2737,11 @@ dependencies = [
"bumpalo", "bumpalo",
"roc_builtins", "roc_builtins",
"roc_collections", "roc_collections",
"roc_error_macros",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_reporting",
"roc_std", "roc_std",
"roc_target",
] ]
[[package]] [[package]]
@ -2782,6 +2786,7 @@ dependencies = [
"roc_region", "roc_region",
"roc_reporting", "roc_reporting",
"roc_solve", "roc_solve",
"roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"ven_pretty", "ven_pretty",
@ -2815,6 +2820,7 @@ dependencies = [
"roc_region", "roc_region",
"roc_solve", "roc_solve",
"roc_std", "roc_std",
"roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"static_assertions", "static_assertions",
@ -2850,6 +2856,44 @@ dependencies = [
"static_assertions", "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]] [[package]]
name = "roc_reporting" name = "roc_reporting"
version = "0.1.0" version = "0.1.0"
@ -2886,6 +2930,13 @@ dependencies = [
name = "roc_std" name = "roc_std"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "roc_target"
version = "0.1.0"
dependencies = [
"target-lexicon",
]
[[package]] [[package]]
name = "roc_types" name = "roc_types"
version = "0.1.0" version = "0.1.0"

View file

@ -26,7 +26,7 @@ pub fn build(b: *Builder) void {
.default_target = CrossTarget{ .default_target = CrossTarget{
.cpu_model = .baseline, .cpu_model = .baseline,
// TODO allow for native target for maximum speed // TODO allow for native target for maximum speed
} },
}); });
const i386_target = makeI386Target(); const i386_target = makeI386Target();
const wasm32_target = makeWasm32Target(); const wasm32_target = makeWasm32Target();

View file

@ -750,7 +750,8 @@ pub fn dictWalk(
const alignment_u32 = alignment.toU32(); const alignment_u32 = alignment.toU32();
// allocate space to write the result of the stepper into // allocate space to write the result of the stepper into
// experimentally aliasing the accum and output pointers is not a good idea // 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 b1 = output orelse unreachable;
var b2 = bytes_ptr; var b2 = bytes_ptr;

View file

@ -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));
}

View file

@ -550,7 +550,8 @@ pub fn listKeepResult(
var output = RocList.allocate(alignment, list.len(), list.len() * after_width); var output = RocList.allocate(alignment, list.len(), list.len() * after_width);
const target_ptr = output.bytes orelse unreachable; 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) { if (data_is_owned) {
inc_n_data(data, size); inc_n_data(data, size);
@ -614,7 +615,8 @@ pub fn listWalk(
inc_n_data(data, list.len()); 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 b1 = output orelse unreachable;
var b2 = bytes_ptr; var b2 = bytes_ptr;
@ -660,7 +662,8 @@ pub fn listWalkBackwards(
inc_n_data(data, list.len()); 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 b1 = output orelse unreachable;
var b2 = bytes_ptr; var b2 = bytes_ptr;
@ -708,7 +711,8 @@ pub fn listWalkUntil(
return; 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 // NOTE: assumes data bytes are the first bytes in a tag
@memcpy(bytes_ptr, accum orelse unreachable, accum_width); @memcpy(bytes_ptr, accum orelse unreachable, accum_width);

View file

@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const math = std.math; const math = std.math;
const utils = @import("utils.zig"); const utils = @import("utils.zig");
const expect = @import("expect.zig");
const ROC_BUILTINS = "roc_builtins"; const ROC_BUILTINS = "roc_builtins";
const NUM = "num"; const NUM = "num";
@ -141,12 +142,14 @@ comptime {
} }
// Utils // Utils
comptime { comptime {
exportUtilsFn(utils.test_panic, "test_panic"); exportUtilsFn(utils.test_panic, "test_panic");
exportUtilsFn(utils.increfC, "incref"); exportUtilsFn(utils.increfC, "incref");
exportUtilsFn(utils.decrefC, "decref"); exportUtilsFn(utils.decrefC, "decref");
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null"); 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 }); @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); 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 // Custom panic function, as builtin Zig version errors during LLVM verification
pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn { pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn {
const builtin = @import("builtin"); const builtin = @import("builtin");

View file

@ -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 // Signals to the host that the program has panicked
extern fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void; 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 { comptime {
const builtin = @import("builtin"); 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) { if (builtin.is_test) {
@export(testing_roc_alloc, .{ .name = "roc_alloc", .linkage = .Strong }); @export(testing_roc_alloc, .{ .name = "roc_alloc", .linkage = .Strong });
@export(testing_roc_realloc, .{ .name = "roc_realloc", .linkage = .Strong }); @export(testing_roc_realloc, .{ .name = "roc_realloc", .linkage = .Strong });
@export(testing_roc_dealloc, .{ .name = "roc_dealloc", .linkage = .Strong }); @export(testing_roc_dealloc, .{ .name = "roc_dealloc", .linkage = .Strong });
@export(testing_roc_panic, .{ .name = "roc_panic", .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"); @panic("Roc panicked");
} }
pub fn alloc(size: usize, alignment: u32) [*]u8 { fn testing_roc_memcpy(dest: *c_void, src: *c_void, bytes: usize) callconv(.C) ?*c_void {
return @ptrCast([*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment })); 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 { 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 }); 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 // indirection because otherwise zig creates an alias to the panic function which our LLVM code
// does not know how to deal with // does not know how to deal with
pub fn test_panic(c_ptr: *c_void, alignment: u32) callconv(.C) void { pub fn test_panic(c_ptr: *c_void, alignment: u32) callconv(.C) void {
@ -173,7 +189,8 @@ pub fn allocateWithRefcount(
switch (alignment) { switch (alignment) {
16 => { 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); var as_usize_array = @ptrCast([*]usize, new_bytes);
as_usize_array[0] = 0; as_usize_array[0] = 0;
@ -185,7 +202,8 @@ pub fn allocateWithRefcount(
return first_slot; return first_slot;
}, },
8 => { 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 new_bytes: [*]align(8) u8 = @alignCast(8, raw);
var as_isize_array = @ptrCast([*]isize, new_bytes); var as_isize_array = @ptrCast([*]isize, new_bytes);
@ -197,7 +215,8 @@ pub fn allocateWithRefcount(
return first_slot; return first_slot;
}, },
4 => { 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 new_bytes: [*]align(@alignOf(isize)) u8 = @alignCast(@alignOf(isize), raw);
var as_isize_array = @ptrCast([*]isize, new_bytes); 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( pub fn unsafeReallocate(
source_ptr: [*]u8, source_ptr: [*]u8,
alignment: u32, alignment: u32,

View file

@ -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_INCREF: &str = "roc_builtins.utils.incref";
pub const UTILS_DECREF: &str = "roc_builtins.utils.decref"; 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_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";

View file

@ -1,6 +1,7 @@
use crate::def::Def; 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::expr::{Expr, Field, Recursive};
use crate::num::{FloatWidth, IntWidth, NumWidth, NumericBound};
use crate::pattern::Pattern; use crate::pattern::Pattern;
use roc_collections::all::SendMap; use roc_collections::all::SendMap;
use roc_module::called_via::CalledVia; use roc_module::called_via::CalledVia;
@ -793,7 +794,7 @@ fn num_is_zero(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::Eq, op: LowLevel::Eq,
args: vec![ args: vec![
(arg_var, Var(Symbol::ARG_1)), (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, ret_var: bool_var,
}; };
@ -816,7 +817,7 @@ fn num_is_negative(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::NumGt, op: LowLevel::NumGt,
args: vec![ 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)), (arg_var, Var(Symbol::ARG_1)),
], ],
ret_var: bool_var, ret_var: bool_var,
@ -841,7 +842,7 @@ fn num_is_positive(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NumGt, op: LowLevel::NumGt,
args: vec![ args: vec![
(arg_var, Var(Symbol::ARG_1)), (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, ret_var: bool_var,
}; };
@ -866,7 +867,7 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def {
args: vec![ args: vec![
( (
arg_var, arg_var,
int::<i128>(var_store.fresh(), var_store.fresh(), 1), int::<i128>(var_store.fresh(), var_store.fresh(), 1, num_no_bound()),
), ),
( (
arg_var, arg_var,
@ -874,7 +875,7 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NumRemUnchecked, op: LowLevel::NumRemUnchecked,
args: vec![ args: vec![
(arg_var, Var(Symbol::ARG_1)), (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, ret_var: arg_var,
}, },
@ -901,14 +902,14 @@ fn num_is_even(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::Eq, op: LowLevel::Eq,
args: vec![ args: vec![
(arg_var, num(arg_num_var, 0)), (arg_var, num(arg_num_var, 0, num_no_bound())),
( (
arg_var, arg_var,
RunLowLevel { RunLowLevel {
op: LowLevel::NumRemUnchecked, op: LowLevel::NumRemUnchecked,
args: vec![ args: vec![
(arg_var, Var(Symbol::ARG_1)), (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, ret_var: arg_var,
}, },
@ -962,7 +963,10 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NumGte, op: LowLevel::NumGte,
args: vec![ args: vec![
(float_var, Var(Symbol::ARG_1)), (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, ret_var: bool_var,
}), }),
@ -1008,7 +1012,10 @@ fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NumGt, op: LowLevel::NumGt,
args: vec![ args: vec![
(float_var, Var(Symbol::ARG_1)), (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, ret_var: bool_var,
}), }),
@ -1246,92 +1253,182 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// Num.minI8: I8 /// Num.minI8: I8
fn num_min_i8(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_min_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i8>(symbol, var_store, i8::MIN) int_min_or_max::<i8>(
symbol,
var_store,
i8::MIN,
NumericBound::Exact(IntWidth::I8),
)
} }
/// Num.maxI8: I8 /// Num.maxI8: I8
fn num_max_i8(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_max_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i8>(symbol, var_store, i8::MAX) int_min_or_max::<i8>(
symbol,
var_store,
i8::MAX,
NumericBound::Exact(IntWidth::I8),
)
} }
/// Num.minU8: U8 /// Num.minU8: U8
fn num_min_u8(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_min_u8(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u8>(symbol, var_store, u8::MIN) int_min_or_max::<u8>(
symbol,
var_store,
u8::MIN,
NumericBound::Exact(IntWidth::U8),
)
} }
/// Num.maxU8: U8 /// Num.maxU8: U8
fn num_max_u8(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_max_u8(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u8>(symbol, var_store, u8::MAX) int_min_or_max::<u8>(
symbol,
var_store,
u8::MAX,
NumericBound::Exact(IntWidth::U8),
)
} }
/// Num.minI16: I16 /// Num.minI16: I16
fn num_min_i16(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_min_i16(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i16>(symbol, var_store, i16::MIN) int_min_or_max::<i16>(
symbol,
var_store,
i16::MIN,
NumericBound::Exact(IntWidth::I16),
)
} }
/// Num.maxI16: I16 /// Num.maxI16: I16
fn num_max_i16(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_max_i16(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i16>(symbol, var_store, i16::MAX) int_min_or_max::<i16>(
symbol,
var_store,
i16::MAX,
NumericBound::Exact(IntWidth::I16),
)
} }
/// Num.minU16: U16 /// Num.minU16: U16
fn num_min_u16(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_min_u16(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u16>(symbol, var_store, u16::MIN) int_min_or_max::<u16>(
symbol,
var_store,
u16::MIN,
NumericBound::Exact(IntWidth::U16),
)
} }
/// Num.maxU16: U16 /// Num.maxU16: U16
fn num_max_u16(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_max_u16(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u16>(symbol, var_store, u16::MAX) int_min_or_max::<u16>(
symbol,
var_store,
u16::MAX,
NumericBound::Exact(IntWidth::U16),
)
} }
/// Num.minI32: I32 /// Num.minI32: I32
fn num_min_i32(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_min_i32(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i32>(symbol, var_store, i32::MIN) int_min_or_max::<i32>(
symbol,
var_store,
i32::MIN,
NumericBound::Exact(IntWidth::I32),
)
} }
/// Num.maxI32: I32 /// Num.maxI32: I32
fn num_max_i32(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_max_i32(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i32>(symbol, var_store, i32::MAX) int_min_or_max::<i32>(
symbol,
var_store,
i32::MAX,
NumericBound::Exact(IntWidth::I32),
)
} }
/// Num.minU32: U32 /// Num.minU32: U32
fn num_min_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_min_u32(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u32>(symbol, var_store, u32::MIN) int_min_or_max::<u32>(
symbol,
var_store,
u32::MIN,
NumericBound::Exact(IntWidth::U32),
)
} }
/// Num.maxU32: U32 /// Num.maxU32: U32
fn num_max_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_max_u32(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u32>(symbol, var_store, u32::MAX) int_min_or_max::<u32>(
symbol,
var_store,
u32::MAX,
NumericBound::Exact(IntWidth::U32),
)
} }
/// Num.minI64: I64 /// Num.minI64: I64
fn num_min_i64(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_min_i64(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i64>(symbol, var_store, i64::MIN) int_min_or_max::<i64>(
symbol,
var_store,
i64::MIN,
NumericBound::Exact(IntWidth::I64),
)
} }
/// Num.maxI64: I64 /// Num.maxI64: I64
fn num_max_i64(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_max_i64(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i64>(symbol, var_store, i64::MAX) int_min_or_max::<i64>(
symbol,
var_store,
i64::MAX,
NumericBound::Exact(IntWidth::I64),
)
} }
/// Num.minU64: U64 /// Num.minU64: U64
fn num_min_u64(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_min_u64(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u64>(symbol, var_store, u64::MIN) int_min_or_max::<u64>(
symbol,
var_store,
u64::MIN,
NumericBound::Exact(IntWidth::U64),
)
} }
/// Num.maxU64: U64 /// Num.maxU64: U64
fn num_max_u64(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_max_u64(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u64>(symbol, var_store, u64::MAX) int_min_or_max::<u64>(
symbol,
var_store,
u64::MAX,
NumericBound::Exact(IntWidth::U64),
)
} }
/// Num.minI128: I128 /// Num.minI128: I128
fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i128>(symbol, var_store, i128::MIN) int_min_or_max::<i128>(
symbol,
var_store,
i128::MIN,
NumericBound::Exact(IntWidth::I128),
)
} }
/// Num.maxI128: I128 /// Num.maxI128: I128
fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i128>(symbol, var_store, i128::MAX) int_min_or_max::<i128>(
symbol,
var_store,
i128::MAX,
NumericBound::Exact(IntWidth::I128),
)
} }
/// List.isEmpty : List * -> Bool /// List.isEmpty : List * -> Bool
@ -1344,7 +1441,7 @@ fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::Eq, op: LowLevel::Eq,
args: vec![ args: vec![
(len_var, num(unbound_zero_var, 0)), (len_var, num(unbound_zero_var, 0, num_no_bound())),
( (
len_var, len_var,
RunLowLevel { RunLowLevel {
@ -1458,7 +1555,12 @@ fn str_to_num(symbol: Symbol, var_store: &mut VarStore) -> Def {
), ),
( (
errorcode_var, errorcode_var,
int::<i128>(errorcode_var, Variable::UNSIGNED8, 0), int::<i128>(
errorcode_var,
Variable::UNSIGNED8,
0,
NumericBound::Exact(IntWidth::U8),
),
), ),
], ],
ret_var: bool_var, 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 { fn list_take_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
let len_var = var_store.fresh(); let len_var = var_store.fresh();
let zero = int::<i128>(len_var, Variable::NATURAL, 0); let zero = int::<i128>(
len_var,
Variable::NATURAL,
0,
NumericBound::Exact(IntWidth::Nat),
);
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::ListSublist, 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 list_var = var_store.fresh();
let len_var = var_store.fresh(); let len_var = var_store.fresh();
let zero = int::<i128>(len_var, Variable::NATURAL, 0); let zero = int::<i128>(
len_var,
Variable::NATURAL,
0,
NumericBound::Exact(IntWidth::Nat),
);
let bool_var = var_store.fresh(); let bool_var = var_store.fresh();
let get_list_len = RunLowLevel { 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 clos_elem_sym = Symbol::ARG_4;
let int_var = var_store.fresh(); let int_var = var_store.fresh();
let zero = int::<i128>(int_var, Variable::NATURAL, 0); let zero = int::<i128>(
int_var,
Variable::NATURAL,
0,
NumericBound::Exact(IntWidth::Nat),
);
// \acc, elem -> acc |> List.append sep |> List.append elem // \acc, elem -> acc |> List.append sep |> List.append elem
let clos = Closure(ClosureData { 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 clos_ret_var = var_store.fresh();
let ret_var = var_store.fresh(); let ret_var = var_store.fresh();
let zero = int::<i128>(index_var, Variable::NATURAL, 0); let zero = int::<i128>(
index_var,
Variable::NATURAL,
0,
NumericBound::Exact(IntWidth::Nat),
);
let clos = Closure(ClosureData { let clos = Closure(ClosureData {
function_type: clos_fun_var, function_type: clos_fun_var,
@ -2579,7 +2701,10 @@ fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListDropAt, op: LowLevel::ListDropAt,
args: vec![ args: vec![
(list_var, Var(Symbol::ARG_1)), (list_var, Var(Symbol::ARG_1)),
(index_var, int::<i128>(num_var, num_precision_var, 0)), (
index_var,
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
),
], ],
ret_var: list_var, ret_var: list_var,
}; };
@ -2676,7 +2801,10 @@ fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
ret_var: len_var, ret_var: len_var,
}, },
), ),
(arg_var, int::<i128>(num_var, num_precision_var, 1)), (
arg_var,
int::<i128>(num_var, num_precision_var, 1, num_no_bound()),
),
], ],
ret_var: len_var, ret_var: len_var,
}, },
@ -2874,7 +3002,10 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel { RunLowLevel {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(len_var, int::<i128>(num_var, num_precision_var, 0)), (
len_var,
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
),
( (
len_var, len_var,
RunLowLevel { RunLowLevel {
@ -2905,7 +3036,15 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListGetUnsafe, op: LowLevel::ListGetUnsafe,
args: vec![ args: vec![
(list_var, Var(Symbol::ARG_1)), (list_var, Var(Symbol::ARG_1)),
(arg_var, int::<i128>(num_var, num_precision_var, 0)), (
arg_var,
int::<i128>(
num_var,
num_precision_var,
0,
num_no_bound(),
),
),
], ],
ret_var: list_elem_var, ret_var: list_elem_var,
}, },
@ -3004,7 +3143,10 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel { RunLowLevel {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(len_var, int::<i128>(num_var, num_precision_var, 0)), (
len_var,
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
),
( (
len_var, len_var,
RunLowLevel { RunLowLevel {
@ -3035,7 +3177,15 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListGetUnsafe, op: LowLevel::ListGetUnsafe,
args: vec![ args: vec![
(list_var, Var(Symbol::ARG_1)), (list_var, Var(Symbol::ARG_1)),
(arg_var, int::<i128>(num_var, num_precision_var, 0)), (
arg_var,
int::<i128>(
num_var,
num_precision_var,
0,
num_no_bound(),
),
),
], ],
ret_var: list_elem_var, ret_var: list_elem_var,
}, },
@ -3129,7 +3279,10 @@ fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def {
Box::new(function), Box::new(function),
vec![ vec![
(list_var, Loc::at_zero(Var(Symbol::ARG_1))), (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))), (closure_var, Loc::at_zero(Var(Symbol::NUM_ADD))),
], ],
CalledVia::Space, CalledVia::Space,
@ -3161,7 +3314,10 @@ fn list_product(symbol: Symbol, var_store: &mut VarStore) -> Def {
Box::new(function), Box::new(function),
vec![ vec![
(list_var, Loc::at_zero(Var(Symbol::ARG_1))), (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))), (closure_var, Loc::at_zero(Var(Symbol::NUM_MUL))),
], ],
CalledVia::Space, CalledVia::Space,
@ -3815,7 +3971,7 @@ fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(num_var, Var(Symbol::ARG_2)), (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, ret_var: bool_var,
}, },
@ -3918,7 +4074,10 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(num_var, Var(Symbol::ARG_2)), (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, 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, Var(Symbol::ARG_2)),
( (
num_var, num_var,
int::<i128>(unbound_zero_var, unbound_zero_precision_var, 0), int::<i128>(
unbound_zero_var,
unbound_zero_precision_var,
0,
num_no_bound(),
),
), ),
], ],
ret_var: bool_var, 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, Var(Symbol::ARG_2)),
( (
num_var, num_var,
int::<i128>(unbound_zero_var, unbound_zero_precision_var, 0), int::<i128>(
unbound_zero_var,
unbound_zero_precision_var,
0,
num_no_bound(),
),
), ),
], ],
ret_var: bool_var, ret_var: bool_var,
@ -4119,7 +4288,10 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel { RunLowLevel {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(len_var, int::<i128>(zero_var, zero_precision_var, 0)), (
len_var,
int::<i128>(zero_var, zero_precision_var, 0, num_no_bound()),
),
( (
len_var, len_var,
RunLowLevel { RunLowLevel {
@ -4143,7 +4315,10 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListGetUnsafe, op: LowLevel::ListGetUnsafe,
args: vec![ args: vec![
(list_var, Var(Symbol::ARG_1)), (list_var, Var(Symbol::ARG_1)),
(len_var, int::<i128>(zero_var, zero_precision_var, 0)), (
len_var,
int::<i128>(zero_var, zero_precision_var, 0, num_no_bound()),
),
], ],
ret_var: list_elem_var, ret_var: list_elem_var,
}, },
@ -4200,7 +4375,10 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel { RunLowLevel {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(len_var, int::<i128>(num_var, num_precision_var, 0)), (
len_var,
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
),
( (
len_var, len_var,
RunLowLevel { RunLowLevel {
@ -4239,7 +4417,15 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
ret_var: len_var, ret_var: len_var,
}, },
), ),
(arg_var, int::<i128>(num_var, num_precision_var, 1)), (
arg_var,
int::<i128>(
num_var,
num_precision_var,
1,
num_no_bound(),
),
),
], ],
ret_var: len_var, ret_var: len_var,
}, },
@ -4867,7 +5053,10 @@ fn num_bytes_to(symbol: Symbol, var_store: &mut VarStore, offset: i64, low_level
add_var, add_var,
RunLowLevel { RunLowLevel {
ret_var: cast_var, 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, op: LowLevel::NumIntCast,
}, },
), ),
@ -4955,13 +5144,18 @@ fn defn_help(
} }
#[inline(always)] #[inline(always)]
fn int_min_or_max<I128>(symbol: Symbol, var_store: &mut VarStore, i: I128) -> Def fn int_min_or_max<I128>(
symbol: Symbol,
var_store: &mut VarStore,
i: I128,
bound: NumericBound<IntWidth>,
) -> Def
where where
I128: Into<i128>, I128: Into<i128>,
{ {
let int_var = var_store.fresh(); let int_var = var_store.fresh();
let int_precision_var = var_store.fresh(); let int_precision_var = var_store.fresh();
let body = int::<I128>(int_var, int_precision_var, i); let body = int::<I128>(int_var, int_precision_var, i, bound);
let std = roc_builtins::std::types(); let std = roc_builtins::std::types();
let solved = std.get(&symbol).unwrap(); let solved = std.get(&symbol).unwrap();
@ -4984,21 +5178,53 @@ where
} }
} }
fn num_no_bound<W: Copy>() -> NumericBound<W> {
NumericBound::None
}
#[inline(always)] #[inline(always)]
fn int<I128>(num_var: Variable, precision_var: Variable, i: I128) -> Expr fn int<I128>(
num_var: Variable,
precision_var: Variable,
i: I128,
bound: NumericBound<IntWidth>,
) -> Expr
where where
I128: Into<i128>, I128: Into<i128>,
{ {
let ii = i.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)] #[inline(always)]
fn float(num_var: Variable, precision_var: Variable, f: f64) -> Expr { fn float(
Float(num_var, precision_var, f.to_string().into_boxed_str(), f) num_var: Variable,
precision_var: Variable,
f: f64,
bound: NumericBound<FloatWidth>,
) -> Expr {
Float(
num_var,
precision_var,
f.to_string().into_boxed_str(),
f,
bound,
)
} }
#[inline(always)] #[inline(always)]
fn num(num_var: Variable, i: i64) -> Expr { fn num<I: Into<i128>>(num_var: Variable, i: I, bound: NumericBound<NumWidth>) -> Expr {
Num(num_var, i.to_string().into_boxed_str(), i) let i = i.into();
Num(
num_var,
i.to_string().into_boxed_str(),
IntValue::I128(i),
bound,
)
} }

View file

@ -840,9 +840,9 @@ fn pattern_to_vars_by_symbol(
} }
} }
NumLiteral(_, _, _) NumLiteral(..)
| IntLiteral(_, _, _) | IntLiteral(..)
| FloatLiteral(_, _, _) | FloatLiteral(..)
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| MalformedPattern(_, _) | MalformedPattern(_, _)

View file

@ -1,9 +1,9 @@
use roc_can::annotation::IntroducedVariables; use crate::annotation::IntroducedVariables;
use roc_can::def::{Declaration, Def}; use crate::def::{Declaration, Def};
use roc_can::env::Env; use crate::env::Env;
use roc_can::expr::{ClosureData, Expr, Recursive}; use crate::expr::{ClosureData, Expr, Recursive};
use roc_can::pattern::Pattern; use crate::pattern::Pattern;
use roc_can::scope::Scope; use crate::scope::Scope;
use roc_collections::all::{MutSet, SendMap}; use roc_collections::all::{MutSet, SendMap};
use roc_module::called_via::CalledVia; use roc_module::called_via::CalledVia;
use roc_module::ident::TagName; 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, signature,
introduced_variables, introduced_variables,
aliases: SendMap::default(), 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, signature,
introduced_variables, introduced_variables,
aliases: SendMap::default(), 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, signature,
introduced_variables, introduced_variables,
aliases: SendMap::default(), 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, signature,
introduced_variables, introduced_variables,
aliases: SendMap::default(), 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, signature,
introduced_variables, introduced_variables,
aliases: SendMap::default(), 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); 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)], patterns: vec![Loc::at_zero(step_pattern)],
value: Loc::at_zero(force_thunk2), value: Loc::at_zero(force_thunk2),
guard: None, guard: None,
@ -1345,7 +1345,7 @@ fn build_effect_loop_inner_body(
let done_tag_name = TagName::Global("Done".into()); let done_tag_name = TagName::Global("Done".into());
let done_pattern = applied_tag_pattern(done_tag_name, &[done_symbol], var_store); 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)], patterns: vec![Loc::at_zero(done_pattern)],
value: Loc::at_zero(Expr::Var(done_symbol)), value: Loc::at_zero(Expr::Var(done_symbol)),
guard: None, guard: None,
@ -1376,7 +1376,7 @@ pub fn build_host_exposed_def(
ident: &str, ident: &str,
effect_tag_name: TagName, effect_tag_name: TagName,
var_store: &mut VarStore, var_store: &mut VarStore,
annotation: roc_can::annotation::Annotation, annotation: crate::annotation::Annotation,
) -> Def { ) -> Def {
let expr_var = var_store.fresh(); let expr_var = var_store.fresh();
let pattern = Pattern::Identifier(symbol); 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, signature: annotation.typ,
introduced_variables: annotation.introduced_variables, introduced_variables: annotation.introduced_variables,
aliases: annotation.aliases, aliases: annotation.aliases,

View file

@ -3,8 +3,8 @@ use crate::builtins::builtin_defs_map;
use crate::def::{can_defs_with_return, Def}; use crate::def::{can_defs_with_return, Def};
use crate::env::Env; use crate::env::Env;
use crate::num::{ use crate::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_int, float_expr_from_result, finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result,
int_expr_from_result, num_expr_from_result, int_expr_from_result, num_expr_from_result, FloatWidth, IntWidth, NumWidth, NumericBound,
}; };
use crate::pattern::{canonicalize_pattern, Pattern}; use crate::pattern::{canonicalize_pattern, Pattern};
use crate::procedure::References; use crate::procedure::References;
@ -20,7 +20,7 @@ use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::Alias; use roc_types::types::Alias;
use std::fmt::Debug; use std::fmt::{Debug, Display};
use std::{char, u32}; use std::{char, u32};
#[derive(Clone, Default, Debug, PartialEq)] #[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)] #[derive(Clone, Debug, PartialEq)]
pub enum Expr { pub enum Expr {
// Literals // Literals
// Num stores the `a` variable in `Num a`. Not the same as the variable // 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 // stored in Int and Float below, which is strictly for better error messages
Num(Variable, Box<str>, i64), Num(Variable, Box<str>, IntValue, NumericBound<NumWidth>),
// Int and Float store a variable to generate better error messages // Int and Float store a variable to generate better error messages
Int(Variable, Variable, Box<str>, i128), Int(
Float(Variable, Variable, Box<str>, f64), Variable,
Variable,
Box<str>,
IntValue,
NumericBound<IntWidth>,
),
Float(Variable, Variable, Box<str>, f64, NumericBound<FloatWidth>),
Str(Box<str>), Str(Box<str>),
List { List {
elem_var: Variable, elem_var: Variable,
@ -208,20 +229,20 @@ pub fn canonicalize_expr<'a>(
use Expr::*; use Expr::*;
let (expr, output) = match expr { let (expr, output) = match expr {
ast::Expr::Num(str) => { &ast::Expr::Num(str) => {
let answer = num_expr_from_result( let answer = num_expr_from_result(
var_store, var_store,
finish_parsing_int(*str).map(|int| (*str, int)), finish_parsing_num(str).map(|result| (str, result)),
region, region,
env, env,
); );
(answer, Output::default()) (answer, Output::default())
} }
ast::Expr::Float(str) => { &ast::Expr::Float(str) => {
let answer = float_expr_from_result( let answer = float_expr_from_result(
var_store, var_store,
finish_parsing_float(str).map(|f| (*str, f)), finish_parsing_float(str).map(|(f, bound)| (str, f, bound)),
region, region,
env, env,
); );
@ -790,21 +811,21 @@ pub fn canonicalize_expr<'a>(
(RuntimeError(problem), Output::default()) (RuntimeError(problem), Output::default())
} }
ast::Expr::NonBase10Int { &ast::Expr::NonBase10Int {
string, string,
base, base,
is_negative, is_negative,
} => { } => {
// the minus sign is added before parsing, to get correct overflow/underflow behavior // the minus sign is added before parsing, to get correct overflow/underflow behavior
let answer = match finish_parsing_base(string, *base, *is_negative) { let answer = match finish_parsing_base(string, base, is_negative) {
Ok(int) => { Ok((int, bound)) => {
// Done in this kinda round about way with intermediate variables // Done in this kinda round about way with intermediate variables
// to keep borrowed values around and make this compile // to keep borrowed values around and make this compile
let int_string = int.to_string(); let int_string = int.to_string();
let int_str = int_string.as_str(); 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()) (answer, Output::default())
@ -1226,9 +1247,9 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
match expr { match expr {
// Num stores the `a` variable in `Num a`. Not the same as the variable // 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 // stored in Int and Float below, which is strictly for better error messages
other @ Num(_, _, _) other @ Num(..)
| other @ Int(_, _, _, _) | other @ Int(..)
| other @ Float(_, _, _, _) | other @ Float(..)
| other @ Str { .. } | other @ Str { .. }
| other @ RuntimeError(_) | other @ RuntimeError(_)
| other @ EmptyRecord | other @ EmptyRecord

View file

@ -5,6 +5,7 @@ pub mod annotation;
pub mod builtins; pub mod builtins;
pub mod constraint; pub mod constraint;
pub mod def; pub mod def;
pub mod effect_module;
pub mod env; pub mod env;
pub mod expected; pub mod expected;
pub mod expr; pub mod expr;

View file

@ -6,15 +6,16 @@ use crate::pattern::Pattern;
use crate::scope::Scope; use crate::scope::Scope;
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::all::{MutMap, MutSet, SendMap}; use roc_collections::all::{MutMap, MutSet, SendMap};
use roc_module::ident::Ident;
use roc_module::ident::Lowercase; use roc_module::ident::Lowercase;
use roc_module::ident::{Ident, TagName};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_parse::ast; use roc_parse::ast;
use roc_parse::header::HeaderFor;
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::Alias; use roc_types::types::{Alias, Type};
#[derive(Debug)] #[derive(Debug)]
pub struct Module { pub struct Module {
@ -44,6 +45,7 @@ pub struct ModuleOutput {
pub fn canonicalize_module_defs<'a, F>( pub fn canonicalize_module_defs<'a, F>(
arena: &Bump, arena: &Bump,
loc_defs: &'a [Loc<ast::Def<'a>>], loc_defs: &'a [Loc<ast::Def<'a>>],
header_for: &roc_parse::header::HeaderFor,
home: ModuleId, home: ModuleId,
module_ids: &ModuleIds, module_ids: &ModuleIds,
exposed_ident_ids: IdentIds, exposed_ident_ids: IdentIds,
@ -59,12 +61,49 @@ where
{ {
let mut can_exposed_imports = MutMap::default(); let mut can_exposed_imports = MutMap::default();
let mut scope = Scope::new(home, var_store); 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(); let num_deps = dep_idents.len();
for (name, alias) in aliases.into_iter() { for (name, alias) in aliases.into_iter() {
scope.add_alias(name, alias.region, alias.type_variables, alias.typ); 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 // Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization. // 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 lookups = Vec::with_capacity(num_deps);
let mut rigid_variables = MutMap::default(); 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, &mut env,
Output::default(), Output::default(),
var_store, var_store,
@ -208,7 +246,21 @@ where
(Ok(mut declarations), output) => { (Ok(mut declarations), output) => {
use crate::def::Declaration::*; 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 { match decl {
Declare(def) => { Declare(def) => {
for (symbol, _) in def.pattern_vars.iter() { for (symbol, _) in def.pattern_vars.iter() {
@ -221,6 +273,59 @@ where
exposed_but_not_defined.remove(symbol); 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) => { DeclareRec(defs) => {
for def in defs { for def in defs {
@ -253,6 +358,18 @@ where
let mut aliases = MutMap::default(); 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 { for (symbol, alias) in output.aliases {
// Remove this from exposed_symbols, // Remove this from exposed_symbols,
// so that at the end of the process, // so that at the end of the process,
@ -397,9 +514,9 @@ fn fix_values_captured_in_closure_pattern(
} }
} }
Identifier(_) Identifier(_)
| NumLiteral(_, _, _) | NumLiteral(..)
| IntLiteral(_, _, _) | IntLiteral(..)
| FloatLiteral(_, _, _) | FloatLiteral(..)
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| Shadowed(..) | 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); fix_values_captured_in_closure_expr(&mut loc_body.value, no_capture_symbols);
} }
Num(_, _, _) Num(..)
| Int(_, _, _, _) | Int(..)
| Float(_, _, _, _) | Float(..)
| Str(_) | Str(_)
| Var(_) | Var(_)
| EmptyRecord | EmptyRecord

View file

@ -1,5 +1,5 @@
use crate::env::Env; use crate::env::Env;
use crate::expr::Expr; use crate::expr::{Expr, IntValue};
use roc_parse::ast::Base; use roc_parse::ast::Base;
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_problem::can::RuntimeError::*; use roc_problem::can::RuntimeError::*;
@ -7,21 +7,33 @@ use roc_problem::can::{FloatErrorKind, IntErrorKind};
use roc_region::all::Region; use roc_region::all::Region;
use roc_types::subs::VarStore; use roc_types::subs::VarStore;
use std::i64; use std::i64;
use std::str;
// TODO use rust's integer parsing again
//
// We're waiting for libcore here, see https://github.com/rust-lang/rust/issues/22639
// There is a nightly API for exposing the parse error.
#[inline(always)] #[inline(always)]
pub fn num_expr_from_result( pub fn num_expr_from_result(
var_store: &mut VarStore, var_store: &mut VarStore,
result: Result<(&str, i64), (&str, IntErrorKind)>, result: Result<(&str, ParsedNumResult), (&str, IntErrorKind)>,
region: Region, region: Region,
env: &mut Env, env: &mut Env,
) -> Expr { ) -> Expr {
match result { 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)) => { Err((raw, error)) => {
// (Num *) compiles to Int if it doesn't // (Num *) compiles to Int if it doesn't
// get specialized to something else first, // get specialized to something else first,
@ -38,14 +50,20 @@ pub fn num_expr_from_result(
#[inline(always)] #[inline(always)]
pub fn int_expr_from_result( pub fn int_expr_from_result(
var_store: &mut VarStore, var_store: &mut VarStore,
result: Result<(&str, i128), (&str, IntErrorKind)>, result: Result<(&str, IntValue, NumericBound<IntWidth>), (&str, IntErrorKind)>,
region: Region, region: Region,
base: Base, base: Base,
env: &mut Env, env: &mut Env,
) -> Expr { ) -> Expr {
// Int stores a variable to generate better error messages // Int stores a variable to generate better error messages
match result { 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)) => { Err((raw, error)) => {
let runtime_error = InvalidInt(error, base, region, raw.into()); let runtime_error = InvalidInt(error, base, region, raw.into());
@ -59,13 +77,19 @@ pub fn int_expr_from_result(
#[inline(always)] #[inline(always)]
pub fn float_expr_from_result( pub fn float_expr_from_result(
var_store: &mut VarStore, var_store: &mut VarStore,
result: Result<(&str, f64), (&str, FloatErrorKind)>, result: Result<(&str, f64, NumericBound<FloatWidth>), (&str, FloatErrorKind)>,
region: Region, region: Region,
env: &mut Env, env: &mut Env,
) -> Expr { ) -> Expr {
// Float stores a variable to generate better error messages // Float stores a variable to generate better error messages
match result { 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)) => { Err((raw, error)) => {
let runtime_error = InvalidFloat(error, region, raw.into()); let runtime_error = InvalidFloat(error, region, raw.into());
@ -76,11 +100,32 @@ pub fn float_expr_from_result(
} }
} }
pub enum ParsedNumResult {
Int(IntValue, NumericBound<IntWidth>),
Float(f64, NumericBound<FloatWidth>),
UnknownNum(IntValue),
}
#[inline(always)] #[inline(always)]
pub fn finish_parsing_int(raw: &str) -> Result<i64, (&str, IntErrorKind)> { pub fn finish_parsing_num(raw: &str) -> Result<ParsedNumResult, (&str, IntErrorKind)> {
// Ignore underscores. // Ignore underscores.
let radix = 10; let radix = 10;
from_str_radix::<i64>(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)] #[inline(always)]
@ -88,7 +133,7 @@ pub fn finish_parsing_base(
raw: &str, raw: &str,
base: Base, base: Base,
is_negative: bool, is_negative: bool,
) -> Result<i64, (&str, IntErrorKind)> { ) -> Result<(IntValue, NumericBound<IntWidth>), (&str, IntErrorKind)> {
let radix = match base { let radix = match base {
Base::Hex => 16, Base::Hex => 16,
Base::Decimal => 10, Base::Decimal => 10,
@ -98,18 +143,36 @@ pub fn finish_parsing_base(
// Ignore underscores, insert - when negative to get correct underflow/overflow behavior // Ignore underscores, insert - when negative to get correct underflow/overflow behavior
(if is_negative { (if is_negative {
from_str_radix::<i64>(format!("-{}", raw.replace("_", "")).as_str(), radix) from_str_radix(format!("-{}", raw.replace("_", "")).as_str(), radix)
} else { } else {
from_str_radix::<i64>(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)] #[inline(always)]
pub fn finish_parsing_float(raw: &str) -> Result<f64, (&str, FloatErrorKind)> { pub fn finish_parsing_float(
raw: &str,
) -> Result<(f64, NumericBound<FloatWidth>), (&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. // Ignore underscores.
match raw.replace("_", "").parse::<f64>() { match raw_without_suffix.replace("_", "").parse::<f64>() {
Ok(float) if float.is_finite() => Ok(float), Ok(float) if float.is_finite() => Ok((float, bound)),
Ok(float) => { Ok(float) => {
if float.is_sign_positive() { if float.is_sign_positive() {
Err((raw, FloatErrorKind::PositiveInfinity)) Err((raw, FloatErrorKind::PositiveInfinity))
@ -121,6 +184,35 @@ pub fn finish_parsing_float(raw: &str) -> Result<f64, (&str, FloatErrorKind)> {
} }
} }
fn parse_literal_suffix(num_str: &str) -> (Option<NumWidth>, &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, /// Integer parsing code taken from the rust libcore,
/// pulled in so we can give custom error messages /// pulled in so we can give custom error messages
/// ///
@ -129,44 +221,11 @@ pub fn finish_parsing_float(raw: &str) -> Result<f64, (&str, FloatErrorKind)> {
/// the LEGAL_DETAILS file in the root directory of this distribution. /// the LEGAL_DETAILS file in the root directory of this distribution.
/// ///
/// Thanks to the Rust project and its contributors! /// Thanks to the Rust project and its contributors!
trait FromStrRadixHelper: PartialOrd + Copy { fn from_str_radix(
fn min_value() -> Self; src: &str,
fn max_value() -> Self; radix: u32,
fn from_u32(u: u32) -> Self; ) -> Result<(IntValue, NumericBound<NumWidth>), IntErrorKind> {
fn checked_mul(&self, other: u32) -> Option<Self>;
fn checked_sub(&self, other: u32) -> Option<Self>;
fn checked_add(&self, other: u32) -> Option<Self>;
}
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> {
Self::checked_mul(*self, other as Self)
}
#[inline]
fn checked_sub(&self, other: u32) -> Option<Self> {
Self::checked_sub(*self, other as Self)
}
#[inline]
fn checked_add(&self, other: u32) -> Option<Self> {
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<T: FromStrRadixHelper>(src: &str, radix: u32) -> Result<T, ParseIntError> {
use self::IntErrorKind::*; use self::IntErrorKind::*;
use self::ParseIntError as PIE;
assert!( assert!(
(2..=36).contains(&radix), (2..=36).contains(&radix),
@ -174,86 +233,260 @@ fn from_str_radix<T: FromStrRadixHelper>(src: &str, radix: u32) -> Result<T, Par
radix radix
); );
if src.is_empty() { let (opt_exact_bound, src) = parse_literal_suffix(src);
return Err(PIE { kind: Empty });
}
let is_signed_ty = T::from_u32(0) > T::min_value(); use std::num::IntErrorKind as StdIEK;
let result = match i128::from_str_radix(src, radix) {
// all valid digits are ascii, so we will just iterate over the utf8 bytes Ok(result) => IntValue::I128(result),
// and cast them to chars. .to_digit() will safely return None for anything Err(pie) => match pie.kind() {
// other than a valid ascii digit for the given radix, including the first-byte StdIEK::Empty => return Err(IntErrorKind::Empty),
// of multi-byte sequences StdIEK::InvalidDigit => return Err(IntErrorKind::InvalidDigit),
let src = src.as_bytes(); StdIEK::NegOverflow => return Err(IntErrorKind::Underflow),
StdIEK::PosOverflow => {
let (is_positive, digits) = match src[0] { // try a u128
b'+' => (true, &src[1..]), match u128::from_str_radix(src, radix) {
b'-' if is_signed_ty => (false, &src[1..]), Ok(result) => IntValue::U128(result),
_ => (true, src), 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() { let (lower_bound, is_negative) = match result {
return Err(PIE { kind: Empty }); IntValue::I128(num) => (lower_bound_of_int(num), num <= 0),
} IntValue::U128(_) => (IntWidth::U128, false),
};
let mut result = T::from_u32(0); match opt_exact_bound {
if is_positive { None => {
// The number is positive // TODO: use the lower bound
for &c in digits { Ok((result, NumericBound::None))
let x = match (c as char).to_digit(radix) { }
Some(x) => x, Some(bound @ NumWidth::Float(_)) => {
None => return Err(PIE { kind: InvalidDigit }), // For now, assume floats can represent all integers
}; // TODO: this is somewhat incorrect, revisit
result = match result.checked_mul(radix) { Ok((result, NumericBound::Exact(bound)))
Some(result) => result, }
None => return Err(PIE { kind: Overflow }), Some(NumWidth::Int(exact_width)) => {
}; // We need to check if the exact bound >= lower bound.
result = match result.checked_add(x) { if exact_width.is_superset(&lower_bound, is_negative) {
Some(result) => result, // Great! Use the exact bound.
None => return Err(PIE { kind: Overflow }), 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 { } else {
// The number is negative // Negative
for &c in digits { if result < I64.min_value() {
let x = match (c as char).to_digit(radix) { I128
Some(x) => x, } else if result < I32.min_value() {
None => return Err(PIE { kind: InvalidDigit }), I64
}; } else if result < I16.min_value() {
result = match result.checked_mul(radix) { I32
Some(result) => result, } else if result < I8.min_value() {
None => return Err(PIE { kind: Underflow }), I16
}; } else {
result = match result.checked_sub(x) { I8
Some(result) => result,
None => return Err(PIE { kind: Underflow }),
};
} }
} }
Ok(result)
} }
/// An error which can be returned when parsing an integer. #[derive(Clone, Copy, PartialEq, Eq, Debug)]
/// enum IntSign {
/// This error is used as the error type for the `from_str_radix()` functions Unsigned,
/// on the primitive integer types, such as [`i8::from_str_radix`]. Signed,
///
/// # Potential causes
///
/// Among other causes, `ParseIntError` can be thrown because of leading or trailing whitespace
/// in the string e.g., when it is obtained from the standard input.
/// Using the [`str.trim()`] method ensures that no whitespace remains before parsing.
///
/// [`str.trim()`]: ../../std/primitive.str.html#method.trim
/// [`i8::from_str_radix`]: ../../std/primitive.i8.html#method.from_str_radix
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseIntError {
kind: IntErrorKind,
} }
impl ParseIntError { #[derive(Clone, Copy, PartialEq, Eq, Debug)]
/// Outputs the detailed cause of parsing an integer failing. pub enum IntWidth {
pub fn kind(&self) -> &IntErrorKind { U8,
&self.kind 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<W>
where
W: Copy,
{
/// There is no bound on the width.
None,
/// Must have exactly the width `W`.
Exact(W),
}

View file

@ -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. /// 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<Expr<'a>>) -> &'a Loc<Expr<'a>> { pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc<Expr<'a>> {
match &loc_expr.value { match &loc_expr.value {
Float(_) Float(..)
| Num(_) | Num(..)
| NonBase10Int { .. } | NonBase10Int { .. }
| Str(_) | Str(_)
| AccessorFunction(_) | AccessorFunction(_)

View file

@ -1,6 +1,9 @@
use crate::env::Env; use crate::env::Env;
use crate::expr::{canonicalize_expr, unescape_char, Expr, Output}; use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output};
use crate::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use crate::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatWidth, IntWidth, NumWidth,
NumericBound, ParsedNumResult,
};
use crate::scope::Scope; use crate::scope::Scope;
use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -9,6 +12,7 @@ use roc_parse::pattern::PatternType;
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
/// A pattern, including possible problems (e.g. shadowing) so that /// A pattern, including possible problems (e.g. shadowing) so that
/// codegen can generate a runtime error if this pattern is reached. /// codegen can generate a runtime error if this pattern is reached.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -25,9 +29,15 @@ pub enum Pattern {
ext_var: Variable, ext_var: Variable,
destructs: Vec<Loc<RecordDestruct>>, destructs: Vec<Loc<RecordDestruct>>,
}, },
IntLiteral(Variable, Box<str>, i64), NumLiteral(Variable, Box<str>, IntValue, NumericBound<NumWidth>),
NumLiteral(Variable, Box<str>, i64), IntLiteral(
FloatLiteral(Variable, Box<str>, f64), Variable,
Variable,
Box<str>,
IntValue,
NumericBound<IntWidth>,
),
FloatLiteral(Variable, Variable, Box<str>, f64, NumericBound<FloatWidth>),
StrLiteral(Box<str>), StrLiteral(Box<str>),
Underscore, Underscore,
@ -85,9 +95,9 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
} }
} }
NumLiteral(_, _, _) NumLiteral(..)
| IntLiteral(_, _, _) | IntLiteral(..)
| FloatLiteral(_, _, _) | FloatLiteral(..)
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| MalformedPattern(_, _) | 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) { WhenBranch => match finish_parsing_float(str) {
Err(_error) => { Err(_error) => {
let problem = MalformedPatternProblem::MalformedFloat; let problem = MalformedPatternProblem::MalformedFloat;
malformed_pattern(env, problem, region) 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), ptype => unsupported_pattern(env, ptype, region),
}, },
@ -200,32 +216,58 @@ pub fn canonicalize_pattern<'a>(
TopLevelDef | DefExpr => bad_underscore(env, region), TopLevelDef | DefExpr => bad_underscore(env, region),
}, },
NumLiteral(str) => match pattern_type { &NumLiteral(str) => match pattern_type {
WhenBranch => match finish_parsing_int(str) { WhenBranch => match finish_parsing_num(str) {
Err(_error) => { Err(_error) => {
let problem = MalformedPatternProblem::MalformedInt; let problem = MalformedPatternProblem::MalformedInt;
malformed_pattern(env, problem, region) 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), ptype => unsupported_pattern(env, ptype, region),
}, },
NonBase10Literal { &NonBase10Literal {
string, string,
base, base,
is_negative, is_negative,
} => match pattern_type { } => match pattern_type {
WhenBranch => match finish_parsing_base(string, *base, *is_negative) { WhenBranch => match finish_parsing_base(string, base, is_negative) {
Err(_error) => { Err(_error) => {
let problem = MalformedPatternProblem::MalformedBase(*base); let problem = MalformedPatternProblem::MalformedBase(base);
malformed_pattern(env, problem, region) malformed_pattern(env, problem, region)
} }
Ok(int) => { Ok((IntValue::U128(_), _)) if is_negative => {
let sign_str = if *is_negative { "-" } else { "" }; // 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 int_str = format!("{}{}", sign_str, int.to_string()).into_boxed_str();
let i = if *is_negative { -int } else { int }; let i = match int {
Pattern::IntLiteral(var_store.fresh(), int_str, i) // 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), ptype => unsupported_pattern(env, ptype, region),
@ -473,9 +515,9 @@ fn add_bindings_from_patterns(
answer.push((*symbol, *region)); answer.push((*symbol, *region));
} }
} }
NumLiteral(_, _, _) NumLiteral(..)
| IntLiteral(_, _, _) | IntLiteral(..)
| FloatLiteral(_, _, _) | FloatLiteral(..)
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| MalformedPattern(_, _) | MalformedPattern(_, _)

View file

@ -15,7 +15,7 @@ mod test_can {
use crate::helpers::{can_expr_with, test_home, CanExprOut}; use crate::helpers::{can_expr_with, test_home, CanExprOut};
use bumpalo::Bump; use bumpalo::Bump;
use roc_can::expr::Expr::{self, *}; 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_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError};
use roc_region::all::{Position, Region}; use roc_region::all::{Position, Region};
use std::{f64, i64}; use std::{f64, i64};
@ -32,7 +32,7 @@ mod test_can {
let actual_out = can_expr_with(&arena, test_home(), input); let actual_out = can_expr_with(&arena, test_home(), input);
match actual_out.loc_expr.value { match actual_out.loc_expr.value {
Expr::Float(_, _, _, actual) => { Expr::Float(_, _, _, actual, _) => {
assert_eq!(expected, actual); assert_eq!(expected, actual);
} }
actual => { actual => {
@ -46,8 +46,8 @@ mod test_can {
let actual_out = can_expr_with(&arena, test_home(), input); let actual_out = can_expr_with(&arena, test_home(), input);
match actual_out.loc_expr.value { match actual_out.loc_expr.value {
Expr::Int(_, _, _, actual) => { Expr::Int(_, _, _, actual, _) => {
assert_eq!(expected, actual); assert_eq!(IntValue::I128(expected), actual);
} }
actual => { actual => {
panic!("Expected an Int *, but got: {:?}", 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 arena = Bump::new();
let actual_out = can_expr_with(&arena, test_home(), input); let actual_out = can_expr_with(&arena, test_home(), input);
match actual_out.loc_expr.value { match actual_out.loc_expr.value {
Expr::Num(_, _, actual) => { Expr::Num(_, _, actual, _) => {
assert_eq!(expected, actual); assert_eq!(IntValue::I128(expected), actual);
} }
actual => { actual => {
panic!("Expected a Num, but got: {:?}", actual); panic!("Expected a Num, but got: {:?}", actual);
@ -79,7 +79,7 @@ mod test_can {
fn int_too_large() { fn int_too_large() {
use roc_parse::ast::Base; 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( assert_can(
&string.clone(), &string.clone(),
@ -96,7 +96,7 @@ mod test_can {
fn int_too_small() { fn int_too_small() {
use roc_parse::ast::Base; 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( assert_can(
&string.clone(), &string.clone(),
@ -186,12 +186,12 @@ mod test_can {
#[test] #[test]
fn num_max() { fn num_max() {
assert_can_num(&(i64::MAX.to_string()), i64::MAX); assert_can_num(&(i64::MAX.to_string()), i64::MAX.into());
} }
#[test] #[test]
fn num_min() { fn num_min() {
assert_can_num(&(i64::MIN.to_string()), i64::MIN); assert_can_num(&(i64::MIN.to_string()), i64::MIN.into());
} }
#[test] #[test]

View file

@ -1,6 +1,7 @@
use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::Constraint::{self, *};
use roc_can::constraint::LetConstraint; use roc_can::constraint::LetConstraint;
use roc_can::expected::Expected::{self, *}; use roc_can::expected::Expected::{self, *};
use roc_can::num::{FloatWidth, IntWidth, NumWidth, NumericBound};
use roc_collections::all::SendMap; use roc_collections::all::SendMap;
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -10,28 +11,49 @@ use roc_types::types::Category;
use roc_types::types::Reason; use roc_types::types::Reason;
use roc_types::types::Type::{self, *}; use roc_types::types::Type::{self, *};
pub fn add_numeric_bound_constr(
constrs: &mut Vec<Constraint>,
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)] #[inline(always)]
pub fn int_literal( pub fn int_literal(
num_var: Variable, num_var: Variable,
precision_var: Variable, precision_var: Variable,
expected: Expected<Type>, expected: Expected<Type>,
region: Region, region: Region,
bound: NumericBound<IntWidth>,
) -> Constraint { ) -> Constraint {
let num_type = Variable(num_var); let num_type = Variable(num_var);
let reason = Reason::IntLiteral; let reason = Reason::IntLiteral;
exists( let mut constrs = Vec::with_capacity(3);
vec![num_var], // Always add the bound first; this improves the resolved type quality in case it's an alias
And(vec![ // like "U8".
Eq( add_numeric_bound_constr(&mut constrs, num_type.clone(), bound, region, Category::Num);
num_type.clone(), constrs.extend(vec![
ForReason(reason, num_int(Type::Variable(precision_var)), region), Eq(
Category::Int, num_type.clone(),
region, ForReason(reason, num_int(Type::Variable(precision_var)), region),
), Category::Int,
Eq(num_type, expected, Category::Int, region), region,
]), ),
) Eq(num_type, expected, Category::Int, region),
]);
exists(vec![num_var], And(constrs))
} }
#[inline(always)] #[inline(always)]
@ -40,22 +62,46 @@ pub fn float_literal(
precision_var: Variable, precision_var: Variable,
expected: Expected<Type>, expected: Expected<Type>,
region: Region, region: Region,
bound: NumericBound<FloatWidth>,
) -> Constraint { ) -> Constraint {
let num_type = Variable(num_var); let num_type = Variable(num_var);
let reason = Reason::FloatLiteral; let reason = Reason::FloatLiteral;
exists( let mut constrs = Vec::with_capacity(3);
vec![num_var, precision_var], add_numeric_bound_constr(
And(vec![ &mut constrs,
Eq( num_type.clone(),
num_type.clone(), bound,
ForReason(reason, num_float(Type::Variable(precision_var)), region), region,
Category::Float, Category::Float,
region, );
), constrs.extend(vec![
Eq(num_type, expected, Category::Float, region), 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<Type>,
region: Region,
bound: NumericBound<NumWidth>,
) -> 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)] #[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)] #[inline(always)]
pub fn num_signed64() -> Type { pub fn num_signed64() -> Type {
let alias_content = Type::TagUnion( let alias_content = Type::TagUnion(
@ -188,3 +285,55 @@ pub fn num_num(typ: Type) -> Type {
Box::new(alias_content), 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<Type>;
}
impl TypedNumericBound for NumericBound<IntWidth> {
fn concrete_num_type(&self) -> Option<Type> {
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<FloatWidth> {
fn concrete_num_type(&self) -> Option<Type> {
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<NumWidth> {
fn concrete_num_type(&self) -> Option<Type> {
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()
}
}
}
}

View file

@ -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 crate::pattern::{constrain_pattern, PatternState};
use roc_can::annotation::IntroducedVariables; use roc_can::annotation::IntroducedVariables;
use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::Constraint::{self, *};
@ -96,17 +98,11 @@ pub fn constrain_expr(
expected: Expected<Type>, expected: Expected<Type>,
) -> Constraint { ) -> Constraint {
match expr { match expr {
Int(var, precision, _, _) => int_literal(*var, *precision, expected, region), &Int(var, precision, _, _, bound) => int_literal(var, precision, expected, region, bound),
Num(var, _, _) => exists( &Num(var, _, _, bound) => num_literal(var, expected, region, bound),
vec![*var], &Float(var, precision, _, _, bound) => {
Eq( float_literal(var, precision, expected, region, bound)
crate::builtins::num_num(Type::Variable(*var)), }
expected,
Category::Num,
region,
),
),
Float(var, precision, _, _) => float_literal(*var, *precision, expected, region),
EmptyRecord => constrain_empty_record(region, expected), EmptyRecord => constrain_empty_record(region, expected),
Expr::Record { record_var, fields } => { Expr::Record { record_var, fields } => {
if fields.is_empty() { if fields.is_empty() {

View file

@ -55,9 +55,9 @@ fn headers_from_annotation_help(
Underscore Underscore
| MalformedPattern(_, _) | MalformedPattern(_, _)
| UnsupportedPattern(_) | UnsupportedPattern(_)
| NumLiteral(_, _, _) | NumLiteral(..)
| IntLiteral(_, _, _) | IntLiteral(..)
| FloatLiteral(_, _, _) | FloatLiteral(..)
| StrLiteral(_) => true, | StrLiteral(_) => true,
RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() { RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() {
@ -178,31 +178,83 @@ pub fn constrain_pattern(
); );
} }
NumLiteral(var, _, _) => { &NumLiteral(var, _, _, bound) => {
state.vars.push(*var); 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( state.constraints.push(Constraint::Pattern(
region, region,
PatternCategory::Num, PatternCategory::Num,
builtins::num_num(Type::Variable(*var)), num_type,
expected, 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( state.constraints.push(Constraint::Pattern(
region, region,
PatternCategory::Int, PatternCategory::Int,
builtins::num_int(Type::Variable(*precision_var)), Type::Variable(num_var),
expected, 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( state.constraints.push(Constraint::Pattern(
region, region,
PatternCategory::Float, PatternCategory::Float,
builtins::num_float(Type::Variable(*precision_var)), Type::Variable(num_var),
expected, expected,
)); ));
} }

View file

@ -46,7 +46,9 @@ impl<'a> Formattable for Def<'a> {
indent + INDENT, indent + INDENT,
); );
} else { } else {
buf.push_str(" : "); buf.spaces(1);
buf.push_str(":");
buf.spaces(1);
loc_annotation.format_with_options( loc_annotation.format_with_options(
buf, buf,
Parens::NotNeeded, Parens::NotNeeded,

View file

@ -27,8 +27,8 @@ impl<'a> Formattable for Expr<'a> {
} }
// These expressions never have newlines // These expressions never have newlines
Float(_) Float(..)
| Num(_) | Num(..)
| NonBase10Int { .. } | NonBase10Int { .. }
| Access(_, _) | Access(_, _)
| AccessorFunction(_) | AccessorFunction(_)
@ -196,17 +196,25 @@ impl<'a> Formattable for Expr<'a> {
buf.push(')'); 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.indent(indent);
buf.push_str(string) buf.push_str(string)
} }
NonBase10Int { &NonBase10Int {
base, base,
string, string,
is_negative, is_negative,
} => { } => {
buf.indent(indent); buf.indent(indent);
if *is_negative { if is_negative {
buf.push('-'); buf.push('-');
} }

View file

@ -5,8 +5,8 @@ use crate::spaces::{fmt_default_spaces, fmt_spaces, INDENT};
use crate::Buf; use crate::Buf;
use roc_parse::ast::{Collection, Module, Spaced}; use roc_parse::ast::{Collection, Module, Spaced};
use roc_parse::header::{ use roc_parse::header::{
AppHeader, Effects, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageEntry, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
}; };
use roc_parse::ident::UppercaseIdent; use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc; 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"); buf.push_str("provides");
fmt_default_spaces(buf, header.after_provides, indent); fmt_default_spaces(buf, header.after_provides, indent);
fmt_provides(buf, header.provides, None, 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) { 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(" }"); 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> { impl<'a> Formattable for TypedIdent<'a> {
fn is_multiline(&self) -> bool { fn is_multiline(&self) -> bool {
false false

View file

@ -31,9 +31,9 @@ impl<'a> Formattable for Pattern<'a> {
| Pattern::GlobalTag(_) | Pattern::GlobalTag(_)
| Pattern::PrivateTag(_) | Pattern::PrivateTag(_)
| Pattern::Apply(_, _) | Pattern::Apply(_, _)
| Pattern::NumLiteral(_) | Pattern::NumLiteral(..)
| Pattern::NonBase10Literal { .. } | Pattern::NonBase10Literal { .. }
| Pattern::FloatLiteral(_) | Pattern::FloatLiteral(..)
| Pattern::StrLiteral(_) | Pattern::StrLiteral(_)
| Pattern::Underscore(_) | Pattern::Underscore(_)
| Pattern::Malformed(_) | Pattern::Malformed(_)
@ -116,17 +116,17 @@ impl<'a> Formattable for Pattern<'a> {
loc_pattern.format(buf, indent); loc_pattern.format(buf, indent);
} }
NumLiteral(string) => { &NumLiteral(string) => {
buf.indent(indent); buf.indent(indent);
buf.push_str(string); buf.push_str(string);
} }
NonBase10Literal { &NonBase10Literal {
base, base,
string, string,
is_negative, is_negative,
} => { } => {
buf.indent(indent); buf.indent(indent);
if *is_negative { if is_negative {
buf.push('-'); buf.push('-');
} }
@ -139,7 +139,7 @@ impl<'a> Formattable for Pattern<'a> {
buf.push_str(string); buf.push_str(string);
} }
FloatLiteral(string) => { &FloatLiteral(string) => {
buf.indent(indent); buf.indent(indent);
buf.push_str(string); buf.push_str(string);
} }

View file

@ -2675,13 +2675,7 @@ mod test_fmt {
exposes [] \ exposes [] \
packages {} \ packages {} \
imports [ Task.{ Task } ] \ imports [ Task.{ Task } ] \
provides [ mainForHost ] \ provides [ mainForHost ]",
effects fx.Effect \
{ \
putLine : Str -> Effect {}, \
putInt : I64 -> Effect {}, \
getInt : Effect { value : I64, errorCode : [ A, B ], isError : Bool } \
}",
); );
} }

View file

@ -771,11 +771,13 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
env.context.bool_type().const_int(*int as u64, false).into() env.context.bool_type().const_int(*int as u64, false).into()
} }
Layout::Builtin(Builtin::Int(int_width)) => { 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), _ => panic!("Invalid layout for int literal = {:?}", layout),
}, },
U128(int) => const_u128(env, *int).into(),
Float(float) => match layout { Float(float) => match layout {
Layout::Builtin(Builtin::Float(float_width)) => { Layout::Builtin(Builtin::Float(float_width)) => {
float_with_precision(env, *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]) .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>( fn build_switch_ir<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, 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)] #[allow(clippy::too_many_arguments)]
fn run_low_level<'a, 'ctx, 'env>( fn run_low_level<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
@ -5299,7 +5309,6 @@ fn run_low_level<'a, 'ctx, 'env>(
op: LowLevel, op: LowLevel,
args: &[Symbol], args: &[Symbol],
update_mode: UpdateMode, update_mode: UpdateMode,
// expect_failed: *const (),
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
use LowLevel::*; use LowLevel::*;
@ -6063,21 +6072,28 @@ fn run_low_level<'a, 'ctx, 'env>(
match env.target_info.ptr_width() { match env.target_info.ptr_width() {
roc_target::PtrWidth::Bytes8 => { roc_target::PtrWidth::Bytes8 => {
let fn_ptr_type = context let func = env
.void_type() .module
.fn_type(&[], false) .get_function(bitcode::UTILS_EXPECT_FAILED)
.ptr_type(AddressSpace::Generic); .unwrap();
let fn_addr = env // TODO get the actual line info instead of
.ptr_int() // hardcoding as zero!
.const_int(expect_failed as *const () as u64, false);
let func: PointerValue<'ctx> = bd.build_int_to_ptr(
fn_addr,
fn_ptr_type,
"cast_expect_failed_addr_to_ptr",
);
let callable = CallableValue::try_from(func).unwrap(); let 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); bd.build_unconditional_branch(then_block);
} }

View file

@ -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 // roc_realloc
{ {
let libc_realloc_val = { let libc_realloc_val = {

View file

@ -38,10 +38,23 @@ macro_rules! run_jit_function {
}}; }};
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr) => {{ ($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 inkwell::context::Context;
use roc_builtins::bitcode;
use roc_gen_llvm::run_roc::RocCallResult; use roc_gen_llvm::run_roc::RocCallResult;
use std::mem::MaybeUninit; 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 { unsafe {
let main: libloading::Symbol<unsafe extern "C" fn(*mut RocCallResult<$ty>) -> ()> = let main: libloading::Symbol<unsafe extern "C" fn(*mut RocCallResult<$ty>) -> ()> =
$lib.get($main_fn_name.as_bytes()) $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)) .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
.expect("errored"); .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::<Failure>(),
mem::align_of::<Failure>(),
);
dealloc(self.failures as *mut u8, layout);
}
}
}
let get_expect_failures: libloading::Symbol<unsafe extern "C" fn() -> 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) => { Ok(success) => {
// only if there are no exceptions thrown, check for errors // only if there are no exceptions thrown, check for errors
assert!($errors.is_empty(), "Encountered errors:\n{}", $errors); assert!($errors.is_empty(), "Encountered errors:\n{}", $errors);

View file

@ -13,7 +13,7 @@ use roc_constrain::module::{
constrain_imports, pre_constrain_imports, ConstrainableImports, Import, constrain_imports, pre_constrain_imports, ConstrainableImports, Import,
}; };
use roc_constrain::module::{constrain_module, ExposedModuleTypes, SubsByModule}; 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::{ use roc_module::symbol::{
IdentIds, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds, PackageQualified, IdentIds, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds, PackageQualified,
Symbol, Symbol,
@ -23,9 +23,9 @@ use roc_mono::ir::{
UpdateModeIds, UpdateModeIds,
}; };
use roc_mono::layout::{Layout, LayoutCache, LayoutProblem}; use roc_mono::layout::{Layout, LayoutCache, LayoutProblem};
use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation}; use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral};
use roc_parse::header::PackageName;
use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent}; 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::ident::UppercaseIdent;
use roc_parse::module::module_defs; use roc_parse::module::module_defs;
use roc_parse::parser::{FileError, Parser, SyntaxError}; use roc_parse::parser::{FileError, Parser, SyntaxError};
@ -35,7 +35,7 @@ use roc_solve::solve;
use roc_target::TargetInfo; use roc_target::TargetInfo;
use roc_types::solved_types::Solved; use roc_types::solved_types::Solved;
use roc_types::subs::{Subs, VarStore, Variable}; 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::hash_map::Entry::{Occupied, Vacant};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::io; use std::io;
@ -165,37 +165,6 @@ impl<'a> Dependencies<'a> {
output output
} }
pub fn add_effect_module(
&mut self,
module_id: ModuleId,
dependencies: &MutSet<ModuleId>,
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) { fn add_to_status(&mut self, module_id: ModuleId, goal_phase: Phase) {
for phase in PHASES.iter() { for phase in PHASES.iter() {
if *phase > goal_phase { if *phase > goal_phase {
@ -674,24 +643,7 @@ struct ModuleHeader<'a> {
exposed_imports: MutMap<Ident, (Symbol, Region)>, exposed_imports: MutMap<Ident, (Symbol, Region)>,
parse_state: roc_parse::state::State<'a>, parse_state: roc_parse::state::State<'a>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
} header_for: HeaderFor<'a>,
#[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,
} }
#[derive(Debug)] #[derive(Debug)]
@ -774,7 +726,6 @@ impl<'a> MonomorphizedModule<'a> {
#[derive(Debug)] #[derive(Debug)]
struct ParsedModule<'a> { struct ParsedModule<'a> {
module_id: ModuleId, module_id: ModuleId,
module_name: ModuleNameEnum<'a>,
module_path: PathBuf, module_path: PathBuf,
src: &'a str, src: &'a str,
module_timing: ModuleTiming, module_timing: ModuleTiming,
@ -783,6 +734,8 @@ struct ParsedModule<'a> {
exposed_ident_ids: IdentIds, exposed_ident_ids: IdentIds,
exposed_imports: MutMap<Ident, (Symbol, Region)>, exposed_imports: MutMap<Ident, (Symbol, Region)>,
parsed_defs: &'a [Loc<roc_parse::ast::Def<'a>>], parsed_defs: &'a [Loc<roc_parse::ast::Def<'a>>],
module_name: ModuleNameEnum<'a>,
header_for: HeaderFor<'a>,
} }
/// A message sent out _from_ a worker thread, /// A message sent out _from_ a worker thread,
@ -790,19 +743,13 @@ struct ParsedModule<'a> {
#[derive(Debug)] #[derive(Debug)]
enum Msg<'a> { enum Msg<'a> {
Many(Vec<Msg<'a>>), Many(Vec<Msg<'a>>),
Header(ModuleHeader<'a>, HeaderFor<'a>), Header(ModuleHeader<'a>),
Parsed(ParsedModule<'a>), Parsed(ParsedModule<'a>),
CanonicalizedAndConstrained { CanonicalizedAndConstrained {
constrained_module: ConstrainedModule, constrained_module: ConstrainedModule,
canonicalization_problems: Vec<roc_problem::can::Problem>, canonicalization_problems: Vec<roc_problem::can::Problem>,
module_docs: Option<ModuleDocumentation>, module_docs: Option<ModuleDocumentation>,
}, },
MadeEffectModule {
type_shortname: &'a str,
constrained_module: ConstrainedModule,
canonicalization_problems: Vec<roc_problem::can::Problem>,
module_docs: ModuleDocumentation,
},
SolvedTypes { SolvedTypes {
module_id: ModuleId, module_id: ModuleId,
ident_ids: IdentIds, ident_ids: IdentIds,
@ -861,6 +808,7 @@ enum PlatformPath<'a> {
NotSpecified, NotSpecified,
Valid(To<'a>), Valid(To<'a>),
RootIsInterface, RootIsInterface,
RootIsHosted,
RootIsPkgConfig, RootIsPkgConfig,
} }
@ -1695,7 +1643,7 @@ fn update<'a>(
Ok(state) Ok(state)
} }
Header(header, header_extra) => { Header(header) => {
use HeaderFor::*; use HeaderFor::*;
log!("loaded header for {:?}", header.module_id); log!("loaded header for {:?}", header.module_id);
@ -1712,13 +1660,13 @@ fn update<'a>(
if let PkgConfig { if let PkgConfig {
config_shorthand, .. config_shorthand, ..
} = header_extra } = header.header_for
{ {
work.extend(state.dependencies.notify_package(config_shorthand)); work.extend(state.dependencies.notify_package(config_shorthand));
} }
} }
match header_extra { match header.header_for {
App { to_platform } => { App { to_platform } => {
debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified)); debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
state.platform_path = PlatformPath::Valid(to_platform); state.platform_path = PlatformPath::Valid(to_platform);
@ -1742,6 +1690,12 @@ fn update<'a>(
state.platform_path = PlatformPath::RootIsInterface; 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 // 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) 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 { SolvedTypes {
module_id, module_id,
ident_ids, ident_ids,
@ -2366,14 +2269,10 @@ fn load_pkg_config<'a>(
// Insert the first entries for this module's timings // Insert the first entries for this module's timings
let mut pkg_module_timing = ModuleTiming::new(module_start_time); 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.read_roc_file = file_io_duration;
pkg_module_timing.parse_header = parse_header_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 { match parsed {
Ok((ast::Module::Interface { header }, _parse_state)) => { Ok((ast::Module::Interface { header }, _parse_state)) => {
Err(LoadingProblem::UnexpectedHeader(format!( Err(LoadingProblem::UnexpectedHeader(format!(
@ -2396,23 +2295,13 @@ fn load_pkg_config<'a>(
filename, filename,
parser_state, parser_state,
module_ids.clone(), module_ids.clone(),
ident_ids_by_module.clone(), ident_ids_by_module,
&header, &header,
pkg_module_timing, pkg_module_timing,
) )
.1; .1;
let effects_module_msg = fabricate_effects_module( Ok(pkg_config_module_msg)
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((ast::Module::Hosted { header }, _parse_state)) => { Ok((ast::Module::Hosted { header }, _parse_state)) => {
Err(LoadingProblem::UnexpectedHeader(format!( Err(LoadingProblem::UnexpectedHeader(format!(
@ -2551,7 +2440,33 @@ fn parse_header<'a>(
packages: &[], packages: &[],
exposes: unspace(arena, header.exposes.items), exposes: unspace(arena, header.exposes.items),
imports: unspace(arena, header.imports.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( Ok(send_header(
@ -2593,7 +2508,9 @@ fn parse_header<'a>(
packages, packages,
exposes, exposes,
imports: unspace(arena, header.imports.items), 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( 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)), To::NewPackage(_package_name) => Ok((module_id, app_module_header_msg)),
} }
} }
Ok((ast::Module::Platform { header }, _parse_state)) => Ok(fabricate_effects_module( Ok((ast::Module::Platform { header }, _parse_state)) => {
arena, Err(LoadingProblem::UnexpectedHeader(format!(
"", "got an unexpected platform header\n{:?}",
module_ids, header
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,
))
} }
Err(fail) => Err(LoadingProblem::ParsingFailed( Err(fail) => Err(LoadingProblem::ParsingFailed(
fail.map_problem(SyntaxError::Header) fail.map_problem(SyntaxError::Header)
.into_file_error(filename), .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)] #[derive(Debug)]
struct HeaderInfo<'a> { struct HeaderInfo<'a> {
loc_name: Loc<ModuleNameEnum<'a>>, loc_name: Loc<ModuleNameEnum<'a>>,
@ -2778,7 +2662,7 @@ struct HeaderInfo<'a> {
packages: &'a [Loc<PackageEntry<'a>>], packages: &'a [Loc<PackageEntry<'a>>],
exposes: &'a [Loc<ExposedName<'a>>], exposes: &'a [Loc<ExposedName<'a>>],
imports: &'a [Loc<ImportsEntry<'a>>], imports: &'a [Loc<ImportsEntry<'a>>],
to_platform: Option<To<'a>>, extra: HeaderFor<'a>,
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@ -2799,7 +2683,7 @@ fn send_header<'a>(
packages, packages,
exposes, exposes,
imports, imports,
to_platform, extra,
} = info; } = info;
let declared_name: ModuleName = match &loc_name.value { 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, // We always need to send these, even if deps is empty,
// because the coordinator thread needs to receive this message // because the coordinator thread needs to receive this message
// to decrement its "pending" count. // 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(); let mut package_qualified_imported_modules = MutSet::default();
for (pq_module_name, module_id) in &deps_by_name { for (pq_module_name, module_id) in &deps_by_name {
match pq_module_name { match pq_module_name {
@ -2957,24 +2836,22 @@ fn send_header<'a>(
( (
home, home,
Msg::Header( Msg::Header(ModuleHeader {
ModuleHeader { module_id: home,
module_id: home, module_path: filename,
module_path: filename, is_root_module,
is_root_module, exposed_ident_ids: ident_ids,
exposed_ident_ids: ident_ids, module_name: loc_name.value,
module_name: loc_name.value, packages: package_entries,
packages: package_entries, imported_modules,
imported_modules, package_qualified_imported_modules,
package_qualified_imported_modules, deps_by_name,
deps_by_name, exposes: exposed,
exposes: exposed, parse_state,
parse_state, exposed_imports: scope,
exposed_imports: scope, module_timing,
module_timing, header_for: extra,
}, }),
extra,
),
) )
} }
@ -3023,7 +2900,6 @@ fn send_header_two<'a>(
HashMap::with_capacity_and_hasher(num_exposes, default_hasher()); HashMap::with_capacity_and_hasher(num_exposes, default_hasher());
// add standard imports // add standard imports
// TODO add Effect by default
imported_modules.insert(app_module_id, Region::zero()); imported_modules.insert(app_module_id, Region::zero());
deps_by_name.insert( deps_by_name.insert(
PQModuleName::Unqualified(ModuleName::APP.into()), PQModuleName::Unqualified(ModuleName::APP.into()),
@ -3198,24 +3074,22 @@ fn send_header_two<'a>(
( (
home, home,
Msg::Header( Msg::Header(ModuleHeader {
ModuleHeader { module_id: home,
module_id: home, module_path: filename,
module_path: filename, is_root_module,
is_root_module, exposed_ident_ids: ident_ids,
exposed_ident_ids: ident_ids, module_name,
module_name, packages: package_entries,
packages: package_entries, imported_modules,
imported_modules, package_qualified_imported_modules,
package_qualified_imported_modules, deps_by_name,
deps_by_name, exposes: exposed,
exposes: exposed, parse_state,
parse_state, exposed_imports: scope,
exposed_imports: scope, module_timing,
module_timing, header_for: extra,
}, }),
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<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
header: PlatformHeader<'a>,
module_timing: ModuleTiming,
) -> (ModuleId, Msg<'a>) {
let num_exposes = header.provides.len() + 1;
let mut exposed: Vec<Symbol> = 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<ModuleId, IdentIds> = 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<Symbol> = {
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<Spaced<'a, TypedIdent<'a>>>],
) -> bumpalo::collections::Vec<'a, (Loc<&'a str>, Loc<TypeAnnotation<'a>>)> {
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::too_many_arguments)]
#[allow(clippy::unnecessary_wraps)] #[allow(clippy::unnecessary_wraps)]
fn canonicalize_and_constrain<'a, F>( fn canonicalize_and_constrain<'a, F>(
@ -3668,6 +3276,7 @@ where
let ParsedModule { let ParsedModule {
module_id, module_id,
module_name, module_name,
header_for,
exposed_ident_ids, exposed_ident_ids,
parsed_defs, parsed_defs,
exposed_imports, exposed_imports,
@ -3680,6 +3289,7 @@ where
let canonicalized = canonicalize_module_defs( let canonicalized = canonicalize_module_defs(
arena, arena,
parsed_defs, parsed_defs,
&header_for,
module_id, module_id,
module_ids, module_ids,
exposed_ident_ids, exposed_ident_ids,
@ -3702,12 +3312,14 @@ where
ModuleNameEnum::PkgConfig => None, ModuleNameEnum::PkgConfig => None,
ModuleNameEnum::App(_) => None, ModuleNameEnum::App(_) => None,
ModuleNameEnum::Interface(name) | ModuleNameEnum::Hosted(name) => { ModuleNameEnum::Interface(name) | ModuleNameEnum::Hosted(name) => {
Some(crate::docs::generate_module_docs( let docs = crate::docs::generate_module_docs(
module_output.scope, module_output.scope,
name.as_str().into(), name.as_str().into(),
&module_output.ident_ids, &module_output.ident_ids,
parsed_defs, parsed_defs,
)) );
Some(docs)
} }
}; };
@ -3784,6 +3396,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
exposed_ident_ids, exposed_ident_ids,
exposed_imports, exposed_imports,
module_path, module_path,
header_for,
.. ..
} = header; } = header;
@ -3798,6 +3411,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
exposed_ident_ids, exposed_ident_ids,
exposed_imports, exposed_imports,
parsed_defs, parsed_defs,
header_for,
}; };
Ok(Msg::Parsed(parsed)) Ok(Msg::Parsed(parsed))
@ -4430,7 +4044,23 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin
} }
RootIsInterface => { RootIsInterface => {
let doc = alloc.stack(vec![ 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.concat(vec![
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "), alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "),
alloc.reflow(r"but won't output any executable."), alloc.reflow(r"but won't output any executable."),

View file

@ -2,5 +2,4 @@
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant)]
pub mod docs; pub mod docs;
pub mod effect_module;
pub mod file; pub mod file;

View file

@ -1638,7 +1638,9 @@ fn literal_spec(
match literal { match literal {
Str(_) => new_static_string(builder, block), 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, &[])
}
} }
} }

View file

@ -87,6 +87,7 @@ enum Test<'a> {
arguments: Vec<(Pattern<'a>, Layout<'a>)>, arguments: Vec<(Pattern<'a>, Layout<'a>)>,
}, },
IsInt(i128, IntWidth), IsInt(i128, IntWidth),
IsU128(u128),
IsFloat(u64, FloatWidth), IsFloat(u64, FloatWidth),
IsDecimal(RocDec), IsDecimal(RocDec),
IsStr(Box<str>), IsStr(Box<str>),
@ -136,6 +137,10 @@ impl<'a> Hash for Test<'a> {
state.write_u8(6); state.write_u8(6);
v.0.hash(state); 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::IsByte { num_alts, .. } => number_of_tests == *num_alts,
Test::IsBit(_) => number_of_tests == 2, Test::IsBit(_) => number_of_tests == 2,
Test::IsInt(_, _) => false, Test::IsInt(_, _) => false,
Test::IsU128(_) => false,
Test::IsFloat(_, _) => false, Test::IsFloat(_, _) => false,
Test::IsDecimal(_) => false, Test::IsDecimal(_) => false,
Test::IsStr(_) => false, Test::IsStr(_) => false,
@ -565,6 +571,7 @@ fn test_at_path<'a>(
num_alts: union.alternatives.len(), num_alts: union.alternatives.len(),
}, },
IntLiteral(v, precision) => IsInt(*v, *precision), IntLiteral(v, precision) => IsInt(*v, *precision),
U128Literal(v) => IsU128(*v),
FloatLiteral(v, precision) => IsFloat(*v, *precision), FloatLiteral(v, precision) => IsFloat(*v, *precision),
DecimalLiteral(v) => IsDecimal(*v), DecimalLiteral(v) => IsDecimal(*v),
StrLiteral(v) => IsStr(v.clone()), StrLiteral(v) => IsStr(v.clone()),
@ -823,6 +830,18 @@ fn to_relevant_branch_help<'a>(
_ => None, _ => 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 { FloatLiteral(float, p1) => match test {
IsFloat(test_float, p2) if float == *test_float => { IsFloat(test_float, p2) if float == *test_float => {
debug_assert_eq!(p1, *p2); debug_assert_eq!(p1, *p2);
@ -934,6 +953,7 @@ fn needs_tests(pattern: &Pattern) -> bool {
| BitLiteral { .. } | BitLiteral { .. }
| EnumLiteral { .. } | EnumLiteral { .. }
| IntLiteral(_, _) | IntLiteral(_, _)
| U128Literal(_)
| FloatLiteral(_, _) | FloatLiteral(_, _)
| DecimalLiteral(_) | DecimalLiteral(_)
| StrLiteral(_) => true, | StrLiteral(_) => true,
@ -1305,6 +1325,14 @@ fn test_to_equality<'a>(
(stores, lhs_symbol, rhs_symbol, None) (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) => { Test::IsFloat(test_int, precision) => {
// TODO maybe we can actually use i64 comparison here? // TODO maybe we can actually use i64 comparison here?
let test_float = f64::from_bits(test_int as u64); let test_float = f64::from_bits(test_int as u64);

View file

@ -54,6 +54,7 @@ pub enum Pattern {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Literal { pub enum Literal {
Int(i128), Int(i128),
U128(u128),
Bit(bool), Bit(bool),
Byte(u8), Byte(u8),
Float(u64), Float(u64),
@ -66,6 +67,7 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern {
match pattern { match pattern {
IntLiteral(v, _) => Literal(Literal::Int(*v)), IntLiteral(v, _) => Literal(Literal::Int(*v)),
U128Literal(v) => Literal(Literal::U128(*v)),
FloatLiteral(v, _) => Literal(Literal::Float(*v)), FloatLiteral(v, _) => Literal(Literal::Float(*v)),
DecimalLiteral(v) => Literal(Literal::Decimal(*v)), DecimalLiteral(v) => Literal(Literal::Decimal(*v)),
StrLiteral(v) => Literal(Literal::Str(v.clone())), StrLiteral(v) => Literal(Literal::Str(v.clone())),

View file

@ -8,7 +8,7 @@ use crate::layout::{
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; 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_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap};
use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
@ -1278,6 +1278,7 @@ impl ModifyRc {
pub enum Literal<'a> { pub enum Literal<'a> {
// Literals // Literals
Int(i128), Int(i128),
U128(u128),
Float(f64), Float(f64),
Decimal(RocDec), Decimal(RocDec),
Str(&'a str), Str(&'a str),
@ -1524,6 +1525,7 @@ impl<'a> Literal<'a> {
match self { match self {
Int(lit) => alloc.text(format!("{}i64", lit)), Int(lit) => alloc.text(format!("{}i64", lit)),
U128(lit) => alloc.text(format!("{}u128", lit)),
Float(lit) => alloc.text(format!("{}f64", lit)), Float(lit) => alloc.text(format!("{}f64", lit)),
// TODO: Add proper Dec.to_str // TODO: Add proper Dec.to_str
Decimal(lit) => alloc.text(format!("{}Dec", lit.0)), Decimal(lit) => alloc.text(format!("{}Dec", lit.0)),
@ -2020,7 +2022,7 @@ fn pattern_to_when<'a>(
(symbol, Loc::at_zero(wrapped_body)) (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 // These patters are refutable, and thus should never occur outside a `when` expression
// They should have been replaced with `UnsupportedPattern` during canonicalization // They should have been replaced with `UnsupportedPattern` during canonicalization
unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value) 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::*; use roc_can::expr::Expr::*;
match can_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) { 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"), _ => 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) { match num_argument_to_int_or_float(env.subs, env.target_info, *precision, true) {
IntOrFloat::Float(_) => Some(Literal::Float(*float)), IntOrFloat::Float(_) => Some(Literal::Float(*float)),
IntOrFloat::DecimalFloatType => { IntOrFloat::DecimalFloatType => {
@ -3036,11 +3041,17 @@ fn try_make_literal<'a>(
// TODO investigate lifetime trouble // TODO investigate lifetime trouble
// Str(string) => Some(Literal::Str(env.arena.alloc(string))), // 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 // first figure out what kind of number this is
match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) {
IntOrFloat::Int(_) => Some(Literal::Int((*num).into())), IntOrFloat::Int(_) => Some(match *num {
IntOrFloat::Float(_) => Some(Literal::Float(*num as f64)), 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 => { IntOrFloat::DecimalFloatType => {
let dec = match RocDec::from_str(num_str) { let dec = match RocDec::from_str(num_str) {
Some(d) => d, Some(d) => d,
@ -3072,11 +3083,14 @@ pub fn with_hole<'a>(
let arena = env.arena; let arena = env.arena;
match can_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) { match num_argument_to_int_or_float(env.subs, env.target_info, precision, false) {
IntOrFloat::Int(precision) => Stmt::Let( IntOrFloat::Int(precision) => Stmt::Let(
assigned, 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)), Layout::Builtin(Builtin::Int(precision)),
hole, 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) { match num_argument_to_int_or_float(env.subs, env.target_info, precision, true) {
IntOrFloat::Float(precision) => Stmt::Let( IntOrFloat::Float(precision) => Stmt::Let(
assigned, assigned,
@ -3115,18 +3129,24 @@ pub fn with_hole<'a>(
hole, hole,
), ),
Num(var, num_str, num) => { Num(var, num_str, num, _bound) => {
// first figure out what kind of number this is // first figure out what kind of number this is
match num_argument_to_int_or_float(env.subs, env.target_info, var, false) { match num_argument_to_int_or_float(env.subs, env.target_info, var, false) {
IntOrFloat::Int(precision) => Stmt::Let( IntOrFloat::Int(precision) => Stmt::Let(
assigned, 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), Layout::int_width(precision),
hole, hole,
), ),
IntOrFloat::Float(precision) => Stmt::Let( IntOrFloat::Float(precision) => Stmt::Let(
assigned, 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), Layout::float_width(precision),
hole, hole,
), ),
@ -6211,6 +6231,7 @@ fn store_pattern_help<'a>(
return StorePattern::NotProductive(stmt); return StorePattern::NotProductive(stmt);
} }
IntLiteral(_, _) IntLiteral(_, _)
| U128Literal(_)
| FloatLiteral(_, _) | FloatLiteral(_, _)
| DecimalLiteral(_) | DecimalLiteral(_)
| EnumLiteral { .. } | EnumLiteral { .. }
@ -7583,6 +7604,7 @@ fn call_specialized_proc<'a>(
pub enum Pattern<'a> { pub enum Pattern<'a> {
Identifier(Symbol), Identifier(Symbol),
Underscore, Underscore,
U128Literal(u128),
IntLiteral(i128, IntWidth), IntLiteral(i128, IntWidth),
FloatLiteral(u64, FloatWidth), FloatLiteral(u64, FloatWidth),
DecimalLiteral(RocDec), DecimalLiteral(RocDec),
@ -7662,9 +7684,15 @@ fn from_can_pattern_help<'a>(
match can_pattern { match can_pattern {
Underscore => Ok(Pattern::Underscore), Underscore => Ok(Pattern::Underscore),
Identifier(symbol) => Ok(Pattern::Identifier(*symbol)), Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
IntLiteral(var, _, int) => { IntLiteral(_, precision_var, _, int, _bound) => {
match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) {
IntOrFloat::Int(precision) => Ok(Pattern::IntLiteral(*int as i128, precision)), IntOrFloat::Int(precision) => {
let int = match *int {
IntValue::I128(n) => Pattern::IntLiteral(n, precision),
IntValue::U128(n) => Pattern::U128Literal(n),
};
Ok(int)
}
other => { other => {
panic!( panic!(
"Invalid precision for int pattern: {:?} has {:?}", "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? // 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(_) => { IntOrFloat::Int(_) => {
panic!("Invalid precision for float pattern {:?}", var) panic!("Invalid precision for float pattern {:?}", precision_var)
} }
IntOrFloat::Float(precision) => { IntOrFloat::Float(precision) => {
Ok(Pattern::FloatLiteral(f64::to_bits(*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? // TODO preserve malformed problem information here?
Err(RuntimeError::UnsupportedPattern(*region)) 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) { 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::Int(precision) => Ok(match num {
IntOrFloat::Float(precision) => Ok(Pattern::FloatLiteral(*num as u64, precision)), 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 => { IntOrFloat::DecimalFloatType => {
let dec = match RocDec::from_str(num_str) { let dec = match RocDec::from_str(num_str) {
Some(d) => d, Some(d) => d,

View file

@ -514,7 +514,7 @@ fn numeric_negate_expression<'a, T>(
let start = state.pos(); let start = state.pos();
let region = Region::new(start, expr.region.end()); let region = Region::new(start, expr.region.end());
let new_expr = match &expr.value { let new_expr = match expr.value {
Expr::Num(string) => { Expr::Num(string) => {
let new_string = let new_string =
unsafe { std::str::from_utf8_unchecked(&state.bytes()[..string.len() + 1]) }; unsafe { std::str::from_utf8_unchecked(&state.bytes()[..string.len() + 1]) };
@ -536,7 +536,7 @@ fn numeric_negate_expression<'a, T>(
Expr::NonBase10Int { Expr::NonBase10Int {
is_negative: !is_negative, is_negative: !is_negative,
string, string,
base: *base, base,
} }
} }
_ => Expr::UnaryOp(arena.alloc(expr), Loc::at(loc_op.region, UnaryOp::Negate)), _ => 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<Pattern<
Ok(Pattern::RecordDestructure(patterns)) Ok(Pattern::RecordDestructure(patterns))
} }
Expr::Float(string) => Ok(Pattern::FloatLiteral(string)), &Expr::Float(string) => Ok(Pattern::FloatLiteral(string)),
Expr::Num(string) => Ok(Pattern::NumLiteral(string)), &Expr::Num(string) => Ok(Pattern::NumLiteral(string)),
Expr::NonBase10Int { Expr::NonBase10Int {
string, string,
base, base,

View file

@ -8,6 +8,28 @@ use crate::string_literal;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use roc_region::all::Loc; use roc_region::all::Loc;
#[derive(Debug)]
pub enum HeaderFor<'a> {
App {
to_platform: To<'a>,
},
Hosted {
generates: UppercaseIdent<'a>,
generates_with: &'a [Loc<ExposedName<'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: roc_module::symbol::Symbol,
},
Interface,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub enum Version<'a> { pub enum Version<'a> {
Exact(&'a str), 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)] #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub struct ExposedName<'a>(&'a str); pub struct ExposedName<'a>(&'a str);
@ -162,7 +193,6 @@ pub struct PlatformHeader<'a> {
pub packages: Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>, pub packages: Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>,
pub imports: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>, pub imports: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
pub provides: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>, pub provides: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
pub effects: Effects<'a>,
// Potential comments and newlines - these will typically all be empty. // Potential comments and newlines - these will typically all be empty.
pub before_header: &'a [CommentOrNewline<'a>], pub before_header: &'a [CommentOrNewline<'a>],
@ -179,17 +209,6 @@ pub struct PlatformHeader<'a> {
pub after_provides: &'a [CommentOrNewline<'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<Spaced<'a, TypedIdent<'a>>>>,
}
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub enum ImportsEntry<'a> { pub enum ImportsEntry<'a> {
/// e.g. `Task` or `Task.{ Task, after }` /// e.g. `Task` or `Task.{ Task, after }`

View file

@ -22,6 +22,12 @@ impl<'a> From<UppercaseIdent<'a>> 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 /// 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 /// appear. This way, canonicalization can give more helpful error messages like
/// "you can't redefine this tag!" if you wrote `Foo = ...` or /// "you can't redefine this tag!" if you wrote `Foo = ...` or

View file

@ -1,15 +1,13 @@
use crate::ast::{Collection, CommentOrNewline, Def, Module, Spaced}; use crate::ast::{Collection, CommentOrNewline, Def, Module, Spaced};
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e}; use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::header::{ 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, InterfaceHeader, ModuleName, PackageEntry, PlatformHeader, PlatformRequires, To, TypedIdent,
}; };
use crate::ident::{ use crate::ident::{self, lowercase_ident, unqualified_ident, uppercase, UppercaseIdent};
self, lowercase_ident, unqualified_ident, uppercase, uppercase_ident, UppercaseIdent,
};
use crate::parser::Progress::{self, *}; use crate::parser::Progress::{self, *};
use crate::parser::{ 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, EGeneratesWith, EHeader, EImports, EPackages, EProvides, ERequires, ETypedIdent, Parser,
SourceError, SyntaxError, 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) = let (_, ((before_provides, after_provides), (provides, _provides_type)), state) =
specialize(EHeader::Provides, provides_without_to()).parse(arena, state)?; specialize(EHeader::Provides, provides_without_to()).parse(arena, state)?;
let (_, effects, state) = specialize(EHeader::Effects, effects()).parse(arena, state)?;
let header = PlatformHeader { let header = PlatformHeader {
name, name,
requires, requires,
@ -332,7 +328,6 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> {
packages: packages.entries, packages: packages.entries,
imports, imports,
provides, provides,
effects,
before_header: &[] as &[_], before_header: &[] as &[_],
after_platform_keyword, after_platform_keyword,
before_requires, 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)] #[inline(always)]
fn typed_ident<'a>() -> impl Parser<'a, Spaced<'a, TypedIdent<'a>>, ETypedIdent<'a>> { fn typed_ident<'a>() -> impl Parser<'a, Spaced<'a, TypedIdent<'a>>, ETypedIdent<'a>> {
// e.g. // e.g.

View file

@ -71,7 +71,6 @@ pub enum EHeader<'a> {
Imports(EImports, Position), Imports(EImports, Position),
Requires(ERequires<'a>, Position), Requires(ERequires<'a>, Position),
Packages(EPackages<'a>, Position), Packages(EPackages<'a>, Position),
Effects(EEffects<'a>, Position),
Generates(EGenerates, Position), Generates(EGenerates, Position),
GeneratesWith(EGeneratesWith, Position), GeneratesWith(EGeneratesWith, Position),
@ -167,22 +166,6 @@ pub enum EPackageEntry<'a> {
Space(BadInputError, Position), 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EImports { pub enum EImports {
Open(Position), Open(Position),

View file

@ -18,14 +18,6 @@ Platform {
packages: [], packages: [],
imports: [], imports: [],
provides: [], 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: [], before_header: [],
after_platform_keyword: [], after_platform_keyword: [],
before_requires: [], before_requires: [],

View file

@ -1 +1 @@
platform "rtfeldman/blah" requires {} { main : {} } exposes [] packages {} imports [] provides [] effects fx.Blah {} platform "rtfeldman/blah" requires {} { main : {} } exposes [] packages {} imports [] provides []

View file

@ -43,105 +43,6 @@ Platform {
"mainForHost", "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: [], before_header: [],
after_platform_keyword: [], after_platform_keyword: [],
before_requires: [ before_requires: [

View file

@ -4,9 +4,3 @@ platform "examples/cli"
packages {} packages {}
imports [ Task.{ Task } ] imports [ Task.{ Task } ]
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect
{
getLine : Effect Str,
putLine : Str -> Effect {},
twoArguments : Int, Int -> Effect {}
}

View file

@ -34,16 +34,6 @@ Platform {
"mainForHost", "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: [], before_header: [],
after_platform_keyword: [], after_platform_keyword: [],
before_requires: [ before_requires: [

View file

@ -4,4 +4,3 @@ platform "foo/barbaz"
packages { foo: "./foo" } packages { foo: "./foo" }
imports [] imports []
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect {}

View file

@ -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,
],
},
)

View file

@ -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,
}

View file

@ -41,16 +41,6 @@ Platform {
"mainForHost", "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: [], before_header: [],
after_platform_keyword: [], after_platform_keyword: [],
before_requires: [ before_requires: [

View file

@ -4,7 +4,6 @@ platform "test/types"
packages {} packages {}
imports [] imports []
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect {}
mainForHost : App Flags Model mainForHost : App Flags Model
mainForHost = main mainForHost = main

View file

@ -182,6 +182,7 @@ mod test_parse {
pass/newline_singleton_list.expr, pass/newline_singleton_list.expr,
pass/nonempty_platform_header.header, pass/nonempty_platform_header.header,
pass/not_docs.expr, pass/not_docs.expr,
pass/number_literal_suffixes.expr,
pass/one_backpassing.expr, pass/one_backpassing.expr,
pass/one_char_string.expr, pass/one_char_string.expr,
pass/one_def.expr, pass/one_def.expr,

View file

@ -107,6 +107,18 @@ pub enum IntErrorKind {
Overflow, Overflow,
/// Integer is too small to store in target integer type. /// Integer is too small to store in target integer type.
Underflow, 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. /// Enum to store the various types of errors that can cause parsing a float to fail.
@ -118,6 +130,8 @@ pub enum FloatErrorKind {
NegativeInfinity, NegativeInfinity,
/// the literal is too large for f64 /// the literal is too large for f64
PositiveInfinity, PositiveInfinity,
/// This is a float, but it has an integer numeric suffix.
IntSuffix,
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]

View file

@ -5044,4 +5044,156 @@ mod solve_expr {
"[ Email Str ] -> Bool", "[ 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 }"#,
)
}
} }

View file

@ -1,6 +1,8 @@
#[cfg(feature = "gen-llvm")] #[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to; use crate::helpers::llvm::assert_evals_to;
#[cfg(feature = "gen-llvm")] #[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_expect_failed;
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_llvm_evals_to; use crate::helpers::llvm::assert_llvm_evals_to;
#[cfg(feature = "gen-llvm")] #[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_non_opt_evals_to; 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")] #[cfg(feature = "gen-dev")]
use crate::helpers::dev::assert_evals_to; use crate::helpers::dev::assert_evals_to;
// #[cfg(feature = "gen-dev")] // #[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; // use crate::helpers::dev::assert_evals_to as assert_llvm_evals_to;
// #[cfg(feature = "gen-dev")] // #[cfg(feature = "gen-dev")]
// use crate::helpers::dev::assert_evals_to as assert_non_opt_evals_to; // 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")] #[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_evals_to; use crate::helpers::wasm::assert_evals_to;
// #[cfg(feature = "gen-wasm")] // #[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; // use crate::helpers::wasm::assert_evals_to as assert_llvm_evals_to;
// #[cfg(feature = "gen-wasm")] // #[cfg(feature = "gen-wasm")]
// use crate::helpers::wasm::assert_evals_to as assert_non_opt_evals_to; // use crate::helpers::wasm::assert_evals_to as assert_non_opt_evals_to;
@ -2485,9 +2491,9 @@ fn call_invalid_layout() {
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm"))]
#[should_panic(expected = "An expectation failed!")] #[should_panic(expected = "Failed with 1 failures. Failures: ")]
fn expect_fail() { fn expect_fail() {
assert_evals_to!( assert_expect_failed!(
indoc!( indoc!(
r#" r#"
expect 1 == 2 expect 1 == 2

View file

@ -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)] #[allow(unused_imports)]
pub(crate) use assert_evals_to; pub(crate) use assert_evals_to;
pub(crate) use assert_expect_failed;

View file

@ -192,7 +192,11 @@ fn create_llvm_module<'a>(
for function in FunctionIterator::from_module(module) { for function in FunctionIterator::from_module(module) {
let name = function.get_name().to_str().unwrap(); let name = function.get_name().to_str().unwrap();
if name.starts_with("roc_builtins") { 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") { 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 { macro_rules! expect_runtime_error_panic {
($src:expr) => {{ ($src:expr) => {{
#[cfg(feature = "wasm-cli-run")] #[cfg(feature = "wasm-cli-run")]
@ -651,6 +695,8 @@ macro_rules! assert_non_opt_evals_to {
#[allow(unused_imports)] #[allow(unused_imports)]
pub(crate) use assert_evals_to; pub(crate) use assert_evals_to;
#[allow(unused_imports)] #[allow(unused_imports)]
pub(crate) use assert_expect_failed;
#[allow(unused_imports)]
pub(crate) use assert_llvm_evals_to; pub(crate) use assert_llvm_evals_to;
#[allow(unused_imports)] #[allow(unused_imports)]
pub(crate) use assert_non_opt_evals_to; pub(crate) use assert_non_opt_evals_to;

View file

@ -24,6 +24,11 @@ pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
libc::malloc(size) 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] #[no_mangle]
pub unsafe fn roc_realloc( pub unsafe fn roc_realloc(
c_ptr: *mut c_void, c_ptr: *mut c_void,

View file

@ -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 crate::types::{name_type_var, RecordField};
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
@ -289,6 +289,17 @@ pub fn content_to_string(
buf 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) { fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, parens: Parens) {
use crate::subs::Content::*; use crate::subs::Content::*;
@ -306,18 +317,19 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
match *symbol { match *symbol {
Symbol::NUM_NUM => { Symbol::NUM_NUM => {
debug_assert_eq!(args.len(), 1); let content = get_single_arg(subs, args);
match *content {
let arg_var_index = args Alias(nested, args, _actual) => match nested {
.into_iter() Symbol::NUM_INTEGER => {
.next() write_integer(
.expect("Num was not applied to a type argument!"); env,
let arg_var = subs[arg_var_index]; get_single_arg(subs, &args),
let content = subs.get_content_without_compacting(arg_var); subs,
buf,
match &content { parens,
Alias(nested, _, _) => match *nested { false,
Symbol::NUM_INTEGER => buf.push_str("I64"), );
}
Symbol::NUM_FLOATINGPOINT => buf.push_str("F64"), Symbol::NUM_FLOATINGPOINT => buf.push_str("F64"),
_ => write_parens!(write_parens, buf, { _ => 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_parens!(write_parens, buf, {
write_symbol(env, *symbol, 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> { enum ExtContent<'a> {
Empty, Empty,
Content(Variable, &'a Content), Content(Variable, &'a Content),

View file

@ -1626,6 +1626,25 @@ pub enum FlatType {
EmptyTagUnion, 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)] #[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum Builtin { pub enum Builtin {
Str, Str,

View file

@ -1186,6 +1186,7 @@ pub enum Reason {
RecordUpdateValue(Lowercase), RecordUpdateValue(Lowercase),
RecordUpdateKeys(Symbol, SendMap<Lowercase, Region>), RecordUpdateKeys(Symbol, SendMap<Lowercase, Region>),
RecordDefaultField(Lowercase), RecordDefaultField(Lowercase),
NumericLiteralSuffix,
} }
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]

View file

@ -33,7 +33,6 @@ platform "test-platform"
packages {} packages {}
imports [] imports []
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str mainForHost : Str
mainForHost = main mainForHost = main

View file

@ -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 }

View file

@ -4,12 +4,6 @@ platform "folkertdev/foo"
packages {} packages {}
imports [ Task.{ Task } ] imports [ Task.{ Task } ]
provides [ mainForHost ] 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 : Task {} [] as Fx
mainForHost = main mainForHost = main

View file

@ -1,6 +1,6 @@
interface Task interface Task
exposes [ Task, succeed, fail, after, map, putLine, putInt, getInt, forever, loop ] 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) Task ok err : Effect.Effect (Result ok err)

View file

@ -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

View file

@ -4,7 +4,6 @@ platform "examples/cli"
packages {} packages {}
imports [ Task.{ Task } ] imports [ Task.{ Task } ]
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect { putLine : Str -> Effect {}, getLine : Effect Str }
mainForHost : Task {} [] as Fx mainForHost : Task {} [] as Fx
mainForHost = main mainForHost = main

View file

@ -1,6 +1,6 @@
interface Stdin interface Stdin
exposes [ line ] exposes [ line ]
imports [ fx.Effect, Task ] imports [ pf.Effect, Task ]
line : Task.Task Str * line : Task.Task Str *
line = Effect.after Effect.getLine Task.succeed# TODO FIXME Effect.getLine should suffice line = Effect.after Effect.getLine Task.succeed# TODO FIXME Effect.getLine should suffice

View file

@ -1,6 +1,6 @@
interface Stdout interface Stdout
exposes [ line ] exposes [ line ]
imports [ fx.Effect, Task.{ Task } ] imports [ pf.Effect, Task.{ Task } ]
# line : Str -> Task.Task {} * # line : Str -> Task.Task {} *
# line = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {}) # line = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {})

View file

@ -1,6 +1,6 @@
interface Task interface Task
exposes [ Task, succeed, fail, await, map, onFail, attempt, forever, loop ] exposes [ Task, succeed, fail, await, map, onFail, attempt, forever, loop ]
imports [ fx.Effect ] imports [ pf.Effect ]
Task ok err : Effect.Effect (Result ok err) Task ok err : Effect.Effect (Result ok err)

View file

@ -1,6 +1,6 @@
app "effect-example" app "effect-example"
packages { pf: "thing/platform-dir" } packages { pf: "thing/platform-dir" }
imports [ fx.Effect ] imports [ pf.Effect ]
provides [ main ] to pf provides [ main ] to pf
main : Effect.Effect {} main : Effect.Effect {}

View file

@ -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

View file

@ -2,13 +2,8 @@ platform "roc-examples/cli"
requires {} { main : Effect {} } requires {} { main : Effect {} }
exposes [] exposes []
packages {} packages {}
imports [ fx.Effect ] imports [ pf.Effect ]
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect
{
putLine : Str -> Effect {},
getLine : Effect Str,
}
mainForHost : Effect.Effect {} as Fx mainForHost : Effect.Effect {} as Fx
mainForHost = main mainForHost = main

View file

@ -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

View file

@ -1,6 +1,6 @@
interface File interface File
exposes [ line, Handle, withOpen, chunk ] exposes [ line, Handle, withOpen, chunk ]
imports [ fx.Effect, Task.{ Task } ] imports [ pf.Effect, Task.{ Task } ]
Handle : [ @Handle U64 ] Handle : [ @Handle U64 ]
@ -24,4 +24,4 @@ withOpen = \path, callback ->
handle <- Task.await (open path) handle <- Task.await (open path)
result <- Task.attempt (callback handle) result <- Task.attempt (callback handle)
{ } <- Task.await (close handle) { } <- Task.await (close handle)
Task.fromResult result Task.fromResult result

View file

@ -4,19 +4,6 @@ platform "examples/cli"
packages {} packages {}
imports [ Task.{ Task } ] imports [ Task.{ Task } ]
provides [ mainForHost ] 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 : Str -> Task {} [] as Fx
mainForHost = \file -> main file mainForHost = \file -> main file

View file

@ -1,6 +1,6 @@
interface Stdin interface Stdin
exposes [ char ] exposes [ char ]
imports [ fx.Effect, Task ] imports [ pf.Effect, Task ]
# line : Task.Task Str * # line : Task.Task Str *
# line = Effect.after Effect.getLine Task.succeed # TODO FIXME Effect.getLine should suffice # line = Effect.after Effect.getLine Task.succeed # TODO FIXME Effect.getLine should suffice

View file

@ -1,9 +1,9 @@
interface Stdout interface Stdout
exposes [ line, raw ] exposes [ line, raw ]
imports [ fx.Effect, Task.{ Task } ] imports [ pf.Effect, Task.{ Task } ]
line : Str -> Task {} * line : Str -> Task {} *
line = \str -> Effect.map (Effect.putLine str) (\_ -> Ok {}) line = \str -> Effect.map (Effect.putLine str) (\_ -> Ok {})
raw : Str -> Task {} * raw : Str -> Task {} *
raw = \str -> Effect.map (Effect.putRaw str) (\_ -> Ok {}) raw = \str -> Effect.map (Effect.putRaw str) (\_ -> Ok {})

View file

@ -1,6 +1,6 @@
interface Task interface Task
exposes [ Task, succeed, fail, await, map, onFail, attempt, fromResult, loop ] exposes [ Task, succeed, fail, await, map, onFail, attempt, fromResult, loop ]
imports [ fx.Effect ] imports [ pf.Effect ]
Task ok err : Effect.Effect (Result ok err) Task ok err : Effect.Effect (Result ok err)

View file

@ -4,7 +4,6 @@ platform "examples/add"
packages {} packages {}
imports [] imports []
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect {}
mainForHost : I64 -> I64 mainForHost : I64 -> I64
mainForHost = \a -> main a mainForHost = \a -> main a

View file

@ -4,7 +4,6 @@ platform "examples/hello-world"
packages {} packages {}
imports [] imports []
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str mainForHost : Str
mainForHost = main mainForHost = main

View file

@ -4,7 +4,6 @@ platform "examples/hello-swift"
packages {} packages {}
imports [] imports []
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str mainForHost : Str
mainForHost = main mainForHost = main

View file

@ -4,7 +4,6 @@ platform "examples/hello-world"
packages {} packages {}
imports [] imports []
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str mainForHost : Str
mainForHost = main mainForHost = main

View file

@ -4,7 +4,6 @@ platform "examples/hello-world"
packages {} packages {}
imports [] imports []
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str mainForHost : Str
mainForHost = main mainForHost = main

View file

@ -4,7 +4,6 @@ platform "examples/hello-world"
packages {} packages {}
imports [] imports []
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str mainForHost : Str
mainForHost = main mainForHost = main

View file

@ -4,7 +4,6 @@ platform "examples/quicksort"
packages {} packages {}
imports [] imports []
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect {}
mainForHost : List I64 -> List I64 mainForHost : List I64 -> List I64
mainForHost = \list -> quicksort list mainForHost = \list -> quicksort list

View file

@ -4,7 +4,6 @@ platform "folkertdev/foo"
packages {} packages {}
imports [] imports []
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect {}
mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View } mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View }
mainForHost = main mainForHost = main

View file

@ -8,18 +8,22 @@ version = "0.1.0"
[dependencies] [dependencies]
bumpalo = {version = "3.8.0", features = ["collections"]} bumpalo = {version = "3.8.0", features = ["collections"]}
const_format = "0.2.22" 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 = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"}
rustyline-derive = {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" 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_mono = {path = "../compiler/mono"}
roc_parse = {path = "../compiler/parse"} roc_parse = {path = "../compiler/parse"}
roc_repl_eval = {path = "../repl_eval"} roc_repl_eval = {path = "../repl_eval"}
roc_target = {path = "../compiler/roc_target"}
[dev-dependencies] roc_types = {path = "../compiler/types"}
indoc = "1.0.3" roc_builtins = {path = "../compiler/builtins"}
roc_test_utils = {path = "../test_utils"}
strip-ansi-escapes = "0.1.1"
[lib] [lib]
name = "roc_repl_cli" name = "roc_repl_cli"

View file

@ -1,15 +1,27 @@
use bumpalo::Bump;
use const_format::concatcp; use const_format::concatcp;
use inkwell::context::Context;
use libloading::Library;
use rustyline::highlight::{Highlighter, PromptInfo}; use rustyline::highlight::{Highlighter, PromptInfo};
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator}; use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
use rustyline_derive::{Completer, Helper, Hinter}; use rustyline_derive::{Completer, Helper, Hinter};
use std::borrow::Cow; use std::borrow::Cow;
use std::io; 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_parse::parser::{EExpr, ELambda, SyntaxError};
use roc_repl_eval::gen::{gen_and_eval, ReplOutput}; use roc_repl_eval::eval::jit_to_ast;
use roc_repl_eval::gen::{compile_to_mono, format_answer, ReplOutput};
#[cfg(test)] use roc_repl_eval::ReplApp;
mod tests; use roc_target::TargetInfo;
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
const BLUE: &str = "\u{001b}[36m"; const BLUE: &str = "\u{001b}[36m";
const PINK: &str = "\u{001b}[35m"; 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: Sized, F: Fn(usize) -> 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<ReplOutput, SyntaxError<'a>> {
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: "<function>".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<String, SyntaxError<'a>> {
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<()> { pub fn main() -> io::Result<()> {
use rustyline::error::ReadlineError; use rustyline::error::ReadlineError;
use rustyline::Editor; use rustyline::Editor;
@ -222,19 +438,3 @@ pub fn main() -> io::Result<()> {
Ok(()) Ok(())
} }
fn report_parse_error(fail: SyntaxError) {
println!("TODO Gracefully report parse error in repl: {:?}", fail);
}
fn eval_and_format<'a>(src: &str) -> Result<String, SyntaxError<'a>> {
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")),
})
}

View file

@ -5,22 +5,13 @@ version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # 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] [dependencies]
bumpalo = {version = "3.8.0", features = ["collections"]} 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_builtins = {path = "../compiler/builtins"}
roc_can = {path = "../compiler/can"} roc_can = {path = "../compiler/can"}
roc_collections = {path = "../compiler/collections"} roc_collections = {path = "../compiler/collections"}
roc_fmt = {path = "../compiler/fmt"} roc_fmt = {path = "../compiler/fmt"}
roc_gen_llvm = {path = "../compiler/gen_llvm", optional = true}
roc_load = {path = "../compiler/load"} roc_load = {path = "../compiler/load"}
roc_module = {path = "../compiler/module"} roc_module = {path = "../compiler/module"}
roc_mono = {path = "../compiler/mono"} roc_mono = {path = "../compiler/mono"}

View file

@ -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::<usize>());
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);
}
}

View file

@ -16,18 +16,12 @@ use roc_region::all::{Loc, Region};
use roc_target::TargetInfo; use roc_target::TargetInfo;
use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable};
#[cfg(feature = "llvm")] use crate::ReplApp;
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
#[cfg(feature = "llvm")] struct Env<'a, 'env, A> {
type AppExecutable = libloading::Library;
use super::app_memory::AppMemory;
struct Env<'a, 'env, M> {
arena: &'a Bump, arena: &'a Bump,
subs: &'env Subs, subs: &'env Subs,
app_memory: &'a M, app: &'a A,
target_info: TargetInfo, target_info: TargetInfo,
interns: &'env Interns, interns: &'env Interns,
home: ModuleId, 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 /// we get to a struct or tag, we know what the labels are and can turn them
/// back into the appropriate user-facing literals. /// back into the appropriate user-facing literals.
#[allow(clippy::too_many_arguments)] #[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, arena: &'a Bump,
app: AppExecutable, app: &'a A,
main_fn_name: &str, main_fn_name: &str,
layout: ProcLayout<'a>, layout: ProcLayout<'a>,
content: &'a Content, content: &'a Content,
@ -56,12 +50,11 @@ pub unsafe fn jit_to_ast<'a, M: AppMemory>(
home: ModuleId, home: ModuleId,
subs: &'a Subs, subs: &'a Subs,
target_info: TargetInfo, target_info: TargetInfo,
app_memory: &'a M,
) -> Result<Expr<'a>, ToAstProblem> { ) -> Result<Expr<'a>, ToAstProblem> {
let env = Env { let env = Env {
arena, arena,
subs, subs,
app_memory, app,
target_info, target_info,
interns, interns,
home, home,
@ -73,7 +66,7 @@ pub unsafe fn jit_to_ast<'a, M: AppMemory>(
result, result,
} => { } => {
// this is a thunk // 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), _ => Err(ToAstProblem::FunctionLayout),
} }
@ -93,8 +86,8 @@ enum NewtypeKind<'a> {
/// ///
/// The returned list of newtype containers is ordered by increasing depth. As an example, /// 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)]`. /// `A ({b : C 123})` will have the unrolled list `[Tag(A), RecordField(b), Tag(C)]`.
fn unroll_newtypes<'a, M: AppMemory>( fn unroll_newtypes<'a, A: ReplApp>(
env: &Env<'a, 'a, M>, env: &Env<'a, 'a, A>,
mut content: &'a Content, mut content: &'a Content,
) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) { ) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) {
let mut newtype_containers = Vec::with_capacity_in(1, env.arena); 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>( fn apply_newtypes<'a, A: ReplApp>(
env: &Env<'a, '_, M>, env: &Env<'a, '_, A>,
newtype_containers: Vec<'a, NewtypeKind<'a>>, newtype_containers: Vec<'a, NewtypeKind<'a>>,
mut expr: Expr<'a>, mut expr: Expr<'a>,
) -> Expr<'a> { ) -> Expr<'a> {
@ -155,15 +148,15 @@ fn apply_newtypes<'a, M: AppMemory>(
expr 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 { while let Content::Alias(_, _, real) = content {
content = env.subs.get_content_without_compacting(*real); content = env.subs.get_content_without_compacting(*real);
} }
content content
} }
fn unroll_recursion_var<'a, M: AppMemory>( fn unroll_recursion_var<'a, A: ReplApp>(
env: &Env<'a, 'a, M>, env: &Env<'a, 'a, A>,
mut content: &'a Content, mut content: &'a Content,
) -> &'a Content { ) -> &'a Content {
while let Content::RecursionVar { structure, .. } = content { while let Content::RecursionVar { structure, .. } = content {
@ -172,8 +165,8 @@ fn unroll_recursion_var<'a, M: AppMemory>(
content content
} }
fn get_tags_vars_and_variant<'a, M: AppMemory>( fn get_tags_vars_and_variant<'a, A: ReplApp>(
env: &Env<'a, '_, M>, env: &Env<'a, '_, A>,
tags: &UnionTags, tags: &UnionTags,
opt_rec_var: Option<Variable>, opt_rec_var: Option<Variable>,
) -> (MutMap<TagName, std::vec::Vec<Variable>>, UnionVariant<'a>) { ) -> (MutMap<TagName, std::vec::Vec<Variable>>, UnionVariant<'a>) {
@ -190,8 +183,8 @@ fn get_tags_vars_and_variant<'a, M: AppMemory>(
(vars_of_tag, union_variant) (vars_of_tag, union_variant)
} }
fn expr_of_tag<'a, M: AppMemory>( fn expr_of_tag<'a, A: ReplApp>(
env: &Env<'a, 'a, M>, env: &Env<'a, 'a, A>,
data_addr: usize, data_addr: usize,
tag_name: &TagName, tag_name: &TagName,
arg_layouts: &'a [Layout<'a>], 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 /// 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. /// tag data. The caller is expected to check that the tag ID is indeed stored this way.
fn tag_id_from_data<'a, M: AppMemory>( fn tag_id_from_data<'a, A: ReplApp>(
env: &Env<'a, 'a, M>, env: &Env<'a, 'a, A>,
union_layout: UnionLayout, union_layout: UnionLayout,
data_addr: usize, data_addr: usize,
) -> i64 { ) -> i64 {
@ -224,13 +217,13 @@ fn tag_id_from_data<'a, M: AppMemory>(
let tag_id_addr = data_addr + offset as usize; let tag_id_addr = data_addr + offset as usize;
match union_layout.tag_id_builtin() { match union_layout.tag_id_builtin() {
Builtin::Bool => env.app_memory.deref_bool(tag_id_addr) as i64, Builtin::Bool => env.app.deref_bool(tag_id_addr) as i64,
Builtin::Int(IntWidth::U8) => env.app_memory.deref_u8(tag_id_addr) as i64, Builtin::Int(IntWidth::U8) => env.app.deref_u8(tag_id_addr) as i64,
Builtin::Int(IntWidth::U16) => env.app_memory.deref_u16(tag_id_addr) as i64, Builtin::Int(IntWidth::U16) => env.app.deref_u16(tag_id_addr) as i64,
Builtin::Int(IntWidth::U64) => { Builtin::Int(IntWidth::U64) => {
// used by non-recursive unions at the // used by non-recursive unions at the
// moment, remove if that is no longer the case // 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"), _ => 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 /// pointer to the data of the union variant). Returns
/// - the tag ID /// - the tag ID
/// - the address of the data of the union variant, unmasked if the pointer held the tag ID /// - the address of the data of the union variant, unmasked if the pointer held the tag ID
fn tag_id_from_recursive_ptr<'a, M: AppMemory>( fn tag_id_from_recursive_ptr<'a, A: ReplApp>(
env: &Env<'a, 'a, M>, env: &Env<'a, 'a, A>,
union_layout: UnionLayout, union_layout: UnionLayout,
rec_addr: usize, rec_addr: usize,
) -> (i64, usize) { ) -> (i64, usize) {
let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.target_info); 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 { if tag_in_ptr {
let (_, tag_id_mask) = UnionLayout::tag_id_pointer_bits_and_mask(env.target_info); let (_, tag_id_mask) = UnionLayout::tag_id_pointer_bits_and_mask(env.target_info);
@ -264,9 +257,8 @@ const OPAQUE_FUNCTION: Expr = Expr::Var {
ident: "<function>", ident: "<function>",
}; };
fn jit_to_ast_help<'a, M: AppMemory>( fn jit_to_ast_help<'a, A: ReplApp>(
env: &Env<'a, 'a, M>, env: &Env<'a, 'a, A>,
app: AppExecutable,
main_fn_name: &str, main_fn_name: &str,
layout: &Layout<'a>, layout: &Layout<'a>,
content: &'a Content, content: &'a Content,
@ -274,26 +266,25 @@ fn jit_to_ast_help<'a, M: AppMemory>(
let (newtype_containers, content) = unroll_newtypes(env, content); let (newtype_containers, content) = unroll_newtypes(env, content);
let content = unroll_aliases(env, content); let content = unroll_aliases(env, content);
let result = match layout { let result = match layout {
Layout::Builtin(Builtin::Bool) => Ok(run_jit_function!(app, main_fn_name, bool, |num| { Layout::Builtin(Builtin::Bool) => Ok(env
bool_to_ast(env, num, content) .app
})), .call_function(main_fn_name, |num: bool| bool_to_ast(env, num, content))),
Layout::Builtin(Builtin::Int(int_width)) => { Layout::Builtin(Builtin::Int(int_width)) => {
use IntWidth::*; use IntWidth::*;
macro_rules! helper { macro_rules! helper {
($ty:ty) => { ($ty:ty) => {
run_jit_function!(app, main_fn_name, $ty, |num| num_to_ast( env.app.call_function(main_fn_name, |num: $ty| {
env, num_to_ast(env, number_literal_to_ast(env.arena, num), content)
number_literal_to_ast(env.arena, num), })
content
))
}; };
} }
let result = match int_width { let result = match int_width {
U8 | I8 => { U8 | I8 => {
// NOTE: this is does not handle 8-bit numbers yet // NOTE: `helper!` does not handle 8-bit numbers yet
run_jit_function!(app, main_fn_name, u8, |num| byte_to_ast(env, num, content)) env.app
.call_function(main_fn_name, |num: u8| byte_to_ast(env, num, content))
} }
U16 => helper!(u16), U16 => helper!(u16),
U32 => helper!(u32), U32 => helper!(u32),
@ -312,11 +303,9 @@ fn jit_to_ast_help<'a, M: AppMemory>(
macro_rules! helper { macro_rules! helper {
($ty:ty) => { ($ty:ty) => {
run_jit_function!(app, main_fn_name, $ty, |num| num_to_ast( env.app.call_function(main_fn_name, |num: $ty| {
env, num_to_ast(env, number_literal_to_ast(env.arena, num), content)
number_literal_to_ast(env.arena, num), })
content
))
}; };
} }
@ -328,18 +317,16 @@ fn jit_to_ast_help<'a, M: AppMemory>(
Ok(result) Ok(result)
} }
Layout::Builtin(Builtin::Str) => Ok(run_jit_function!( Layout::Builtin(Builtin::Str) => {
app, Ok(env.app.call_function(main_fn_name, |string: &'static str| {
main_fn_name, str_to_ast(env.arena, env.arena.alloc(string))
&'static str, }))
|string: &'static str| { str_to_ast(env.arena, env.arena.alloc(string)) } }
)), Layout::Builtin(Builtin::List(elem_layout)) => Ok(env
Layout::Builtin(Builtin::List(elem_layout)) => Ok(run_jit_function!( .app
app, .call_function(main_fn_name, |(addr, len): (usize, usize)| {
main_fn_name, list_to_ast(env, addr, len, elem_layout, content)
(usize, usize), })),
|(addr, len): (usize, usize)| { list_to_ast(env, addr, len, elem_layout, content) }
)),
Layout::Builtin(other) => { Layout::Builtin(other) => {
todo!("add support for rendering builtin {:?} to the REPL", 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); let result_stack_size = layout.stack_size(env.target_info);
run_jit_function_dynamic_type!( env.app.call_function_dynamic_size(
app,
main_fn_name, main_fn_name,
result_stack_size as usize, result_stack_size as usize,
|bytes_addr: usize| { struct_addr_to_ast(bytes_addr as usize) } struct_addr_to_ast,
) )
} }
Layout::Union(UnionLayout::NonRecursive(_)) => { Layout::Union(UnionLayout::NonRecursive(_)) => {
let size = layout.stack_size(env.target_info); let size = layout.stack_size(env.target_info);
Ok(run_jit_function_dynamic_type!( Ok(env
app, .app
main_fn_name, .call_function_dynamic_size(main_fn_name, size as usize, |addr: usize| {
size as usize,
|addr: usize| {
addr_to_ast(env, addr, layout, WhenRecursive::Unreachable, content) addr_to_ast(env, addr, layout, WhenRecursive::Unreachable, content)
} }))
))
} }
Layout::Union(UnionLayout::Recursive(_)) Layout::Union(UnionLayout::Recursive(_))
| Layout::Union(UnionLayout::NonNullableUnwrapped(_)) | Layout::Union(UnionLayout::NonNullableUnwrapped(_))
| Layout::Union(UnionLayout::NullableUnwrapped { .. }) | Layout::Union(UnionLayout::NullableUnwrapped { .. })
| Layout::Union(UnionLayout::NullableWrapped { .. }) => { | Layout::Union(UnionLayout::NullableWrapped { .. }) => {
let size = layout.stack_size(env.target_info); let size = layout.stack_size(env.target_info);
Ok(run_jit_function_dynamic_type!( Ok(env
app, .app
main_fn_name, .call_function_dynamic_size(main_fn_name, size as usize, |addr: usize| {
size as usize,
|addr: usize| {
addr_to_ast(env, addr, layout, WhenRecursive::Loop(*layout), content) addr_to_ast(env, addr, layout, WhenRecursive::Loop(*layout), content)
} }))
))
} }
Layout::RecursivePointer => { Layout::RecursivePointer => {
unreachable!("RecursivePointers can only be inside structures") 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)) 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 { match tag_name {
TagName::Global(_) => Expr::GlobalTag( TagName::Global(_) => Expr::GlobalTag(
env.arena env.arena
@ -457,8 +437,8 @@ enum WhenRecursive<'a> {
Loop(Layout<'a>), Loop(Layout<'a>),
} }
fn addr_to_ast<'a, M: AppMemory>( fn addr_to_ast<'a, A: ReplApp>(
env: &Env<'a, 'a, M>, env: &Env<'a, 'a, A>,
addr: usize, addr: usize,
layout: &Layout<'a>, layout: &Layout<'a>,
when_recursive: WhenRecursive<'a>, when_recursive: WhenRecursive<'a>,
@ -466,7 +446,7 @@ fn addr_to_ast<'a, M: AppMemory>(
) -> Expr<'a> { ) -> Expr<'a> {
macro_rules! helper { macro_rules! helper {
($method: ident, $ty: ty) => {{ ($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) 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)) => { (_, Layout::Builtin(Builtin::Bool)) => {
// TODO: bits are not as expected here. // TODO: bits are not as expected here.
// num is always false at the moment. // 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) bool_to_ast(env, num, content)
} }
@ -510,13 +490,13 @@ fn addr_to_ast<'a, M: AppMemory>(
} }
} }
(_, Layout::Builtin(Builtin::List(elem_layout))) => { (_, Layout::Builtin(Builtin::List(elem_layout))) => {
let elem_addr = env.app_memory.deref_usize(addr); let elem_addr = env.app.deref_usize(addr);
let len = env.app_memory.deref_usize(addr + env.target_info.ptr_width() as usize); let len = env.app.deref_usize(addr + env.target_info.ptr_width() as usize);
list_to_ast(env, elem_addr, len, elem_layout, content) list_to_ast(env, elem_addr, len, elem_layout, content)
} }
(_, Layout::Builtin(Builtin::Str)) => { (_, 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) 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"), _ => 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( expr_of_tag(
env, env,
@ -666,7 +646,7 @@ fn addr_to_ast<'a, M: AppMemory>(
_ => unreachable!("any other variant would have a different layout"), _ => 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 { if data_addr == 0 {
tag_name_to_expr(env, &nullable_name) tag_name_to_expr(env, &nullable_name)
} else { } else {
@ -697,7 +677,7 @@ fn addr_to_ast<'a, M: AppMemory>(
_ => unreachable!("any other variant would have a different layout"), _ => 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 { if data_addr == 0 {
tag_name_to_expr(env, &nullable_name) tag_name_to_expr(env, &nullable_name)
} else { } else {
@ -726,8 +706,8 @@ fn addr_to_ast<'a, M: AppMemory>(
apply_newtypes(env, newtype_containers, expr) apply_newtypes(env, newtype_containers, expr)
} }
fn list_to_ast<'a, M: AppMemory>( fn list_to_ast<'a, A: ReplApp>(
env: &Env<'a, 'a, M>, env: &Env<'a, 'a, A>,
addr: usize, addr: usize,
len: usize, len: usize,
elem_layout: &Layout<'a>, elem_layout: &Layout<'a>,
@ -775,8 +755,8 @@ fn list_to_ast<'a, M: AppMemory>(
Expr::List(Collection::with_items(output)) Expr::List(Collection::with_items(output))
} }
fn single_tag_union_to_ast<'a, M: AppMemory>( fn single_tag_union_to_ast<'a, A: ReplApp>(
env: &Env<'a, 'a, M>, env: &Env<'a, 'a, A>,
addr: usize, addr: usize,
field_layouts: &'a [Layout<'a>], field_layouts: &'a [Layout<'a>],
tag_name: &TagName, tag_name: &TagName,
@ -801,8 +781,8 @@ fn single_tag_union_to_ast<'a, M: AppMemory>(
Expr::Apply(loc_tag_expr, output, CalledVia::Space) Expr::Apply(loc_tag_expr, output, CalledVia::Space)
} }
fn sequence_of_expr<'a, I, M: AppMemory>( fn sequence_of_expr<'a, I, A: ReplApp>(
env: &Env<'a, 'a, M>, env: &Env<'a, 'a, A>,
addr: usize, addr: usize,
sequence: I, sequence: I,
when_recursive: WhenRecursive<'a>, when_recursive: WhenRecursive<'a>,
@ -832,8 +812,8 @@ where
output output
} }
fn struct_to_ast<'a, M: AppMemory>( fn struct_to_ast<'a, A: ReplApp>(
env: &Env<'a, 'a, M>, env: &Env<'a, 'a, A>,
addr: usize, addr: usize,
field_layouts: &'a [Layout<'a>], field_layouts: &'a [Layout<'a>],
record_fields: RecordFields, record_fields: RecordFields,
@ -949,7 +929,7 @@ fn unpack_two_element_tag_union(
(tag_name1, payload_vars1, tag_name2, payload_vars2) (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::*; use Content::*;
let arena = env.arena; 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::*; use Content::*;
let arena = env.arena; 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>( fn num_to_ast<'a, A: ReplApp>(
env: &Env<'a, '_, M>, env: &Env<'a, '_, A>,
num_expr: Expr<'a>, num_expr: Expr<'a>,
content: &Content, content: &Content,
) -> Expr<'a> { ) -> Expr<'a> {
@ -1199,15 +1179,15 @@ fn number_literal_to_ast<T: std::fmt::Display>(arena: &Bump, num: T) -> Expr<'_>
} }
#[cfg(target_endian = "little")] #[cfg(target_endian = "little")]
#[cfg(target_pointer_width = "64")] /// NOTE: As of this writing, we don't have big-endian small strings implemented yet!
/// 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!
fn str_to_ast<'a>(arena: &'a Bump, string: &'a str) -> Expr<'a> { 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) }; const STR_SIZE: usize = 2 * std::mem::size_of::<usize>();
let is_small = (bytes[15] & 0b1000_0000) != 0;
let bytes: [u8; STR_SIZE] = unsafe { std::mem::transmute(string) };
let is_small = (bytes[STR_SIZE - 1] & 0b1000_0000) != 0;
if is_small { 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); let mut string = bumpalo::collections::String::with_capacity_in(len, arena);
for byte in bytes.iter().take(len) { for byte in bytes.iter().take(len) {

Some files were not shown because too many files have changed in this diff Show more