mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 03:42:17 +00:00
Merge branch 'main' into underivable-rigid-better-error
Signed-off-by: Ayaz <20735482+ayazhafiz@users.noreply.github.com>
This commit is contained in:
commit
15e372373a
164 changed files with 5187 additions and 3909 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
2142
TUTORIAL.md
2142
TUTORIAL.md
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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!(),
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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."#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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!"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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;
|
||||
|
|
16
crates/compiler/builtins/bitcode/src/panic.zig
Normal file
16
crates/compiler/builtins/bitcode/src/panic.zig
Normal 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 });
|
||||
}
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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!(),
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>>,
|
||||
|
|
|
@ -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>>,
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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",
|
||||
);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -208,3 +208,18 @@ The diagram below illustrates this process.
|
|||
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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!(),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -299,6 +299,6 @@ fn insert_jumps<'a>(
|
|||
|
||||
Ret(_) => None,
|
||||
Jump(_, _) => None,
|
||||
RuntimeError(_) => None,
|
||||
Crash(..) => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"as"
|
||||
"is"
|
||||
"expect"
|
||||
"dbg"
|
||||
|
||||
"app"
|
||||
"platform"
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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()))),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
_ = crash ""
|
||||
_ = crash "" ""
|
||||
_ = crash 15 123
|
||||
_ = try foo (\_ -> crash "")
|
||||
_ =
|
||||
_ = crash ""
|
||||
|
||||
crash
|
||||
|
||||
{ f: crash "" }
|
210
crates/compiler/parse/tests/snapshots/pass/crash.expr.result-ast
Normal file
210
crates/compiler/parse/tests/snapshots/pass/crash.expr.result-ast
Normal 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,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
_ = crash ""
|
||||
_ = crash "" ""
|
||||
_ = crash 15 123
|
||||
_ = try foo (\_ -> crash "")
|
||||
_ =
|
||||
_ = crash ""
|
||||
crash
|
||||
|
||||
{ f: crash "" }
|
|
@ -0,0 +1,4 @@
|
|||
dbg
|
||||
1 == 1
|
||||
|
||||
4
|
|
@ -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,
|
||||
],
|
||||
),
|
||||
)
|
3
crates/compiler/parse/tests/snapshots/pass/dbg.expr.roc
Normal file
3
crates/compiler/parse/tests/snapshots/pass/dbg.expr.roc
Normal file
|
@ -0,0 +1,3 @@
|
|||
dbg 1 == 1
|
||||
|
||||
4
|
|
@ -0,0 +1,4 @@
|
|||
f : (Str)a -> (Str)a
|
||||
f = \x -> x
|
||||
|
||||
f ("Str", 42)
|
|
@ -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,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,4 @@
|
|||
f : (Str)a -> (Str)a
|
||||
f = \x -> x
|
||||
|
||||
f ("Str", 42)
|
|
@ -0,0 +1,4 @@
|
|||
f : I64 -> (I64, I64)
|
||||
f = \x -> (x, x + 1)
|
||||
|
||||
f 42
|
|
@ -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,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,4 @@
|
|||
f : I64 -> (I64, I64)
|
||||
f = \x -> (x, x + 1)
|
||||
|
||||
f 42
|
|
@ -0,0 +1 @@
|
|||
({ a: 0 }, { b: 1 }).0.a
|
|
@ -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",
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
({a: 0}, {b: 1}).0.a
|
|
@ -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",
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
{ a: (1, 2) }.a.0
|
|
@ -0,0 +1,4 @@
|
|||
f : (Str, Str) -> (Str, Str)
|
||||
f = \x -> x
|
||||
|
||||
f (1, 2)
|
|
@ -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,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,4 @@
|
|||
f: (Str, Str) -> (Str, Str)
|
||||
f = \x -> x
|
||||
|
||||
f (1, 2)
|
|
@ -0,0 +1,4 @@
|
|||
f : (Str, Str)a -> (Str, Str)a
|
||||
f = \x -> x
|
||||
|
||||
f (1, 2)
|
|
@ -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,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,4 @@
|
|||
f: (Str, Str)a -> (Str, Str)a
|
||||
f = \x -> x
|
||||
|
||||
f (1, 2)
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!(),
|
||||
|
|
85
crates/compiler/test_gen/src/gen_panic.rs
Normal file
85
crates/compiler/test_gen/src/gen_panic.rs
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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 }
|
||||
"#
|
||||
),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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!(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 [];
|
||||
|
|
|
@ -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 [];
|
||||
|
|
18
crates/compiler/test_mono/generated/crash.txt
Normal file
18
crates/compiler/test_mono/generated/crash.txt
Normal 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;
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue