Merge branch 'main' of github.com:roc-lang/roc into wasm_module_crate

This commit is contained in:
Brian Carroll 2022-11-17 21:29:59 +00:00
commit c16d425936
No known key found for this signature in database
GPG Key ID: 5C7B2EC4101703C0
142 changed files with 1239 additions and 296 deletions

View File

@ -36,6 +36,9 @@ jobs:
- name: zig version
run: zig version
- name: install rust nightly 1.64
run: rustup install nightly-2022-08-06
- name: set up llvm 13
run: |
curl.exe -L -O https://github.com/roc-lang/llvm-package-windows/releases/download/v13.0.1/LLVM-13.0.1-win64.7z
@ -46,7 +49,7 @@ jobs:
# Why are these tests not build with previous command? => fingerprint error. Use `CARGO_LOG=cargo::core::compiler::fingerprint=info` to investigate
- name: Build specific tests without running. Twice for zig lld-link error.
run: cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker || cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker
run: cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker -p roc_cli || cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker -p roc_cli
- name: Actually run the tests.
run: cargo test --locked --release -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker
run: cargo test --locked --release -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker -p roc_cli

View File

@ -64,7 +64,8 @@ This command will generate the documentation in the [`generated-docs`](generated
### Forgot to sign commits?
You can find which commits need to be signed by running `git log --show-signature`.
You can view your commits on github, those without the "Verified" badge still need to be signed.
If any of those is a merge commit, follow [these steps](https://stackoverflow.com/a/9958215/4200103) instead of the ones below.
If you have only one commit, running `git commit --amend --no-edit -S` would sign the latest commit 🚀.
@ -76,7 +77,7 @@ In case you have multiple commits, you can sign them in two ways:
pick hash2 commit message 2
pick hash1 commit message 1
```
- After every commit you want to sign, add `exec git commit --amend --no-edit -S`.
- On a new line below a commit you want to sign, add `exec git commit --amend --no-edit -S`. Do this for all your unsigned commits.
2. Or run git rebase recursively:
- Find the oldest commit you want to sign, using the `git log --show-signature` command.
- Run the command `git rebase --exec 'git commit --amend --no-edit -n -S' -i HASH` which would sign all commits up to commit `HASH`.

View File

@ -115,14 +115,14 @@ mod cli_run {
let is_reporting_runtime = stderr.starts_with("runtime: ") && stderr.ends_with("ms\n");
if !(stderr.is_empty() || is_reporting_runtime) {
panic!("`roc` command had unexpected stderr: {}", stderr);
panic!("\n___________\nThe roc command:\n\n {:?}\n\nhad unexpected stderr:\n\n {}\n___________\n", compile_out.cmd_str, stderr);
}
assert!(
compile_out.status.success(),
"bad status stderr:\n{}\nstdout:\n{}",
compile_out.stderr,
compile_out.stdout
"\n___________\nRoc command failed with status {:?}:\n\n {:?}\n___________\n",
compile_out.status,
compile_out
);
compile_out
@ -252,10 +252,7 @@ mod cli_run {
),
};
// strip out any carriage return characters to make the output on windows match unix
let stdout = out.stdout.replace('\r', "");
if !stdout.ends_with(expected_ending) {
if !&out.stdout.ends_with(expected_ending) {
panic!(
"expected output to end with {:?} but instead got {:#?} - stderr was: {:#?}",
expected_ending, out.stdout, out.stderr
@ -409,6 +406,7 @@ mod cli_run {
#[test]
#[serial(cli_platform)]
#[cfg_attr(windows, ignore)]
fn hello_world() {
test_roc_app_slim(
"examples",
@ -425,6 +423,7 @@ mod cli_run {
const LINE_ENDING: &str = "\n";
#[test]
#[cfg_attr(windows, ignore)]
// uses C platform
fn platform_switching_main() {
test_roc_app_slim(
@ -441,6 +440,7 @@ mod cli_run {
// If we don't, a race condition leads to test flakiness.
#[test]
#[cfg_attr(windows, ignore)]
fn platform_switching_rust() {
test_roc_app_slim(
"examples/platform-switching",
@ -452,6 +452,7 @@ mod cli_run {
}
#[test]
#[cfg_attr(windows, ignore)]
fn platform_switching_zig() {
test_roc_app_slim(
"examples/platform-switching",
@ -485,11 +486,16 @@ mod cli_run {
}
#[test]
#[cfg_attr(
windows,
ignore = "this platform is broken, and `roc run --lib` is missing on windows"
)]
fn ruby_interop() {
test_roc_app_slim("examples/ruby-interop", "main.roc", "libhello", "", true)
}
#[test]
#[cfg_attr(windows, ignore)]
fn fibonacci() {
test_roc_app_slim(
"crates/cli_testing_examples/algorithms",
@ -517,6 +523,7 @@ mod cli_run {
}
#[test]
#[cfg_attr(windows, ignore)]
fn quicksort() {
test_roc_app_slim(
"crates/cli_testing_examples/algorithms",
@ -551,6 +558,7 @@ mod cli_run {
}
#[test]
#[cfg_attr(windows, ignore)]
fn interactive_effects() {
test_roc_app(
"examples/cli",
@ -566,6 +574,7 @@ mod cli_run {
}
#[test]
#[cfg_attr(windows, ignore)]
// tea = The Elm Architecture
fn terminal_ui_tea() {
test_roc_app(
@ -582,6 +591,7 @@ mod cli_run {
}
#[test]
#[cfg_attr(windows, ignore)]
fn false_interpreter() {
test_roc_app(
"examples/cli/false-interpreter",
@ -590,7 +600,7 @@ mod cli_run {
&[],
&[Arg::ExamplePath("examples/hello.false")],
&[],
"Hello, World!\n",
&("Hello, World!".to_string() + LINE_ENDING),
false,
true,
)
@ -602,6 +612,7 @@ mod cli_run {
}
#[test]
#[cfg_attr(windows, ignore)]
fn static_site_gen() {
test_roc_app(
"examples/static-site-gen",
@ -618,6 +629,7 @@ mod cli_run {
#[test]
#[serial(cli_platform)]
#[cfg_attr(windows, ignore)]
fn with_env_vars() {
test_roc_app(
"examples/cli",
@ -639,6 +651,7 @@ mod cli_run {
}
#[test]
#[cfg_attr(windows, ignore)]
fn parse_movies_csv() {
test_roc_app_slim(
"examples/parser",
@ -856,16 +869,19 @@ mod cli_run {
}
#[test]
#[cfg_attr(windows, ignore)]
fn nqueens() {
test_benchmark("NQueens.roc", "nqueens", &["6"], "4\n", true)
}
#[test]
#[cfg_attr(windows, ignore)]
fn cfold() {
test_benchmark("CFold.roc", "cfold", &["3"], "11 & 11\n", true)
}
#[test]
#[cfg_attr(windows, ignore)]
fn deriv() {
test_benchmark(
"Deriv.roc",
@ -877,11 +893,13 @@ mod cli_run {
}
#[test]
#[cfg_attr(windows, ignore)]
fn rbtree_ck() {
test_benchmark("RBTreeCk.roc", "rbtree-ck", &["100"], "10\n", true)
}
#[test]
#[cfg_attr(windows, ignore)]
fn rbtree_insert() {
test_benchmark(
"RBTreeInsert.roc",
@ -907,11 +925,13 @@ mod cli_run {
}*/
#[test]
#[cfg_attr(windows, ignore)]
fn astar() {
test_benchmark("TestAStar.roc", "test-astar", &[], "True\n", false)
}
#[test]
#[cfg_attr(windows, ignore)]
fn base64() {
test_benchmark(
"TestBase64.roc",
@ -923,11 +943,13 @@ mod cli_run {
}
#[test]
#[cfg_attr(windows, ignore)]
fn closure() {
test_benchmark("Closure.roc", "closure", &[], "", false)
}
#[test]
#[cfg_attr(windows, ignore)]
fn issue2279() {
test_benchmark("Issue2279.roc", "issue2279", &[], "Hello, world!\n", true)
}
@ -946,6 +968,7 @@ mod cli_run {
#[test]
#[serial(multi_dep_str)]
#[cfg_attr(windows, ignore)]
fn run_multi_dep_str_unoptimized() {
check_output_with_stdin(
&fixture_file("multi-dep-str", "Main.roc"),
@ -962,6 +985,7 @@ mod cli_run {
#[test]
#[serial(multi_dep_str)]
#[cfg_attr(windows, ignore)]
fn run_multi_dep_str_optimized() {
check_output_with_stdin(
&fixture_file("multi-dep-str", "Main.roc"),
@ -978,6 +1002,7 @@ mod cli_run {
#[test]
#[serial(multi_dep_thunk)]
#[cfg_attr(windows, ignore)]
fn run_multi_dep_thunk_unoptimized() {
check_output_with_stdin(
&fixture_file("multi-dep-thunk", "Main.roc"),
@ -994,6 +1019,7 @@ mod cli_run {
#[test]
#[serial(multi_dep_thunk)]
#[cfg_attr(windows, ignore)]
fn run_multi_dep_thunk_optimized() {
check_output_with_stdin(
&fixture_file("multi-dep-thunk", "Main.roc"),

View File

@ -9,7 +9,7 @@ use roc_utils::root_dir;
use serde::Deserialize;
use serde_xml_rs::from_str;
use std::env;
use std::ffi::OsStr;
use std::ffi::{OsStr, OsString};
use std::io::Read;
use std::io::Write;
use std::path::Path;
@ -19,6 +19,7 @@ use tempfile::NamedTempFile;
#[derive(Debug)]
pub struct Out {
pub cmd_str: OsString, // command with all its arguments, for easy debugging
pub stdout: String,
pub stderr: String,
pub status: ExitStatus,
@ -157,7 +158,7 @@ where
roc_cmd.env(k, v);
}
let roc_cmd_str = format!("{:?}", roc_cmd);
let roc_cmd_str = pretty_command_string(&roc_cmd);
let mut roc_cmd_child = roc_cmd
.stdin(Stdio::piped())
@ -165,7 +166,7 @@ where
.stderr(Stdio::piped())
.spawn()
.unwrap_or_else(|err| {
panic!("Failed to execute command\n\n {roc_cmd_str}\n\nwith error:\n\n {err}",)
panic!("Failed to execute command\n\n {roc_cmd_str:?}\n\nwith error:\n\n {err}",)
});
{
@ -176,17 +177,18 @@ where
.write_all(stdin_str.as_bytes())
.unwrap_or_else(|err| {
panic!(
"Failed to write to stdin for command\n\n {roc_cmd_str}\n\nwith error:\n\n {err}",
"Failed to write to stdin for command\n\n {roc_cmd_str:?}\n\nwith error:\n\n {err}",
)
});
}
}
let roc_cmd_output = roc_cmd_child.wait_with_output().unwrap_or_else(|err| {
panic!("Failed to get output for command\n\n {roc_cmd_str}\n\nwith error:\n\n {err}",)
panic!("Failed to get output for command\n\n {roc_cmd_str:?}\n\nwith error:\n\n {err}",)
});
Out {
cmd_str: roc_cmd_str,
stdout: String::from_utf8(roc_cmd_output.stdout).unwrap(),
stderr: String::from_utf8(roc_cmd_output.stderr).unwrap(),
status: roc_cmd_output.status,
@ -209,6 +211,8 @@ pub fn run_cmd<'a, I: IntoIterator<Item = &'a str>, E: IntoIterator<Item = (&'a
cmd.env(env, val);
}
let cmd_str = pretty_command_string(&cmd);
let mut child = cmd
.stdin(Stdio::piped())
.stdout(Stdio::piped())
@ -231,6 +235,7 @@ pub fn run_cmd<'a, I: IntoIterator<Item = &'a str>, E: IntoIterator<Item = (&'a
.unwrap_or_else(|_| panic!("failed to execute cmd `{}` in CLI test", cmd_name));
Out {
cmd_str,
stdout: String::from_utf8(output.stdout).unwrap(),
stderr: String::from_utf8(output.stderr).unwrap(),
status: output.status,
@ -274,6 +279,8 @@ pub fn run_with_valgrind<'a, I: IntoIterator<Item = &'a str>>(
cmd.arg(arg);
}
let cmd_str = pretty_command_string(&cmd);
cmd.stdin(Stdio::piped());
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());
@ -303,6 +310,7 @@ pub fn run_with_valgrind<'a, I: IntoIterator<Item = &'a str>>(
(
Out {
cmd_str,
stdout: String::from_utf8(output.stdout).unwrap(),
stderr: String::from_utf8(output.stderr).unwrap(),
status: output.status,
@ -439,3 +447,15 @@ pub fn known_bad_file(file_name: &str) -> PathBuf {
path
}
fn pretty_command_string(command: &Command) -> OsString {
let mut command_string = std::ffi::OsString::new();
command_string.push(command.get_program());
for arg in command.get_args() {
command_string.push(" ");
command_string.push(arg);
}
command_string
}

View File

@ -1543,11 +1543,32 @@ fn expr_spec<'a>(
}
_ => unreachable!("empty array does not have a list layout"),
},
Reset { symbol, .. } => {
let type_id = layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
let value_id = env.symbols[symbol];
Reset {
symbol,
update_mode,
} => {
let tag_value_id = env.symbols[symbol];
builder.add_unknown_with(block, &[value_id], type_id)
let union_layout = match layout {
Layout::Union(ul) => ul,
_ => unreachable!(),
};
let type_name_bytes = recursive_tag_union_name_bytes(union_layout).as_bytes();
let type_name = TypeName(&type_name_bytes);
// unwrap the named wrapper
let union_id = builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?;
let heap_cell = builder.add_get_tuple_field(block, union_id, TAG_CELL_INDEX)?;
let union_data = builder.add_get_tuple_field(block, union_id, TAG_DATA_INDEX)?;
let mode = update_mode.to_bytes();
let update_mode_var = UpdateModeVar(&mode);
let _unit = builder.add_update(block, update_mode_var, heap_cell)?;
with_new_heap_cell(builder, block, union_data)
}
RuntimeErrorFunction(_) => {
let type_id = layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;

View File

@ -186,11 +186,15 @@ pub fn build_zig_host_native(
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
) -> Command {
// to prevent `clang failed with stderr: zig: error: unable to make temporary file: No such file or directory`
let env_userprofile = env::var("USERPROFILE").unwrap_or_else(|_| "".to_string());
let mut zig_cmd = zig();
zig_cmd
.env_clear()
.env("PATH", env_path)
.env("HOME", env_home);
.env("HOME", env_home)
.env("USERPROFILE", env_userprofile);
if let Some(shared_lib_path) = shared_lib_path {
zig_cmd.args(&[
@ -419,7 +423,7 @@ pub fn build_c_host_native(
dest,
sources[0],
find_zig_str_path().to_str().unwrap(),
"x86_64-windows-gnu",
get_target_str(target),
opt_level,
Some(shared_lib_path),
);
@ -578,23 +582,16 @@ pub fn rebuild_host(
shared_lib_path,
)
}
Architecture::X86_64 => {
let target = match target.operating_system {
OperatingSystem::Windows => "x86_64-windows-gnu",
_ => "native",
};
build_zig_host_native(
&env_path,
&env_home,
host_dest.to_str().unwrap(),
zig_host_src.to_str().unwrap(),
zig_str_path.to_str().unwrap(),
target,
opt_level,
shared_lib_path,
)
}
Architecture::X86_64 => build_zig_host_native(
&env_path,
&env_home,
host_dest.to_str().unwrap(),
zig_host_src.to_str().unwrap(),
zig_str_path.to_str().unwrap(),
get_target_str(target),
opt_level,
shared_lib_path,
),
Architecture::X86_32(_) => build_zig_host_native(
&env_path,
&env_home,
@ -636,7 +633,7 @@ pub fn rebuild_host(
// on windows, we need the nightly toolchain so we can use `-Z export-executable-symbols`
// using `+nightly` only works when running cargo through rustup
let mut cmd = rustup();
cmd.args(["run", "nightly", "cargo"]);
cmd.args(["run", "nightly-2022-08-06", "cargo"]);
cmd
} else {
@ -807,6 +804,16 @@ pub fn rebuild_host(
host_dest
}
fn get_target_str(target: &Triple) -> &str {
if target.operating_system == OperatingSystem::Windows
&& target.environment == target_lexicon::Environment::Gnu
{
"x86_64-windows-gnu"
} else {
"native"
}
}
fn nix_path_opt() -> Option<String> {
env::var_os("NIX_GLIBC_PATH").map(|path| path.into_string().unwrap())
}
@ -1227,7 +1234,7 @@ fn link_wasm32(
}
fn link_windows(
_target: &Triple,
target: &Triple,
output_path: PathBuf,
input_paths: &[&str],
link_type: LinkType,
@ -1263,7 +1270,7 @@ fn link_windows(
.args(input_paths)
.args([
"-target",
"x86_64-windows-gnu",
get_target_str(target),
"--subsystem",
"console",
"-lc",
@ -1370,7 +1377,15 @@ pub fn preprocess_host_wasm32(host_input_path: &Path, preprocessed_host_path: &P
}
fn run_build_command(mut command: Command, file_to_build: &str, flaky_fail_counter: usize) {
let cmd_str = format!("{:?}", &command);
let mut command_string = std::ffi::OsString::new();
command_string.push(command.get_program());
for arg in command.get_args() {
command_string.push(" ");
command_string.push(arg);
}
let cmd_str = command_string.to_str().unwrap();
let cmd_output = command.output().unwrap();
let max_flaky_fail_count = 10;
@ -1378,24 +1393,26 @@ fn run_build_command(mut command: Command, file_to_build: &str, flaky_fail_count
match std::str::from_utf8(&cmd_output.stderr) {
Ok(stderr) => {
// flaky error seen on macos 12 apple silicon, related to https://github.com/ziglang/zig/issues/9711
if stderr.contains("unable to save cached ZIR code") && flaky_fail_counter < max_flaky_fail_count {
run_build_command(command, file_to_build, flaky_fail_counter + 1)
if stderr.contains("unable to save cached ZIR code") {
if flaky_fail_counter < max_flaky_fail_count {
run_build_command(command, file_to_build, flaky_fail_counter + 1)
} else {
internal_error!(
"Error:\n Failed to rebuild {} {} times, this is not a flaky failure:\n The executed command was:\n {}\n stderr of that command:\n {}",
file_to_build,
max_flaky_fail_count,
cmd_str,
stderr
)
}
} else {
internal_error!(
"Error:\n Failed to rebuild {} {} times, this is not a flaky failure:\n The executed command was:\n {}\n stderr of that command:\n {}",
"Error:\n Failed to rebuild {}:\n The executed command was:\n {}\n stderr of that command:\n {}",
file_to_build,
max_flaky_fail_count,
cmd_str,
stderr
)
}
internal_error!(
"Error:\n Failed to rebuild {}:\n The executed command was:\n {}\n stderr of that command:\n {}",
file_to_build,
cmd_str,
stderr
)
},
Err(utf8_err) => internal_error!(
"Error:\n Failed to rebuild {}:\n The executed command was:\n {}\n stderr of that command could not be parsed as valid utf8:\n {}",

View File

@ -222,8 +222,12 @@ fn run_command(mut command: Command, flaky_fail_counter: usize) {
} else {
run_command(command, flaky_fail_counter + 1)
}
} else if error_str
.contains("lld-link: error: failed to write the output file: Permission denied")
{
panic!("{} failed with:\n\n {}\n\nWorkaround:\n\n Re-run the cargo command that triggered this build.\n\n", command_str, error_str);
} else {
panic!("{} failed: {}", command_str, error_str);
panic!("{} failed with:\n\n {}\n", command_str, error_str);
}
}
},

View File

@ -105,42 +105,29 @@ impl FlatHash {
//
FlatType::Func(..) => Err(Underivable),
},
Content::Alias(sym, _, real_var, _) => match sym {
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => {
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_U8))
}
Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => {
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_U16))
}
Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => {
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_U32))
}
Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => {
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_U64))
}
Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => {
Ok(SingleLambdaSetImmediate(Symbol::HASH_ADD_U128))
}
Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => {
Ok(SingleLambdaSetImmediate(Symbol::HASH_HASH_I8))
}
Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => {
Ok(SingleLambdaSetImmediate(Symbol::HASH_HASH_I16))
}
Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => {
Ok(SingleLambdaSetImmediate(Symbol::HASH_HASH_I32))
}
Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => {
Ok(SingleLambdaSetImmediate(Symbol::HASH_HASH_I64))
}
Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => {
Ok(SingleLambdaSetImmediate(Symbol::HASH_HASH_I128))
}
Content::Alias(sym, _, real_var, _) => match num_symbol_to_hash_lambda(sym) {
Some(lambda) => Ok(lambda),
// NB: I believe it is okay to unwrap opaques here because derivers are only used
// by the backend, and the backend treats opaques like structural aliases.
_ => Self::from_var(subs, real_var),
None => Self::from_var(subs, real_var),
},
Content::RangedNumber(_) => Err(Underivable),
Content::RangedNumber(range) => {
// Find the integer we're going to compile to, that'll tell us what lambda we
// should resolve to.
//
// Note that at this point, we don't need to update the underlying type variable.
// That's because
//
// - If the type variable always had a ground constructor after solving, we would
// have already refined the ranged number during obligation checking.
//
// - If the type variable was generalized, then this branch is only reached
// during monomorphization, at which point we always choose a default layout
// for ranged numbers, without concern for reification to a ground type.
let chosen_width = range.default_compilation_width();
let lambda = num_symbol_to_hash_lambda(chosen_width.symbol()).unwrap();
Ok(lambda)
}
//
Content::RecursionVar { structure, .. } => Self::from_var(subs, structure),
//
@ -153,3 +140,40 @@ impl FlatHash {
}
}
}
const fn num_symbol_to_hash_lambda(symbol: Symbol) -> Option<FlatHash> {
use FlatHash::*;
match symbol {
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => {
Some(SingleLambdaSetImmediate(Symbol::HASH_ADD_U8))
}
Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => {
Some(SingleLambdaSetImmediate(Symbol::HASH_ADD_U16))
}
Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => {
Some(SingleLambdaSetImmediate(Symbol::HASH_ADD_U32))
}
Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => {
Some(SingleLambdaSetImmediate(Symbol::HASH_ADD_U64))
}
Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => {
Some(SingleLambdaSetImmediate(Symbol::HASH_ADD_U128))
}
Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => {
Some(SingleLambdaSetImmediate(Symbol::HASH_HASH_I8))
}
Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => {
Some(SingleLambdaSetImmediate(Symbol::HASH_HASH_I16))
}
Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => {
Some(SingleLambdaSetImmediate(Symbol::HASH_HASH_I32))
}
Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => {
Some(SingleLambdaSetImmediate(Symbol::HASH_HASH_I64))
}
Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => {
Some(SingleLambdaSetImmediate(Symbol::HASH_HASH_I128))
}
_ => None,
}
}

View File

@ -71,7 +71,7 @@ pub enum Derived {
}
/// The builtin ability member to derive.
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
pub enum DeriveBuiltin {
ToEncoder,
Decoder,

View File

@ -8,6 +8,7 @@ use crate::{
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Braces {
Round,
Square,
Curly,
}
@ -22,11 +23,13 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
<T as ExtractSpaces<'a>>::Item: Formattable,
{
let start = match braces {
Braces::Round => '(',
Braces::Curly => '{',
Braces::Square => '[',
};
let end = match braces {
Braces::Round => ')',
Braces::Curly => '}',
Braces::Square => ']',
};

View File

@ -134,7 +134,7 @@ impl<'a> Formattable for TypeDef<'a> {
if !self.is_multiline() {
debug_assert_eq!(members.len(), 1);
buf.push_str(" ");
buf.spaces(1);
members[0].format_with_options(
buf,
Parens::NotNeeded,

View File

@ -326,9 +326,6 @@ impl<'a> Formattable for Expr<'a> {
Record(fields) => {
fmt_record(buf, None, *fields, indent);
}
Tuple(_fields) => {
todo!("format tuple");
}
RecordUpdate { update, fields } => {
fmt_record(buf, Some(*update), *fields, indent);
}
@ -386,6 +383,7 @@ impl<'a> Formattable for Expr<'a> {
fmt_if(buf, branches, final_else, self.is_multiline(), indent);
}
When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent),
Tuple(items) => fmt_collection(buf, indent, Braces::Round, *items, Newlines::No),
List(items) => fmt_collection(buf, indent, Braces::Square, *items, Newlines::No),
BinOps(lefts, right) => fmt_binops(buf, lefts, right, false, parens, indent),
UnaryOp(sub_expr, unary_op) => {
@ -421,7 +419,10 @@ impl<'a> Formattable for Expr<'a> {
buf.push('.');
buf.push_str(key);
}
MalformedIdent(_, _) => {}
MalformedIdent(str, _) => {
buf.indent(indent);
buf.push_str(str)
}
MalformedClosure => {}
PrecedenceConflict { .. } => {}
}
@ -505,10 +506,10 @@ fn push_op(buf: &mut Buf, op: BinOp) {
pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u16) {
use roc_parse::ast::StrLiteral::*;
buf.indent(indent);
buf.push('"');
match literal {
PlainLine(string) => {
buf.indent(indent);
buf.push('"');
// When a PlainLine contains '\n' or '"', format as a block string
if string.contains('"') || string.contains('\n') {
buf.push_str("\"\"");
@ -523,15 +524,21 @@ pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u
} else {
buf.push_str_allow_spaces(string);
};
buf.push('"');
}
Line(segments) => {
buf.indent(indent);
buf.push('"');
for seg in segments.iter() {
format_str_segment(seg, buf, 0)
}
buf.push('"');
}
Block(lines) => {
// Block strings will always be formatted with """ on new lines
buf.push_str("\"\"");
buf.ensure_ends_with_newline();
buf.indent(indent);
buf.push_str("\"\"\"");
buf.newline();
for segments in lines.iter() {
@ -543,10 +550,9 @@ pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u
buf.newline();
}
buf.indent(indent);
buf.push_str("\"\"");
buf.push_str("\"\"\"");
}
}
buf.push('"');
}
fn fmt_binops<'a, 'buf>(

View File

@ -13,6 +13,11 @@ pub mod spaces;
use bumpalo::{collections::String, Bump};
use roc_parse::ast::Module;
#[cfg(windows)]
const NEWLINE: &str = "\r\n";
#[cfg(not(windows))]
const NEWLINE: &str = "\n";
#[derive(Debug)]
pub struct Ast<'a> {
pub module: Module<'a>,
@ -99,7 +104,9 @@ impl<'a> Buf<'a> {
pub fn newline(&mut self) {
self.spaces_to_flush = 0;
self.text.push('\n');
self.text.push_str(NEWLINE);
self.beginning_of_line = true;
}
@ -109,7 +116,7 @@ impl<'a> Buf<'a> {
if self.spaces_to_flush > 0 {
self.flush_spaces();
self.newline();
} else if !self.text.ends_with('\n') {
} else if !self.text.ends_with('\n') && !self.text.is_empty() {
self.newline()
}
}
@ -183,14 +190,14 @@ fn fmt_text_eof(text: &mut bumpalo::collections::String<'_>) {
// There's some whitespace at the end of this file, but the first
// whitespace char after the last non-whitespace char isn't a newline.
// So replace that whitespace char (and everything after it) with a newline.
text.replace_range(last_whitespace_index.., "\n");
text.replace_range(last_whitespace_index.., NEWLINE);
}
None => {
debug_assert!(last_whitespace_index == text.len());
debug_assert!(!text.ends_with(char::is_whitespace));
// This doesn't end in whitespace at all, so add a newline.
text.push('\n');
text.push_str(NEWLINE);
}
}
}

View File

@ -5,6 +5,8 @@ extern crate roc_fmt;
#[cfg(test)]
mod test_fmt {
use std::path::PathBuf;
use bumpalo::Bump;
use roc_fmt::annotation::{Formattable, Newlines, Parens};
use roc_fmt::def::fmt_defs;
@ -16,11 +18,14 @@ mod test_fmt {
use roc_parse::state::State;
use roc_test_utils::{assert_multiline_str_eq, workspace_root};
// Not intended to be used directly in tests; please use expr_formats_to or expr_formats_same
fn expr_formats_to(input: &str, expected: &str) {
/// Check that the given input is formatted to the given output.
/// If the expected output is `None`, then this only checks that the input
/// parses without error, formats without error, and
/// (optionally, based on the value of `check_stability`) re-parses to
/// the same AST as the original.
fn expr_formats(input: &str, check_formatting: impl Fn(&str), check_stability: bool) {
let arena = Bump::new();
let input = input.trim();
let expected = expected.trim();
match roc_parse::test_helpers::parse_expr_with(&arena, input) {
Ok(actual) => {
@ -32,12 +37,15 @@ mod test_fmt {
let output = buf.as_str();
assert_multiline_str_eq!(expected, output);
check_formatting(output);
let reparsed_ast = roc_parse::test_helpers::parse_expr_with(&arena, output).unwrap_or_else(|err| {
panic!(
"After formatting, the source code no longer parsed!\n\nParse error was: {:?}\n\nThe code that failed to parse:\n\n{}\n\n",
err, output
"After formatting, the source code no longer parsed!\n\n\
Parse error was: {:?}\n\n\
The code that failed to parse:\n\n{}\n\n\
The original ast was:\n\n{:#?}\n\n",
err, output, actual
);
});
@ -60,21 +68,34 @@ mod test_fmt {
}
// Now verify that the resultant formatting is _stable_ - i.e. that it doesn't change again if re-formatted
let mut reformatted_buf = Buf::new_in(&arena);
reparsed_ast.format_with_options(&mut reformatted_buf, Parens::NotNeeded, Newlines::Yes, 0);
if check_stability {
let mut reformatted_buf = Buf::new_in(&arena);
reparsed_ast.format_with_options(&mut reformatted_buf, Parens::NotNeeded, Newlines::Yes, 0);
if output != reformatted_buf.as_str() {
eprintln!("Formatting bug; formatting is not stable. Reformatting the formatted code changed it again, as follows:\n\n");
if output != reformatted_buf.as_str() {
eprintln!("Formatting bug; formatting is not stable. Reformatting the formatted code changed it again, as follows:\n\n");
assert_multiline_str_eq!(output, reformatted_buf.as_str());
assert_multiline_str_eq!(output, reformatted_buf.as_str());
}
}
}
Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{}\n\nParse error was:\n\n{:?}\n\n", input, error)
};
}
fn check_formatting(expected: &'_ str) -> impl Fn(&str) + '_ {
let expected = expected.trim();
move |output| {
assert_multiline_str_eq!(expected, output);
}
}
fn expr_formats_to(input: &str, expected: &str) {
expr_formats(input, check_formatting(expected), true);
}
fn expr_formats_same(input: &str) {
expr_formats_to(input, input);
expr_formats(input, check_formatting(input), true);
}
fn fmt_module_and_defs<'a>(
@ -5825,4 +5846,78 @@ mod test_fmt {
// "#
// ));
// }
#[test]
fn parse_test_snapshots_format_without_error() {
fn list(dir: &std::path::Path) -> std::vec::Vec<String> {
std::fs::read_dir(dir)
.unwrap()
.map(|f| f.unwrap().file_name().to_str().unwrap().to_string())
.collect::<std::vec::Vec<_>>()
}
fn check_saved_formatting(original: &'_ str, result_path: PathBuf) -> impl Fn(&str) + '_ {
move |actual_result: &str| {
if std::env::var("ROC_SNAPSHOT_TEST_OVERWRITE").is_ok() {
if original == actual_result {
std::fs::remove_file(&result_path)
.or_else(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
Ok(())
} else {
Err(e)
}
})
.unwrap();
} else {
std::fs::write(&result_path, actual_result).unwrap();
}
} else if original == actual_result {
// We represent this expectation on the filesystem as the _absence_ of the .formatted.expr.roc file.
// This makes the directory a bit cleaner.
assert!(!result_path.exists(),
"Expected no file at {}\n\
This how we represent the expectation that the formatting of the input file should not change.\n\
consider running the tests with:\n\
`env ROC_SNAPSHOT_TEST_OVERWRITE=1 cargo test ...` (which will delete the file for you),\n\
and commiting the delete.",
result_path.display());
} else {
let expected_result =
std::fs::read_to_string(&result_path).unwrap_or_else(|e| {
panic!(
"Error opening test output file {}:\n\
{:?}
Supposing the file is missing, consider running the tests with:\n\
`env ROC_SNAPSHOT_TEST_OVERWRITE=1 cargo test ...`\n\
and committing the file that creates.",
result_path.display(),
e
);
});
assert_multiline_str_eq!(expected_result.as_str(), actual_result);
}
}
}
let base = std::path::PathBuf::from("../parse/tests/snapshots/pass");
for file in list(&base) {
if let Some(prefix) = file.strip_suffix(".expr.roc") {
println!("formatting {}", file);
let contents = std::fs::read_to_string(base.join(&file)).unwrap();
let formatted_path = base.join(format!("{}.expr.formatted.roc", prefix));
expr_formats(
&contents,
check_saved_formatting(&contents, formatted_path),
false,
);
} else if file.ends_with(".module.roc") {
// TODO: re-format module defs and ensure they're correct.
// Note that these tests don't have an actual module header,
// so we'll have to pre-pend that for this test.
}
}
}
}

View File

@ -1053,7 +1053,16 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
load_roc_value(env, *layout, value.into_pointer_value(), "load_boxed_value")
}
Reset { symbol, .. } => {
Reset {
symbol,
update_mode,
} => {
let bytes = update_mode.to_bytes();
let update_var = UpdateModeVar(&bytes);
let update_mode = func_spec_solutions
.update_mode(update_var)
.unwrap_or(UpdateMode::Immutable);
let (tag_ptr, layout) = load_symbol_and_layout(scope, symbol);
let tag_ptr = tag_ptr.into_pointer_value();
@ -1070,7 +1079,11 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
let refcount_ptr =
PointerToRefcount::from_ptr_to_data(env, tag_pointer_clear_tag_id(env, tag_ptr));
let is_unique = refcount_ptr.is_1(env);
let is_unique = match update_mode {
UpdateMode::InPlace => env.context.bool_type().const_int(1, false),
UpdateMode::Immutable => refcount_ptr.is_1(env),
};
env.builder
.build_conditional_branch(is_unique, then_block, else_block);

View File

@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
use bumpalo::Bump;
use roc_module::symbol::ModuleId;
#[cfg(not(windows))]
const ROC_SKIP_SUBS_CACHE: &str = "ROC_SKIP_SUBS_CACHE";
const SKIP_SUBS_CACHE: bool = {

View File

@ -2276,33 +2276,12 @@ impl<'a> Layout<'a> {
env: &mut Env<'a, '_>,
range: NumericRange,
) -> Cacheable<LayoutResult<'a>> {
use roc_types::num::IntLitWidth;
// If we chose the default int layout then the real var might have been `Num *`, or
// similar. In this case fix-up width if we need to. Choose I64 if the range says
// that the number will fit, otherwise choose the next-largest number layout.
//
// We don't pass the range down because `RangedNumber`s are somewhat rare, they only
// appear due to number literals, so no need to increase parameter list sizes.
let num_layout = match range {
NumericRange::IntAtLeastSigned(w) | NumericRange::NumAtLeastSigned(w) => {
[IntLitWidth::I64, IntLitWidth::I128]
.iter()
.find(|candidate| candidate.is_superset(&w, true))
.expect("if number doesn't fit, should have been a type error")
}
NumericRange::IntAtLeastEitherSign(w) | NumericRange::NumAtLeastEitherSign(w) => [
IntLitWidth::I64,
IntLitWidth::U64,
IntLitWidth::I128,
IntLitWidth::U128,
]
.iter()
.find(|candidate| candidate.is_superset(&w, false))
.expect("if number doesn't fit, should have been a type error"),
};
let num_layout = range.default_compilation_width();
cacheable(Ok(Layout::int_literal_width_to_int(
*num_layout,
num_layout,
env.target_info,
)))
}

View File

@ -0,0 +1,4 @@
Hash has
hash : a -> U64
1

View File

@ -0,0 +1,5 @@
Hash has
hash : a -> U64
hash2 : a -> U64
1

View File

@ -0,0 +1,3 @@
Hash has hash : a -> U64 | a has Hash
1

View File

@ -0,0 +1,5 @@
Ab1 has ab1 : a -> {} | a has Ab1
Ab2 has ab2 : a -> {} | a has Ab2
1

View File

@ -0,0 +1,4 @@
{ x, y } : Foo
{ x, y } = { x: "foo", y: 3.14 }
x

View File

@ -0,0 +1,4 @@
UserId x : [UserId I64]
(UserId x) = UserId 42
x

View File

@ -0,0 +1,4 @@
(x, y) : Foo
(x, y) = ("foo", 3.14)
x

View File

@ -0,0 +1 @@
whee 12 34

View File

@ -0,0 +1 @@
!whee 12 foo

View File

@ -0,0 +1,9 @@
## first line of docs
## second line
## third line
## fourth line
##
## sixth line after doc new line
x = 5
42

View File

@ -0,0 +1,3 @@
12
* # test!
92

View File

@ -0,0 +1,2 @@
3 # test!
+ 4

View File

@ -0,0 +1,2 @@
3 # 2 × 2
+ 4

View File

@ -0,0 +1,3 @@
(Email str) = Email "blah@example.com"
str

View File

@ -0,0 +1 @@
x == y

View File

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

View File

@ -0,0 +1,3 @@
iffy = 5
42

View File

@ -0,0 +1,2 @@
\x ->
1

View File

@ -0,0 +1,10 @@
my_list = [
0,
[
a,
b,
],
1,
]
42

View File

@ -0,0 +1,9 @@
when [] is
[] -> {}
[..] -> {}
[_, .., _, ..] -> {}
[a, b, c, d] -> {}
[a, b, ..] -> {}
[.., c, d] -> {}
[[A], [..], [a]] -> {}
[[[], []], [[], x]] -> {}

View File

@ -0,0 +1,3 @@
when x is
bar.and -> 1
_ -> 4

View File

@ -0,0 +1,3 @@
when x is
Foo.and -> 1
_ -> 4

View File

@ -0,0 +1,7 @@
# ## not docs!
## docs, but with a problem
## (namely that this is a mix of docs and regular comments)
# not docs
x = 5
42

View File

@ -0,0 +1,3 @@
x, y <- List.map2 [] []
x + y

View File

@ -0,0 +1,13 @@
a = "Hello,\n\nWorld!"
b =
"""
Hello,\n\nWorld!
"""
c =
"""
Hello,
World!
"""
42

View File

@ -0,0 +1,11 @@
(
# before 1
1,
# after 1
# before 2
2,
# after 2
# before 3
3,
# after 3
)

View File

@ -0,0 +1,61 @@
Tuple(
[
@20-21 SpaceBefore(
SpaceAfter(
Num(
"1",
),
[
Newline,
LineComment(
"after 1",
),
],
),
[
Newline,
LineComment(
"before 1",
),
],
),
@59-60 SpaceBefore(
SpaceAfter(
Num(
"2",
),
[
Newline,
LineComment(
"after 2",
),
],
),
[
Newline,
LineComment(
"before 2",
),
],
),
@98-99 SpaceBefore(
SpaceAfter(
Num(
"3",
),
[
Newline,
LineComment(
" after 3",
),
],
),
[
Newline,
LineComment(
"before 3",
),
],
),
],
)

View File

@ -0,0 +1,13 @@
(
#before 1
1
#after 1
,
#before 2
2
#after 2
,
#before 3
3
# after 3
)

View File

@ -0,0 +1 @@
31 * 42 + 534

View File

@ -0,0 +1,6 @@
if t1 then
1
else if t2 then
2
else
3

View File

@ -0,0 +1,7 @@
# ######
# ## not docs!
# #still not docs
# #####
x = 5
42

View File

@ -0,0 +1,37 @@
{
u8: 123u8,
u16: 123u16,
u32: 123u32,
u64: 123u64,
u128: 123u128,
i8: 123i8,
i16: 123i16,
i32: 123i32,
i64: 123i64,
i128: 123i128,
nat: 123nat,
dec: 123dec,
u8Neg: -123u8,
u16Neg: -123u16,
u32Neg: -123u32,
u64Neg: -123u64,
u128Neg: -123u128,
i8Neg: -123i8,
i16Neg: -123i16,
i32Neg: -123i32,
i64Neg: -123i64,
i128Neg: -123i128,
natNeg: -123nat,
decNeg: -123dec,
u8Bin: 0b101u8,
u16Bin: 0b101u16,
u32Bin: 0b101u32,
u64Bin: 0b101u64,
u128Bin: 0b101u128,
i8Bin: 0b101i8,
i16Bin: 0b101i16,
i32Bin: 0b101i32,
i64Bin: 0b101i64,
i128Bin: 0b101i128,
natBin: 0b101nat,
}

View File

@ -0,0 +1,4 @@
# leading comment
x <- \y -> y
x

View File

@ -0,0 +1,4 @@
# leading comment
x = 5
42

View File

@ -0,0 +1,4 @@
# leading comment
x = 5
42

View File

@ -0,0 +1,3 @@
(@Thunk it) = id (@A {})
it {}

View File

@ -0,0 +1,24 @@
A := U8 has [Eq, Hash]
A := a | a has Other
has [Eq, Hash]
A := a | a has Other
has [Eq, Hash]
A := U8 has [Eq { eq }, Hash { hash }]
A := U8 has [Eq { eq, eq1 }]
A := U8 has [Eq { eq, eq1 }, Hash]
A := U8 has [Hash, Eq { eq, eq1 }]
A := U8 has []
A := a | a has Other
has [Eq { eq }, Hash { hash }]
A := U8 has [Eq {}]
0

View File

@ -0,0 +1,2 @@
when n is
@Age -> 1

View File

@ -0,0 +1,2 @@
when n is
@Add n m -> n + m

View File

@ -0,0 +1,8 @@
x = foo
(
baz {
bar: blah,
}
)
x

View File

@ -0,0 +1,7 @@
a = [
1,
2,
3,
]
a

View File

@ -0,0 +1,5 @@
x = foo {
bar: blah,
}
x

View File

@ -0,0 +1,3 @@
Blah a b : Foo.Bar.Baz x y
42

View File

@ -0,0 +1,3 @@
foo : Foo.Bar.Baz x y as Blah a b
42

View File

@ -0,0 +1,2 @@
when Delmin (Del rx) 0 is
Delmin (Del ry) _ -> Node Black 0 Bool.false ry

View File

@ -0,0 +1 @@
1 * if Bool.true then 1 else 1

View File

@ -0,0 +1,5 @@
1
+
when Foo is
Foo -> 2
Bar -> 3

View File

@ -0,0 +1,5 @@
# leading comment
{ x, y } = 5
y = 6
42

View File

@ -0,0 +1,8 @@
f : {
getLine : Effect Str,
putLine : Str -> Effect Int,
text : Str,
value : Int *,
}
42

View File

@ -0,0 +1,3 @@
x : { init : {} -> Model, update : Model, Str -> Model, view : Model -> Str }
42

View File

@ -0,0 +1 @@
{ x: if Bool.true then 1 else 2, y: 3 }

View File

@ -0,0 +1,5 @@
# leading comment
x <- \y -> y
z <- {}
x

View File

@ -0,0 +1,3 @@
when x is
"" -> 1
"mise" -> 2

View File

@ -0,0 +1,5 @@
# leading comment
x = 5
y = 6
42

View File

@ -0,0 +1,3 @@
doStuff : UserId -> Task Str _
42

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