Merge branch 'main' into underivable-rigid-better-error

Signed-off-by: Ayaz <20735482+ayazhafiz@users.noreply.github.com>
This commit is contained in:
Ayaz 2022-11-25 16:33:57 -06:00 committed by GitHub
commit 15e372373a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
164 changed files with 5187 additions and 3909 deletions

2
Cargo.lock generated
View file

@ -3315,6 +3315,7 @@ name = "roc_build"
version = "0.0.1"
dependencies = [
"bumpalo",
"const_format",
"inkwell",
"libloading",
"roc_builtins",
@ -4159,6 +4160,7 @@ version = "0.1.0"
dependencies = [
"bitvec 1.0.1",
"bumpalo",
"clap 3.2.20",
"roc_wasm_module",
]

View file

@ -3,7 +3,7 @@
Roc is not ready for a 0.1 release yet, but we do have:
- [**installation** guide](https://github.com/roc-lang/roc/tree/main/getting_started)
- [**tutorial**](https://github.com/roc-lang/roc/blob/main/TUTORIAL.md)
- [**tutorial**](https://roc-lang.org/tutorial)
- [**docs** for the standard library](https://www.roc-lang.org/builtins/Str)
- [frequently asked questions](https://github.com/roc-lang/roc/blob/main/FAQ.md)
- [Zulip chat](https://roc.zulipchat.com) for help, questions and discussions

File diff suppressed because it is too large Load diff

View file

@ -14,12 +14,10 @@ cd crates/cli && cargo criterion --no-run && cd ../..
mkdir -p bench-folder/crates/cli_testing_examples/benchmarks
mkdir -p bench-folder/crates/compiler/builtins/bitcode/src
mkdir -p bench-folder/target/release/deps
mkdir -p bench-folder/target/release/lib
cp "crates/cli_testing_examples/benchmarks/"*".roc" bench-folder/crates/cli_testing_examples/benchmarks/
cp -r crates/cli_testing_examples/benchmarks/platform bench-folder/crates/cli_testing_examples/benchmarks/
cp crates/compiler/builtins/bitcode/src/str.zig bench-folder/crates/compiler/builtins/bitcode/src
cp target/release/roc bench-folder/target/release
cp -r target/release/lib bench-folder/target/release
# copy the most recent time bench to bench-folder
cp target/release/deps/`ls -t target/release/deps/ | grep time_bench | head -n 1` bench-folder/target/release/deps/time_bench

View file

@ -4,5 +4,4 @@
set -euxo pipefail
cp target/release/roc ./roc # to be able to exclude "target" later in the tar command
cp -r target/release/lib ./lib
tar -czvf $1 --exclude="target" --exclude="zig-cache" roc lib LICENSE LEGAL_DETAILS examples/helloWorld.roc examples/platform-switching examples/cli crates/roc_std
tar -czvf $1 --exclude="target" --exclude="zig-cache" roc LICENSE LEGAL_DETAILS examples/helloWorld.roc examples/platform-switching examples/cli crates/roc_std

View file

@ -278,6 +278,7 @@ fn to_pending_def<'a>(
Type(TypeDef::Opaque { .. }) => internal_error!("opaques not implemented"),
Type(TypeDef::Ability { .. }) => todo_abilities!(),
Value(AstValueDef::Dbg { .. }) => todo!(),
Value(AstValueDef::Expect { .. }) => todo!(),
Value(AstValueDef::ExpectFx { .. }) => todo!(),

View file

@ -405,6 +405,9 @@ pub fn to_type2<'a>(
Type2::Variable(var)
}
Tuple { fields: _, ext: _ } => {
todo!("tuple type");
}
Record { fields, ext, .. } => {
let field_types_map =
can_assigned_fields(env, scope, references, &fields.items, region);

View file

@ -1,6 +1,9 @@
use bumpalo::Bump;
use roc_build::{
link::{link, preprocess_host_wasm32, rebuild_host, LinkType, LinkingStrategy},
link::{
legacy_host_filename, link, preprocess_host_wasm32, preprocessed_host_filename,
rebuild_host, LinkType, LinkingStrategy,
},
program::{self, CodeGenOptions},
};
use roc_builtins::bitcode;
@ -106,28 +109,27 @@ pub fn build_file<'a>(
}
};
use target_lexicon::Architecture;
let emit_wasm = matches!(target.architecture, Architecture::Wasm32);
// TODO wasm host extension should be something else ideally
// .bc does not seem to work because
//
// > Non-Emscripten WebAssembly hasn't implemented __builtin_return_address
//
// and zig does not currently emit `.a` webassembly static libraries
let (host_extension, app_extension, extension) = {
let (app_extension, extension, host_filename) = {
use roc_target::OperatingSystem::*;
match roc_target::OperatingSystem::from(target.operating_system) {
Wasi => {
if matches!(code_gen_options.opt_level, OptLevel::Development) {
("wasm", "wasm", Some("wasm"))
("wasm", Some("wasm"), "host.zig".to_string())
} else {
("zig", "bc", Some("wasm"))
("bc", Some("wasm"), "host.zig".to_string())
}
}
Unix => ("o", "o", None),
Windows => ("obj", "obj", Some("exe")),
Unix => (
"o",
None,
legacy_host_filename(target, code_gen_options.opt_level).unwrap(),
),
Windows => (
"obj",
Some("exe"),
legacy_host_filename(target, code_gen_options.opt_level).unwrap(),
),
}
};
@ -140,9 +142,7 @@ pub fn build_file<'a>(
let host_input_path = if let EntryPoint::Executable { platform_path, .. } = &loaded.entry_point
{
cwd.join(platform_path)
.with_file_name("host")
.with_extension(host_extension)
cwd.join(platform_path).with_file_name(host_filename)
} else {
unreachable!();
};
@ -172,23 +172,43 @@ pub fn build_file<'a>(
})
.collect();
let preprocessed_host_path = if emit_wasm {
host_input_path.with_file_name("preprocessedhost.o")
let preprocessed_host_path = if linking_strategy == LinkingStrategy::Legacy {
host_input_path
.with_file_name(legacy_host_filename(target, code_gen_options.opt_level).unwrap())
} else {
host_input_path.with_file_name("preprocessedhost")
host_input_path.with_file_name(preprocessed_host_filename(target).unwrap())
};
let rebuild_thread = spawn_rebuild_thread(
code_gen_options.opt_level,
linking_strategy,
prebuilt,
host_input_path.clone(),
preprocessed_host_path.clone(),
binary_path.clone(),
target,
exposed_values,
exposed_closure_types,
);
// We don't need to spawn a rebuild thread when using a prebuilt host.
let rebuild_thread = if prebuilt {
if !preprocessed_host_path.exists() {
eprintln!(
"\nBecause I was run with --prebuilt-platform=true, I was expecting this file to exist:\n\n {}\n\nHowever, it was not there!\n\nIf you have the platform's source code locally, you may be able to regenerate it by re-running this command with --prebuilt-platform=false\n",
preprocessed_host_path.to_string_lossy()
);
std::process::exit(1);
}
if linking_strategy == LinkingStrategy::Surgical {
// Copy preprocessed host to executable location.
// The surgical linker will modify that copy in-place.
std::fs::copy(&preprocessed_host_path, binary_path.as_path()).unwrap();
}
None
} else {
Some(spawn_rebuild_thread(
code_gen_options.opt_level,
linking_strategy,
host_input_path.clone(),
preprocessed_host_path.clone(),
binary_path.clone(),
target,
exposed_values,
exposed_closure_types,
))
};
let buf = &mut String::with_capacity(1024);
@ -247,19 +267,24 @@ pub fn build_file<'a>(
ConcurrentWithApp(JoinHandle<u128>),
}
let rebuild_timing = if linking_strategy == LinkingStrategy::Additive {
let rebuild_duration = rebuild_thread
.join()
.expect("Failed to (re)build platform.");
if emit_timings && !prebuilt {
println!(
"Finished rebuilding the platform in {} ms\n",
rebuild_duration
);
let opt_rebuild_timing = if let Some(rebuild_thread) = rebuild_thread {
if linking_strategy == LinkingStrategy::Additive {
let rebuild_duration = rebuild_thread
.join()
.expect("Failed to (re)build platform.");
if emit_timings && !prebuilt {
println!(
"Finished rebuilding the platform in {} ms\n",
rebuild_duration
);
}
Some(HostRebuildTiming::BeforeApp(rebuild_duration))
} else {
Some(HostRebuildTiming::ConcurrentWithApp(rebuild_thread))
}
HostRebuildTiming::BeforeApp(rebuild_duration)
} else {
HostRebuildTiming::ConcurrentWithApp(rebuild_thread)
None
};
let (roc_app_bytes, code_gen_timing, expect_metadata) = program::gen_from_mono_module(
@ -300,7 +325,7 @@ pub fn build_file<'a>(
);
}
if let HostRebuildTiming::ConcurrentWithApp(thread) = rebuild_timing {
if let Some(HostRebuildTiming::ConcurrentWithApp(thread)) = opt_rebuild_timing {
let rebuild_duration = thread.join().expect("Failed to (re)build platform.");
if emit_timings && !prebuilt {
println!(
@ -361,9 +386,8 @@ pub fn build_file<'a>(
inputs.push(builtins_host_tempfile.path().to_str().unwrap());
}
let (mut child, _) = // TODO use lld
link(target, binary_path.clone(), &inputs, link_type)
.map_err(|_| todo!("gracefully handle `ld` failing to spawn."))?;
let (mut child, _) = link(target, binary_path.clone(), &inputs, link_type)
.map_err(|_| todo!("gracefully handle `ld` failing to spawn."))?;
let exit_status = child
.wait()
@ -376,12 +400,10 @@ pub fn build_file<'a>(
if exit_status.success() {
problems
} else {
let mut problems = problems;
// Add an error for `ld` failing
problems.errors += 1;
problems
todo!(
"gracefully handle `ld` (or `zig` in the case of wasm with --optimize) returning exit code {:?}",
exit_status.code()
);
}
}
};
@ -406,7 +428,6 @@ pub fn build_file<'a>(
fn spawn_rebuild_thread(
opt_level: OptLevel,
linking_strategy: LinkingStrategy,
prebuilt: bool,
host_input_path: PathBuf,
preprocessed_host_path: PathBuf,
binary_path: PathBuf,
@ -416,54 +437,49 @@ fn spawn_rebuild_thread(
) -> std::thread::JoinHandle<u128> {
let thread_local_target = target.clone();
std::thread::spawn(move || {
if !prebuilt {
// Printing to stderr because we want stdout to contain only the output of the roc program.
// We are aware of the trade-offs.
// `cargo run` follows the same approach
eprintln!("🔨 Rebuilding platform...");
}
// Printing to stderr because we want stdout to contain only the output of the roc program.
// We are aware of the trade-offs.
// `cargo run` follows the same approach
eprintln!("🔨 Rebuilding platform...");
let rebuild_host_start = Instant::now();
if !prebuilt {
match linking_strategy {
LinkingStrategy::Additive => {
let host_dest = rebuild_host(
opt_level,
&thread_local_target,
host_input_path.as_path(),
None,
);
match linking_strategy {
LinkingStrategy::Additive => {
let host_dest = rebuild_host(
opt_level,
&thread_local_target,
host_input_path.as_path(),
None,
);
preprocess_host_wasm32(host_dest.as_path(), &preprocessed_host_path);
}
LinkingStrategy::Surgical => {
roc_linker::build_and_preprocess_host(
opt_level,
&thread_local_target,
host_input_path.as_path(),
preprocessed_host_path.as_path(),
exported_symbols,
exported_closure_types,
);
}
LinkingStrategy::Legacy => {
rebuild_host(
opt_level,
&thread_local_target,
host_input_path.as_path(),
None,
);
}
preprocess_host_wasm32(host_dest.as_path(), &preprocessed_host_path);
}
LinkingStrategy::Surgical => {
roc_linker::build_and_preprocess_host(
opt_level,
&thread_local_target,
host_input_path.as_path(),
preprocessed_host_path.as_path(),
exported_symbols,
exported_closure_types,
);
// Copy preprocessed host to executable location.
// The surgical linker will modify that copy in-place.
std::fs::copy(&preprocessed_host_path, binary_path.as_path()).unwrap();
}
LinkingStrategy::Legacy => {
rebuild_host(
opt_level,
&thread_local_target,
host_input_path.as_path(),
None,
);
}
}
if linking_strategy == LinkingStrategy::Surgical {
// Copy preprocessed host to executable location.
std::fs::copy(preprocessed_host_path, binary_path.as_path()).unwrap();
}
let rebuild_host_end = rebuild_host_start.elapsed();
rebuild_host_end.as_millis()
rebuild_host_start.elapsed().as_millis()
})
}

View file

@ -940,7 +940,11 @@ fn roc_dev_native(
expect_metadata: ExpectMetadata,
) -> ! {
use roc_repl_expect::run::ExpectMemory;
use signal_hook::{consts::signal::SIGCHLD, consts::signal::SIGUSR1, iterator::Signals};
use signal_hook::{
consts::signal::SIGCHLD,
consts::signal::{SIGUSR1, SIGUSR2},
iterator::Signals,
};
let ExpectMetadata {
mut expectations,
@ -948,7 +952,7 @@ fn roc_dev_native(
layout_interner,
} = expect_metadata;
let mut signals = Signals::new(&[SIGCHLD, SIGUSR1]).unwrap();
let mut signals = Signals::new(&[SIGCHLD, SIGUSR1, SIGUSR2]).unwrap();
// let shm_name =
let shm_name = format!("/roc_expect_buffer_{}", std::process::id());
@ -994,6 +998,19 @@ fn roc_dev_native(
)
.unwrap();
}
SIGUSR2 => {
// this is the signal we use for a dbg
roc_repl_expect::run::render_dbgs_in_memory(
&mut writer,
arena,
&mut expectations,
&interns,
&layout_interner,
&memory,
)
.unwrap();
}
_ => println!("received signal {}", sig),
}
}

View file

@ -1056,50 +1056,29 @@ mod cli_run {
&[],
indoc!(
r#"
TYPE MISMATCH ...known_bad/../../../../examples/cli/cli-platform/main.roc
TYPE MISMATCH tests/known_bad/TypeError.roc
Something is off with the type annotation of the main required symbol:
This 2nd argument to attempt has an unexpected type:
2 requires {} { main : InternalProgram }
^^^^^^^^^^^^^^^
15> Task.attempt task /result ->
16> when result is
17> Ok {} -> Stdout.line "Done!"
18> # Type mismatch because the File.readUtf8 error case is not handled
19> Err {} -> Stdout.line "Problem!"
This #UserApp.main value is a:
The argument is an anonymous function of type:
Task.Task {} * [Write [Stdout]]
[Err {}a, Ok {}] -> Task {} *
But the type annotation on main says it should be:
But attempt needs its 2nd argument to be:
InternalProgram.InternalProgram
Tip: Type comparisons between an opaque type are only ever equal if
both types are the same opaque type. Did you mean to create an opaque
type by wrapping it? If I have an opaque type Age := U32 I can create
an instance of this opaque type by doing @Age 23.
TYPE MISMATCH ...known_bad/../../../../examples/cli/cli-platform/main.roc
This 1st argument to toEffect has an unexpected type:
9 mainForHost = InternalProgram.toEffect main
^^^^
This #UserApp.main value is a:
Task.Task {} * [Write [Stdout]]
But toEffect needs its 1st argument to be:
InternalProgram.InternalProgram
Tip: Type comparisons between an opaque type are only ever equal if
both types are the same opaque type. Did you mean to create an opaque
type by wrapping it? If I have an opaque type Age := U32 I can create
an instance of this opaque type by doing @Age 23.
Result {} [FileReadErr Path.Path InternalFile.ReadErr,
FileReadUtf8Err Path.Path [BadUtf8 Utf8ByteProblem Nat]*]* ->
Task {} *
2 errors and 1 warning found in <ignored for test> ms."#
1 error and 0 warnings found in <ignored for test> ms."#
),
);
}

View file

@ -1,13 +1,19 @@
app "type-error"
packages { pf: "../../../../examples/cli/cli-platform/main.roc" }
imports [pf.Stdout.{ line }, pf.Task.{ await }, pf.Program]
imports [pf.Stdout.{ line }, pf.Task.{ await }, pf.Path, pf.File]
provides [main] to pf
main =
_ <- await (line "a")
_ <- await (line "b")
_ <- await (line "c")
_ <- await (line "d")
line "e"
# Type mismatch because this line is missing:
# |> Program.quick
task =
_ <- await (line "a")
_ <- await (line "b")
_ <- await (line "c")
_ <- await (line "d")
_ <- await (File.readUtf8 (Path.fromStr "blah.txt"))
line "e"
Task.attempt task \result ->
when result is
Ok {} -> Stdout.line "Done!"
# Type mismatch because the File.readUtf8 error case is not handled
Err {} -> Stdout.line "Problem!"

View file

@ -640,10 +640,13 @@ fn stmt_spec<'a>(
let jpid = env.join_points[id];
builder.add_jump(block, jpid, argument, ret_type_id)
}
RuntimeError(_) => {
let type_id = layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
Crash(msg, _) => {
// Model this as a foreign call rather than TERMINATE because
// we want ownership of the message.
let result_type =
layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
builder.add_terminate(block, type_id)
builder.add_unknown_with(block, &[env.symbols[msg]], result_type)
}
}
}

View file

@ -31,11 +31,12 @@ roc_utils = { path = "../../utils" }
wasi_libc_sys = { path = "../../wasi-libc-sys" }
const_format.workspace = true
bumpalo.workspace = true
libloading.workspace = true
tempfile.workspace = true
target-lexicon.workspace = true
inkwell.workspace = true
inkwell.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
serde_json = "1.0.85"

View file

@ -1,4 +1,5 @@
use crate::target::{arch_str, target_zig_str};
use const_format::concatcp;
use libloading::{Error, Library};
use roc_builtins::bitcode;
use roc_error_macros::internal_error;
@ -59,6 +60,86 @@ pub fn link(
}
}
const fn legacy_host_filename_ext(
os: roc_target::OperatingSystem,
opt_level: OptLevel,
) -> &'static str {
use roc_target::OperatingSystem::*;
match os {
Wasi => {
// TODO wasm host extension should be something else ideally
// .bc does not seem to work because
//
// > Non-Emscripten WebAssembly hasn't implemented __builtin_return_address
//
// and zig does not currently emit `.a` webassembly static libraries
if matches!(opt_level, OptLevel::Development) {
"wasm"
} else {
"zig"
}
}
Unix => "o",
Windows => "obj",
}
}
const PRECOMPILED_HOST_EXT: &str = "rh1"; // Short for "roc host version 1" (so we can change format in the future)
pub const fn preprocessed_host_filename(target: &Triple) -> Option<&'static str> {
match target {
Triple {
architecture: Architecture::Wasm32,
..
} => Some(concatcp!("wasm32", '.', PRECOMPILED_HOST_EXT)),
Triple {
operating_system: OperatingSystem::Linux,
architecture: Architecture::X86_64,
..
} => Some(concatcp!("linux-x64", '.', PRECOMPILED_HOST_EXT)),
Triple {
operating_system: OperatingSystem::Linux,
architecture: Architecture::Aarch64(_),
..
} => Some(concatcp!("linux-arm64", '.', PRECOMPILED_HOST_EXT)),
Triple {
operating_system: OperatingSystem::Darwin,
architecture: Architecture::Aarch64(_),
..
} => Some(concatcp!("macos-arm64", '.', PRECOMPILED_HOST_EXT)),
Triple {
operating_system: OperatingSystem::Darwin,
architecture: Architecture::X86_64,
..
} => Some(concatcp!("macos-x64", '.', PRECOMPILED_HOST_EXT)),
Triple {
operating_system: OperatingSystem::Windows,
architecture: Architecture::X86_64,
..
} => Some(concatcp!("windows-x64", '.', PRECOMPILED_HOST_EXT)),
Triple {
operating_system: OperatingSystem::Windows,
architecture: Architecture::X86_32(_),
..
} => Some(concatcp!("windows-x86", '.', PRECOMPILED_HOST_EXT)),
Triple {
operating_system: OperatingSystem::Windows,
architecture: Architecture::Aarch64(_),
..
} => Some(concatcp!("windows-arm64", '.', PRECOMPILED_HOST_EXT)),
_ => None,
}
}
/// Same format as the precompiled host filename, except with a file extension like ".o" or ".obj"
pub fn legacy_host_filename(target: &Triple, opt_level: OptLevel) -> Option<String> {
let os = roc_target::OperatingSystem::from(target.operating_system);
let ext = legacy_host_filename_ext(os, opt_level);
Some(preprocessed_host_filename(target)?.replace(PRECOMPILED_HOST_EXT, ext))
}
fn find_zig_str_path() -> PathBuf {
// First try using the lib path relative to the executable location.
let lib_path_opt = get_lib_path();
@ -489,6 +570,10 @@ pub fn build_swift_host_native(
.arg("swiftc")
.args(sources)
.arg("-emit-object")
// `-module-name host` renames the .o file to "host" - otherwise you get an error like:
// error: module name "legacy_macos-arm64" is not a valid identifier; use -module-name flag to specify an alternate name
.arg("-module-name")
.arg("host")
.arg("-parse-as-library")
.args(["-o", dest]);
@ -527,26 +612,18 @@ pub fn rebuild_host(
roc_target::OperatingSystem::Wasi => "",
};
let object_extension = match os {
roc_target::OperatingSystem::Windows => "obj",
roc_target::OperatingSystem::Unix => "o",
roc_target::OperatingSystem::Wasi => "o",
};
let host_dest = if matches!(target.architecture, Architecture::Wasm32) {
if matches!(opt_level, OptLevel::Development) {
host_input_path.with_file_name("host.o")
host_input_path.with_extension("o")
} else {
host_input_path.with_file_name("host.bc")
host_input_path.with_extension("bc")
}
} else if shared_lib_path.is_some() {
host_input_path
.with_file_name("dynhost")
.with_extension(executable_extension)
} else {
host_input_path
.with_file_name("host")
.with_extension(object_extension)
host_input_path.with_file_name(legacy_host_filename(target, opt_level).unwrap())
};
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());

View file

@ -7,7 +7,7 @@ const math = std.math;
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
const RocStr = str.RocStr;
const WithOverflow = utils.WithOverflow;
const roc_panic = utils.panic;
const roc_panic = @import("panic.zig").panic_help;
const U256 = num_.U256;
const mul_u128 = num_.mul_u128;
@ -233,7 +233,7 @@ pub const RocDec = extern struct {
const answer = RocDec.addWithOverflow(self, other);
if (answer.has_overflowed) {
roc_panic("Decimal addition overflowed!", 1);
roc_panic("Decimal addition overflowed!", 0);
unreachable;
} else {
return answer.value;
@ -265,7 +265,7 @@ pub const RocDec = extern struct {
const answer = RocDec.subWithOverflow(self, other);
if (answer.has_overflowed) {
roc_panic("Decimal subtraction overflowed!", 1);
roc_panic("Decimal subtraction overflowed!", 0);
unreachable;
} else {
return answer.value;
@ -329,7 +329,7 @@ pub const RocDec = extern struct {
const answer = RocDec.mulWithOverflow(self, other);
if (answer.has_overflowed) {
roc_panic("Decimal multiplication overflowed!", 1);
roc_panic("Decimal multiplication overflowed!", 0);
unreachable;
} else {
return answer.value;

View file

@ -2,6 +2,7 @@ const std = @import("std");
const builtin = @import("builtin");
const SIGUSR1: c_int = if (builtin.os.tag.isDarwin()) 30 else 10;
const SIGUSR2: c_int = if (builtin.os.tag.isDarwin()) 31 else 12;
const O_RDWR: c_int = 2;
const O_CREAT: c_int = 64;
@ -87,3 +88,11 @@ pub fn expectFailedFinalize() callconv(.C) void {
_ = roc_send_signal(parent_pid, SIGUSR1);
}
}
pub fn sendDbg() callconv(.C) void {
if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
const parent_pid = roc_getppid();
_ = roc_send_signal(parent_pid, SIGUSR2);
}
}

View file

@ -3,6 +3,7 @@ const builtin = @import("builtin");
const math = std.math;
const utils = @import("utils.zig");
const expect = @import("expect.zig");
const panic_utils = @import("panic.zig");
const ROC_BUILTINS = "roc_builtins";
const NUM = "num";
@ -166,12 +167,13 @@ comptime {
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null");
exportUtilsFn(utils.allocateWithRefcountC, "allocate_with_refcount");
@export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
@export(panic_utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
if (builtin.target.cpu.arch != .wasm32) {
exportUtilsFn(expect.expectFailedStartSharedBuffer, "expect_failed_start_shared_buffer");
exportUtilsFn(expect.expectFailedStartSharedFile, "expect_failed_start_shared_file");
exportUtilsFn(expect.expectFailedFinalize, "expect_failed_finalize");
exportUtilsFn(expect.sendDbg, "send_dbg");
// sets the buffer used for expect failures
@export(expect.setSharedBuffer, .{ .name = "set_shared_buffer", .linkage = .Weak });

View file

@ -4,7 +4,7 @@ const math = std.math;
const RocList = @import("list.zig").RocList;
const RocStr = @import("str.zig").RocStr;
const WithOverflow = @import("utils.zig").WithOverflow;
const roc_panic = @import("utils.zig").panic;
const roc_panic = @import("panic.zig").panic_help;
pub fn NumParseResult(comptime T: type) type {
// on the roc side we sort by alignment; putting the errorcode last
@ -284,7 +284,7 @@ pub fn exportAddOrPanic(comptime T: type, comptime name: []const u8) void {
fn func(self: T, other: T) callconv(.C) T {
const result = addWithOverflow(T, self, other);
if (result.has_overflowed) {
roc_panic("integer addition overflowed!", 1);
roc_panic("integer addition overflowed!", 0);
unreachable;
} else {
return result.value;
@ -343,7 +343,7 @@ pub fn exportSubOrPanic(comptime T: type, comptime name: []const u8) void {
fn func(self: T, other: T) callconv(.C) T {
const result = subWithOverflow(T, self, other);
if (result.has_overflowed) {
roc_panic("integer subtraction overflowed!", 1);
roc_panic("integer subtraction overflowed!", 0);
unreachable;
} else {
return result.value;
@ -451,7 +451,7 @@ pub fn exportMulOrPanic(comptime T: type, comptime W: type, comptime name: []con
fn func(self: T, other: T) callconv(.C) T {
const result = @call(.{ .modifier = always_inline }, mulWithOverflow, .{ T, W, self, other });
if (result.has_overflowed) {
roc_panic("integer multiplication overflowed!", 1);
roc_panic("integer multiplication overflowed!", 0);
unreachable;
} else {
return result.value;

View file

@ -0,0 +1,16 @@
const std = @import("std");
const RocStr = @import("str.zig").RocStr;
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
// Signals to the host that the program has panicked
extern fn roc_panic(msg: *const RocStr, tag_id: u32) callconv(.C) void;
pub fn panic_help(msg: []const u8, tag_id: u32) void {
var str = RocStr.init(msg.ptr, msg.len);
roc_panic(&str, tag_id);
}
// must export this explicitly because right now it is not used from zig code
pub fn panic(msg: *const RocStr, alignment: u32) callconv(.C) void {
return @call(.{ .modifier = always_inline }, roc_panic, .{ msg, alignment });
}

View file

@ -16,9 +16,6 @@ extern fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, align
// This should never be passed a null pointer.
extern fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void;
// Signals to the host that the program has panicked
extern fn roc_panic(c_ptr: *const anyopaque, 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;
@ -108,11 +105,6 @@ pub fn dealloc(c_ptr: [*]u8, alignment: u32) void {
return @call(.{ .modifier = always_inline }, roc_dealloc, .{ c_ptr, alignment });
}
// must export this explicitly because right now it is not used from zig code
pub fn panic(c_ptr: *const anyopaque, alignment: u32) callconv(.C) void {
return @call(.{ .modifier = always_inline }, roc_panic, .{ c_ptr, alignment });
}
pub fn memcpy(dst: [*]u8, src: [*]u8, size: usize) void {
@call(.{ .modifier = always_inline }, roc_memcpy, .{ dst, src, size });
}

View file

@ -139,58 +139,80 @@ Utf8ByteProblem : [
Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem }
## Returns `Bool.true` if the string is empty, and `Bool.false` otherwise.
## Returns [Bool.true] if the string is empty, and [Bool.false] otherwise.
##
## expect Str.isEmpty "hi!" == Bool.false
## expect Str.isEmpty "" == Bool.true
isEmpty : Str -> Bool
## Concatenate two [Str] values together.
## Concatenates two strings together.
##
## expect Str.concat "Hello" "World" == "HelloWorld"
## expect Str.concat "ab" "cd" == "abcd"
## expect Str.concat "hello" "" == "hello"
## expect Str.concat "" "" == ""
concat : Str, Str -> Str
## Returns a [Str] of the specified capacity [Num] without any content
## Returns a string of the specified capacity without any content.
withCapacity : Nat -> Str
## Combine a [List] of [Str] into a single [Str], with a separator
## [Str] in between each.
## Combines a [List] of strings into a single string, with a separator
## string in between each.
##
## expect Str.joinWith ["one", "two", "three"] ", " == "one, two, three"
## expect Str.joinWith ["1", "2", "3", "4"] "." == "1.2.3.4"
joinWith : List Str, Str -> Str
## Split a [Str] around a separator. Passing `""` for the separator is not
## useful; it returns the original string wrapped in a list. To split a string
## Split a string around a separator.
##
## Passing `""` for the separator is not useful;
## it returns the original string wrapped in a [List]. To split a string
## into its individual [graphemes](https://stackoverflow.com/a/27331885/4200103), use `Str.graphemes`
##
## expect Str.split "1,2,3" "," == ["1","2","3"]
## expect Str.split "1,2,3" "" == ["1,2,3"]
split : Str, Str -> List Str
## Repeat a given [Str] value [Nat] times.
## Repeats a string the given number of times.
##
## expect Str.repeat ">" 3 == ">>>"
## expect Str.repeat "z" 3 == "zzz"
## expect Str.repeat "na" 8 == "nananananananana"
##
## Returns `""` when given `""` for the string or `0` for the count.
##
## expect Str.repeat "" 10 == ""
## expect Str.repeat "anything" 0 == ""
repeat : Str, Nat -> Str
## Count the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
## Counts the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
## in the string.
##
## expect Str.countGraphemes "Roc!" == 4
## expect Str.countGraphemes "七巧板" == 9
## expect Str.countGraphemes "üïä" == 4
## Note that the number of extended grapheme clusters can be different from the number
## of visual glyphs rendered! Consider the following examples:
##
## expect Str.countGraphemes "Roc" == 3
## expect Str.countGraphemes "👩‍👩‍👦‍👦" == 4
## expect Str.countGraphemes "🕊" == 1
##
## Note that "👩‍👩‍👦‍👦" takes up 4 graphemes (even though visually it appears as a single
## glyph) because under the hood it's represented using an emoji modifier sequence.
## In contrast, "🕊" only takes up 1 grapheme because under the hood it's represented
## using a single Unicode code point.
countGraphemes : Str -> Nat
## Split a string into its constituent grapheme clusters
graphemes : Str -> List Str
## If the string begins with a [Unicode code point](http://www.unicode.org/glossary/#code_point)
## equal to the given [U32], return `Bool.true`. Otherwise return `Bool.false`.
## equal to the given [U32], returns [Bool.true]. Otherwise returns [Bool.false].
##
## If the given [Str] is empty, or if the given [U32] is not a valid
## code point, this will return `Bool.false`.
## If the given string is empty, or if the given [U32] is not a valid
## code point, returns [Bool.false].
##
## **Performance Note:** This runs slightly faster than `Str.startsWith`, so
## expect Str.startsWithScalar "鹏 means 'roc'" 40527 # "鹏" is Unicode scalar 40527
## expect !Str.startsWithScalar "9" 9 # the Unicode scalar for "9" is 57, not 9
## expect !Str.startsWithScalar "" 40527
##
## **Performance Note:** This runs slightly faster than [Str.startsWith], so
## if you want to check whether a string begins with something that's representable
## in a single code point, you can use (for example) `Str.startsWithScalar '鹏'`
## instead of `Str.startsWith "鹏"`. ('鹏' evaluates to the [U32] value `40527`.)
@ -200,26 +222,41 @@ graphemes : Str -> List Str
## You'd need to use `Str.startsWithScalar "🕊"` instead.
startsWithScalar : Str, U32 -> Bool
## Return a [List] of the [unicode scalar values](https://unicode.org/glossary/#unicode_scalar_value)
## in the given string. Strings contain only scalar values, not [surrogate code points](https://unicode.org/glossary/#surrogate_code_point),
## so this is equivalent to returning a list of the string's [code points](https://unicode.org/glossary/#code_point).
## Returns a [List] of the [Unicode scalar values](https://unicode.org/glossary/#unicode_scalar_value)
## in the given string.
##
## (Roc strings contain only scalar values, not [surrogate code points](https://unicode.org/glossary/#surrogate_code_point),
## so this is equivalent to returning a list of the string's [code points](https://unicode.org/glossary/#code_point).)
##
## expect Str.toScalars "Roc" == [82, 111, 99]
## expect Str.toScalars "鹏" == [40527]
## expect Str.toScalars "சி" == [2970, 3007]
## expect Str.toScalars "🐦" == [128038]
## expect Str.toScalars "👩‍👩‍👦‍👦" == [128105, 8205, 128105, 8205, 128102, 8205, 128102]
## expect Str.toScalars "I ♥ Roc" == [73, 32, 9829, 32, 82, 111, 99]
## expect Str.toScalars "" == []
toScalars : Str -> List U32
## Return a [List] of the string's [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit).
## To split the string into a [List] of smaller [Str] values instead of [U8] values,
## see `Str.split`.
## Returns a [List] of the string's [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit).
## (To split the string into a [List] of smaller [Str] values instead of [U8] values,
## see [Str.split].)
##
## expect Str.toUtf8 "Roc" == [82, 111, 99]
## expect Str.toUtf8 "鹏" == [233, 185, 143]
## expect Str.toUtf8 "சி" == [224, 174, 154, 224, 174, 191]
## expect Str.toUtf8 "🐦" == [240, 159, 144, 166]
toUtf8 : Str -> List U8
## Encode a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit)
## into a [Str]
## Converts a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit) to a string.
##
## Returns `Err` if the given bytes are invalid UTF-8, and returns `Ok ""` when given `[]`.
##
## expect Str.fromUtf8 [82, 111, 99] == Ok "Roc"
## expect Str.fromUtf8 [233, 185, 143] == Ok "鹏"
## expect Str.fromUtf8 [0xb0] == Err (BadUtf8 InvalidStartByte 0)
## expect Str.fromUtf8 [224, 174, 154, 224, 174, 191] == Ok "சி"
## expect Str.fromUtf8 [240, 159, 144, 166] == Ok "🐦"
## expect Str.fromUtf8 [] == Ok ""
## expect Str.fromUtf8 [255] |> Result.isErr
fromUtf8 : List U8 -> Result Str [BadUtf8 Utf8ByteProblem Nat]
fromUtf8 = \bytes ->
result = fromUtf8RangeLowlevel bytes 0 (List.len bytes)
@ -673,14 +710,14 @@ walkUtf8WithIndexHelp = \string, state, step, index, length ->
else
state
## Enlarge the given [Str] for at least capacity additional bytes.
## Enlarge a string for at least the given number additional bytes.
reserve : Str, Nat -> Str
## is UB when the scalar is invalid
appendScalarUnsafe : Str, U32 -> Str
## Append a [U32] scalar to the given [Str]. If the given scalar is not a valid
## unicode value, it will return [Err InvalidScalar].
## Append a [U32] scalar to the given string. If the given scalar is not a valid
## unicode value, it returns [Err InvalidScalar].
##
## expect Str.appendScalar "H" 105 == Ok "Hi"
## expect Str.appendScalar "😢" 0xabcdef == Err InvalidScalar

View file

@ -426,6 +426,7 @@ pub const UTILS_EXPECT_FAILED_START_SHARED_FILE: &str =
"roc_builtins.utils.expect_failed_start_shared_file";
pub const UTILS_EXPECT_FAILED_FINALIZE: &str = "roc_builtins.utils.expect_failed_finalize";
pub const UTILS_EXPECT_READ_ENV_SHARED_BUFFER: &str = "roc_builtins.utils.read_env_shared_buffer";
pub const UTILS_SEND_DBG: &str = "roc_builtins.utils.send_dbg";
pub const UTILS_LONGJMP: &str = "longjmp";
pub const UTILS_SETJMP: &str = "setjmp";

View file

@ -448,6 +448,9 @@ pub fn find_type_def_symbols(
As(actual, _, _) => {
stack.push(&actual.value);
}
Tuple { fields: _, ext: _ } => {
todo!("find_type_def_symbols: Tuple");
}
Record { fields, ext } => {
let mut inner_stack = Vec::with_capacity(fields.items.len());
@ -869,6 +872,9 @@ fn can_annotation_help(
}
}
Tuple { fields: _, ext: _ } => {
todo!("tuple");
}
Record { fields, ext } => {
let ext_type = can_extension_type(
env,

View file

@ -87,6 +87,7 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
LowLevel::PtrCast => unimplemented!(),
LowLevel::RefCountInc => unimplemented!(),
LowLevel::RefCountDec => unimplemented!(),
LowLevel::Dbg => unimplemented!(),
// these are not implemented, not sure why
LowLevel::StrFromInt => unimplemented!(),

View file

@ -376,6 +376,10 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
*called_via,
)
}
Crash { msg, ret_var } => Crash {
msg: Box::new(msg.map(|m| go_help!(m))),
ret_var: sub!(*ret_var),
},
RunLowLevel { op, args, ret_var } => RunLowLevel {
op: *op,
args: args
@ -611,6 +615,18 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
lookups_in_cond: lookups_in_cond.to_vec(),
},
Dbg {
loc_condition,
loc_continuation,
variable,
symbol,
} => Dbg {
loc_condition: Box::new(loc_condition.map(|e| go_help!(e))),
loc_continuation: Box::new(loc_continuation.map(|e| go_help!(e))),
variable: sub!(*variable),
symbol: *symbol,
},
TypedHole(v) => TypedHole(sub!(*v)),
RuntimeError(err) => RuntimeError(err.clone()),

View file

@ -88,20 +88,21 @@ pub struct Annotation {
#[derive(Debug)]
pub(crate) struct CanDefs {
defs: Vec<Option<Def>>,
expects: Expects,
expects_fx: Expects,
dbgs: ExpectsOrDbgs,
expects: ExpectsOrDbgs,
expects_fx: ExpectsOrDbgs,
def_ordering: DefOrdering,
aliases: VecMap<Symbol, Alias>,
}
#[derive(Clone, Debug)]
pub struct Expects {
pub struct ExpectsOrDbgs {
pub conditions: Vec<Expr>,
pub regions: Vec<Region>,
pub preceding_comment: Vec<Region>,
}
impl Expects {
impl ExpectsOrDbgs {
fn with_capacity(capacity: usize) -> Self {
Self {
conditions: Vec::with_capacity(capacity),
@ -239,8 +240,8 @@ pub enum Declaration {
Declare(Def),
DeclareRec(Vec<Def>, IllegalCycleMark),
Builtin(Def),
Expects(Expects),
ExpectsFx(Expects),
Expects(ExpectsOrDbgs),
ExpectsFx(ExpectsOrDbgs),
/// If we know a cycle is illegal during canonicalization.
/// Otherwise we will try to detect this during solving; see [`IllegalCycleMark`].
InvalidCycle(Vec<CycleEntry>),
@ -1017,6 +1018,7 @@ fn canonicalize_value_defs<'a>(
// the ast::Expr values in pending_exprs for further canonicalization
// once we've finished assembling the entire scope.
let mut pending_value_defs = Vec::with_capacity(value_defs.len());
let mut pending_dbgs = Vec::with_capacity(value_defs.len());
let mut pending_expects = Vec::with_capacity(value_defs.len());
let mut pending_expect_fx = Vec::with_capacity(value_defs.len());
@ -1030,10 +1032,12 @@ fn canonicalize_value_defs<'a>(
pending_value_defs.push(pending_def);
}
PendingValue::SignatureDefMismatch => { /* skip */ }
PendingValue::Dbg(pending_dbg) => {
pending_dbgs.push(pending_dbg);
}
PendingValue::Expect(pending_expect) => {
pending_expects.push(pending_expect);
}
PendingValue::ExpectFx(pending_expect) => {
pending_expect_fx.push(pending_expect);
}
@ -1094,8 +1098,23 @@ fn canonicalize_value_defs<'a>(
def_ordering.insert_symbol_references(def_id as u32, &temp_output.references)
}
let mut expects = Expects::with_capacity(pending_expects.len());
let mut expects_fx = Expects::with_capacity(pending_expects.len());
let mut dbgs = ExpectsOrDbgs::with_capacity(pending_dbgs.len());
let mut expects = ExpectsOrDbgs::with_capacity(pending_expects.len());
let mut expects_fx = ExpectsOrDbgs::with_capacity(pending_expects.len());
for pending in pending_dbgs {
let (loc_can_condition, can_output) = canonicalize_expr(
env,
var_store,
scope,
pending.condition.region,
&pending.condition.value,
);
dbgs.push(loc_can_condition, pending.preceding_comment);
output.union(can_output);
}
for pending in pending_expects {
let (loc_can_condition, can_output) = canonicalize_expr(
@ -1127,6 +1146,7 @@ fn canonicalize_value_defs<'a>(
let can_defs = CanDefs {
defs,
dbgs,
expects,
expects_fx,
def_ordering,
@ -1534,6 +1554,7 @@ pub(crate) fn sort_can_defs_new(
) -> (Declarations, Output) {
let CanDefs {
defs,
dbgs: _,
expects,
expects_fx,
def_ordering,
@ -1750,6 +1771,7 @@ pub(crate) fn sort_can_defs(
) -> (Vec<Declaration>, Output) {
let CanDefs {
mut defs,
dbgs,
expects,
expects_fx,
def_ordering,
@ -1852,6 +1874,10 @@ pub(crate) fn sort_can_defs(
}
}
if !dbgs.conditions.is_empty() {
declarations.push(Declaration::Expects(dbgs));
}
if !expects.conditions.is_empty() {
declarations.push(Declaration::Expects(expects));
}
@ -2581,12 +2607,13 @@ fn to_pending_type_def<'a>(
enum PendingValue<'a> {
Def(PendingValueDef<'a>),
Expect(PendingExpect<'a>),
ExpectFx(PendingExpect<'a>),
Dbg(PendingExpectOrDbg<'a>),
Expect(PendingExpectOrDbg<'a>),
ExpectFx(PendingExpectOrDbg<'a>),
SignatureDefMismatch,
}
struct PendingExpect<'a> {
struct PendingExpectOrDbg<'a> {
condition: &'a Loc<ast::Expr<'a>>,
preceding_comment: Region,
}
@ -2684,10 +2711,18 @@ fn to_pending_value_def<'a>(
}
}
Dbg {
condition,
preceding_comment,
} => PendingValue::Dbg(PendingExpectOrDbg {
condition,
preceding_comment: *preceding_comment,
}),
Expect {
condition,
preceding_comment,
} => PendingValue::Expect(PendingExpect {
} => PendingValue::Expect(PendingExpectOrDbg {
condition,
preceding_comment: *preceding_comment,
}),
@ -2695,7 +2730,7 @@ fn to_pending_value_def<'a>(
ExpectFx {
condition,
preceding_comment,
} => PendingValue::ExpectFx(PendingExpect {
} => PendingValue::ExpectFx(PendingExpectOrDbg {
condition,
preceding_comment: *preceding_comment,
}),

View file

@ -166,6 +166,12 @@ pub enum Expr {
/// Empty record constant
EmptyRecord,
/// The "crash" keyword
Crash {
msg: Box<Loc<Expr>>,
ret_var: Variable,
},
/// Look up exactly one field on a record, e.g. (expr).foo.
Access {
record_var: Variable,
@ -240,6 +246,13 @@ pub enum Expr {
lookups_in_cond: Vec<ExpectLookup>,
},
Dbg {
loc_condition: Box<Loc<Expr>>,
loc_continuation: Box<Loc<Expr>>,
variable: Variable,
symbol: Symbol,
},
/// Rendered as empty box in editor
TypedHole(Variable),
@ -254,6 +267,14 @@ pub struct ExpectLookup {
pub ability_info: Option<SpecializationId>,
}
#[derive(Clone, Copy, Debug)]
pub struct DbgLookup {
pub symbol: Symbol,
pub var: Variable,
pub region: Region,
pub ability_info: Option<SpecializationId>,
}
impl Expr {
pub fn category(&self) -> Category {
match self {
@ -294,6 +315,9 @@ impl Expr {
}
Self::Expect { .. } => Category::Expect,
Self::ExpectFx { .. } => Category::Expect,
Self::Crash { .. } => Category::Crash,
Self::Dbg { .. } => Category::Expect,
// these nodes place no constraints on the expression's type
Self::TypedHole(_) | Self::RuntimeError(..) => Category::Unknown,
@ -767,6 +791,47 @@ pub fn canonicalize_expr<'a>(
}
}
}
} else if let ast::Expr::Crash = loc_fn.value {
// We treat crash specially, since crashing must be applied with one argument.
debug_assert!(!args.is_empty());
let mut args = Vec::new();
let mut output = Output::default();
for loc_arg in loc_args.iter() {
let (arg_expr, arg_out) =
canonicalize_expr(env, var_store, scope, loc_arg.region, &loc_arg.value);
args.push(arg_expr);
output.references.union_mut(&arg_out.references);
}
let crash = if args.len() > 1 {
let args_region = Region::span_across(
&loc_args.first().unwrap().region,
&loc_args.last().unwrap().region,
);
env.problem(Problem::OverAppliedCrash {
region: args_region,
});
// Still crash, just with our own message, and drop the references.
Crash {
msg: Box::new(Loc::at(
region,
Expr::Str(String::from("hit a crash!").into_boxed_str()),
)),
ret_var: var_store.fresh(),
}
} else {
let msg = args.pop().unwrap();
Crash {
msg: Box::new(msg),
ret_var: var_store.fresh(),
}
};
(crash, output)
} else {
// Canonicalize the function expression and its arguments
let (fn_expr, fn_expr_output) =
@ -857,6 +922,22 @@ pub fn canonicalize_expr<'a>(
(RuntimeError(problem), Output::default())
}
ast::Expr::Crash => {
// Naked crashes aren't allowed; we'll admit this with our own message, but yield an
// error.
env.problem(Problem::UnappliedCrash { region });
(
Crash {
msg: Box::new(Loc::at(
region,
Expr::Str(String::from("hit a crash!").into_boxed_str()),
)),
ret_var: var_store.fresh(),
},
Output::default(),
)
}
ast::Expr::Defs(loc_defs, loc_ret) => {
// The body expression gets a new scope for canonicalization,
scope.inner_scope(|inner_scope| {
@ -1031,6 +1112,41 @@ pub fn canonicalize_expr<'a>(
output,
)
}
ast::Expr::Dbg(condition, continuation) => {
let mut output = Output::default();
let (loc_condition, output1) =
canonicalize_expr(env, var_store, scope, condition.region, &condition.value);
let (loc_continuation, output2) = canonicalize_expr(
env,
var_store,
scope,
continuation.region,
&continuation.value,
);
output.union(output1);
output.union(output2);
// the symbol is used to bind the condition `x = condition`, and identify this `dbg`.
// That would cause issues if we dbg a variable, like `dbg y`, because in the IR we
// cannot alias variables. Hence, we make the dbg use that same variable `y`
let symbol = match &loc_condition.value {
Expr::Var(symbol, _) => *symbol,
_ => scope.gen_unique_symbol(),
};
(
Dbg {
loc_condition: Box::new(loc_condition),
loc_continuation: Box::new(loc_continuation),
variable: var_store.fresh(),
symbol,
},
output,
)
}
ast::Expr::If(if_thens, final_else_branch) => {
let mut branches = Vec::with_capacity(if_thens.len());
let mut output = Output::default();
@ -1676,7 +1792,8 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
| other @ RunLowLevel { .. }
| other @ TypedHole { .. }
| other @ ForeignCall { .. }
| other @ OpaqueWrapFunction(_) => other,
| other @ OpaqueWrapFunction(_)
| other @ Crash { .. } => other,
List {
elem_var,
@ -1826,6 +1943,30 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
}
}
Dbg {
loc_condition,
loc_continuation,
variable,
symbol,
} => {
let loc_condition = Loc {
region: loc_condition.region,
value: inline_calls(var_store, loc_condition.value),
};
let loc_continuation = Loc {
region: loc_continuation.region,
value: inline_calls(var_store, loc_continuation.value),
};
Dbg {
loc_condition: Box::new(loc_condition),
loc_continuation: Box::new(loc_continuation),
variable,
symbol,
}
}
LetRec(defs, loc_expr, mark) => {
let mut new_defs = Vec::with_capacity(defs.len());
@ -2552,9 +2693,10 @@ impl Declarations {
})
}
pub fn expects(&self) -> VecMap<Region, Vec<ExpectLookup>> {
pub fn expects(&self) -> ExpectCollector {
let mut collector = ExpectCollector {
expects: VecMap::default(),
dbgs: VecMap::default(),
};
let var = Variable::EMPTY_RECORD;
@ -2587,7 +2729,7 @@ impl Declarations {
}
}
collector.expects
collector
}
}
@ -2740,12 +2882,16 @@ fn get_lookup_symbols(expr: &Expr) -> Vec<ExpectLookup> {
}
| Expr::ExpectFx {
loc_continuation, ..
}
| Expr::Dbg {
loc_continuation, ..
} => {
stack.push(&loc_continuation.value);
// Intentionally ignore the lookups in the nested `expect` condition itself,
// because they couldn't possibly influence the outcome of this `expect`!
}
Expr::Crash { msg, .. } => stack.push(&msg.value),
Expr::Num(_, _, _, _)
| Expr::Float(_, _, _, _, _)
| Expr::Int(_, _, _, _, _)
@ -2864,8 +3010,9 @@ fn toplevel_expect_to_inline_expect_help(mut loc_expr: Loc<Expr>, has_effects: b
loc_expr
}
struct ExpectCollector {
expects: VecMap<Region, Vec<ExpectLookup>>,
pub struct ExpectCollector {
pub expects: VecMap<Region, Vec<ExpectLookup>>,
pub dbgs: VecMap<Symbol, DbgLookup>,
}
impl crate::traverse::Visitor for ExpectCollector {
@ -2884,6 +3031,21 @@ impl crate::traverse::Visitor for ExpectCollector {
self.expects
.insert(loc_condition.region, lookups_in_cond.to_vec());
}
Expr::Dbg {
loc_condition,
variable,
symbol,
..
} => {
let lookup = DbgLookup {
symbol: *symbol,
var: *variable,
region: loc_condition.region,
ability_info: None,
};
self.dbgs.insert(*symbol, lookup);
}
_ => (),
}

View file

@ -3,7 +3,9 @@ use crate::annotation::{canonicalize_annotation, AnnotationFor};
use crate::def::{canonicalize_defs, Def};
use crate::effect_module::HostedGeneratedFunctions;
use crate::env::Env;
use crate::expr::{ClosureData, Declarations, ExpectLookup, Expr, Output, PendingDerives};
use crate::expr::{
ClosureData, DbgLookup, Declarations, ExpectLookup, Expr, Output, PendingDerives,
};
use crate::pattern::{BindingsFromPattern, Pattern};
use crate::scope::Scope;
use bumpalo::Bump;
@ -131,6 +133,7 @@ pub struct Module {
pub rigid_variables: RigidVariables,
pub abilities_store: PendingAbilitiesStore,
pub loc_expects: VecMap<Region, Vec<ExpectLookup>>,
pub loc_dbgs: VecMap<Symbol, DbgLookup>,
}
#[derive(Debug, Default)]
@ -153,6 +156,7 @@ pub struct ModuleOutput {
pub pending_derives: PendingDerives,
pub scope: Scope,
pub loc_expects: VecMap<Region, Vec<ExpectLookup>>,
pub loc_dbgs: VecMap<Symbol, DbgLookup>,
}
fn validate_generate_with<'a>(
@ -776,7 +780,7 @@ pub fn canonicalize_module_defs<'a>(
}
}
let loc_expects = declarations.expects();
let collected = declarations.expects();
ModuleOutput {
scope,
@ -789,7 +793,8 @@ pub fn canonicalize_module_defs<'a>(
problems: env.problems,
symbols_from_requires,
pending_derives,
loc_expects,
loc_expects: collected.expects,
loc_dbgs: collected.dbgs,
}
}
@ -952,7 +957,17 @@ fn fix_values_captured_in_closure_expr(
Expect {
loc_condition,
loc_continuation,
lookups_in_cond: _,
..
}
| ExpectFx {
loc_condition,
loc_continuation,
..
}
| Dbg {
loc_condition,
loc_continuation,
..
} => {
fix_values_captured_in_closure_expr(
&mut loc_condition.value,
@ -966,18 +981,9 @@ fn fix_values_captured_in_closure_expr(
);
}
ExpectFx {
loc_condition,
loc_continuation,
lookups_in_cond: _,
} => {
Crash { msg, ret_var: _ } => {
fix_values_captured_in_closure_expr(
&mut loc_condition.value,
no_capture_symbols,
closure_captures,
);
fix_values_captured_in_closure_expr(
&mut loc_continuation.value,
&mut msg.value,
no_capture_symbols,
closure_captures,
);

View file

@ -82,6 +82,16 @@ fn desugar_value_def<'a>(arena: &'a Bump, def: &'a ValueDef<'a>) -> ValueDef<'a>
body_pattern,
body_expr: desugar_expr(arena, body_expr),
},
Dbg {
condition,
preceding_comment,
} => {
let desugared_condition = &*arena.alloc(desugar_expr(arena, condition));
Dbg {
condition: desugared_condition,
preceding_comment: *preceding_comment,
}
}
Expect {
condition,
preceding_comment,
@ -128,7 +138,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
| MalformedClosure
| PrecedenceConflict { .. }
| Tag(_)
| OpaqueRef(_) => loc_expr,
| OpaqueRef(_)
| Crash => loc_expr,
TupleAccess(_sub_expr, _paths) => todo!("Handle TupleAccess"),
RecordAccess(sub_expr, paths) => {
@ -348,6 +359,14 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
region: loc_expr.region,
})
}
Dbg(condition, continuation) => {
let desugared_condition = &*arena.alloc(desugar_expr(arena, condition));
let desugared_continuation = &*arena.alloc(desugar_expr(arena, continuation));
arena.alloc(Loc {
value: Dbg(desugared_condition, desugared_continuation),
region: loc_expr.region,
})
}
}
}

View file

@ -203,6 +203,9 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
let (fn_var, loc_fn, _closure_var, _ret_var) = &**f;
walk_call(visitor, *fn_var, loc_fn, args);
}
Expr::Crash { msg, .. } => {
visitor.visit_expr(&msg.value, msg.region, Variable::STR);
}
Expr::RunLowLevel {
op: _,
args,
@ -268,8 +271,7 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
loc_continuation,
lookups_in_cond: _,
} => {
// TODO: what type does an expect have? bool
visitor.visit_expr(&loc_condition.value, loc_condition.region, Variable::NULL);
visitor.visit_expr(&loc_condition.value, loc_condition.region, Variable::BOOL);
visitor.visit_expr(
&loc_continuation.value,
loc_continuation.region,
@ -281,8 +283,20 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
loc_continuation,
lookups_in_cond: _,
} => {
// TODO: what type does an expect have? bool
visitor.visit_expr(&loc_condition.value, loc_condition.region, Variable::NULL);
visitor.visit_expr(&loc_condition.value, loc_condition.region, Variable::BOOL);
visitor.visit_expr(
&loc_continuation.value,
loc_continuation.region,
Variable::NULL,
);
}
Expr::Dbg {
variable,
loc_condition,
loc_continuation,
symbol: _,
} => {
visitor.visit_expr(&loc_condition.value, loc_condition.region, *variable);
visitor.visit_expr(
&loc_continuation.value,
loc_continuation.region,

View file

@ -482,6 +482,28 @@ pub fn constrain_expr(
let and_constraint = constraints.and_constraint(and_cons);
constraints.exists(vars, and_constraint)
}
Expr::Crash { msg, ret_var } => {
let str_index = constraints.push_type(types, Types::STR);
let expected_msg = constraints.push_expected_type(Expected::ForReason(
Reason::CrashArg,
str_index,
msg.region,
));
let msg_is_str = constrain_expr(
types,
constraints,
env,
msg.region,
&msg.value,
expected_msg,
);
let magic = constraints.equal_types_var(*ret_var, expected, Category::Crash, region);
let and = constraints.and_constraint([msg_is_str, magic]);
constraints.exists([*ret_var], and)
}
Var(symbol, variable) => {
// Save the expectation in the variable, then lookup the symbol's type in the environment
let expected_type = *constraints[expected].get_type_ref();
@ -656,6 +678,36 @@ pub fn constrain_expr(
constraints.exists_many(vars, all_constraints)
}
Dbg {
loc_condition,
loc_continuation,
variable,
symbol: _,
} => {
let dbg_type = constraints.push_variable(*variable);
let expected_dbg = constraints.push_expected_type(Expected::NoExpectation(dbg_type));
let cond_con = constrain_expr(
types,
constraints,
env,
loc_condition.region,
&loc_condition.value,
expected_dbg,
);
let continuation_con = constrain_expr(
types,
constraints,
env,
loc_continuation.region,
&loc_continuation.value,
expected,
);
constraints.exists_many([], [cond_con, continuation_con])
}
If {
cond_var,
branch_var,

View file

@ -176,6 +176,15 @@ impl<'a> Formattable for TypeAnnotation<'a> {
annot.is_multiline() || has_clauses.iter().any(|has| has.is_multiline())
}
Tuple { fields, ext } => {
match ext {
Some(ann) if ann.value.is_multiline() => return true,
_ => {}
}
fields.items.iter().any(|field| field.value.is_multiline())
}
Record { fields, ext } => {
match ext {
Some(ann) if ann.value.is_multiline() => return true,
@ -297,6 +306,14 @@ impl<'a> Formattable for TypeAnnotation<'a> {
}
}
Tuple { fields, ext } => {
fmt_collection(buf, indent, Braces::Round, *fields, newlines);
if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent);
}
}
Record { fields, ext } => {
fmt_collection(buf, indent, Braces::Curly, *fields, newlines);

View file

@ -168,6 +168,7 @@ impl<'a> Formattable for ValueDef<'a> {
AnnotatedBody { .. } => true,
Expect { condition, .. } => condition.is_multiline(),
ExpectFx { condition, .. } => condition.is_multiline(),
Dbg { condition, .. } => condition.is_multiline(),
}
}
@ -241,6 +242,7 @@ impl<'a> Formattable for ValueDef<'a> {
Body(loc_pattern, loc_expr) => {
fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent);
}
Dbg { condition, .. } => fmt_dbg_in_def(buf, condition, self.is_multiline(), indent),
Expect { condition, .. } => fmt_expect(buf, condition, self.is_multiline(), indent),
ExpectFx { condition, .. } => {
fmt_expect_fx(buf, condition, self.is_multiline(), indent)
@ -294,6 +296,27 @@ impl<'a> Formattable for ValueDef<'a> {
}
}
fn fmt_dbg_in_def<'a, 'buf>(
buf: &mut Buf<'buf>,
condition: &'a Loc<Expr<'a>>,
is_multiline: bool,
indent: u16,
) {
buf.ensure_ends_with_newline();
buf.indent(indent);
buf.push_str("dbg");
let return_indent = if is_multiline {
buf.newline();
indent + INDENT
} else {
buf.spaces(1);
indent
};
condition.format(buf, return_indent);
}
fn fmt_expect<'a, 'buf>(
buf: &mut Buf<'buf>,
condition: &'a Loc<Expr<'a>>,

View file

@ -43,7 +43,8 @@ impl<'a> Formattable for Expr<'a> {
| MalformedIdent(_, _)
| MalformedClosure
| Tag(_)
| OpaqueRef(_) => false,
| OpaqueRef(_)
| Crash => false,
// These expressions always have newlines
Defs(_, _) | When(_, _) => true,
@ -71,6 +72,7 @@ impl<'a> Formattable for Expr<'a> {
Expect(condition, continuation) => {
condition.is_multiline() || continuation.is_multiline()
}
Dbg(condition, continuation) => condition.is_multiline() || continuation.is_multiline(),
If(branches, final_else) => {
final_else.is_multiline()
@ -190,6 +192,10 @@ impl<'a> Formattable for Expr<'a> {
buf.push('_');
buf.push_str(name);
}
Crash => {
buf.indent(indent);
buf.push_str("crash");
}
Apply(loc_expr, loc_args, _) => {
buf.indent(indent);
if apply_needs_parens && !loc_args.is_empty() {
@ -379,6 +385,9 @@ impl<'a> Formattable for Expr<'a> {
Expect(condition, continuation) => {
fmt_expect(buf, condition, continuation, self.is_multiline(), indent);
}
Dbg(condition, continuation) => {
fmt_dbg(buf, condition, continuation, self.is_multiline(), indent);
}
If(branches, final_else) => {
fmt_if(buf, branches, final_else, self.is_multiline(), indent);
}
@ -843,6 +852,33 @@ fn fmt_when<'a, 'buf>(
}
}
fn fmt_dbg<'a, 'buf>(
buf: &mut Buf<'buf>,
condition: &'a Loc<Expr<'a>>,
continuation: &'a Loc<Expr<'a>>,
is_multiline: bool,
indent: u16,
) {
buf.ensure_ends_with_newline();
buf.indent(indent);
buf.push_str("dbg");
let return_indent = if is_multiline {
buf.newline();
indent + INDENT
} else {
buf.spaces(1);
indent
};
condition.format(buf, return_indent);
// Always put a blank line after the `dbg` line(s)
buf.ensure_ends_with_blank_line();
continuation.format(buf, indent);
}
fn fmt_expect<'a, 'buf>(
buf: &mut Buf<'buf>,
condition: &'a Loc<Expr<'a>>,

View file

@ -540,6 +540,13 @@ impl<'a> RemoveSpaces<'a> for ValueDef<'a> {
body_pattern: arena.alloc(body_pattern.remove_spaces(arena)),
body_expr: arena.alloc(body_expr.remove_spaces(arena)),
},
Dbg {
condition,
preceding_comment: _,
} => Dbg {
condition: arena.alloc(condition.remove_spaces(arena)),
preceding_comment: Region::zero(),
},
Expect {
condition,
preceding_comment: _,
@ -659,6 +666,7 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Expr::Crash => Expr::Crash,
Expr::Defs(a, b) => {
let mut defs = a.clone();
defs.space_before = vec![Default::default(); defs.len()];
@ -685,6 +693,10 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Expr::Dbg(a, b) => Expr::Dbg(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Expr::Apply(a, b, c) => Expr::Apply(
arena.alloc(a.remove_spaces(arena)),
b.remove_spaces(arena),
@ -776,6 +788,10 @@ impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> {
vars: vars.remove_spaces(arena),
},
),
TypeAnnotation::Tuple { fields, ext } => TypeAnnotation::Tuple {
fields: fields.remove_spaces(arena),
ext: ext.remove_spaces(arena),
},
TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record {
fields: fields.remove_spaces(arena),
ext: ext.remove_spaces(arena),

View file

@ -5829,6 +5829,42 @@ mod test_fmt {
);
}
#[test]
fn format_crash() {
expr_formats_same(indoc!(
r#"
_ = crash
_ = crash ""
crash "" ""
"#
));
expr_formats_to(
indoc!(
r#"
_ = crash
_ = crash ""
_ = crash "" ""
try
foo
(\_ -> crash "")
"#
),
indoc!(
r#"
_ = crash
_ = crash ""
_ = crash "" ""
try
foo
(\_ -> crash "")
"#
),
);
}
// this is a parse error atm
// #[test]
// fn multiline_apply() {

View file

@ -1110,7 +1110,7 @@ trait Backend<'a> {
Stmt::Expect { .. } => todo!("expect is not implemented in the dev backend"),
Stmt::ExpectFx { .. } => todo!("expect-fx is not implemented in the dev backend"),
Stmt::RuntimeError(_) => {}
Stmt::Crash(..) => todo!("crash is not implemented in the dev backend"),
}
}

View file

@ -39,8 +39,8 @@ use roc_debug_flags::dbg_do;
use roc_debug_flags::ROC_PRINT_LLVM_FN_VERIFICATION;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::{
BranchInfo, CallType, EntryPoint, JoinPointId, ListLiteralElement, ModifyRc, OptLevel,
ProcLayout,
BranchInfo, CallType, CrashTag, EntryPoint, JoinPointId, ListLiteralElement, ModifyRc,
OptLevel, ProcLayout,
};
use roc_mono::layout::{
Builtin, CapturesNiche, LambdaName, LambdaSet, Layout, LayoutIds, RawFunctionLayout,
@ -158,7 +158,7 @@ impl LlvmBackendMode {
}
}
fn runs_expects(self) -> bool {
pub(crate) fn runs_expects(self) -> bool {
match self {
LlvmBackendMode::Binary => false,
LlvmBackendMode::BinaryDev => true,
@ -183,22 +183,6 @@ pub struct Env<'a, 'ctx, 'env> {
pub exposed_to_host: MutSet<Symbol>,
}
#[repr(u32)]
pub enum PanicTagId {
NullTerminatedString = 0,
}
impl std::convert::TryFrom<u32> for PanicTagId {
type Error = ();
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0 => Ok(PanicTagId::NullTerminatedString),
_ => Err(()),
}
}
}
impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> {
/// The integer type representing a pointer
///
@ -344,16 +328,33 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> {
)
}
pub fn call_panic(&self, message: PointerValue<'ctx>, tag_id: PanicTagId) {
pub fn call_panic(
&self,
env: &Env<'a, 'ctx, 'env>,
message: BasicValueEnum<'ctx>,
tag: CrashTag,
) {
let function = self.module.get_function("roc_panic").unwrap();
let tag_id = self
.context
.i32_type()
.const_int(tag_id as u32 as u64, false);
let tag_id = self.context.i32_type().const_int(tag as u32 as u64, false);
let msg = match env.target_info.ptr_width() {
PtrWidth::Bytes4 => {
// we need to pass the message by reference, but we currently hold the value.
let alloca = env
.builder
.build_alloca(message.get_type(), "alloca_panic_msg");
env.builder.build_store(alloca, message);
alloca.into()
}
PtrWidth::Bytes8 => {
// string is already held by reference
message
}
};
let call = self
.builder
.build_call(function, &[message.into(), tag_id.into()], "roc_panic");
.build_call(function, &[msg.into(), tag_id.into()], "roc_panic");
call.set_call_convention(C_CALL_CONV);
}
@ -750,25 +751,30 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
}
Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(),
Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(),
Str(str_literal) => {
if str_literal.len() < env.small_str_bytes() as usize {
match env.small_str_bytes() {
24 => small_str_ptr_width_8(env, parent, str_literal).into(),
12 => small_str_ptr_width_4(env, str_literal).into(),
_ => unreachable!("incorrect small_str_bytes"),
}
} else {
let ptr = define_global_str_literal_ptr(env, str_literal);
let number_of_elements = env.ptr_int().const_int(str_literal.len() as u64, false);
Str(str_literal) => build_string_literal(env, parent, str_literal),
}
}
let alloca =
const_str_alloca_ptr(env, parent, ptr, number_of_elements, number_of_elements);
fn build_string_literal<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
str_literal: &str,
) -> BasicValueEnum<'ctx> {
if str_literal.len() < env.small_str_bytes() as usize {
match env.small_str_bytes() {
24 => small_str_ptr_width_8(env, parent, str_literal).into(),
12 => small_str_ptr_width_4(env, str_literal).into(),
_ => unreachable!("incorrect small_str_bytes"),
}
} else {
let ptr = define_global_str_literal_ptr(env, str_literal);
let number_of_elements = env.ptr_int().const_int(str_literal.len() as u64, false);
match env.target_info.ptr_width() {
PtrWidth::Bytes4 => env.builder.build_load(alloca, "load_const_str"),
PtrWidth::Bytes8 => alloca.into(),
}
}
let alloca = const_str_alloca_ptr(env, parent, ptr, number_of_elements, number_of_elements);
match env.target_info.ptr_width() {
PtrWidth::Bytes4 => env.builder.build_load(alloca, "load_const_str"),
PtrWidth::Bytes8 => alloca.into(),
}
}
}
@ -2621,7 +2627,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
}
roc_target::PtrWidth::Bytes4 => {
// temporary WASM implementation
throw_exception(env, "An expectation failed!");
throw_internal_exception(env, parent, "An expectation failed!");
}
}
} else {
@ -2683,7 +2689,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
}
roc_target::PtrWidth::Bytes4 => {
// temporary WASM implementation
throw_exception(env, "An expectation failed!");
throw_internal_exception(env, parent, "An expectation failed!");
}
}
} else {
@ -2703,8 +2709,8 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
)
}
RuntimeError(error_msg) => {
throw_exception(env, error_msg);
Crash(sym, tag) => {
throw_exception(env, scope, sym, *tag);
// unused value (must return a BasicValue)
let zero = env.context.i64_type().const_zero();
@ -3336,7 +3342,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>(
builder.position_at_end(entry);
let wrapped_layout = roc_result_layout(env.arena, return_layout, env.target_info);
let wrapped_layout = roc_call_result_layout(env.arena, return_layout, env.target_info);
call_roc_function(env, roc_function, &wrapped_layout, arguments_for_call)
} else {
call_roc_function(env, roc_function, &return_layout, arguments_for_call)
@ -3366,7 +3372,8 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
// a tagged union to indicate to the test loader that a panic occurred.
// especially when running 32-bit binaries on a 64-bit machine, there
// does not seem to be a smarter solution
let wrapper_return_type = roc_result_type(env, basic_type_from_layout(env, &return_layout));
let wrapper_return_type =
roc_call_result_type(env, basic_type_from_layout(env, &return_layout));
let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena);
for layout in arguments {
@ -3755,7 +3762,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
let return_type = match env.mode {
LlvmBackendMode::GenTest | LlvmBackendMode::WasmGenTest | LlvmBackendMode::CliTest => {
roc_result_type(env, roc_function.get_type().get_return_type().unwrap()).into()
roc_call_result_type(env, roc_function.get_type().get_return_type().unwrap()).into()
}
LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev => {
@ -3862,14 +3869,29 @@ pub fn build_setjmp_call<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValu
}
}
/// Pointer to pointer of the panic message.
/// Pointer to RocStr which is the panic message.
pub fn get_panic_msg_ptr<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> {
let ptr_to_u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let str_typ = zig_str_type(env);
let global_name = "roc_panic_msg_ptr";
let global_name = "roc_panic_msg_str";
let global = env.module.get_global(global_name).unwrap_or_else(|| {
let global = env.module.add_global(ptr_to_u8_ptr, None, global_name);
global.set_initializer(&ptr_to_u8_ptr.const_zero());
let global = env.module.add_global(str_typ, None, global_name);
global.set_initializer(&str_typ.const_zero());
global
});
global.as_pointer_value()
}
/// Pointer to the panic tag.
/// Only non-zero values must be written into here.
pub fn get_panic_tag_ptr<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> {
let i64_typ = env.context.i64_type();
let global_name = "roc_panic_msg_tag";
let global = env.module.get_global(global_name).unwrap_or_else(|| {
let global = env.module.add_global(i64_typ, None, global_name);
global.set_initializer(&i64_typ.const_zero());
global
});
@ -3887,7 +3909,7 @@ fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>(
let builder = env.builder;
let return_type = basic_type_from_layout(env, &return_layout);
let call_result_type = roc_result_type(env, return_type.as_basic_type_enum());
let call_result_type = roc_call_result_type(env, return_type.as_basic_type_enum());
let result_alloca = builder.build_alloca(call_result_type, "result");
let then_block = context.append_basic_block(parent, "then_block");
@ -3922,26 +3944,21 @@ fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>(
{
builder.position_at_end(catch_block);
let error_msg = {
// u8**
let ptr_int_ptr = get_panic_msg_ptr(env);
// u8* again
builder.build_load(ptr_int_ptr, "ptr_int")
};
// RocStr* global
let error_msg_ptr = get_panic_msg_ptr(env);
// i64* global
let error_tag_ptr = get_panic_tag_ptr(env);
let return_value = {
let v1 = call_result_type.const_zero();
// flag is non-zero, indicating failure
let flag = context.i64_type().const_int(1, false);
// tag must be non-zero, indicating failure
let tag = builder.build_load(error_tag_ptr, "load_panic_tag");
let v2 = builder
.build_insert_value(v1, flag, 0, "set_error")
.unwrap();
let v2 = builder.build_insert_value(v1, tag, 0, "set_error").unwrap();
let v3 = builder
.build_insert_value(v2, error_msg, 1, "set_exception")
.build_insert_value(v2, error_msg_ptr, 1, "set_exception")
.unwrap();
v3
};
@ -3971,7 +3988,7 @@ fn make_exception_catcher<'a, 'ctx, 'env>(
function_value
}
fn roc_result_layout<'a>(
fn roc_call_result_layout<'a>(
arena: &'a Bump,
return_layout: Layout<'a>,
target_info: TargetInfo,
@ -3981,14 +3998,14 @@ fn roc_result_layout<'a>(
Layout::struct_no_name_order(arena.alloc(elements))
}
fn roc_result_type<'a, 'ctx, 'env>(
fn roc_call_result_type<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
return_type: BasicTypeEnum<'ctx>,
) -> StructType<'ctx> {
env.context.struct_type(
&[
env.context.i64_type().into(),
env.context.i8_type().ptr_type(AddressSpace::Generic).into(),
zig_str_type(env).ptr_type(AddressSpace::Generic).into(),
return_type,
],
false,
@ -4003,7 +4020,7 @@ fn make_good_roc_result<'a, 'ctx, 'env>(
let context = env.context;
let builder = env.builder;
let v1 = roc_result_type(env, basic_type_from_layout(env, &return_layout)).const_zero();
let v1 = roc_call_result_type(env, basic_type_from_layout(env, &return_layout)).const_zero();
let v2 = builder
.build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error")
@ -4050,7 +4067,8 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
}
};
let wrapper_return_type = roc_result_type(env, basic_type_from_layout(env, &return_layout));
let wrapper_return_type =
roc_call_result_type(env, basic_type_from_layout(env, &return_layout));
// argument_types.push(wrapper_return_type.ptr_type(AddressSpace::Generic).into());
@ -5520,51 +5538,33 @@ fn define_global_str_literal<'a, 'ctx, 'env>(
}
}
fn define_global_error_str<'a, 'ctx, 'env>(
pub(crate) fn throw_internal_exception<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
message: &str,
) -> inkwell::values::GlobalValue<'ctx> {
let module = env.module;
// hash the name so we don't re-define existing messages
let name = {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
message.hash(&mut hasher);
let hash = hasher.finish();
format!("_Error_message_{}", hash)
};
match module.get_global(&name) {
Some(current) => current,
None => unsafe { env.builder.build_global_string(message, name.as_str()) },
}
}
pub(crate) fn throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, message: &str) {
) {
let builder = env.builder;
// define the error message as a global
// (a hash is used such that the same value is not defined repeatedly)
let error_msg_global = define_global_error_str(env, message);
let str = build_string_literal(env, parent, message);
let cast = env
.builder
.build_bitcast(
error_msg_global.as_pointer_value(),
env.context.i8_type().ptr_type(AddressSpace::Generic),
"cast_void",
)
.into_pointer_value();
env.call_panic(cast, PanicTagId::NullTerminatedString);
env.call_panic(env, str, CrashTag::Roc);
builder.build_unreachable();
}
pub(crate) fn throw_exception<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &mut Scope<'a, 'ctx>,
message: &Symbol,
tag: CrashTag,
) {
let msg_val = load_symbol(scope, message);
env.call_panic(env, msg_val, tag);
env.builder.build_unreachable();
}
fn get_foreign_symbol<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
foreign_symbol: roc_module::ident::ForeignSymbol,

View file

@ -104,6 +104,13 @@ pub(crate) fn finalize(env: &Env) {
.build_call(func, &[], "call_expect_failed_finalize");
}
pub(crate) fn send_dbg(env: &Env) {
let func = env.module.get_function(bitcode::UTILS_SEND_DBG).unwrap();
env.builder
.build_call(func, &[], "call_expect_failed_finalize");
}
pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,

View file

@ -1,5 +1,5 @@
use crate::llvm::bitcode::call_void_bitcode_fn;
use crate::llvm::build::{add_func, get_panic_msg_ptr, C_CALL_CONV};
use crate::llvm::build::{add_func, get_panic_msg_ptr, get_panic_tag_ptr, C_CALL_CONV};
use crate::llvm::build::{CCReturn, Env, FunctionSpec};
use inkwell::module::Linkage;
use inkwell::types::BasicType;
@ -193,10 +193,9 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
// already been defined by the builtins, which rely on it.
let fn_val = module.get_function("roc_panic").unwrap();
let mut params = fn_val.get_param_iter();
let ptr_arg = params.next().unwrap();
let roc_str_arg = params.next().unwrap();
// in debug mode, this is assumed to be NullTerminatedString
let _tag_id_arg = params.next().unwrap();
let tag_id_arg = params.next().unwrap();
debug_assert!(params.next().is_none());
@ -210,8 +209,38 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
builder.position_at_end(entry);
// write our error message pointer
env.builder.build_store(get_panic_msg_ptr(env), ptr_arg);
// write our error message to the RocStr pointer
{
let loaded_roc_str = match env.target_info.ptr_width() {
roc_target::PtrWidth::Bytes4 => roc_str_arg,
// On 64-bit we pass RocStrs by reference internally
roc_target::PtrWidth::Bytes8 => {
builder.build_load(roc_str_arg.into_pointer_value(), "load_roc_str")
}
};
env.builder
.build_store(get_panic_msg_ptr(env), loaded_roc_str);
}
// write the panic tag.
// increment by 1, since the tag we'll get from the Roc program is 0-based,
// but we use 0 for marking a successful call.
{
let cast_tag_id = builder.build_int_z_extend(
tag_id_arg.into_int_value(),
env.context.i64_type(),
"zext_panic_tag",
);
let inc_tag_id = builder.build_int_add(
cast_tag_id,
env.context.i64_type().const_int(1, false),
"inc_panic_tag",
);
env.builder.build_store(get_panic_tag_ptr(env), inc_tag_id);
}
build_longjmp_call(env);

View file

@ -41,9 +41,9 @@ use crate::llvm::{
},
};
use super::convert::zig_with_overflow_roc_dec;
use super::{build::throw_internal_exception, convert::zig_with_overflow_roc_dec};
use super::{
build::{load_symbol, load_symbol_and_layout, throw_exception, Env, Scope},
build::{load_symbol, load_symbol_and_layout, Env, Scope},
convert::zig_dec_type,
};
@ -1119,6 +1119,27 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
ptr.into()
}
},
Dbg => {
// now what
arguments!(condition);
if env.mode.runs_expects() {
let region = unsafe { std::mem::transmute::<_, roc_region::all::Region>(args[0]) };
crate::llvm::expect::clone_to_shared_memory(
env,
scope,
layout_ids,
args[0],
region,
&[args[0]],
);
crate::llvm::expect::send_dbg(env);
}
condition
}
}
}
@ -1536,7 +1557,7 @@ fn throw_on_overflow<'a, 'ctx, 'env>(
bd.position_at_end(throw_block);
throw_exception(env, message);
throw_internal_exception(env, parent, message);
bd.position_at_end(then_block);
@ -1982,8 +2003,9 @@ fn int_neg_raise_on_overflow<'a, 'ctx, 'env>(
builder.position_at_end(then_block);
throw_exception(
throw_internal_exception(
env,
parent,
"integer negation overflowed because its argument is the minimum value",
);
@ -2012,8 +2034,9 @@ fn int_abs_raise_on_overflow<'a, 'ctx, 'env>(
builder.position_at_end(then_block);
throw_exception(
throw_internal_exception(
env,
parent,
"integer absolute overflowed because its argument is the minimum value",
);

View file

@ -1,6 +1,7 @@
use std::ffi::CStr;
use std::mem::MaybeUninit;
use std::os::raw::c_char;
use roc_mono::ir::CrashTag;
use roc_std::RocStr;
/// This must have the same size as the repr() of RocCallResult!
pub const ROC_CALL_RESULT_DISCRIMINANT_SIZE: usize = std::mem::size_of::<u64>();
@ -8,7 +9,7 @@ pub const ROC_CALL_RESULT_DISCRIMINANT_SIZE: usize = std::mem::size_of::<u64>();
#[repr(C)]
pub struct RocCallResult<T> {
tag: u64,
error_msg: *mut c_char,
error_msg: *mut RocStr,
value: MaybeUninit<T>,
}
@ -32,14 +33,18 @@ impl<T: Default> Default for RocCallResult<T> {
}
}
impl<T: Sized> From<RocCallResult<T>> for Result<T, String> {
impl<T: Sized> From<RocCallResult<T>> for Result<T, (String, CrashTag)> {
fn from(call_result: RocCallResult<T>) -> Self {
match call_result.tag {
0 => Ok(unsafe { call_result.value.assume_init() }),
_ => Err({
let raw = unsafe { CStr::from_ptr(call_result.error_msg) };
n => Err({
let msg: &RocStr = unsafe { &*call_result.error_msg };
let tag = (n - 1) as u32;
let tag = tag
.try_into()
.unwrap_or_else(|_| panic!("received illegal tag: {tag}"));
raw.to_str().unwrap().to_owned()
(msg.as_str().to_owned(), tag)
}),
}
}
@ -120,7 +125,7 @@ macro_rules! run_jit_function {
$transform(success)
}
Err(error_msg) => {
Err((error_msg, _)) => {
eprintln!("This Roc code crashed with: \"{error_msg}\"");
Expr::MalformedClosure

View file

@ -208,3 +208,18 @@ The diagram below illustrates this process.
&nbsp;
![Diagram showing how host-to-app calls are linked.](./docs/host-to-app-calls.svg)
## Tips for debugging Wasm code generation
In general, WebAssembly runtimes often have terrible error messages. Especially command-line ones. And most especially Wasm3, which we use nonetheless because it's fast.
- Install the WABT (WebAssembly Binary Toolkit)
- We have a debug setting to dump out the test binary. In `gen_wasm/src/lib.rs`, set `DEBUG_LOG_SETTINGS.keep_test_binary` to `true`
- Run `wasm-validate` to make sure the module is valid WebAssembly
- Use `wasm-objdump` with options `-d`, `-x`, or `-s` depending on the issue
- Browsers are **much** better for debugging Wasm than any of the command line tools.
- I highly recommend this, even if you are more comfortable with the command line than the browser!
- Browsers have by far the best error messages and debugging tools. There is nothing comparable on the command line.
- We have a web page that can run gen_wasm unit tests:
crates/compiler/test_gen/src/helpers/debug-wasm-test.html
- The page itself contains instructions explaining how to open the browser debug tools. No web dev background should be required. If there's something useful missing, let Brian Carroll know or add him as a reviewer on a PR.

View file

@ -8,8 +8,8 @@ use roc_module::low_level::{LowLevel, LowLevelWrapperType};
use roc_module::symbol::{Interns, Symbol};
use roc_mono::code_gen_help::{CodeGenHelp, HelperOp, REFCOUNT_MAX};
use roc_mono::ir::{
BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, ModifyRc, Param, Proc,
ProcLayout, Stmt,
BranchInfo, CallType, CrashTag, Expr, JoinPointId, ListLiteralElement, Literal, ModifyRc,
Param, Proc, ProcLayout, Stmt,
};
use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout};
use roc_std::RocDec;
@ -717,7 +717,7 @@ impl<'a> WasmBackend<'a> {
Stmt::Expect { .. } => todo!("expect is not implemented in the wasm backend"),
Stmt::ExpectFx { .. } => todo!("expect-fx is not implemented in the wasm backend"),
Stmt::RuntimeError(msg) => self.stmt_runtime_error(msg),
Stmt::Crash(sym, tag) => self.stmt_crash(*sym, *tag),
}
}
@ -987,19 +987,31 @@ impl<'a> WasmBackend<'a> {
self.stmt(rc_stmt);
}
pub fn stmt_runtime_error(&mut self, msg: &'a str) {
// Create a zero-terminated version of the message string
let mut bytes = Vec::with_capacity_in(msg.len() + 1, self.env.arena);
bytes.extend_from_slice(msg.as_bytes());
bytes.push(0);
pub fn stmt_internal_error(&mut self, msg: &'a str) {
let msg_sym = self.create_symbol("panic_str");
let msg_storage = self.storage.allocate_var(
self.env.layout_interner,
Layout::Builtin(Builtin::Str),
msg_sym,
StoredVarKind::Variable,
);
// Store it in the app's data section
let elements_addr = self.store_bytes_in_data_section(&bytes);
// Store the message as a RocStr on the stack
let (local_id, offset) = match msg_storage {
StoredValue::StackMemory { location, .. } => {
location.local_and_offset(self.storage.stack_frame_pointer)
}
_ => internal_error!("String must always have stack memory"),
};
self.expr_string_literal(msg, local_id, offset);
// Pass its address to roc_panic
let tag_id = 0;
self.code_builder.i32_const(elements_addr as i32);
self.code_builder.i32_const(tag_id);
self.stmt_crash(msg_sym, CrashTag::Roc);
}
pub fn stmt_crash(&mut self, msg: Symbol, tag: CrashTag) {
// load the pointer
self.storage.load_symbols(&mut self.code_builder, &[msg]);
self.code_builder.i32_const(tag as _);
self.call_host_fn_after_loading_args("roc_panic", 2, false);
self.code_builder.unreachable_();
@ -1128,45 +1140,7 @@ impl<'a> WasmBackend<'a> {
let (local_id, offset) =
location.local_and_offset(self.storage.stack_frame_pointer);
let len = string.len();
if len < 12 {
// Construct the bytes of the small string
let mut bytes = [0; 12];
bytes[0..len].clone_from_slice(string.as_bytes());
bytes[11] = 0x80 | (len as u8);
// Transform into two integers, to minimise number of instructions
let bytes_split: &([u8; 8], [u8; 4]) =
unsafe { std::mem::transmute(&bytes) };
let int64 = i64::from_le_bytes(bytes_split.0);
let int32 = i32::from_le_bytes(bytes_split.1);
// Write the integers to memory
self.code_builder.get_local(local_id);
self.code_builder.i64_const(int64);
self.code_builder.i64_store(Align::Bytes4, offset);
self.code_builder.get_local(local_id);
self.code_builder.i32_const(int32);
self.code_builder.i32_store(Align::Bytes4, offset + 8);
} else {
let bytes = string.as_bytes();
let elements_addr = self.store_bytes_in_data_section(bytes);
// ptr
self.code_builder.get_local(local_id);
self.code_builder.i32_const(elements_addr as i32);
self.code_builder.i32_store(Align::Bytes4, offset);
// len
self.code_builder.get_local(local_id);
self.code_builder.i32_const(string.len() as i32);
self.code_builder.i32_store(Align::Bytes4, offset + 4);
// capacity
self.code_builder.get_local(local_id);
self.code_builder.i32_const(string.len() as i32);
self.code_builder.i32_store(Align::Bytes4, offset + 8);
};
self.expr_string_literal(string, local_id, offset);
}
// Bools and bytes should not be stored in the stack frame
Literal::Bool(_) | Literal::Byte(_) => invalid_error(),
@ -1177,6 +1151,47 @@ impl<'a> WasmBackend<'a> {
};
}
fn expr_string_literal(&mut self, string: &str, local_id: LocalId, offset: u32) {
let len = string.len();
if len < 12 {
// Construct the bytes of the small string
let mut bytes = [0; 12];
bytes[0..len].clone_from_slice(string.as_bytes());
bytes[11] = 0x80 | (len as u8);
// Transform into two integers, to minimise number of instructions
let bytes_split: &([u8; 8], [u8; 4]) = unsafe { std::mem::transmute(&bytes) };
let int64 = i64::from_le_bytes(bytes_split.0);
let int32 = i32::from_le_bytes(bytes_split.1);
// Write the integers to memory
self.code_builder.get_local(local_id);
self.code_builder.i64_const(int64);
self.code_builder.i64_store(Align::Bytes4, offset);
self.code_builder.get_local(local_id);
self.code_builder.i32_const(int32);
self.code_builder.i32_store(Align::Bytes4, offset + 8);
} else {
let bytes = string.as_bytes();
let elements_addr = self.store_bytes_in_data_section(bytes);
// ptr
self.code_builder.get_local(local_id);
self.code_builder.i32_const(elements_addr as i32);
self.code_builder.i32_store(Align::Bytes4, offset);
// len
self.code_builder.get_local(local_id);
self.code_builder.i32_const(string.len() as i32);
self.code_builder.i32_store(Align::Bytes4, offset + 4);
// capacity
self.code_builder.get_local(local_id);
self.code_builder.i32_const(string.len() as i32);
self.code_builder.i32_store(Align::Bytes4, offset + 8);
};
}
/// Create a string constant in the module data section
/// Return the data we need for code gen: linker symbol index and memory address
fn store_bytes_in_data_section(&mut self, bytes: &[u8]) -> u32 {

View file

@ -56,7 +56,8 @@ impl Env<'_> {
/// Parse the preprocessed host binary
/// If successful, the module can be passed to build_app_binary
pub fn parse_host<'a>(arena: &'a Bump, host_bytes: &[u8]) -> Result<WasmModule<'a>, ParseError> {
WasmModule::preload(arena, host_bytes)
let require_relocatable = true;
WasmModule::preload(arena, host_bytes, require_relocatable)
}
/// Generate a Wasm module in binary form, ready to write to a file. Entry point from roc_build.

View file

@ -1362,7 +1362,7 @@ impl<'a> LowLevelCall<'a> {
backend.code_builder.i32_const(i32::MIN);
backend.code_builder.i32_eq();
backend.code_builder.if_();
backend.stmt_runtime_error(PANIC_MSG);
backend.stmt_internal_error(PANIC_MSG);
backend.code_builder.end();
// x
@ -1388,7 +1388,7 @@ impl<'a> LowLevelCall<'a> {
backend.code_builder.i64_const(i64::MIN);
backend.code_builder.i64_eq();
backend.code_builder.if_();
backend.stmt_runtime_error(PANIC_MSG);
backend.stmt_internal_error(PANIC_MSG);
backend.code_builder.end();
// x
@ -1422,7 +1422,7 @@ impl<'a> LowLevelCall<'a> {
backend.code_builder.i32_const(i32::MIN);
backend.code_builder.i32_eq();
backend.code_builder.if_();
backend.stmt_runtime_error(PANIC_MSG);
backend.stmt_internal_error(PANIC_MSG);
backend.code_builder.end();
backend.code_builder.i32_const(0);
@ -1433,7 +1433,7 @@ impl<'a> LowLevelCall<'a> {
backend.code_builder.i64_const(i64::MIN);
backend.code_builder.i64_eq();
backend.code_builder.if_();
backend.stmt_runtime_error(PANIC_MSG);
backend.stmt_internal_error(PANIC_MSG);
backend.code_builder.end();
backend.code_builder.i64_const(0);
@ -1874,6 +1874,8 @@ impl<'a> LowLevelCall<'a> {
},
StoredValue::StackMemory { .. } => { /* do nothing */ }
},
Dbg => todo!("{:?}", self.lowlevel),
}
}

View file

@ -200,6 +200,11 @@ fn generate_entry_docs<'a>(
ValueDef::Body(_, _) => (),
ValueDef::Dbg { .. } => {
// Don't generate docs for `dbg`s
}
ValueDef::Expect { .. } => {
// Don't generate docs for `expect`s
}

View file

@ -7,7 +7,7 @@ use parking_lot::Mutex;
use roc_builtins::roc::module_source;
use roc_can::abilities::{AbilitiesStore, PendingAbilitiesStore, ResolvedImpl};
use roc_can::constraint::{Constraint as ConstraintSoa, Constraints, TypeOrVar};
use roc_can::expr::PendingDerives;
use roc_can::expr::{DbgLookup, PendingDerives};
use roc_can::expr::{Declarations, ExpectLookup};
use roc_can::module::{
canonicalize_module_defs, ExposedByModule, ExposedForModule, ExposedModuleTypes, Module,
@ -731,6 +731,7 @@ pub struct Expectations {
pub subs: roc_types::subs::Subs,
pub path: PathBuf,
pub expectations: VecMap<Region, Vec<ExpectLookup>>,
pub dbgs: VecMap<Symbol, DbgLookup>,
pub ident_ids: IdentIds,
}
@ -775,6 +776,7 @@ struct ParsedModule<'a> {
}
type LocExpects = VecMap<Region, Vec<ExpectLookup>>;
type LocDbgs = VecMap<Symbol, DbgLookup>;
/// A message sent out _from_ a worker thread,
/// representing a result of work done, or a request for further work
@ -794,6 +796,7 @@ enum Msg<'a> {
module_timing: ModuleTiming,
abilities_store: AbilitiesStore,
loc_expects: LocExpects,
loc_dbgs: LocDbgs,
},
FinishedAllTypeChecking {
solved_subs: Solved<Subs>,
@ -2403,6 +2406,7 @@ fn update<'a>(
mut module_timing,
abilities_store,
loc_expects,
loc_dbgs,
} => {
log!("solved types for {:?}", module_id);
module_timing.end_time = Instant::now();
@ -2412,7 +2416,7 @@ fn update<'a>(
.type_problems
.insert(module_id, solved_module.problems);
let should_include_expects = !loc_expects.is_empty() && {
let should_include_expects = (!loc_expects.is_empty() || !loc_dbgs.is_empty()) && {
let modules = state.arc_modules.lock();
modules
.package_eq(module_id, state.root_id)
@ -2424,6 +2428,7 @@ fn update<'a>(
let expectations = Expectations {
expectations: loc_expects,
dbgs: loc_dbgs,
subs: solved_subs.clone().into_inner(),
path: path.to_owned(),
ident_ids: ident_ids.clone(),
@ -4552,6 +4557,7 @@ fn run_solve<'a>(
let mut module = module;
let loc_expects = std::mem::take(&mut module.loc_expects);
let loc_dbgs = std::mem::take(&mut module.loc_dbgs);
let module = module;
let (solved_subs, solved_implementations, exposed_vars_by_symbol, problems, abilities_store) = {
@ -4626,6 +4632,7 @@ fn run_solve<'a>(
module_timing,
abilities_store,
loc_expects,
loc_dbgs,
}
}
@ -4832,6 +4839,7 @@ fn canonicalize_and_constrain<'a>(
rigid_variables: module_output.rigid_variables,
abilities_store: module_output.scope.abilities_store,
loc_expects: module_output.loc_expects,
loc_dbgs: module_output.loc_dbgs,
};
let constrained_module = ConstrainedModule {

View file

@ -112,6 +112,7 @@ pub enum LowLevel {
RefCountDec,
BoxExpr,
UnboxExpr,
Dbg,
Unreachable,
}
@ -208,11 +209,13 @@ macro_rules! map_symbol_to_lowlevel {
LowLevel::NumToIntChecked => unreachable!(),
LowLevel::NumToFloatChecked => unreachable!(),
// these are used internally and not tied to a symbol
LowLevel::Hash => unimplemented!(),
LowLevel::PtrCast => unimplemented!(),
LowLevel::RefCountInc => unimplemented!(),
LowLevel::RefCountDec => unimplemented!(),
LowLevel::Dbg => unreachable!(),
// these are not implemented, not sure why
LowLevel::StrFromInt => unimplemented!(),

View file

@ -321,7 +321,7 @@ impl<'a> ParamMap<'a> {
}
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
Ret(_) | Jump(_, _) | RuntimeError(_) => {
Ret(_) | Jump(_, _) | Crash(..) => {
// these are terminal, do nothing
}
}
@ -827,7 +827,12 @@ impl<'a> BorrowInfState<'a> {
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
Ret(_) | RuntimeError(_) => {
Crash(msg, _) => {
// Crash is a foreign call, so we must own the argument.
self.own_var(*msg);
}
Ret(_) => {
// these are terminal, do nothing
}
}
@ -937,6 +942,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListIsUnique => arena.alloc_slice_copy(&[borrowed]),
Dbg => arena.alloc_slice_copy(&[borrowed]),
BoxExpr | UnboxExpr => {
unreachable!("These lowlevel operations are turned into mono Expr's")
}
@ -999,7 +1006,7 @@ fn call_info_stmt<'a>(arena: &'a Bump, stmt: &Stmt<'a>, info: &mut CallInfo<'a>)
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
Ret(_) | Jump(_, _) | RuntimeError(_) => {
Ret(_) | Jump(_, _) | Crash(..) => {
// these are terminal, do nothing
}
}

View file

@ -158,7 +158,9 @@ pub fn occurring_variables(stmt: &Stmt<'_>) -> (MutSet<Symbol>, MutSet<Symbol>)
stack.push(default_branch.1);
}
RuntimeError(_) => {}
Crash(sym, _) => {
result.insert(*sym);
}
}
}
@ -1240,7 +1242,20 @@ impl<'a, 'i> Context<'a, 'i> {
(expect, b_live_vars)
}
RuntimeError(_) | Refcounting(_, _) => (stmt, MutSet::default()),
Crash(x, _) => {
let info = self.get_var_info(*x);
let mut live_vars = MutSet::default();
live_vars.insert(*x);
if info.reference && !info.consume {
(self.add_inc(*x, 1, stmt), live_vars)
} else {
(stmt, live_vars)
}
}
Refcounting(_, _) => (stmt, MutSet::default()),
}
}
}
@ -1411,7 +1426,10 @@ pub fn collect_stmt(
vars
}
RuntimeError(_) => vars,
Crash(m, _) => {
vars.insert(*m);
vars
}
}
}

View file

@ -71,6 +71,16 @@ roc_error_macros::assert_sizeof_non_wasm!(ProcLayout, 8 * 8);
roc_error_macros::assert_sizeof_non_wasm!(Call, 9 * 8);
roc_error_macros::assert_sizeof_non_wasm!(CallType, 7 * 8);
fn runtime_error<'a>(env: &mut Env<'a, '_>, msg: &'a str) -> Stmt<'a> {
let sym = env.unique_symbol();
Stmt::Let(
sym,
Expr::Literal(Literal::Str(msg)),
Layout::Builtin(Builtin::Str),
env.arena.alloc(Stmt::Crash(sym, CrashTag::Roc)),
)
}
macro_rules! return_on_layout_error {
($env:expr, $layout_result:expr, $context_msg:expr) => {
match $layout_result {
@ -84,15 +94,17 @@ macro_rules! return_on_layout_error_help {
($env:expr, $error:expr, $context_msg:expr) => {{
match $error {
LayoutProblem::UnresolvedTypeVar(_) => {
return Stmt::RuntimeError(
return runtime_error(
$env,
$env.arena
.alloc(format!("UnresolvedTypeVar: {}", $context_msg,)),
);
)
}
LayoutProblem::Erroneous => {
return Stmt::RuntimeError(
return runtime_error(
$env,
$env.arena.alloc(format!("Erroneous: {}", $context_msg,)),
);
)
}
}
}};
@ -1611,6 +1623,7 @@ pub fn cond<'a>(
}
pub type Stores<'a> = &'a [(Symbol, Layout<'a>, Expr<'a>)];
#[derive(Clone, Debug, PartialEq)]
pub enum Stmt<'a> {
Let(Symbol, Expr<'a>, Layout<'a>, &'a Stmt<'a>),
@ -1655,7 +1668,29 @@ pub enum Stmt<'a> {
remainder: &'a Stmt<'a>,
},
Jump(JoinPointId, &'a [Symbol]),
RuntimeError(&'a str),
Crash(Symbol, CrashTag),
}
/// Source of crash, and its runtime representation to roc_panic.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum CrashTag {
/// The crash is due to Roc, either via a builtin or type error.
Roc = 0,
/// The crash is user-defined.
User = 1,
}
impl TryFrom<u32> for CrashTag {
type Error = ();
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Roc),
1 => Ok(Self::User),
_ => Err(()),
}
}
}
/// in the block below, symbol `scrutinee` is assumed be be of shape `tag_id`
@ -2303,7 +2338,7 @@ impl<'a> Stmt<'a> {
}
}
RuntimeError(s) => alloc.text(format!("Error {}", s)),
Crash(s, _src) => alloc.text("Crash ").append(symbol_to_doc(alloc, *s)),
Join {
id,
@ -3152,7 +3187,7 @@ fn generate_runtime_error_function<'a>(
);
});
let runtime_error = Stmt::RuntimeError(msg.into_bump_str());
let runtime_error = runtime_error(env, msg.into_bump_str());
let (args, ret_layout) = match layout {
RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout) => {
@ -4300,7 +4335,7 @@ pub fn with_hole<'a>(
};
let sorted_fields = match sorted_fields_result {
Ok(fields) => fields,
Err(_) => return Stmt::RuntimeError("Can't create record with improper layout"),
Err(_) => return runtime_error(env, "Can't create record with improper layout"),
};
let mut field_symbols = Vec::with_capacity_in(fields.len(), env.arena);
@ -4352,7 +4387,7 @@ pub fn with_hole<'a>(
// creating a record from the var will unpack it if it's just a single field.
let layout = match layout_cache.from_var(env.arena, record_var, env.subs) {
Ok(layout) => layout,
Err(_) => return Stmt::RuntimeError("Can't create record with improper layout"),
Err(_) => return runtime_error(env, "Can't create record with improper layout"),
};
let field_symbols = field_symbols.into_bump_slice();
@ -4403,6 +4438,7 @@ pub fn with_hole<'a>(
Expect { .. } => unreachable!("I think this is unreachable"),
ExpectFx { .. } => unreachable!("I think this is unreachable"),
Dbg { .. } => unreachable!("I think this is unreachable"),
If {
cond_var,
@ -4530,8 +4566,8 @@ pub fn with_hole<'a>(
}
}
}
(Err(_), _) => Stmt::RuntimeError("invalid ret_layout"),
(_, Err(_)) => Stmt::RuntimeError("invalid cond_layout"),
(Err(_), _) => runtime_error(env, "invalid ret_layout"),
(_, Err(_)) => runtime_error(env, "invalid cond_layout"),
}
}
@ -4697,7 +4733,7 @@ pub fn with_hole<'a>(
};
let sorted_fields = match sorted_fields_result {
Ok(fields) => fields,
Err(_) => return Stmt::RuntimeError("Can't access record with improper layout"),
Err(_) => return runtime_error(env, "Can't access record with improper layout"),
};
let mut index = None;
@ -4815,7 +4851,8 @@ pub fn with_hole<'a>(
}
}
Err(_error) => Stmt::RuntimeError(
Err(_error) => runtime_error(
env,
"TODO convert anonymous function error to a RuntimeError string",
),
}
@ -4871,7 +4908,8 @@ pub fn with_hole<'a>(
}
}
Err(_error) => Stmt::RuntimeError(
Err(_error) => runtime_error(
env,
"TODO convert anonymous function error to a RuntimeError string",
),
}
@ -4906,7 +4944,7 @@ pub fn with_hole<'a>(
let sorted_fields = match sorted_fields_result {
Ok(fields) => fields,
Err(_) => return Stmt::RuntimeError("Can't update record with improper layout"),
Err(_) => return runtime_error(env, "Can't update record with improper layout"),
};
let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
@ -5092,10 +5130,10 @@ pub fn with_hole<'a>(
layout_cache,
);
if let Err(runtime_error) = inserted {
return Stmt::RuntimeError(
env.arena
.alloc(format!("RuntimeError: {:?}", runtime_error,)),
if let Err(e) = inserted {
return runtime_error(
env,
env.arena.alloc(format!("RuntimeError: {:?}", e,)),
);
} else {
drop(inserted);
@ -5559,8 +5597,20 @@ pub fn with_hole<'a>(
}
}
}
TypedHole(_) => Stmt::RuntimeError("Hit a blank"),
RuntimeError(e) => Stmt::RuntimeError(env.arena.alloc(e.runtime_message())),
TypedHole(_) => runtime_error(env, "Hit a blank"),
RuntimeError(e) => runtime_error(env, env.arena.alloc(e.runtime_message())),
Crash { msg, ret_var: _ } => {
let msg_sym = possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
&msg.value,
Variable::STR,
);
let stmt = Stmt::Crash(msg_sym, CrashTag::User);
assign_to_symbol(env, procs, layout_cache, Variable::STR, *msg, msg_sym, stmt)
}
}
}
@ -5819,16 +5869,22 @@ fn convert_tag_union<'a>(
let variant = match res_variant {
Ok(cached) => cached,
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
return Stmt::RuntimeError(env.arena.alloc(format!(
"Unresolved type variable for tag {}",
tag_name.0.as_str()
)))
return runtime_error(
env,
env.arena.alloc(format!(
"Unresolved type variable for tag {}",
tag_name.0.as_str()
)),
)
}
Err(LayoutProblem::Erroneous) => {
return Stmt::RuntimeError(env.arena.alloc(format!(
"Tag {} was part of a type error!",
tag_name.0.as_str()
)));
return runtime_error(
env,
env.arena.alloc(format!(
"Tag {} was part of a type error!",
tag_name.0.as_str()
)),
);
}
};
@ -5856,7 +5912,7 @@ fn convert_tag_union<'a>(
Layout::Builtin(Builtin::Int(IntWidth::U8)),
hole,
),
None => Stmt::RuntimeError("tag must be in its own type"),
None => runtime_error(env, "tag must be in its own type"),
}
}
@ -5896,7 +5952,7 @@ fn convert_tag_union<'a>(
if dataful_tag != tag_name {
// this tag is not represented, and hence will never be reached, at runtime.
Stmt::RuntimeError("voided tag constructor is unreachable")
runtime_error(env, "voided tag constructor is unreachable")
} else {
let field_symbols_temp = sorted_field_symbols(env, procs, layout_cache, args);
@ -6160,10 +6216,13 @@ fn tag_union_to_function<'a>(
}
}
Err(runtime_error) => Stmt::RuntimeError(env.arena.alloc(format!(
"Could not produce tag function due to a runtime error: {:?}",
runtime_error,
))),
Err(e) => runtime_error(
env,
env.arena.alloc(format!(
"Could not produce tag function due to a runtime error: {:?}",
e,
)),
),
}
}
@ -6546,6 +6605,50 @@ pub fn from_can<'a>(
stmt
}
Dbg {
loc_condition,
loc_continuation,
variable,
symbol: dbg_symbol,
} => {
let rest = from_can(env, variable, loc_continuation.value, procs, layout_cache);
let call = crate::ir::Call {
call_type: CallType::LowLevel {
op: LowLevel::Dbg,
update_mode: env.next_update_mode_id(),
},
arguments: env.arena.alloc([dbg_symbol]),
};
let dbg_layout = layout_cache
.from_var(env.arena, variable, env.subs)
.expect("invalid dbg_layout");
let expr = Expr::Call(call);
let mut stmt = Stmt::Let(dbg_symbol, expr, dbg_layout, env.arena.alloc(rest));
let symbol_is_reused = matches!(
can_reuse_symbol(env, procs, &loc_condition.value, variable),
ReuseSymbol::Value(_)
);
// skip evaluating the condition if it's just a symbol
if !symbol_is_reused {
stmt = with_hole(
env,
loc_condition.value,
variable,
procs,
layout_cache,
dbg_symbol,
env.arena.alloc(stmt),
);
}
stmt
}
LetRec(defs, cont, _cycle_mark) => {
// because Roc is strict, only functions can be recursive!
for def in defs.into_iter() {
@ -6677,7 +6780,7 @@ fn from_can_when<'a>(
if branches.is_empty() {
// A when-expression with no branches is a runtime error.
// We can't know what to return!
return Stmt::RuntimeError("Hit a 0-branch when expression");
return runtime_error(env, "Hit a 0-branch when expression");
}
let opt_branches = to_opt_branches(env, procs, branches, exhaustive_mark, layout_cache);
@ -6980,8 +7083,7 @@ fn substitute_in_stmt_help<'a>(
None
}
}
RuntimeError(_) => None,
Crash(msg, tag) => substitute(subs, *msg).map(|new| &*arena.alloc(Crash(new, *tag))),
}
}
@ -8364,7 +8466,7 @@ fn evaluate_arguments_then_runtime_error<'a>(
let arena = env.arena;
// eventually we will throw this runtime error
let result = Stmt::RuntimeError(env.arena.alloc(msg));
let result = runtime_error(env, env.arena.alloc(msg));
// but, we also still evaluate and specialize the arguments to give better error messages
let arg_symbols = Vec::from_iter_in(
@ -8589,7 +8691,7 @@ fn call_by_name_help<'a>(
Err(_) => {
// One of this function's arguments code gens to a runtime error,
// so attempting to call it will immediately crash.
return Stmt::RuntimeError("TODO runtime error for invalid layout");
return runtime_error(env, "TODO runtime error for invalid layout");
}
}
}
@ -10088,7 +10190,7 @@ where
ToLowLevelCall: Fn(ToLowLevelCallArguments<'a>) -> Call<'a> + Copy,
{
match lambda_set.call_by_name_options(&layout_cache.interner) {
ClosureCallOptions::Void => empty_lambda_set_error(),
ClosureCallOptions::Void => empty_lambda_set_error(env),
ClosureCallOptions::Union(union_layout) => {
let closure_tag_id_symbol = env.unique_symbol();
@ -10267,9 +10369,9 @@ where
}
}
fn empty_lambda_set_error() -> Stmt<'static> {
fn empty_lambda_set_error<'a>(env: &mut Env<'a, '_>) -> Stmt<'a> {
let msg = "a Lambda Set is empty. Most likely there is a type error in your program.";
Stmt::RuntimeError(msg)
runtime_error(env, msg)
}
/// Use the lambda set to figure out how to make a call-by-name
@ -10287,7 +10389,7 @@ fn match_on_lambda_set<'a>(
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
match lambda_set.call_by_name_options(&layout_cache.interner) {
ClosureCallOptions::Void => empty_lambda_set_error(),
ClosureCallOptions::Void => empty_lambda_set_error(env),
ClosureCallOptions::Union(union_layout) => {
let closure_tag_id_symbol = env.unique_symbol();
@ -10441,7 +10543,7 @@ fn union_lambda_set_to_switch<'a>(
// there is really nothing we can do here. We generate a runtime error here which allows
// code gen to proceed. We then assume that we hit another (more descriptive) error before
// hitting this one
return empty_lambda_set_error();
return empty_lambda_set_error(env);
}
let join_point_id = JoinPointId(env.unique_symbol());

View file

@ -241,7 +241,7 @@ fn function_s<'a, 'i>(
}
}
Ret(_) | Jump(_, _) | RuntimeError(_) => stmt,
Ret(_) | Jump(_, _) | Crash(..) => stmt,
}
}
@ -535,7 +535,7 @@ fn function_d_main<'a, 'i>(
(arena.alloc(new_join), found)
}
Ret(_) | Jump(_, _) | RuntimeError(_) => (stmt, has_live_var(&env.jp_live_vars, stmt, x)),
Ret(_) | Jump(_, _) | Crash(..) => (stmt, has_live_var(&env.jp_live_vars, stmt, x)),
}
}
@ -696,7 +696,7 @@ fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a>
arena.alloc(expect)
}
Ret(_) | Jump(_, _) | RuntimeError(_) => {
Ret(_) | Jump(_, _) | Crash(..) => {
// terminals
stmt
}
@ -761,7 +761,7 @@ fn has_live_var<'a>(jp_live_vars: &JPLiveVarMap, stmt: &'a Stmt<'a>, needle: Sym
Jump(id, arguments) => {
arguments.iter().any(|s| *s == needle) || jp_live_vars[id].contains(&needle)
}
RuntimeError(_) => false,
Crash(m, _) => *m == needle,
}
}

View file

@ -299,6 +299,6 @@ fn insert_jumps<'a>(
Ret(_) => None,
Jump(_, _) => None,
RuntimeError(_) => None,
Crash(..) => None,
}
}

View file

@ -5,6 +5,7 @@
"as"
"is"
"expect"
"dbg"
"app"
"platform"

View file

@ -196,6 +196,9 @@ pub enum Expr<'a> {
Underscore(&'a str),
// The "crash" keyword
Crash,
// Tags
Tag(&'a str),
@ -208,6 +211,7 @@ pub enum Expr<'a> {
Defs(&'a Defs<'a>, &'a Loc<Expr<'a>>),
Backpassing(&'a [Loc<Pattern<'a>>], &'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>),
Expect(&'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>),
Dbg(&'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>),
// Application
/// To apply by name, do Apply(Var(...), ...)
@ -339,6 +343,11 @@ pub enum ValueDef<'a> {
body_expr: &'a Loc<Expr<'a>>,
},
Dbg {
condition: &'a Loc<Expr<'a>>,
preceding_comment: Region,
},
Expect {
condition: &'a Loc<Expr<'a>>,
preceding_comment: Region,
@ -527,6 +536,13 @@ pub enum TypeAnnotation<'a> {
ext: Option<&'a Loc<TypeAnnotation<'a>>>,
},
Tuple {
fields: Collection<'a, Loc<TypeAnnotation<'a>>>,
/// The row type variable in an open tuple, e.g. the `r` in `( Str, Str )r`.
/// This is None if it's a closed tuple annotation like `( Str, Str )`.
ext: Option<&'a Loc<TypeAnnotation<'a>>>,
},
/// A tag union, e.g. `[
TagUnion {
/// The row type variable in an open tag union, e.g. the `a` in `[Foo, Bar]a`.

View file

@ -6,7 +6,7 @@ use crate::blankspace::{
space0_after_e, space0_around_e_no_after_indent_check, space0_around_ee, space0_before_e,
space0_before_optional_after, space0_e,
};
use crate::ident::{lowercase_ident, parse_ident, Ident};
use crate::ident::{integer_ident, lowercase_ident, parse_ident, Accessor, Ident};
use crate::keyword;
use crate::parser::{
self, backtrackable, increment_min_indent, line_min_indent, optional, reset_min_indent,
@ -124,13 +124,9 @@ fn loc_expr_in_parens_etc_help<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>
map_with_arena!(
loc!(and!(
specialize(EExpr::InParens, loc_expr_in_parens_help()),
one_of![record_field_access_chain(), |a, s, _m| Ok((
NoProgress,
Vec::new_in(a),
s
))]
record_field_access_chain()
)),
move |arena: &'a Bump, value: Loc<(Loc<Expr<'a>>, Vec<'a, &'a str>)>| {
move |arena: &'a Bump, value: Loc<(Loc<Expr<'a>>, Vec<'a, Accessor<'a>>)>| {
let Loc {
mut region,
value: (loc_expr, field_accesses),
@ -143,12 +139,7 @@ fn loc_expr_in_parens_etc_help<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>
if field_accesses.is_empty() {
region = loc_expr.region;
} else {
for field in field_accesses {
// Wrap the previous answer in the new one, so we end up
// with a nested Expr. That way, `foo.bar.baz` gets represented
// in the AST as if it had been written (foo.bar).baz all along.
value = Expr::RecordAccess(arena.alloc(value), field);
}
value = apply_expr_access_chain(arena, value, field_accesses);
}
Loc::at(region, value)
@ -156,39 +147,17 @@ fn loc_expr_in_parens_etc_help<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>
)
}
fn record_field_access_chain<'a>() -> impl Parser<'a, Vec<'a, &'a str>, EExpr<'a>> {
|arena, state: State<'a>, min_indent| match record_field_access().parse(
arena,
state.clone(),
min_indent,
) {
Ok((_, initial, state)) => {
let mut accesses = Vec::with_capacity_in(1, arena);
accesses.push(initial);
let mut loop_state = state;
loop {
match record_field_access().parse(arena, loop_state.clone(), min_indent) {
Ok((_, next, state)) => {
accesses.push(next);
loop_state = state;
}
Err((MadeProgress, fail)) => return Err((MadeProgress, fail)),
Err((NoProgress, _)) => return Ok((MadeProgress, accesses, loop_state)),
}
}
}
Err((MadeProgress, fail)) => Err((MadeProgress, fail)),
Err((NoProgress, _)) => Err((NoProgress, EExpr::Access(state.pos()))),
}
}
fn record_field_access<'a>() -> impl Parser<'a, &'a str, EExpr<'a>> {
skip_first!(
fn record_field_access_chain<'a>() -> impl Parser<'a, Vec<'a, Accessor<'a>>, EExpr<'a>> {
zero_or_more!(skip_first!(
word1(b'.', EExpr::Access),
specialize(|_, pos| EExpr::Access(pos), lowercase_ident())
)
specialize(
|_, pos| EExpr::Access(pos),
one_of!(
map!(lowercase_ident(), Accessor::RecordField),
map!(integer_ident(), Accessor::TupleIndex),
)
)
))
}
/// In some contexts we want to parse the `_` as an expression, so it can then be turned into a
@ -204,6 +173,7 @@ fn loc_term_or_underscore_or_conditional<'a>(
loc!(specialize(EExpr::SingleQuote, single_quote_literal_help())),
loc!(specialize(EExpr::Number, positive_number_literal_help())),
loc!(specialize(EExpr::Closure, closure_help(options))),
loc!(crash_kw()),
loc!(underscore_expression()),
loc!(record_literal_help()),
loc!(specialize(EExpr::List, list_literal_help())),
@ -269,6 +239,15 @@ fn underscore_expression<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
}
}
fn crash_kw<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
move |arena: &'a Bump, state: State<'a>, min_indent: u32| {
let (_, _, next_state) = crate::parser::keyword_e(crate::keyword::CRASH, EExpr::Crash)
.parse(arena, state, min_indent)?;
Ok((MadeProgress, Expr::Crash, next_state))
}
}
fn loc_possibly_negative_or_negated_term<'a>(
options: ExprParseOptions,
) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
@ -327,6 +306,7 @@ fn expr_start<'a>(options: ExprParseOptions) -> impl Parser<'a, Loc<Expr<'a>>, E
loc!(specialize(EExpr::If, if_expr_help(options))),
loc!(specialize(EExpr::When, when::expr_help(options))),
loc!(specialize(EExpr::Expect, expect_help(options))),
loc!(specialize(EExpr::Dbg, dbg_help(options))),
loc!(specialize(EExpr::Closure, closure_help(options))),
loc!(expr_operator_chain(options)),
fail_expr_start_e()
@ -586,6 +566,7 @@ pub fn parse_single_def<'a>(
let start = state.pos();
let parse_dbg = crate::parser::keyword_e(crate::keyword::DBG, EExpect::Dbg);
let parse_expect_vanilla = crate::parser::keyword_e(crate::keyword::EXPECT, EExpect::Expect);
let parse_expect_fx = crate::parser::keyword_e(crate::keyword::EXPECT_FX, EExpect::Expect);
let parse_expect = either!(parse_expect_fx, parse_expect_vanilla);
@ -596,37 +577,35 @@ pub fn parse_single_def<'a>(
min_indent,
) {
Err((NoProgress, _)) => {
match parse_expect.parse(arena, state, min_indent) {
match parse_expect.parse(arena, state.clone(), min_indent) {
Err((_, _)) => {
// a hacky way to get expression-based error messages. TODO fix this
Ok((NoProgress, None, initial))
}
Ok((_, expect_flavor, state)) => {
let parse_def_expr =
space0_before_e(increment_min_indent(loc_expr()), EExpr::IndentEnd);
let (_, loc_def_expr, state) =
parse_def_expr.parse(arena, state, min_indent)?;
let end = loc_def_expr.region.end();
let region = Region::new(start, end);
// drop newlines before the preceding comment
let spaces_before_start = spaces_before_current_start.offset as usize;
let spaces_before_end = start.offset as usize;
let mut spaces_before_current_start = spaces_before_current_start;
for byte in &state.original_bytes()[spaces_before_start..spaces_before_end] {
match byte {
b' ' | b'\n' => {
spaces_before_current_start.offset += 1;
}
_ => break,
match parse_dbg.parse(arena, state, min_indent) {
Ok((_, _, state)) => parse_statement_inside_def(
arena,
state,
min_indent,
start,
spaces_before_current_start,
spaces_before_current,
|preceding_comment, loc_def_expr| ValueDef::Dbg {
condition: arena.alloc(loc_def_expr),
preceding_comment,
},
),
Err((_, _)) => {
// a hacky way to get expression-based error messages. TODO fix this
Ok((NoProgress, None, initial))
}
}
let preceding_comment = Region::new(spaces_before_current_start, start);
let value_def = match expect_flavor {
}
Ok((_, expect_flavor, state)) => parse_statement_inside_def(
arena,
state,
min_indent,
start,
spaces_before_current_start,
spaces_before_current,
|preceding_comment, loc_def_expr| match expect_flavor {
Either::Second(_) => ValueDef::Expect {
condition: arena.alloc(loc_def_expr),
preceding_comment,
@ -635,18 +614,8 @@ pub fn parse_single_def<'a>(
condition: arena.alloc(loc_def_expr),
preceding_comment,
},
};
Ok((
MadeProgress,
Some(SingleDef {
type_or_value: Either::Second(value_def),
region,
spaces_before: spaces_before_current,
}),
state,
))
}
},
),
}
}
Err((MadeProgress, _)) => {
@ -870,6 +839,49 @@ pub fn parse_single_def<'a>(
}
}
/// e.g. Things that can be on their own line in a def, e.g. `expect`, `expect-fx`, or `dbg`
fn parse_statement_inside_def<'a>(
arena: &'a Bump,
state: State<'a>,
min_indent: u32,
start: Position,
spaces_before_current_start: Position,
spaces_before_current: &'a [CommentOrNewline<'a>],
get_value_def: impl Fn(Region, Loc<Expr<'a>>) -> ValueDef<'a>,
) -> Result<(Progress, Option<SingleDef<'a>>, State<'a>), (Progress, EExpr<'a>)> {
let parse_def_expr = space0_before_e(increment_min_indent(loc_expr()), EExpr::IndentEnd);
let (_, loc_def_expr, state) = parse_def_expr.parse(arena, state, min_indent)?;
let end = loc_def_expr.region.end();
let region = Region::new(start, end);
// drop newlines before the preceding comment
let spaces_before_start = spaces_before_current_start.offset as usize;
let spaces_before_end = start.offset as usize;
let mut spaces_before_current_start = spaces_before_current_start;
for byte in &state.original_bytes()[spaces_before_start..spaces_before_end] {
match byte {
b' ' | b'\n' => {
spaces_before_current_start.offset += 1;
}
_ => break,
}
}
let preceding_comment = Region::new(spaces_before_current_start, start);
let value_def = get_value_def(preceding_comment, loc_def_expr);
Ok((
MadeProgress,
Some(SingleDef {
type_or_value: Either::Second(value_def),
region,
spaces_before: spaces_before_current,
}),
state,
))
}
// This is a macro only because trying to make it be a function caused lifetime issues.
#[macro_export]
macro_rules! join_ann_to_body {
@ -1880,10 +1892,12 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
| Expr::If(_, _)
| Expr::When(_, _)
| Expr::Expect(_, _)
| Expr::Dbg(_, _)
| Expr::MalformedClosure
| Expr::PrecedenceConflict { .. }
| Expr::RecordUpdate { .. }
| Expr::UnaryOp(_, _) => Err(()),
| Expr::UnaryOp(_, _)
| Expr::Crash => Err(()),
Expr::Str(string) => Ok(Pattern::StrLiteral(*string)),
Expr::SingleQuote(string) => Ok(Pattern::SingleQuote(string)),
@ -2298,6 +2312,36 @@ fn expect_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EExpe
}
}
fn dbg_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EExpect<'a>> {
move |arena: &'a Bump, state: State<'a>, min_indent| {
let start_column = state.column();
let (_, _, state) =
parser::keyword_e(keyword::DBG, EExpect::Dbg).parse(arena, state, min_indent)?;
let (_, condition, state) = space0_before_e(
specialize_ref(
EExpect::Condition,
set_min_indent(start_column + 1, expr_start(options)),
),
EExpect::IndentCondition,
)
.parse(arena, state, start_column + 1)
.map_err(|(_, f)| (MadeProgress, f))?;
let parse_cont = specialize_ref(
EExpect::Continuation,
space0_before_e(loc_expr(), EExpr::IndentEnd),
);
let (_, loc_cont, state) = parse_cont.parse(arena, state, min_indent)?;
let expr = Expr::Dbg(arena.alloc(condition), arena.alloc(loc_cont));
Ok((MadeProgress, expr, state))
}
}
fn if_expr_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EIf<'a>> {
move |arena: &'a Bump, state, min_indent| {
let (_, _, state) =
@ -2557,13 +2601,13 @@ fn record_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
and!(
loc!(specialize(EExpr::Record, record_help())),
// there can be field access, e.g. `{ x : 4 }.x`
optional(record_field_access_chain())
record_field_access_chain()
),
move |arena, state, _, (loc_record, accesses)| {
move |arena, state, _, (loc_record, accessors)| {
let (opt_update, loc_assigned_fields_with_comments) = loc_record.value;
// This is a record literal, not a destructure.
let mut value = match opt_update {
let value = match opt_update {
Some(update) => Expr::RecordUpdate {
update: &*arena.alloc(update),
fields: Collection::with_items_and_comments(
@ -2579,20 +2623,26 @@ fn record_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
)),
};
if let Some(fields) = accesses {
for field in fields {
// Wrap the previous answer in the new one, so we end up
// with a nested Expr. That way, `foo.bar.baz` gets represented
// in the AST as if it had been written (foo.bar).baz all along.
value = Expr::RecordAccess(arena.alloc(value), field);
}
}
let value = apply_expr_access_chain(arena, value, accessors);
Ok((MadeProgress, value, state))
},
)
}
fn apply_expr_access_chain<'a>(
arena: &'a Bump,
value: Expr<'a>,
accessors: Vec<'a, Accessor<'a>>,
) -> Expr<'a> {
accessors
.into_iter()
.fold(value, |value, accessor| match accessor {
Accessor::RecordField(field) => Expr::RecordAccess(arena.alloc(value), field),
Accessor::TupleIndex(field) => Expr::TupleAccess(arena.alloc(value), field),
})
}
fn string_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EString<'a>> {
map!(crate::string_literal::parse(), Expr::Str)
}

View file

@ -100,6 +100,17 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, ()> {
}
}
/// This is a tuple accessor, e.g. "1" in `.1`
pub fn integer_ident<'a>() -> impl Parser<'a, &'a str, ()> {
move |_, state: State<'a>, _min_indent: u32| match chomp_integer_part(state.bytes()) {
Err(progress) => Err((progress, ())),
Ok(ident) => {
let width = ident.len();
Ok((MadeProgress, ident, state.advance(width)))
}
}
}
pub fn tag_name<'a>() -> impl Parser<'a, &'a str, ()> {
move |arena, state: State<'a>, min_indent: u32| {
uppercase_ident().parse(arena, state, min_indent)

View file

@ -4,7 +4,9 @@ pub const ELSE: &str = "else";
pub const WHEN: &str = "when";
pub const AS: &str = "as";
pub const IS: &str = "is";
pub const DBG: &str = "dbg";
pub const EXPECT: &str = "expect";
pub const EXPECT_FX: &str = "expect-fx";
pub const CRASH: &str = "crash";
pub const KEYWORDS: [&str; 8] = [IF, THEN, ELSE, WHEN, AS, IS, EXPECT, EXPECT_FX];
pub const KEYWORDS: [&str; 10] = [IF, THEN, ELSE, WHEN, AS, IS, DBG, EXPECT, EXPECT_FX, CRASH];

View file

@ -354,9 +354,11 @@ pub enum EExpr<'a> {
If(EIf<'a>, Position),
Expect(EExpect<'a>, Position),
Dbg(EExpect<'a>, Position),
Closure(EClosure<'a>, Position),
Underscore(Position),
Crash(Position),
InParens(EInParens<'a>, Position),
Record(ERecord<'a>, Position),
@ -544,6 +546,7 @@ pub enum EIf<'a> {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EExpect<'a> {
Space(BadInputError, Position),
Dbg(Position),
Expect(Position),
Condition(&'a EExpr<'a>, Position),
Continuation(&'a EExpr<'a>, Position),
@ -672,6 +675,9 @@ pub enum ETypeTagUnion<'a> {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ETypeInParens<'a> {
/// e.g. (), which isn't a valid type
Empty(Position),
End(Position),
Open(Position),
///
@ -1784,7 +1790,7 @@ macro_rules! one_or_more {
move |arena, state: State<'a>, min_indent: u32| {
use bumpalo::collections::Vec;
match $parser.parse(arena, state, min_indent) {
match $parser.parse(arena, state.clone(), min_indent) {
Ok((_, first_output, next_state)) => {
let mut state = next_state;
let mut buf = Vec::with_capacity_in(1, arena);
@ -1802,14 +1808,12 @@ macro_rules! one_or_more {
return Ok((MadeProgress, buf, old_state));
}
Err((MadeProgress, fail)) => {
return Err((MadeProgress, fail, old_state));
return Err((MadeProgress, fail));
}
}
}
}
Err((progress, _, new_state)) => {
Err((progress, $to_error(new_state.pos), new_state))
}
Err((progress, _)) => Err((progress, $to_error(state.pos()))),
}
}
};

View file

@ -116,7 +116,7 @@ fn term<'a>(stop_at_surface_has: bool) -> impl Parser<'a, Loc<TypeAnnotation<'a>
one_of!(
loc_wildcard(),
loc_inferred(),
specialize(EType::TInParens, loc_type_in_parens()),
specialize(EType::TInParens, loc_type_in_parens(stop_at_surface_has)),
loc!(specialize(EType::TRecord, record_type(stop_at_surface_has))),
loc!(specialize(
EType::TTagUnion,
@ -185,7 +185,7 @@ fn loc_applied_arg<'a>(
one_of!(
loc_wildcard(),
loc_inferred(),
specialize(EType::TInParens, loc_type_in_parens()),
specialize(EType::TInParens, loc_type_in_parens(stop_at_surface_has)),
loc!(specialize(EType::TRecord, record_type(stop_at_surface_has))),
loc!(specialize(
EType::TTagUnion,
@ -206,16 +206,45 @@ fn loc_applied_arg<'a>(
)
}
fn loc_type_in_parens<'a>() -> impl Parser<'a, Loc<TypeAnnotation<'a>>, ETypeInParens<'a>> {
between!(
word1(b'(', ETypeInParens::Open),
space0_around_ee(
specialize_ref(ETypeInParens::Type, expression(true, false)),
ETypeInParens::IndentOpen,
ETypeInParens::IndentEnd,
),
word1(b')', ETypeInParens::IndentEnd)
fn loc_type_in_parens<'a>(
stop_at_surface_has: bool,
) -> impl Parser<'a, Loc<TypeAnnotation<'a>>, ETypeInParens<'a>> {
then(
loc!(and!(
collection_trailing_sep_e!(
word1(b'(', ETypeInParens::Open),
specialize_ref(ETypeInParens::Type, expression(true, false)),
word1(b',', ETypeInParens::End),
word1(b')', ETypeInParens::End),
ETypeInParens::Open,
ETypeInParens::IndentEnd,
TypeAnnotation::SpaceBefore
),
optional(allocated(specialize_ref(
ETypeInParens::Type,
term(stop_at_surface_has)
)))
)),
|_arena, state, progress, item| {
let Loc {
region,
value: (fields, ext),
} = item;
if fields.len() > 1 || ext.is_some() {
Ok((
MadeProgress,
Loc::at(region, TypeAnnotation::Tuple { fields, ext }),
state,
))
} else if fields.len() == 1 {
Ok((MadeProgress, fields.items[0], state))
} else {
debug_assert!(fields.is_empty());
Err((progress, ETypeInParens::Empty(state.pos())))
}
},
)
.trace("type_annotation:type_in_parens")
}
#[inline(always)]
@ -322,29 +351,24 @@ fn record_type_field<'a>() -> impl Parser<'a, AssignedField<'a, TypeAnnotation<'
fn record_type<'a>(
stop_at_surface_has: bool,
) -> impl Parser<'a, TypeAnnotation<'a>, ETypeRecord<'a>> {
use crate::type_annotation::TypeAnnotation::*;
(move |arena, state, min_indent| {
let (_, fields, state) = collection_trailing_sep_e!(
// word1_check_indent!(b'{', TRecord::Open, min_indent, TRecord::IndentOpen),
word1(b'{', ETypeRecord::Open),
loc!(record_type_field()),
word1(b',', ETypeRecord::End),
// word1_check_indent!(b'}', TRecord::End, min_indent, TRecord::IndentEnd),
word1(b'}', ETypeRecord::End),
ETypeRecord::Open,
ETypeRecord::IndentEnd,
AssignedField::SpaceBefore
)
.parse(arena, state, min_indent)?;
let field_term = specialize_ref(ETypeRecord::Type, term(stop_at_surface_has));
let (_, ext, state) = optional(allocated(field_term)).parse(arena, state, min_indent)?;
let result = Record { fields, ext };
Ok((MadeProgress, result, state))
})
map!(
and!(
collection_trailing_sep_e!(
word1(b'{', ETypeRecord::Open),
loc!(record_type_field()),
word1(b',', ETypeRecord::End),
word1(b'}', ETypeRecord::End),
ETypeRecord::Open,
ETypeRecord::IndentEnd,
AssignedField::SpaceBefore
),
optional(allocated(specialize_ref(
ETypeRecord::Type,
term(stop_at_surface_has)
)))
),
|(fields, ext)| { TypeAnnotation::Record { fields, ext } }
)
.trace("type_annotation:record_type")
}

View file

@ -0,0 +1,10 @@
_ = crash ""
_ = crash "" ""
_ = crash 15 123
_ = try foo (\_ -> crash "")
_ =
_ = crash ""
crash
{ f: crash "" }

View file

@ -0,0 +1,210 @@
Defs(
Defs {
tags: [
Index(2147483648),
Index(2147483649),
Index(2147483650),
Index(2147483651),
Index(2147483652),
],
regions: [
@0-12,
@13-28,
@29-45,
@46-74,
@75-101,
],
space_before: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
Slice(start = 1, length = 1),
Slice(start = 2, length = 1),
Slice(start = 3, length = 1),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 1, length = 0),
Slice(start = 2, length = 0),
Slice(start = 3, length = 0),
Slice(start = 4, length = 0),
],
spaces: [
Newline,
Newline,
Newline,
Newline,
],
type_defs: [],
value_defs: [
Body(
@0-1 Underscore(
"",
),
@4-12 Apply(
@4-9 Crash,
[
@10-12 Str(
PlainLine(
"",
),
),
],
Space,
),
),
Body(
@13-14 Underscore(
"",
),
@17-28 Apply(
@17-22 Crash,
[
@23-25 Str(
PlainLine(
"",
),
),
@26-28 Str(
PlainLine(
"",
),
),
],
Space,
),
),
Body(
@29-30 Underscore(
"",
),
@33-45 Apply(
@33-38 Crash,
[
@39-41 Num(
"15",
),
@42-45 Num(
"123",
),
],
Space,
),
),
Body(
@46-47 Underscore(
"",
),
@50-74 Apply(
@50-53 Var {
module_name: "",
ident: "try",
},
[
@54-57 Var {
module_name: "",
ident: "foo",
},
@59-73 ParensAround(
Closure(
[
@60-61 Underscore(
"",
),
],
@65-73 Apply(
@65-70 Crash,
[
@71-73 Str(
PlainLine(
"",
),
),
],
Space,
),
),
),
],
Space,
),
),
Body(
@75-76 Underscore(
"",
),
@81-101 SpaceBefore(
Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@81-93,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@81-82 Underscore(
"",
),
@85-93 Apply(
@85-90 Crash,
[
@91-93 Str(
PlainLine(
"",
),
),
],
Space,
),
),
],
},
@96-101 SpaceBefore(
Crash,
[
Newline,
],
),
),
[
Newline,
],
),
),
],
},
@103-118 SpaceBefore(
Record(
[
@105-116 RequiredValue(
@105-106 "f",
[],
@108-116 Apply(
@108-113 Crash,
[
@114-116 Str(
PlainLine(
"",
),
),
],
Space,
),
),
],
),
[
Newline,
Newline,
],
),
)

View file

@ -0,0 +1,9 @@
_ = crash ""
_ = crash "" ""
_ = crash 15 123
_ = try foo (\_ -> crash "")
_ =
_ = crash ""
crash
{ f: crash "" }

View file

@ -0,0 +1,4 @@
dbg
1 == 1
4

View file

@ -0,0 +1,24 @@
Dbg(
@4-10 BinOps(
[
(
@4-5 Num(
"1",
),
@6-8 Equals,
),
],
@9-10 Num(
"1",
),
),
@12-13 SpaceBefore(
Num(
"4",
),
[
Newline,
Newline,
],
),
)

View file

@ -0,0 +1,3 @@
dbg 1 == 1
4

View file

@ -0,0 +1,4 @@
f : (Str)a -> (Str)a
f = \x -> x
f ("Str", 42)

View file

@ -0,0 +1,136 @@
Defs(
Defs {
tags: [
Index(2147483649),
],
regions: [
@0-32,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Annotation(
@0-1 Identifier(
"f",
),
@4-20 Function(
[
@4-10 Tuple {
fields: [
@5-8 Apply(
"",
"Str",
[],
),
],
ext: Some(
@9-10 BoundVariable(
"a",
),
),
},
],
@14-20 Tuple {
fields: [
@15-18 Apply(
"",
"Str",
[],
),
],
ext: Some(
@19-20 BoundVariable(
"a",
),
),
},
),
),
AnnotatedBody {
ann_pattern: @0-1 Identifier(
"f",
),
ann_type: @4-20 Function(
[
@4-10 Tuple {
fields: [
@5-8 Apply(
"",
"Str",
[],
),
],
ext: Some(
@9-10 BoundVariable(
"a",
),
),
},
],
@14-20 Tuple {
fields: [
@15-18 Apply(
"",
"Str",
[],
),
],
ext: Some(
@19-20 BoundVariable(
"a",
),
),
},
),
comment: None,
body_pattern: @21-22 Identifier(
"f",
),
body_expr: @25-32 Closure(
[
@26-27 Identifier(
"x",
),
],
@31-32 Var {
module_name: "",
ident: "x",
},
),
},
],
},
@34-47 SpaceBefore(
Apply(
@34-35 Var {
module_name: "",
ident: "f",
},
[
@36-47 Tuple(
[
@37-42 Str(
PlainLine(
"Str",
),
),
@44-46 Num(
"42",
),
],
),
],
Space,
),
[
Newline,
Newline,
],
),
)

View file

@ -0,0 +1,4 @@
f : (Str)a -> (Str)a
f = \x -> x
f ("Str", 42)

View file

@ -0,0 +1,4 @@
f : I64 -> (I64, I64)
f = \x -> (x, x + 1)
f 42

View file

@ -0,0 +1,129 @@
Defs(
Defs {
tags: [
Index(2147483649),
],
regions: [
@0-42,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Annotation(
@0-1 Identifier(
"f",
),
@4-21 Function(
[
@4-7 Apply(
"",
"I64",
[],
),
],
@11-21 Tuple {
fields: [
@12-15 Apply(
"",
"I64",
[],
),
@17-20 Apply(
"",
"I64",
[],
),
],
ext: None,
},
),
),
AnnotatedBody {
ann_pattern: @0-1 Identifier(
"f",
),
ann_type: @4-21 Function(
[
@4-7 Apply(
"",
"I64",
[],
),
],
@11-21 Tuple {
fields: [
@12-15 Apply(
"",
"I64",
[],
),
@17-20 Apply(
"",
"I64",
[],
),
],
ext: None,
},
),
comment: None,
body_pattern: @22-23 Identifier(
"f",
),
body_expr: @26-42 Closure(
[
@27-28 Identifier(
"x",
),
],
@32-42 Tuple(
[
@33-34 Var {
module_name: "",
ident: "x",
},
@36-41 BinOps(
[
(
@36-37 Var {
module_name: "",
ident: "x",
},
@38-39 Plus,
),
],
@40-41 Num(
"1",
),
),
],
),
),
},
],
},
@44-48 SpaceBefore(
Apply(
@44-45 Var {
module_name: "",
ident: "f",
},
[
@46-48 Num(
"42",
),
],
Space,
),
[
Newline,
Newline,
],
),
)

View file

@ -0,0 +1,4 @@
f : I64 -> (I64, I64)
f = \x -> (x, x + 1)
f 42

View file

@ -0,0 +1 @@
({ a: 0 }, { b: 1 }).0.a

View file

@ -0,0 +1,32 @@
RecordAccess(
TupleAccess(
Tuple(
[
@1-7 Record(
[
@2-6 RequiredValue(
@2-3 "a",
[],
@5-6 Num(
"0",
),
),
],
),
@9-15 Record(
[
@10-14 RequiredValue(
@10-11 "b",
[],
@13-14 Num(
"1",
),
),
],
),
],
),
"0",
),
"a",
)

View file

@ -0,0 +1 @@
({a: 0}, {b: 1}).0.a

View file

@ -0,0 +1,24 @@
TupleAccess(
RecordAccess(
Record(
[
@2-11 RequiredValue(
@2-3 "a",
[],
@5-11 Tuple(
[
@6-7 Num(
"1",
),
@9-10 Num(
"2",
),
],
),
),
],
),
"a",
),
"0",
)

View file

@ -0,0 +1 @@
{ a: (1, 2) }.a.0

View file

@ -0,0 +1,4 @@
f : (Str, Str) -> (Str, Str)
f = \x -> x
f (1, 2)

View file

@ -0,0 +1,138 @@
Defs(
Defs {
tags: [
Index(2147483649),
],
regions: [
@0-39,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Annotation(
@0-1 Identifier(
"f",
),
@3-27 Function(
[
@3-13 Tuple {
fields: [
@4-7 Apply(
"",
"Str",
[],
),
@9-12 Apply(
"",
"Str",
[],
),
],
ext: None,
},
],
@17-27 Tuple {
fields: [
@18-21 Apply(
"",
"Str",
[],
),
@23-26 Apply(
"",
"Str",
[],
),
],
ext: None,
},
),
),
AnnotatedBody {
ann_pattern: @0-1 Identifier(
"f",
),
ann_type: @3-27 Function(
[
@3-13 Tuple {
fields: [
@4-7 Apply(
"",
"Str",
[],
),
@9-12 Apply(
"",
"Str",
[],
),
],
ext: None,
},
],
@17-27 Tuple {
fields: [
@18-21 Apply(
"",
"Str",
[],
),
@23-26 Apply(
"",
"Str",
[],
),
],
ext: None,
},
),
comment: None,
body_pattern: @28-29 Identifier(
"f",
),
body_expr: @32-39 Closure(
[
@33-34 Identifier(
"x",
),
],
@38-39 Var {
module_name: "",
ident: "x",
},
),
},
],
},
@41-49 SpaceBefore(
Apply(
@41-42 Var {
module_name: "",
ident: "f",
},
[
@43-49 Tuple(
[
@44-45 Num(
"1",
),
@47-48 Num(
"2",
),
],
),
],
Space,
),
[
Newline,
Newline,
],
),
)

View file

@ -0,0 +1,4 @@
f: (Str, Str) -> (Str, Str)
f = \x -> x
f (1, 2)

View file

@ -0,0 +1,4 @@
f : (Str, Str)a -> (Str, Str)a
f = \x -> x
f (1, 2)

View file

@ -0,0 +1,154 @@
Defs(
Defs {
tags: [
Index(2147483649),
],
regions: [
@0-41,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Annotation(
@0-1 Identifier(
"f",
),
@3-29 Function(
[
@3-14 Tuple {
fields: [
@4-7 Apply(
"",
"Str",
[],
),
@9-12 Apply(
"",
"Str",
[],
),
],
ext: Some(
@13-14 BoundVariable(
"a",
),
),
},
],
@18-29 Tuple {
fields: [
@19-22 Apply(
"",
"Str",
[],
),
@24-27 Apply(
"",
"Str",
[],
),
],
ext: Some(
@28-29 BoundVariable(
"a",
),
),
},
),
),
AnnotatedBody {
ann_pattern: @0-1 Identifier(
"f",
),
ann_type: @3-29 Function(
[
@3-14 Tuple {
fields: [
@4-7 Apply(
"",
"Str",
[],
),
@9-12 Apply(
"",
"Str",
[],
),
],
ext: Some(
@13-14 BoundVariable(
"a",
),
),
},
],
@18-29 Tuple {
fields: [
@19-22 Apply(
"",
"Str",
[],
),
@24-27 Apply(
"",
"Str",
[],
),
],
ext: Some(
@28-29 BoundVariable(
"a",
),
),
},
),
comment: None,
body_pattern: @30-31 Identifier(
"f",
),
body_expr: @34-41 Closure(
[
@35-36 Identifier(
"x",
),
],
@40-41 Var {
module_name: "",
ident: "x",
},
),
},
],
},
@43-51 SpaceBefore(
Apply(
@43-44 Var {
module_name: "",
ident: "f",
},
[
@45-51 Tuple(
[
@46-47 Num(
"1",
),
@49-50 Num(
"2",
),
],
),
],
Space,
),
[
Newline,
Newline,
],
),
)

View file

@ -0,0 +1,4 @@
f: (Str, Str)a -> (Str, Str)a
f = \x -> x
f (1, 2)

View file

@ -156,6 +156,7 @@ mod test_parse {
pass/comment_before_op.expr,
pass/comment_inside_empty_list.expr,
pass/comment_with_non_ascii.expr,
pass/crash.expr,
pass/destructure_tag_assignment.expr,
pass/empty_app_header.header,
pass/empty_hosted_header.header,
@ -168,6 +169,7 @@ mod test_parse {
pass/equals.expr,
pass/expect_fx.module,
pass/multiline_tuple_with_comments.expr,
pass/dbg.expr,
pass/expect.expr,
pass/float_with_underscores.expr,
pass/full_app_header_trailing_commas.header,
@ -186,6 +188,10 @@ mod test_parse {
pass/list_patterns.expr,
pass/lowest_float.expr,
pass/lowest_int.expr,
pass/tuple_type.expr,
pass/tuple_access_after_record.expr,
pass/record_access_after_tuple.expr,
pass/tuple_type_ext.expr,
pass/malformed_ident_due_to_underscore.expr,
pass/malformed_pattern_field_access.expr, // See https://github.com/roc-lang/roc/issues/399
pass/malformed_pattern_module_name.expr, // See https://github.com/roc-lang/roc/issues/399
@ -302,6 +308,8 @@ mod test_parse {
pass/when_with_negative_numbers.expr,
pass/when_with_numbers.expr,
pass/when_with_records.expr,
pass/function_with_tuple_type.expr,
pass/function_with_tuple_ext_type.expr,
pass/where_clause_function.expr,
pass/where_clause_multiple_bound_abilities.expr,
pass/where_clause_multiple_has_across_newlines.expr,

View file

@ -195,6 +195,12 @@ pub enum Problem {
type_got: u8,
alias_kind: AliasKind,
},
UnappliedCrash {
region: Region,
},
OverAppliedCrash {
region: Region,
},
}
impl Problem {
@ -325,7 +331,9 @@ impl Problem {
}
| Problem::MultipleListRestPattern { region }
| Problem::BadTypeArguments { region, .. }
| Problem::UnnecessaryOutputWildcard { region } => Some(*region),
| Problem::UnnecessaryOutputWildcard { region }
| Problem::OverAppliedCrash { region }
| Problem::UnappliedCrash { region } => Some(*region),
Problem::RuntimeError(RuntimeError::CircularDef(cycle_entries))
| Problem::BadRecursion(cycle_entries) => {
cycle_entries.first().map(|entry| entry.expr_region)

View file

@ -12,17 +12,24 @@ pub enum OperatingSystem {
Wasi,
}
impl OperatingSystem {
pub const fn new(target: target_lexicon::OperatingSystem) -> Option<Self> {
match target {
target_lexicon::OperatingSystem::Windows => Some(OperatingSystem::Windows),
target_lexicon::OperatingSystem::Wasi => Some(OperatingSystem::Wasi),
target_lexicon::OperatingSystem::Linux => Some(OperatingSystem::Unix),
target_lexicon::OperatingSystem::MacOSX { .. } => Some(OperatingSystem::Unix),
target_lexicon::OperatingSystem::Darwin => Some(OperatingSystem::Unix),
target_lexicon::OperatingSystem::Unknown => Some(OperatingSystem::Unix),
_ => None,
}
}
}
impl From<target_lexicon::OperatingSystem> for OperatingSystem {
fn from(target: target_lexicon::OperatingSystem) -> Self {
match target {
target_lexicon::OperatingSystem::Windows => OperatingSystem::Windows,
target_lexicon::OperatingSystem::Wasi => OperatingSystem::Wasi,
target_lexicon::OperatingSystem::Linux => OperatingSystem::Unix,
target_lexicon::OperatingSystem::MacOSX { .. } => OperatingSystem::Unix,
target_lexicon::OperatingSystem::Darwin => OperatingSystem::Unix,
target_lexicon::OperatingSystem::Unknown => OperatingSystem::Unix,
other => unreachable!("unsupported operating system {:?}", other),
}
Self::new(target)
.unwrap_or_else(|| unreachable!("unsupported operating system {:?}", target))
}
}

View file

@ -8352,4 +8352,20 @@ mod solve_expr {
@"translateStatic : [Element (List a)] as a -[[translateStatic(0)]]-> [Element (List b)]* as b"
)
}
#[test]
fn infer_contextual_crash() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [getInfallible] to "./platform"
getInfallible = \result -> when result is
Ok x -> x
_ -> crash "turns out this was fallible"
"#
),
"[Ok a]* -> a",
);
}
}

View file

@ -279,8 +279,10 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
)
.group()
),
Crash { .. } => todo!(),
ZeroArgumentTag { .. } => todo!(),
OpaqueRef { .. } => todo!(),
Dbg { .. } => todo!(),
Expect { .. } => todo!(),
ExpectFx { .. } => todo!(),
TypedHole(_) => todo!(),

View file

@ -0,0 +1,85 @@
use indoc::indoc;
use roc_std::RocList;
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to;
#[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_evals_to;
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic = r#"User crash with message: "hello crash""#]
fn crash_literal() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main = if Bool.true then crash "hello crash" else 1u8
"#
),
1u8,
u8
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic = r#"User crash with message: "hello crash""#]
fn crash_variable() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
msg = "hello crash"
if Bool.true then crash msg else 1u8
"#
),
1u8,
u8
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic = r#"User crash with message: "turns out this was fallible""#]
fn crash_in_call() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
getInfallible = \result -> when result is
Ok x -> x
_ -> crash "turns out this was fallible"
main =
x : [Ok U64, Err Str]
x = Err ""
getInfallible x
"#
),
1u64,
u64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic = r#"User crash with message: "no new even primes""#]
fn crash_in_passed_closure() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main = List.map [1, 2, 3] \n -> if n == 2 then crash "no new even primes" else ""
"#
),
RocList::from_slice(&[1u8]),
RocList<u8>
);
}

View file

@ -1,11 +1,11 @@
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::{assert_evals_to, expect_runtime_error_panic};
use crate::helpers::llvm::assert_evals_to;
#[cfg(feature = "gen-dev")]
use crate::helpers::dev::assert_evals_to;
#[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::{assert_evals_to, expect_runtime_error_panic};
use crate::helpers::wasm::assert_evals_to;
// use crate::assert_wasm_evals_to as assert_evals_to;
use indoc::indoc;
@ -447,7 +447,7 @@ fn optional_field_when_use_default_nested() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn optional_field_when_no_use_default() {
fn optional_field_destructure_module() {
assert_evals_to!(
indoc!(
r#"
@ -468,15 +468,15 @@ fn optional_field_when_no_use_default() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn optional_field_when_no_use_default_nested() {
fn optional_field_destructure_expr() {
assert_evals_to!(
indoc!(
r#"
f = \r ->
fn = \r ->
{ x ? 10, y } = r
x + y
f { x: 4, y: 9 }
fn { x: 4, y: 9 }
"#
),
13,
@ -1019,8 +1019,9 @@ fn different_proc_types_specialized_to_same_layout() {
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic(expected = r#"Roc failed with message: "Can't create record with improper layout""#)]
fn call_with_bad_record_runtime_error() {
expect_runtime_error_panic!(indoc!(
r#"
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
@ -1028,7 +1029,12 @@ fn call_with_bad_record_runtime_error() {
get = \{a} -> a
get {b: ""}
"#
))
),
true,
bool,
|x| x,
true // ignore type errors
)
}
#[test]
@ -1056,9 +1062,9 @@ fn update_record_that_is_a_thunk() {
app "test" provides [main] to "./platform"
main = Num.toStr fromOriginal.birds
original = { birds: 5, iguanas: 7, zebras: 2, goats: 1 }
fromOriginal = { original & birds: 4, iguanas: 3 }
"#
),
@ -1076,9 +1082,9 @@ fn update_record_that_is_a_thunk_single_field() {
app "test" provides [main] to "./platform"
main = Num.toStr fromOriginal.birds
original = { birds: 5 }
fromOriginal = { original & birds: 4 }
"#
),

View file

@ -8,7 +8,7 @@ use roc_collections::all::MutSet;
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_gen_llvm::{llvm::build::LlvmBackendMode, run_roc::RocCallResult};
use roc_load::{EntryPoint, ExecutionMode, LoadConfig, Threading};
use roc_mono::ir::OptLevel;
use roc_mono::ir::{CrashTag, OptLevel};
use roc_region::all::LineInfo;
use roc_reporting::report::{RenderTarget, DEFAULT_PALETTE};
use roc_utils::zig;
@ -544,7 +544,10 @@ macro_rules! assert_wasm_evals_to {
}
#[allow(dead_code)]
pub fn try_run_lib_function<T>(main_fn_name: &str, lib: &libloading::Library) -> Result<T, String> {
pub fn try_run_lib_function<T>(
main_fn_name: &str,
lib: &libloading::Library,
) -> Result<T, (String, CrashTag)> {
unsafe {
let main: libloading::Symbol<unsafe extern "C" fn(*mut RocCallResult<T>)> = lib
.get(main_fn_name.as_bytes())
@ -565,6 +568,7 @@ macro_rules! assert_llvm_evals_to {
use bumpalo::Bump;
use inkwell::context::Context;
use roc_gen_llvm::llvm::build::LlvmBackendMode;
use roc_mono::ir::CrashTag;
let arena = Bump::new();
let context = Context::create();
@ -594,7 +598,10 @@ macro_rules! assert_llvm_evals_to {
#[cfg(windows)]
std::mem::forget(given);
}
Err(msg) => panic!("Roc failed with message: \"{}\"", msg),
Err((msg, tag)) => match tag {
CrashTag::Roc => panic!(r#"Roc failed with message: "{}""#, msg),
CrashTag::User => panic!(r#"User crash with message: "{}""#, msg),
},
}
// artificially extend the lifetime of `lib`
@ -655,29 +662,6 @@ macro_rules! assert_evals_to {
}};
}
#[allow(unused_macros)]
macro_rules! expect_runtime_error_panic {
($src:expr) => {{
#[cfg(feature = "gen-llvm-wasm")]
$crate::helpers::llvm::assert_wasm_evals_to!(
$src,
false, // fake value/type for eval
bool,
$crate::helpers::llvm::identity,
true // ignore problems
);
#[cfg(not(feature = "gen-llvm-wasm"))]
$crate::helpers::llvm::assert_llvm_evals_to!(
$src,
false, // fake value/type for eval
bool,
$crate::helpers::llvm::identity,
true // ignore problems
);
}};
}
#[allow(dead_code)]
pub fn identity<T>(value: T) -> T {
value
@ -689,5 +673,3 @@ pub(crate) use assert_evals_to;
pub(crate) use assert_llvm_evals_to;
#[allow(unused_imports)]
pub(crate) use assert_wasm_evals_to;
#[allow(unused_imports)]
pub(crate) use expect_runtime_error_panic;

View file

@ -32,23 +32,3 @@ pub unsafe fn roc_realloc(
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
libc::free(c_ptr)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
use roc_gen_llvm::llvm::build::PanicTagId;
use std::ffi::CStr;
use std::os::raw::c_char;
match PanicTagId::try_from(tag_id) {
Ok(PanicTagId::NullTerminatedString) => {
let slice = CStr::from_ptr(c_ptr as *const c_char);
let string = slice.to_str().unwrap();
eprintln!("Roc hit a panic: {}", string);
std::process::exit(1);
}
Err(_) => unreachable!(),
}
}

View file

@ -5,6 +5,7 @@ use roc_gen_wasm::wasm32_result::Wasm32Result;
use roc_gen_wasm::DEBUG_SETTINGS;
use roc_load::{ExecutionMode, LoadConfig, Threading};
use roc_reporting::report::DEFAULT_PALETTE_HTML;
use roc_std::RocStr;
use roc_wasm_module::{Export, ExportType};
use std::marker::PhantomData;
use std::path::PathBuf;
@ -193,7 +194,7 @@ where
let parsed = Module::parse(&env, &wasm_bytes[..]).expect("Unable to parse module");
let mut module = rt.load_module(parsed).expect("Unable to load module");
let panic_msg: Rc<Mutex<Option<(i32, i32)>>> = Default::default();
let panic_msg: Rc<Mutex<Option<(i32, u32)>>> = Default::default();
link_module(&mut module, panic_msg.clone());
let test_wrapper = module
@ -202,12 +203,18 @@ where
match test_wrapper.call() {
Err(e) => {
if let Some((msg_ptr, msg_len)) = *panic_msg.lock().unwrap() {
if let Some((msg_ptr, tag)) = *panic_msg.lock().unwrap() {
let memory: &[u8] = get_memory(&rt);
let msg_bytes = &memory[msg_ptr as usize..][..msg_len as usize];
let msg = std::str::from_utf8(msg_bytes).unwrap();
let msg = RocStr::decode(memory, msg_ptr as _);
Err(format!("Roc failed with message: \"{}\"", msg))
dbg!(tag);
let msg = match tag {
0 => format!(r#"Roc failed with message: "{}""#, msg),
1 => format!(r#"User crash with message: "{}""#, msg),
tag => format!(r#"Got an invald panic tag: "{}""#, tag),
};
Err(msg)
} else {
Err(format!("{}", e))
}
@ -253,7 +260,7 @@ where
let parsed = Module::parse(&env, wasm_bytes).expect("Unable to parse module");
let mut module = rt.load_module(parsed).expect("Unable to load module");
let panic_msg: Rc<Mutex<Option<(i32, i32)>>> = Default::default();
let panic_msg: Rc<Mutex<Option<(i32, u32)>>> = Default::default();
link_module(&mut module, panic_msg.clone());
let expected_len = num_refcounts as i32;
@ -316,13 +323,13 @@ fn read_i32(memory: &[u8], ptr: usize) -> i32 {
i32::from_le_bytes(bytes)
}
fn link_module(module: &mut Module, panic_msg: Rc<Mutex<Option<(i32, i32)>>>) {
fn link_module(module: &mut Module, panic_msg: Rc<Mutex<Option<(i32, u32)>>>) {
let try_link_panic = module.link_closure(
"env",
"send_panic_msg_to_rust",
move |_call_context, args: (i32, i32)| {
move |_call_context, (msg_ptr, tag): (i32, u32)| {
let mut w = panic_msg.lock().unwrap();
*w = Some(args);
*w = Some((msg_ptr, tag));
Ok(())
},
);
@ -390,19 +397,6 @@ macro_rules! assert_evals_to {
}};
}
#[allow(unused_macros)]
macro_rules! expect_runtime_error_panic {
($src:expr) => {{
$crate::helpers::wasm::assert_evals_to!(
$src,
false, // fake value/type for eval
bool,
$crate::helpers::wasm::identity,
true // ignore problems
);
}};
}
#[allow(dead_code)]
pub fn identity<T>(value: T) -> T {
value
@ -430,8 +424,5 @@ macro_rules! assert_refcounts {
#[allow(unused_imports)]
pub(crate) use assert_evals_to;
#[allow(unused_imports)]
pub(crate) use expect_runtime_error_panic;
#[allow(unused_imports)]
pub(crate) use assert_refcounts;

View file

@ -1,4 +1,5 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@ -123,12 +124,11 @@ void roc_dealloc(void *ptr, unsigned int alignment)
//--------------------------
extern void send_panic_msg_to_rust(char* msg, int len);
extern void send_panic_msg_to_rust(void* msg, uint32_t tag_id);
void roc_panic(char *msg, unsigned int tag_id)
void roc_panic(void* msg, unsigned int tag_id)
{
int len = strlen(msg);
send_panic_msg_to_rust(msg, len);
send_panic_msg_to_rust(msg, tag_id);
exit(101);
}

View file

@ -9,6 +9,7 @@ pub mod gen_compare;
pub mod gen_dict;
pub mod gen_list;
pub mod gen_num;
pub mod gen_panic;
pub mod gen_primitives;
pub mod gen_records;
pub mod gen_refcount;

View file

@ -5,7 +5,8 @@ procedure List.5 (#Attr.2, #Attr.3):
procedure Test.2 (Test.3):
let Test.7 : {} = Struct {};
Error a Lambda Set is empty. Most likely there is a type error in your program.
let Test.8 : Str = "a Lambda Set is empty. Most likely there is a type error in your program.";
Crash Test.8
procedure Test.0 ():
let Test.1 : List [] = Array [];

View file

@ -5,7 +5,8 @@ procedure List.5 (#Attr.2, #Attr.3):
procedure Test.2 (Test.3):
let Test.7 : {} = Struct {};
Error a Lambda Set is empty. Most likely there is a type error in your program.
let Test.8 : Str = "a Lambda Set is empty. Most likely there is a type error in your program.";
Crash Test.8
procedure Test.0 ():
let Test.1 : List [] = Array [];

View file

@ -0,0 +1,18 @@
procedure Test.1 (Test.2):
let Test.10 : U8 = 1i64;
let Test.11 : U8 = GetTagId Test.2;
let Test.12 : Int1 = lowlevel Eq Test.10 Test.11;
if Test.12 then
let Test.3 : U64 = UnionAtIndex (Id 1) (Index 0) Test.2;
dec Test.2;
ret Test.3;
else
dec Test.2;
let Test.9 : Str = "turns out this was fallible";
Crash Test.9
procedure Test.0 ():
let Test.13 : U64 = 78i64;
let Test.4 : [C Str, C U64] = TagId(1) Test.13;
let Test.6 : U64 = CallByName Test.1 Test.4;
ret Test.6;

View file

@ -205,58 +205,58 @@ procedure Json.96 (Json.97, Json.473, Json.95):
ret Json.475;
procedure List.137 (List.138, List.139, List.136):
let List.449 : {List U8, U64} = CallByName Json.114 List.138 List.139;
ret List.449;
let List.450 : {List U8, U64} = CallByName Json.114 List.138 List.139;
ret List.450;
procedure List.137 (List.138, List.139, List.136):
let List.521 : {List U8, U64} = CallByName Json.114 List.138 List.139;
ret List.521;
let List.523 : {List U8, U64} = CallByName Json.114 List.138 List.139;
ret List.523;
procedure List.18 (List.134, List.135, List.136):
let List.431 : {List U8, U64} = CallByName List.89 List.134 List.135 List.136;
ret List.431;
procedure List.18 (List.134, List.135, List.136):
let List.503 : {List U8, U64} = CallByName List.89 List.134 List.135 List.136;
ret List.503;
let List.504 : {List U8, U64} = CallByName List.89 List.134 List.135 List.136;
ret List.504;
procedure List.4 (List.105, List.106):
let List.502 : U64 = 1i64;
let List.501 : List U8 = CallByName List.70 List.105 List.502;
let List.500 : List U8 = CallByName List.71 List.501 List.106;
ret List.500;
let List.503 : U64 = 1i64;
let List.502 : List U8 = CallByName List.70 List.105 List.503;
let List.501 : List U8 = CallByName List.71 List.502 List.106;
ret List.501;
procedure List.6 (#Attr.2):
let List.409 : U64 = lowlevel ListLen #Attr.2;
ret List.409;
procedure List.6 (#Attr.2):
let List.451 : U64 = lowlevel ListLen #Attr.2;
ret List.451;
let List.452 : U64 = lowlevel ListLen #Attr.2;
ret List.452;
procedure List.6 (#Attr.2):
let List.524 : U64 = lowlevel ListLen #Attr.2;
ret List.524;
let List.526 : U64 = lowlevel ListLen #Attr.2;
ret List.526;
procedure List.66 (#Attr.2, #Attr.3):
let List.446 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.446;
let List.447 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.447;
procedure List.66 (#Attr.2, #Attr.3):
let List.518 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.518;
let List.520 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.520;
procedure List.70 (#Attr.2, #Attr.3):
let List.481 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3;
ret List.481;
let List.482 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3;
ret List.482;
procedure List.71 (#Attr.2, #Attr.3):
let List.479 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3;
ret List.479;
let List.480 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3;
ret List.480;
procedure List.8 (#Attr.2, #Attr.3):
let List.523 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3;
ret List.523;
let List.525 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3;
ret List.525;
procedure List.89 (List.385, List.386, List.387):
let List.435 : U64 = 0i64;
@ -265,38 +265,38 @@ procedure List.89 (List.385, List.386, List.387):
ret List.434;
procedure List.89 (List.385, List.386, List.387):
let List.507 : U64 = 0i64;
let List.508 : U64 = CallByName List.6 List.385;
let List.506 : {List U8, U64} = CallByName List.90 List.385 List.386 List.387 List.507 List.508;
ret List.506;
let List.508 : U64 = 0i64;
let List.509 : U64 = CallByName List.6 List.385;
let List.507 : {List U8, U64} = CallByName List.90 List.385 List.386 List.387 List.508 List.509;
ret List.507;
procedure List.90 (List.461, List.462, List.463, List.464, List.465):
procedure List.90 (List.462, List.463, List.464, List.465, List.466):
joinpoint List.437 List.388 List.389 List.390 List.391 List.392:
let List.439 : Int1 = CallByName Num.22 List.391 List.392;
if List.439 then
let List.445 : {Str, Str} = CallByName List.66 List.388 List.391;
let List.440 : {List U8, U64} = CallByName List.137 List.389 List.445 List.390;
let List.446 : {Str, Str} = CallByName List.66 List.388 List.391;
let List.440 : {List U8, U64} = CallByName List.137 List.389 List.446 List.390;
let List.443 : U64 = 1i64;
let List.442 : U64 = CallByName Num.19 List.391 List.443;
jump List.437 List.388 List.440 List.390 List.442 List.392;
else
ret List.389;
in
jump List.437 List.461 List.462 List.463 List.464 List.465;
jump List.437 List.462 List.463 List.464 List.465 List.466;
procedure List.90 (List.534, List.535, List.536, List.537, List.538):
joinpoint List.509 List.388 List.389 List.390 List.391 List.392:
let List.511 : Int1 = CallByName Num.22 List.391 List.392;
if List.511 then
let List.517 : {Str, Str} = CallByName List.66 List.388 List.391;
let List.512 : {List U8, U64} = CallByName List.137 List.389 List.517 List.390;
let List.515 : U64 = 1i64;
let List.514 : U64 = CallByName Num.19 List.391 List.515;
jump List.509 List.388 List.512 List.390 List.514 List.392;
procedure List.90 (List.536, List.537, List.538, List.539, List.540):
joinpoint List.510 List.388 List.389 List.390 List.391 List.392:
let List.512 : Int1 = CallByName Num.22 List.391 List.392;
if List.512 then
let List.519 : {Str, Str} = CallByName List.66 List.388 List.391;
let List.513 : {List U8, U64} = CallByName List.137 List.389 List.519 List.390;
let List.516 : U64 = 1i64;
let List.515 : U64 = CallByName Num.19 List.391 List.516;
jump List.510 List.388 List.513 List.390 List.515 List.392;
else
ret List.389;
in
jump List.509 List.534 List.535 List.536 List.537 List.538;
jump List.510 List.536 List.537 List.538 List.539 List.540;
procedure Num.125 (#Attr.2):
let Num.282 : U8 = lowlevel NumIntCast #Attr.2;

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