WIP Merge remote-tracking branch 'remote/main' into rebuild-platform

This commit is contained in:
Luke Boswell 2024-10-09 09:21:46 +11:00
commit e3afeaa7ff
No known key found for this signature in database
GPG key ID: F6DB3C9DB47377B0
138 changed files with 3544 additions and 4651 deletions

3
.gitignore vendored
View file

@ -119,3 +119,6 @@ crates/glue/tests/fixtures/*/build.rs
crates/glue/tests/fixtures/*/host.c crates/glue/tests/fixtures/*/host.c
crates/glue/tests/fixtures/*/src/main.rs crates/glue/tests/fixtures/*/src/main.rs
crates/glue/tests/fixtures/*/test_glue/ crates/glue/tests/fixtures/*/test_glue/
# ignore the zig glue files copied into test platforms
**/*platform/glue/*

65
Cargo.lock generated
View file

@ -384,11 +384,11 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.83" version = "1.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6"
dependencies = [ dependencies = [
"libc", "shlex",
] ]
[[package]] [[package]]
@ -1400,9 +1400,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.149" version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -1541,6 +1541,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "memmap2"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "memoffset" name = "memoffset"
version = "0.6.5" version = "0.6.5"
@ -1657,6 +1666,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.17" version = "0.2.17"
@ -1865,9 +1880,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.69" version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -1933,9 +1948,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.33" version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -2366,6 +2381,7 @@ name = "roc_cli"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"chrono",
"clap 4.4.6", "clap 4.4.6",
"cli_utils", "cli_utils",
"const_format", "const_format",
@ -2374,6 +2390,7 @@ dependencies = [
"errno", "errno",
"indoc", "indoc",
"inkwell", "inkwell",
"insta",
"libc", "libc",
"libloading", "libloading",
"mimalloc", "mimalloc",
@ -2704,7 +2721,7 @@ dependencies = [
"indoc", "indoc",
"libc", "libc",
"mach_object", "mach_object",
"memmap2", "memmap2 0.5.10",
"object", "object",
"roc_collections", "roc_collections",
"roc_error_macros", "roc_error_macros",
@ -3132,6 +3149,14 @@ dependencies = [
"static_assertions", "static_assertions",
] ]
[[package]]
name = "roc_std_heap"
version = "0.0.1"
dependencies = [
"memmap2 0.9.4",
"roc_std",
]
[[package]] [[package]]
name = "roc_target" name = "roc_target"
version = "0.0.1" version = "0.0.1"
@ -3296,9 +3321,9 @@ dependencies = [
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.14" version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
[[package]] [[package]]
name = "rusty-fork" name = "rusty-fork"
@ -3526,6 +3551,12 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "signal-hook" name = "signal-hook"
version = "0.3.17" version = "0.3.17"
@ -3939,12 +3970,13 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.30" version = "0.3.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
dependencies = [ dependencies = [
"deranged", "deranged",
"itoa", "itoa",
"num-conv",
"powerfmt", "powerfmt",
"serde", "serde",
"time-core", "time-core",
@ -3959,10 +3991,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]] [[package]]
name = "time-macros" name = "time-macros"
version = "0.2.15" version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
dependencies = [ dependencies = [
"num-conv",
"time-core", "time-core",
] ]

View file

@ -29,6 +29,7 @@ members = [
"crates/wasm_module", "crates/wasm_module",
"crates/wasm_interp", "crates/wasm_interp",
"crates/language_server", "crates/language_server",
"crates/roc_std_heap",
] ]
exclude = [ exclude = [

View file

@ -19,12 +19,8 @@ You can 💜 **sponsor** 💜 Roc on:
- [GitHub](https://github.com/sponsors/roc-lang) - [GitHub](https://github.com/sponsors/roc-lang)
- [Liberapay](https://liberapay.com/roc_lang) - [Liberapay](https://liberapay.com/roc_lang)
We are very grateful for our corporate sponsors [Vendr](https://www.vendr.com/), [RWX](https://www.rwx.com), [Tweede golf](https://tweedegolf.nl/en), [ohne-makler](https://www.ohne-makler.net), and [Decem](https://www.decem.com.au): We are very grateful for our corporate sponsors [Tweede golf](https://tweedegolf.nl/en), [ohne-makler](https://www.ohne-makler.net), and [Decem](https://www.decem.com.au):
[<img src="https://user-images.githubusercontent.com/1094080/223597445-81755626-a080-4299-a38c-3c92e7548489.png" height="60" alt="Vendr logo"/>](https://www.vendr.com)
&nbsp;&nbsp;&nbsp;&nbsp;
[<img src="https://github.com/roc-lang/roc/assets/1094080/82c0868e-d23f-42a0-ac2d-c6e6b2e16575" height="60" alt="RWX logo"/>](https://www.rwx.com)
&nbsp;&nbsp;&nbsp;&nbsp;
[<img src="https://user-images.githubusercontent.com/1094080/183123052-856815b1-8cc9-410a-83b0-589f03613188.svg" height="60" alt="tweede golf logo"/>](https://tweedegolf.nl/en) [<img src="https://user-images.githubusercontent.com/1094080/183123052-856815b1-8cc9-410a-83b0-589f03613188.svg" height="60" alt="tweede golf logo"/>](https://tweedegolf.nl/en)
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;
[<img src="https://www.ohne-makler.net/static/img/brand/logo.svg" height="60" alt="ohne-makler logo"/>](https://www.ohne-makler.net) [<img src="https://www.ohne-makler.net/static/img/brand/logo.svg" height="60" alt="ohne-makler logo"/>](https://www.ohne-makler.net)
@ -35,6 +31,7 @@ If you would like your company to become a corporate sponsor of Roc's developmen
We'd also like to express our gratitude to our generous [individual sponsors](https://github.com/sponsors/roc-lang/)! A special thanks to those sponsoring $25/month or more: We'd also like to express our gratitude to our generous [individual sponsors](https://github.com/sponsors/roc-lang/)! A special thanks to those sponsoring $25/month or more:
- [Peter Marreck](https://github.com/pmarreck)
- [Barry Moore](https://github.com/chiroptical) - [Barry Moore](https://github.com/chiroptical)
- Eric Andresen - Eric Andresen
- [Jackson Lucky](https://github.com/jluckyiv) - [Jackson Lucky](https://github.com/jluckyiv)

View file

@ -86,7 +86,7 @@ Surgical linker that links platforms to Roc applications. We created our own lin
## `repl_cli/` - `roc_repl_cli` ## `repl_cli/` - `roc_repl_cli`
Command Line Interface(CLI) functionality for the Read-Evaluate-Print-Loop (REPL). Command Line Interface (CLI) functionality for the Read-Evaluate-Print-Loop (REPL).
## `repl_eval/` - `roc_repl_eval` ## `repl_eval/` - `roc_repl_eval`

View file

@ -2,6 +2,7 @@
name = "roc_cli" name = "roc_cli"
description = "The Roc binary that brings together all functionality in the Roc toolset." description = "The Roc binary that brings together all functionality in the Roc toolset."
default-run = "roc" default-run = "roc"
build = "build.rs"
authors.workspace = true authors.workspace = true
edition.workspace = true edition.workspace = true
@ -30,7 +31,13 @@ target-wasm32 = ["roc_build/target-wasm32"]
target-x86 = ["roc_build/target-x86", "roc_repl_cli/target-x86"] target-x86 = ["roc_build/target-x86", "roc_repl_cli/target-x86"]
target-x86_64 = ["roc_build/target-x86_64", "roc_repl_cli/target-x86_64"] target-x86_64 = ["roc_build/target-x86_64", "roc_repl_cli/target-x86_64"]
target-all = ["target-aarch64", "target-arm", "target-x86", "target-x86_64", "target-wasm32"] target-all = [
"target-aarch64",
"target-arm",
"target-x86",
"target-x86_64",
"target-wasm32",
]
sanitizers = ["roc_build/sanitizers"] sanitizers = ["roc_build/sanitizers"]
@ -90,6 +97,10 @@ criterion.workspace = true
indoc.workspace = true indoc.workspace = true
parking_lot.workspace = true parking_lot.workspace = true
pretty_assertions.workspace = true pretty_assertions.workspace = true
insta.workspace = true
[build-dependencies]
chrono.workspace = true
[[bench]] [[bench]]
name = "time_bench" name = "time_bench"

71
crates/cli/build.rs Normal file
View file

@ -0,0 +1,71 @@
use chrono::prelude::*;
use std::fs;
use std::process::Command;
use std::str;
fn main() {
// Rebuild if this build.rs file changes
println!("cargo:rerun-if-changed=build.rs");
// The version file is located at the root of the repository
let version_file_path = "../../version.txt";
// Rebuild if version file changes
println!("cargo:rerun-if-changed={}", version_file_path);
// Read the version file
let version_file_contents = fs::read_to_string(version_file_path).unwrap();
// If the version is "built-from-source", replace it with the git commit information
let version = match version_file_contents.trim() {
"built-from-source" => {
let git_head_file_path = "../../.git/HEAD";
// Rebuild if a new Git commit is made
println!("cargo:rerun-if-changed={}", git_head_file_path);
// Check if the .git/HEAD file exists
let git_head_exists = fs::metadata(git_head_file_path).is_ok();
if git_head_exists {
// Get the hash of the current commit
let git_describe_output = Command::new("git")
.arg("describe")
.arg("--always")
.arg("--dirty= with additional changes") // Add a suffix if the working directory is dirty
.output()
.expect("Failed to execute git describe command");
println!("git_describe_output: {:?}", git_describe_output);
let git_commit_hash = str::from_utf8(&git_describe_output.stdout)
.expect("Failed to parse git describe output")
.trim();
// Get the datetime of the last commit
let git_show_output = Command::new("git")
.arg("show")
.arg("--no-patch")
.arg("--format=%ct") // Outputting a UNIX timestamp is the only way to always use UTC
.output()
.expect("Failed to execute git show command");
println!("git_show_output: {:?}", git_show_output);
let git_commit_timestamp = {
let timestamp = str::from_utf8(&git_show_output.stdout)
.expect("Failed to parse git show output as a string")
.trim()
.parse::<i64>()
.expect("Failed to parse timestamp as an integer");
DateTime::from_timestamp(timestamp, 0)
.expect("Failed to parse timestamp")
.format("%Y-%m-%d %H:%M:%S")
};
format!(
"built from commit {git_commit_hash}, committed at {git_commit_timestamp} UTC"
)
} else {
// If the .git/HEAD file does not exist, e.g. in a Nix build, use a generic message
"built from source".to_string()
}
}
_ => version_file_contents.trim().to_string(),
};
// Emit the version to a build-time environment variable
println!("cargo:rustc-env=ROC_VERSION={}", version);
}

View file

@ -266,20 +266,23 @@ mod tests {
const FORMATTED_ROC: &str = r#"app [main] { pf: platform "platform/main.roc" } const FORMATTED_ROC: &str = r#"app [main] { pf: platform "platform/main.roc" }
import pf.Stdout import pf.Stdout
import pf.Task import pf.Stdin
main = main =
Stdout.line! "I'm a Roc application!""#; Stdout.line! "What's your name?"
name = Stdin.line!
Stdout.line! "Hi $(name)!""#;
const UNFORMATTED_ROC: &str = r#"app [main] { pf: platform "platform/main.roc" } const UNFORMATTED_ROC: &str = r#"app [main] { pf: platform "platform/main.roc" }
import pf.Stdout import pf.Stdout
import pf.Stdin
import pf.Task
main = main =
Stdout.line! "I'm a Roc application!" Stdout.line! "What's your name?"
name = Stdin.line!
Stdout.line! "Hi $(name)!"
"#; "#;
fn setup_test_file(dir: &Path, file_name: &str, contents: &str) -> PathBuf { fn setup_test_file(dir: &Path, file_name: &str, contents: &str) -> PathBuf {

View file

@ -68,6 +68,8 @@ pub const FLAG_NO_LINK: &str = "no-link";
pub const FLAG_TARGET: &str = "target"; pub const FLAG_TARGET: &str = "target";
pub const FLAG_TIME: &str = "time"; pub const FLAG_TIME: &str = "time";
pub const FLAG_VERBOSE: &str = "verbose"; pub const FLAG_VERBOSE: &str = "verbose";
pub const FLAG_NO_COLOR: &str = "no-color";
pub const FLAG_NO_HEADER: &str = "no-header";
pub const FLAG_LINKER: &str = "linker"; pub const FLAG_LINKER: &str = "linker";
pub const FLAG_BUILD_HOST: &str = "build-host"; pub const FLAG_BUILD_HOST: &str = "build-host";
pub const FLAG_SUPPRESS_BUILD_HOST_WARNING: &str = "suppress-build-host-warning"; pub const FLAG_SUPPRESS_BUILD_HOST_WARNING: &str = "suppress-build-host-warning";
@ -88,7 +90,7 @@ pub const FLAG_PP_HOST: &str = "host";
pub const FLAG_PP_PLATFORM: &str = "platform"; pub const FLAG_PP_PLATFORM: &str = "platform";
pub const FLAG_PP_DYLIB: &str = "lib"; pub const FLAG_PP_DYLIB: &str = "lib";
const VERSION: &str = include_str!("../../../version.txt"); pub const VERSION: &str = env!("ROC_VERSION");
const DEFAULT_GENERATED_DOCS_DIR: &str = "generated-docs"; const DEFAULT_GENERATED_DOCS_DIR: &str = "generated-docs";
pub fn build_app() -> Command { pub fn build_app() -> Command {
@ -186,7 +188,7 @@ pub fn build_app() -> Command {
PossibleValuesParser::new(Target::iter().map(Into::<&'static str>::into)); PossibleValuesParser::new(Target::iter().map(Into::<&'static str>::into));
Command::new("roc") Command::new("roc")
.version(concatcp!(VERSION, "\n")) .version(VERSION)
.about("Run the given .roc file, if there are no compilation errors.\nYou can use one of the SUBCOMMANDS below to do something else!") .about("Run the given .roc file, if there are no compilation errors.\nYou can use one of the SUBCOMMANDS below to do something else!")
.args_conflicts_with_subcommands(true) .args_conflicts_with_subcommands(true)
.subcommand(Command::new(CMD_BUILD) .subcommand(Command::new(CMD_BUILD)
@ -279,6 +281,20 @@ pub fn build_app() -> Command {
) )
.subcommand(Command::new(CMD_REPL) .subcommand(Command::new(CMD_REPL)
.about("Launch the interactive Read Eval Print Loop (REPL)") .about("Launch the interactive Read Eval Print Loop (REPL)")
.arg(
Arg::new(FLAG_NO_COLOR)
.long(FLAG_NO_COLOR)
.help("Do not use any ANSI color codes in the repl output")
.action(ArgAction::SetTrue)
.required(false)
)
.arg(
Arg::new(FLAG_NO_HEADER)
.long(FLAG_NO_HEADER)
.help("Do not print the repl header")
.action(ArgAction::SetTrue)
.required(false)
)
) )
.subcommand(Command::new(CMD_RUN) .subcommand(Command::new(CMD_RUN)
.about("Run a .roc file even if it has build errors") .about("Run a .roc file even if it has build errors")
@ -542,7 +558,7 @@ pub fn test(matches: &ArgMatches, target: Target) -> io::Result<i32> {
arena, arena,
path.to_path_buf(), path.to_path_buf(),
opt_main_path.cloned(), opt_main_path.cloned(),
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()), RocCacheDir::Persistent(cache::roc_cache_packages_dir().as_path()),
load_config, load_config,
); );
@ -1266,8 +1282,8 @@ fn roc_dev_native(
break if libc::WIFEXITED(status) { break if libc::WIFEXITED(status) {
libc::WEXITSTATUS(status) libc::WEXITSTATUS(status)
} else { } else {
// we don't have an exit code, so we probably shouldn't make one up // we don't have an exit code, but something went wrong if we're in this else
0 1
}; };
} }
ChildProcessMsg::Expect => { ChildProcessMsg::Expect => {

View file

@ -5,9 +5,9 @@ use roc_build::program::{check_file, CodeGenBackend};
use roc_cli::{ use roc_cli::{
build_app, format_files, format_src, test, BuildConfig, FormatMode, CMD_BUILD, CMD_CHECK, build_app, format_files, format_src, test, BuildConfig, FormatMode, CMD_BUILD, CMD_CHECK,
CMD_DEV, CMD_DOCS, CMD_FORMAT, CMD_GLUE, CMD_PREPROCESS_HOST, CMD_REPL, CMD_RUN, CMD_TEST, CMD_DEV, CMD_DOCS, CMD_FORMAT, CMD_GLUE, CMD_PREPROCESS_HOST, CMD_REPL, CMD_RUN, CMD_TEST,
CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_DEV, FLAG_LIB, FLAG_MAIN, FLAG_NO_LINK, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_DEV, FLAG_LIB, FLAG_MAIN, FLAG_NO_COLOR,
FLAG_OUTPUT, FLAG_PP_DYLIB, FLAG_PP_HOST, FLAG_PP_PLATFORM, FLAG_STDIN, FLAG_STDOUT, FLAG_NO_HEADER, FLAG_NO_LINK, FLAG_OUTPUT, FLAG_PP_DYLIB, FLAG_PP_HOST, FLAG_PP_PLATFORM,
FLAG_TARGET, FLAG_TIME, GLUE_DIR, GLUE_SPEC, ROC_FILE, FLAG_STDIN, FLAG_STDOUT, FLAG_TARGET, FLAG_TIME, GLUE_DIR, GLUE_SPEC, ROC_FILE, VERSION,
}; };
use roc_docs::generate_docs_html; use roc_docs::generate_docs_html;
use roc_error_macros::user_error; use roc_error_macros::user_error;
@ -22,9 +22,6 @@ use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
use target_lexicon::Triple; use target_lexicon::Triple;
#[macro_use]
extern crate const_format;
#[global_allocator] #[global_allocator]
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
@ -51,7 +48,7 @@ fn main() -> io::Result<()> {
BuildConfig::BuildAndRunIfNoErrors, BuildConfig::BuildAndRunIfNoErrors,
Triple::host().into(), Triple::host().into(),
None, None,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()), RocCacheDir::Persistent(cache::roc_cache_packages_dir().as_path()),
LinkType::Executable, LinkType::Executable,
) )
} else { } else {
@ -66,7 +63,7 @@ fn main() -> io::Result<()> {
BuildConfig::BuildAndRun, BuildConfig::BuildAndRun,
Triple::host().into(), Triple::host().into(),
None, None,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()), RocCacheDir::Persistent(cache::roc_cache_packages_dir().as_path()),
LinkType::Executable, LinkType::Executable,
) )
} else { } else {
@ -92,7 +89,7 @@ fn main() -> io::Result<()> {
BuildConfig::BuildAndRunIfNoErrors, BuildConfig::BuildAndRunIfNoErrors,
Triple::host().into(), Triple::host().into(),
None, None,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()), RocCacheDir::Persistent(cache::roc_cache_packages_dir().as_path()),
LinkType::Executable, LinkType::Executable,
) )
} else { } else {
@ -190,7 +187,7 @@ fn main() -> io::Result<()> {
BuildConfig::BuildOnly, BuildConfig::BuildOnly,
target, target,
out_path, out_path,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()), RocCacheDir::Persistent(cache::roc_cache_packages_dir().as_path()),
link_type, link_type,
)?) )?)
} }
@ -213,7 +210,7 @@ fn main() -> io::Result<()> {
roc_file_path.to_owned(), roc_file_path.to_owned(),
opt_main_path.cloned(), opt_main_path.cloned(),
emit_timings, emit_timings,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()), RocCacheDir::Persistent(cache::roc_cache_packages_dir().as_path()),
threading, threading,
) { ) {
Ok((problems, total_time)) => { Ok((problems, total_time)) => {
@ -231,7 +228,12 @@ fn main() -> io::Result<()> {
} }
} }
} }
Some((CMD_REPL, _)) => Ok(roc_repl_cli::main()), Some((CMD_REPL, matches)) => {
let has_color = !matches.get_one::<bool>(FLAG_NO_COLOR).unwrap();
let has_header = !matches.get_one::<bool>(FLAG_NO_HEADER).unwrap();
Ok(roc_repl_cli::main(has_color, has_header))
}
Some((CMD_DOCS, matches)) => { Some((CMD_DOCS, matches)) => {
let root_path = matches.get_one::<PathBuf>(ROC_FILE).unwrap(); let root_path = matches.get_one::<PathBuf>(ROC_FILE).unwrap();
let out_dir = matches.get_one::<OsString>(FLAG_OUTPUT).unwrap(); let out_dir = matches.get_one::<OsString>(FLAG_OUTPUT).unwrap();
@ -347,11 +349,7 @@ fn main() -> io::Result<()> {
Ok(format_exit_code) Ok(format_exit_code)
} }
Some((CMD_VERSION, _)) => { Some((CMD_VERSION, _)) => {
print!( println!("roc {}", VERSION);
"{}",
concatcp!("roc ", include_str!("../../../version.txt"))
);
Ok(0) Ok(0)
} }
_ => unreachable!(), _ => unreachable!(),

View file

@ -767,6 +767,66 @@ mod cli_run {
let out = runner.run(); let out = runner.run();
out.assert_stdout_and_stderr_ends_with(expected_ending); out.assert_stdout_and_stderr_ends_with(expected_ending);
} }
#[test]
#[cfg_attr(windows, ignore)]
fn module_params() {
build_platform_host();
let expected_ending = indoc!(
r#"
App1.baseUrl: https://api.example.com/one
App2.baseUrl: http://api.example.com/two
App3.baseUrl: https://api.example.com/three
App1.getUser 1: https://api.example.com/one/users/1
App2.getUser 2: http://api.example.com/two/users/2
App3.getUser 3: https://api.example.com/three/users/3
App1.getPost 1: https://api.example.com/one/posts/1
App2.getPost 2: http://api.example.com/two/posts/2
App3.getPost 3: https://api.example.com/three/posts/3
App1.getPosts [1, 2]: ["https://api.example.com/one/posts/1", "https://api.example.com/one/posts/2"]
App2.getPosts [3, 4]: ["http://api.example.com/two/posts/3", "http://api.example.com/two/posts/4"]
App2.getPosts [5, 6]: ["http://api.example.com/two/posts/5", "http://api.example.com/two/posts/6"]
App1.getPostComments 1: https://api.example.com/one/posts/1/comments
App2.getPostComments 2: http://api.example.com/two/posts/2/comments
App2.getPostComments 3: http://api.example.com/two/posts/3/comments
App1.getCompanies [1, 2]: ["https://api.example.com/one/companies/1", "https://api.example.com/one/companies/2"]
App2.getCompanies [3, 4]: ["http://api.example.com/two/companies/3", "http://api.example.com/two/companies/4"]
App2.getCompanies [5, 6]: ["http://api.example.com/two/companies/5", "http://api.example.com/two/companies/6"]
App1.getPostAliased 1: https://api.example.com/one/posts/1
App2.getPostAliased 2: http://api.example.com/two/posts/2
App3.getPostAliased 3: https://api.example.com/three/posts/3
App1.baseUrlAliased: https://api.example.com/one
App2.baseUrlAliased: http://api.example.com/two
App3.baseUrlAliased: https://api.example.com/three
App1.getUserSafe 1: https://api.example.com/one/users/1
Prod.getUserSafe 2: http://api.example.com/prod_1/users/2?safe=true
usersApp1: ["https://api.example.com/one/users/1", "https://api.example.com/one/users/2", "https://api.example.com/one/users/3"]
getUserApp3Nested 3: https://api.example.com/three/users/3
usersApp3Passed: ["https://api.example.com/three/users/1", "https://api.example.com/three/users/2", "https://api.example.com/three/users/3"]
"#
);
let runner = cli_utils::helpers::Run::new_roc()
.arg(CMD_RUN)
.arg(file_from_root("crates/cli/tests/module_params", "app.roc").as_path());
let out = runner.run();
out.assert_stdout_and_stderr_ends_with(expected_ending);
}
#[test]
#[cfg_attr(windows, ignore)]
fn module_params_arity_mismatch() {
build_platform_host();
let runner = cli_utils::helpers::Run::new_roc().arg(CMD_DEV).arg(
file_from_root("crates/cli/tests/module_params", "arity_mismatch.roc").as_path(),
);
let out = runner.run();
insta::assert_snapshot!(out.normalize_stdout_and_stderr());
}
} }
// TODO not sure if this cfg should still be here: #[cfg(not(debug_assertions))] // TODO not sure if this cfg should still be here: #[cfg(not(debug_assertions))]

View file

@ -0,0 +1,8 @@
module {
sendHttpReq,
getEnvVar
} -> [hi]
hi : Str
hi =
"hi"

View file

@ -1,5 +1,5 @@
app [main] { app [main] {
pf: platform "../fixtures/multi-dep-str/platform/main.roc", pf: platform "../test-platform-simple-zig/main.roc",
} }
import Api { appId: "one", protocol: https } as App1 import Api { appId: "one", protocol: https } as App1

View file

@ -1,5 +1,5 @@
app [main] { app [main] {
pf: platform "../fixtures/multi-dep-str/platform/main.roc", pf: platform "../test-platform-simple-zig/main.roc",
} }
import Api { appId: "one", protocol: https } import Api { appId: "one", protocol: https }

View file

@ -0,0 +1,11 @@
app [main] {
pf: platform "../fixtures/multi-dep-str/platform/main.roc",
}
import MultilineParams {
sendHttpReq: \_ -> crash "todo",
getEnvVar: \_ -> crash "todo",
}
main =
MultilineParams.hi

View file

@ -1,5 +1,5 @@
app [main] { app [main] {
pf: platform "../fixtures/multi-dep-str/platform/main.roc", pf: platform "../test-platform-simple-zig/main.roc",
} }
import Api { appId: "one", protocol: https } import Api { appId: "one", protocol: https }

View file

@ -0,0 +1,40 @@
---
source: crates/cli/tests/cli_run.rs
expression: out.normalize_stdout_and_stderr()
---
── TOO MANY ARGS in tests/module_params/arity_mismatch.roc ─────────────────────
The getUser function expects 1 argument, but it got 2 instead:
12│ $(Api.getUser 1 2)
^^^^^^^^^^^
Are there any missing commas? Or missing parentheses?
── TOO MANY ARGS in tests/module_params/arity_mismatch.roc ─────────────────────
This value is not a function, but it was given 1 argument:
13│ $(Api.baseUrl 1)
^^^^^^^^^^^
Are there any missing commas? Or missing parentheses?
── TOO FEW ARGS in tests/module_params/arity_mismatch.roc ──────────────────────
The getPostComment function expects 2 arguments, but it got only 1:
16│ $(Api.getPostComment 1)
^^^^^^^^^^^^^^^^^^
Roc does not allow functions to be partially applied. Use a closure to
make partial application explicit.
────────────────────────────────────────────────────────────────────────────────
3 error and 0 warning found in <ignored for test> ms
.
You can run the program anyway <ignored for tests>

View file

@ -101,9 +101,18 @@ impl Out {
Regex::new(r"(\d+) error(?:s)? and (\d+) warning(?:s)? found in \d+ ms") Regex::new(r"(\d+) error(?:s)? and (\d+) warning(?:s)? found in \d+ ms")
.expect("Invalid error summary regex pattern"); .expect("Invalid error summary regex pattern");
let error_summary_replacement = "$1 error and $2 warning found in <ignored for test> ms"; let error_summary_replacement = "$1 error and $2 warning found in <ignored for test> ms";
error_summary_regex let without_error_summary = error_summary_regex
.replace_all(&without_filepaths, error_summary_replacement) .replace_all(&without_filepaths, error_summary_replacement)
.to_string() .to_string();
// replace the entire line with the placeholder
let run_command_line_regex = Regex::new(r"(?m)^You can run the program anyway .*$")
.expect("Invalid run command line regex pattern");
let run_command_line_replacement = "You can run the program anyway <ignored for tests>";
let normalized_output = run_command_line_regex
.replace_all(&without_error_summary, run_command_line_replacement);
normalized_output.trim().to_string()
} }
/// Assert that the stdout ends with the expected string /// Assert that the stdout ends with the expected string
@ -129,6 +138,14 @@ impl Out {
ANSI_STYLE_CODES.reset, ANSI_STYLE_CODES.reset,
); );
} }
pub fn normalize_stdout_and_stderr(&self) -> String {
format!(
"{}{}",
Out::normalize_for_tests(&self.stdout),
Out::normalize_for_tests(&self.stderr)
)
}
} }
/// A builder for running a command. /// A builder for running a command.

View file

@ -618,6 +618,16 @@ pub fn listDropAt(
) callconv(.C) RocList { ) callconv(.C) RocList {
const size = list.len(); const size = list.len();
const size_u64 = @as(u64, @intCast(size)); const size_u64 = @as(u64, @intCast(size));
// NOTE
// we need to return an empty list explicitly,
// because we rely on the pointer field being null if the list is empty
// which also requires duplicating the utils.decref call to spend the RC token
if (size <= 1) {
list.decref(alignment, element_width, elements_refcounted, dec);
return RocList.empty();
}
// If droping the first or last element, return a seamless slice. // If droping the first or last element, return a seamless slice.
// For simplicity, do this by calling listSublist. // For simplicity, do this by calling listSublist.
// In the future, we can test if it is faster to manually inline the important parts here. // In the future, we can test if it is faster to manually inline the important parts here.
@ -638,25 +648,16 @@ pub fn listDropAt(
// were >= than `size`, and we know `size` fits in usize. // were >= than `size`, and we know `size` fits in usize.
const drop_index: usize = @intCast(drop_index_u64); const drop_index: usize = @intCast(drop_index_u64);
if (elements_refcounted) {
const element = source_ptr + drop_index * element_width;
dec(element);
}
// NOTE
// we need to return an empty list explicitly,
// because we rely on the pointer field being null if the list is empty
// which also requires duplicating the utils.decref call to spend the RC token
if (size < 2) {
list.decref(alignment, element_width, elements_refcounted, dec);
return RocList.empty();
}
if (list.isUnique()) { if (list.isUnique()) {
var i = drop_index; if (elements_refcounted) {
const copy_target = source_ptr; const element = source_ptr + drop_index * element_width;
dec(element);
}
const copy_target = source_ptr + (drop_index * element_width);
const copy_source = copy_target + element_width; const copy_source = copy_target + element_width;
std.mem.copyForwards(u8, copy_target[i..size], copy_source[i..size]); const copy_size = (size - drop_index - 1) * element_width;
std.mem.copyForwards(u8, copy_target[0..copy_size], copy_source[0..copy_size]);
var new_list = list; var new_list = list;

View file

@ -509,33 +509,33 @@ removeHelper = \buckets, bucketIndex, distAndFingerprint, data, key ->
## is missing. This is more efficient than doing both a `Dict.get` and then a ## is missing. This is more efficient than doing both a `Dict.get` and then a
## `Dict.insert` call, and supports being piped. ## `Dict.insert` call, and supports being piped.
## ```roc ## ```roc
## alterValue : [Present Bool, Missing] -> [Present Bool, Missing] ## alterValue : Result Bool [Missing] -> Result Bool [Missing]
## alterValue = \possibleValue -> ## alterValue = \possibleValue ->
## when possibleValue is ## when possibleValue is
## Missing -> Present Bool.false ## Err -> Ok Bool.false
## Present value -> if value then Missing else Present Bool.true ## Ok value -> if value then Err Missing else Ok Bool.true
## ##
## expect Dict.update (Dict.empty {}) "a" alterValue == Dict.single "a" Bool.false ## expect Dict.update (Dict.empty {}) "a" alterValue == Dict.single "a" Bool.false
## expect Dict.update (Dict.single "a" Bool.false) "a" alterValue == Dict.single "a" Bool.true ## expect Dict.update (Dict.single "a" Bool.false) "a" alterValue == Dict.single "a" Bool.true
## expect Dict.update (Dict.single "a" Bool.true) "a" alterValue == Dict.empty {} ## expect Dict.update (Dict.single "a" Bool.true) "a" alterValue == Dict.empty {}
## ``` ## ```
update : Dict k v, k, ([Present v, Missing] -> [Present v, Missing]) -> Dict k v update : Dict k v, k, (Result v [Missing] -> Result v [Missing]) -> Dict k v
update = \@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }, key, alter -> update = \@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }, key, alter ->
{ bucketIndex, result } = find (@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }) key { bucketIndex, result } = find (@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }) key
when result is when result is
Ok value -> Ok value ->
when alter (Present value) is when alter (Ok value) is
Present newValue -> Ok newValue ->
bucket = listGetUnsafe buckets bucketIndex bucket = listGetUnsafe buckets bucketIndex
newData = List.set data (Num.toU64 bucket.dataIndex) (key, newValue) newData = List.set data (Num.toU64 bucket.dataIndex) (key, newValue)
@Dict { buckets, data: newData, maxBucketCapacity, maxLoadFactor, shifts } @Dict { buckets, data: newData, maxBucketCapacity, maxLoadFactor, shifts }
Missing -> Err Missing ->
removeBucket (@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }) bucketIndex removeBucket (@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }) bucketIndex
Err KeyNotFound -> Err KeyNotFound ->
when alter Missing is when alter (Err Missing) is
Present newValue -> Ok newValue ->
if List.len data >= maxBucketCapacity then if List.len data >= maxBucketCapacity then
# Need to reallocate let regular insert handle that. # Need to reallocate let regular insert handle that.
insert (@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }) key newValue insert (@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }) key newValue
@ -556,7 +556,7 @@ update = \@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }, key
distAndFingerprint = incrementDistN baseDistAndFingerprint (Num.toU32 dist) distAndFingerprint = incrementDistN baseDistAndFingerprint (Num.toU32 dist)
insertHelper buckets data bucketIndex distAndFingerprint key newValue maxBucketCapacity maxLoadFactor shifts insertHelper buckets data bucketIndex distAndFingerprint key newValue maxBucketCapacity maxLoadFactor shifts
Missing -> Err Missing ->
@Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts } @Dict { buckets, data, maxBucketCapacity, maxLoadFactor, shifts }
circularDist = \start, end, size -> circularDist = \start, end, size ->
@ -1216,8 +1216,8 @@ expect
List.walk badKeys (Dict.empty {}) \acc, k -> List.walk badKeys (Dict.empty {}) \acc, k ->
Dict.update acc k \val -> Dict.update acc k \val ->
when val is when val is
Present p -> Present (p |> Num.addWrap 1) Ok p -> Ok (p |> Num.addWrap 1)
Missing -> Present 0 Err Missing -> Ok 0
allInsertedCorrectly = allInsertedCorrectly =
List.walk badKeys Bool.true \acc, k -> List.walk badKeys Bool.true \acc, k ->

View file

@ -548,6 +548,23 @@ tau = 2 * pi
## [F64] or [F32] value, the returned string will be `"NaN"`, `"∞"`, or `"-∞"`. ## [F64] or [F32] value, the returned string will be `"NaN"`, `"∞"`, or `"-∞"`.
## ##
toStr : Num * -> Str toStr : Num * -> Str
## Convert an [Int] to a new [Int] of the expected type:
##
## ```roc
## # Casts a U8 to a U16
## x : U16
## x = Num.intCast 255u8
## ```
##
## In the case of downsizing, information is lost:
##
## ```roc
## # returns 0, as the bits were truncated.
## x : U8
## x = Num.intCast 256u16
## ```
##
intCast : Int a -> Int b intCast : Int a -> Int b
compare : Num a, Num a -> [LT, EQ, GT] compare : Num a, Num a -> [LT, EQ, GT]

View file

@ -367,6 +367,8 @@ module [
withCapacity, withCapacity,
withPrefix, withPrefix,
contains, contains,
dropPrefix,
dropSuffix,
] ]
import Bool exposing [Bool] import Bool exposing [Bool]
@ -1052,3 +1054,37 @@ contains = \haystack, needle ->
when firstMatch haystack needle is when firstMatch haystack needle is
Some _index -> Bool.true Some _index -> Bool.true
None -> Bool.false None -> Bool.false
## Drops the given prefix [Str] from the start of a [Str]
## If the prefix is not found, returns the original string.
##
## ```roc
## expect Str.dropPrefix "bar" "foo" == "bar"
## expect Str.dropPrefix "foobar" "foo" == "bar"
## ```
dropPrefix : Str, Str -> Str
dropPrefix = \haystack, prefix ->
if Str.startsWith haystack prefix then
start = Str.countUtf8Bytes prefix
len = Num.subWrap (Str.countUtf8Bytes haystack) start
substringUnsafe haystack start len
else
haystack
## Drops the given suffix [Str] from the end of a [Str]
## If the suffix is not found, returns the original string.
##
## ```roc
## expect Str.dropSuffix "bar" "foo" == "bar"
## expect Str.dropSuffix "barfoo" "foo" == "bar"
## ```
dropSuffix : Str, Str -> Str
dropSuffix = \haystack, suffix ->
if Str.endsWith haystack suffix then
start = 0
len = Num.subWrap (Str.countUtf8Bytes haystack) (Str.countUtf8Bytes suffix)
substringUnsafe haystack start len
else
haystack

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,7 @@ use roc_collections::{MutMap, VecSet};
use roc_module::ident::{Ident, ModuleName}; use roc_module::ident::{Ident, ModuleName};
use roc_module::symbol::{IdentIdsByModule, ModuleId, PQModuleName, PackageModuleIds, Symbol}; use roc_module::symbol::{IdentIdsByModule, ModuleId, PQModuleName, PackageModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Loc, Region}; use roc_region::all::{LineInfo, Loc, Region};
use roc_types::subs::Variable; use roc_types::subs::Variable;
/// The canonicalization environment for a particular module. /// The canonicalization environment for a particular module.
@ -44,11 +44,19 @@ pub struct Env<'a> {
pub arena: &'a Bump, pub arena: &'a Bump,
pub opt_shorthand: Option<&'a str>, pub opt_shorthand: Option<&'a str>,
pub src: &'a str,
/// Lazily calculated line info. This data is only needed if the code contains calls to `dbg`,
/// otherwise we can leave it as `None` and never pay the cost of scanning the source an extra
/// time.
line_info: &'a mut Option<LineInfo>,
} }
impl<'a> Env<'a> { impl<'a> Env<'a> {
pub fn new( pub fn new(
arena: &'a Bump, arena: &'a Bump,
src: &'a str,
home: ModuleId, home: ModuleId,
module_path: &'a Path, module_path: &'a Path,
dep_idents: &'a IdentIdsByModule, dep_idents: &'a IdentIdsByModule,
@ -57,6 +65,7 @@ impl<'a> Env<'a> {
) -> Env<'a> { ) -> Env<'a> {
Env { Env {
arena, arena,
src,
home, home,
module_path, module_path,
dep_idents, dep_idents,
@ -69,6 +78,7 @@ impl<'a> Env<'a> {
top_level_symbols: VecSet::default(), top_level_symbols: VecSet::default(),
home_params_record: None, home_params_record: None,
opt_shorthand, opt_shorthand,
line_info: arena.alloc(None),
} }
} }
@ -219,4 +229,11 @@ impl<'a> Env<'a> {
pub fn problem(&mut self, problem: Problem) { pub fn problem(&mut self, problem: Problem) {
self.problems.push(problem) self.problems.push(problem)
} }
pub fn line_info(&mut self) -> &LineInfo {
if self.line_info.is_none() {
*self.line_info = Some(LineInfo::new(self.src));
}
self.line_info.as_ref().unwrap()
}
} }

View file

@ -1013,11 +1013,8 @@ pub fn canonicalize_expr<'a>(
can_defs_with_return(env, var_store, inner_scope, env.arena.alloc(defs), loc_ret) can_defs_with_return(env, var_store, inner_scope, env.arena.alloc(defs), loc_ret)
}) })
} }
ast::Expr::OldRecordBuilder(_) => {
internal_error!("Old record builder should have been desugared by now")
}
ast::Expr::RecordBuilder { .. } => { ast::Expr::RecordBuilder { .. } => {
internal_error!("New record builder should have been desugared by now") internal_error!("Record builder should have been desugared by now")
} }
ast::Expr::Backpassing(_, _, _) => { ast::Expr::Backpassing(_, _, _) => {
internal_error!("Backpassing should have been desugared by now") internal_error!("Backpassing should have been desugared by now")
@ -1209,8 +1206,20 @@ pub fn canonicalize_expr<'a>(
output, output,
) )
} }
ast::Expr::Dbg | ast::Expr::DbgStmt(_, _) => { ast::Expr::Dbg => {
internal_error!("Dbg should have been desugared by now") // Dbg was not desugared as either part of an `Apply` or a `Pizza` binop, so it's
// invalid.
env.problem(Problem::UnappliedDbg { region });
let invalid_dbg_expr = crate::desugar::desugar_invalid_dbg_expr(env, scope, region);
let (loc_expr, output) =
canonicalize_expr(env, var_store, scope, region, invalid_dbg_expr);
(loc_expr.value, output)
}
ast::Expr::DbgStmt(_, _) => {
internal_error!("DbgStmt should have been desugared by now")
} }
ast::Expr::LowLevelDbg((source_location, source), message, continuation) => { ast::Expr::LowLevelDbg((source_location, source), message, continuation) => {
let mut output = Output::default(); let mut output = Output::default();
@ -1249,7 +1258,11 @@ pub fn canonicalize_expr<'a>(
output, output,
) )
} }
ast::Expr::If(if_thens, final_else_branch) => { ast::Expr::If {
if_thens,
final_else: final_else_branch,
..
} => {
let mut branches = Vec::with_capacity(if_thens.len()); let mut branches = Vec::with_capacity(if_thens.len());
let mut output = Output::default(); let mut output = Output::default();
@ -1340,22 +1353,6 @@ pub fn canonicalize_expr<'a>(
use roc_problem::can::RuntimeError::*; use roc_problem::can::RuntimeError::*;
(RuntimeError(MalformedSuffixed(region)), Output::default()) (RuntimeError(MalformedSuffixed(region)), Output::default())
} }
ast::Expr::MultipleOldRecordBuilders(sub_expr) => {
use roc_problem::can::RuntimeError::*;
let problem = MultipleOldRecordBuilders(sub_expr.region);
env.problem(Problem::RuntimeError(problem.clone()));
(RuntimeError(problem), Output::default())
}
ast::Expr::UnappliedOldRecordBuilder(sub_expr) => {
use roc_problem::can::RuntimeError::*;
let problem = UnappliedOldRecordBuilder(sub_expr.region);
env.problem(Problem::RuntimeError(problem.clone()));
(RuntimeError(problem), Output::default())
}
ast::Expr::EmptyRecordBuilder(sub_expr) => { ast::Expr::EmptyRecordBuilder(sub_expr) => {
use roc_problem::can::RuntimeError::*; use roc_problem::can::RuntimeError::*;
@ -2536,8 +2533,6 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
.iter() .iter()
.all(|loc_field| is_valid_interpolation(&loc_field.value)), .all(|loc_field| is_valid_interpolation(&loc_field.value)),
ast::Expr::MalformedSuffixed(loc_expr) ast::Expr::MalformedSuffixed(loc_expr)
| ast::Expr::MultipleOldRecordBuilders(loc_expr)
| ast::Expr::UnappliedOldRecordBuilder(loc_expr)
| ast::Expr::EmptyRecordBuilder(loc_expr) | ast::Expr::EmptyRecordBuilder(loc_expr)
| ast::Expr::SingleFieldRecordBuilder(loc_expr) | ast::Expr::SingleFieldRecordBuilder(loc_expr)
| ast::Expr::OptionalFieldInRecordBuilder(_, loc_expr) | ast::Expr::OptionalFieldInRecordBuilder(_, loc_expr)
@ -2560,7 +2555,11 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
.iter() .iter()
.all(|(loc_expr, _binop)| is_valid_interpolation(&loc_expr.value)) .all(|(loc_expr, _binop)| is_valid_interpolation(&loc_expr.value))
} }
ast::Expr::If(branches, final_branch) => { ast::Expr::If {
if_thens: branches,
final_else: final_branch,
..
} => {
is_valid_interpolation(&final_branch.value) is_valid_interpolation(&final_branch.value)
&& branches.iter().all(|(loc_before, loc_after)| { && branches.iter().all(|(loc_before, loc_after)| {
is_valid_interpolation(&loc_before.value) is_valid_interpolation(&loc_before.value)
@ -2583,27 +2582,6 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
| ast::AssignedField::SpaceAfter(_, _) => false, | ast::AssignedField::SpaceAfter(_, _) => false,
}) })
} }
ast::Expr::OldRecordBuilder(fields) => {
fields.iter().all(|loc_field| match loc_field.value {
ast::OldRecordBuilderField::Value(_label, comments, loc_expr) => {
comments.is_empty() && is_valid_interpolation(&loc_expr.value)
}
ast::OldRecordBuilderField::ApplyValue(
_label,
comments_before,
comments_after,
loc_expr,
) => {
comments_before.is_empty()
&& comments_after.is_empty()
&& is_valid_interpolation(&loc_expr.value)
}
ast::OldRecordBuilderField::Malformed(_)
| ast::OldRecordBuilderField::LabelOnly(_) => true,
ast::OldRecordBuilderField::SpaceBefore(_, _)
| ast::OldRecordBuilderField::SpaceAfter(_, _) => false,
})
}
ast::Expr::RecordBuilder { mapper, fields } => { ast::Expr::RecordBuilder { mapper, fields } => {
is_valid_interpolation(&mapper.value) is_valid_interpolation(&mapper.value)
&& fields.iter().all(|loc_field| match loc_field.value { && fields.iter().all(|loc_field| match loc_field.value {

View file

@ -3,6 +3,7 @@ use std::path::Path;
use crate::abilities::{AbilitiesStore, ImplKey, PendingAbilitiesStore, ResolvedImpl}; use crate::abilities::{AbilitiesStore, ImplKey, PendingAbilitiesStore, ResolvedImpl};
use crate::annotation::{canonicalize_annotation, AnnotationFor}; use crate::annotation::{canonicalize_annotation, AnnotationFor};
use crate::def::{canonicalize_defs, report_unused_imports, Def}; use crate::def::{canonicalize_defs, report_unused_imports, Def};
use crate::desugar::desugar_record_destructures;
use crate::env::Env; use crate::env::Env;
use crate::expr::{ use crate::expr::{
ClosureData, DbgLookup, Declarations, ExpectLookup, Expr, Output, PendingDerives, ClosureData, DbgLookup, Declarations, ExpectLookup, Expr, Output, PendingDerives,
@ -240,6 +241,7 @@ pub fn canonicalize_module_defs<'a>(
); );
let mut env = Env::new( let mut env = Env::new(
arena, arena,
src,
home, home,
arena.alloc(Path::new(module_path)), arena.alloc(Path::new(module_path)),
dep_idents, dep_idents,
@ -266,16 +268,7 @@ pub fn canonicalize_module_defs<'a>(
// operators, and then again on *their* nested operators, ultimately applying the // operators, and then again on *their* nested operators, ultimately applying the
// rules multiple times unnecessarily. // rules multiple times unnecessarily.
crate::desugar::desugar_defs_node_values( crate::desugar::desugar_defs_node_values(&mut env, &mut scope, loc_defs, true);
arena,
var_store,
loc_defs,
src,
&mut None,
module_path,
true,
&mut env.problems,
);
let mut rigid_variables = RigidVariables::default(); let mut rigid_variables = RigidVariables::default();
@ -334,13 +327,16 @@ pub fn canonicalize_module_defs<'a>(
before_arrow: _, before_arrow: _,
after_arrow: _, after_arrow: _,
}| { }| {
let desugared_patterns =
desugar_record_destructures(&mut env, &mut scope, pattern.value);
let (destructs, _) = canonicalize_record_destructs( let (destructs, _) = canonicalize_record_destructs(
&mut env, &mut env,
var_store, var_store,
&mut scope, &mut scope,
&mut output, &mut output,
PatternType::ModuleParams, PatternType::ModuleParams,
&pattern.value, &desugared_patterns,
pattern.region, pattern.region,
PermitShadows(false), PermitShadows(false),
); );

View file

@ -914,7 +914,10 @@ pub fn canonicalize_record_destructs<'a>(
} }
}; };
} }
_ => unreachable!("Any other pattern should have given a parse error"), _ => unreachable!(
"Any other pattern should have given a parse error: {:?}",
loc_pattern.value
),
} }
} }

View file

@ -466,7 +466,7 @@ impl Scope {
self.home.register_debug_idents(&self.locals.ident_ids) self.home.register_debug_idents(&self.locals.ident_ids)
} }
/// Generates a unique, new symbol like "$1" or "$5", /// Generates a unique, new symbol like "1" or "5",
/// using the home module as the module_id. /// using the home module as the module_id.
/// ///
/// This is used, for example, during canonicalization of an Expr::Closure /// This is used, for example, during canonicalization of an Expr::Closure
@ -475,6 +475,12 @@ impl Scope {
Symbol::new(self.home, self.locals.gen_unique()) Symbol::new(self.home, self.locals.gen_unique())
} }
/// Generates a unique new symbol and return the symbol's unqualified identifier name.
pub fn gen_unique_symbol_name(&mut self) -> &str {
let ident_id = self.locals.gen_unique();
self.locals.ident_ids.get_name(ident_id).unwrap()
}
/// Introduce a new ignored variable (variable starting with an underscore). /// Introduce a new ignored variable (variable starting with an underscore).
/// The underscore itself should not be included in `ident`. /// The underscore itself should not be included in `ident`.
pub fn introduce_ignored_local(&mut self, ident: &str, region: Region) { pub fn introduce_ignored_local(&mut self, ident: &str, region: Region) {

View file

@ -131,7 +131,7 @@ pub fn unwrap_suffixed_expression<'a>(
Expr::When(..) => unwrap_suffixed_expression_when_help(arena, loc_expr, maybe_def_pat), Expr::When(..) => unwrap_suffixed_expression_when_help(arena, loc_expr, maybe_def_pat),
Expr::If(..) => { Expr::If { .. } => {
unwrap_suffixed_expression_if_then_else_help(arena, loc_expr, maybe_def_pat) unwrap_suffixed_expression_if_then_else_help(arena, loc_expr, maybe_def_pat)
} }
@ -325,7 +325,14 @@ pub fn unwrap_suffixed_expression_apply_help<'a>(
let new_apply = arena.alloc(Loc::at(loc_expr.region, Expr::Apply(unwrapped_function, local_args, called_via))); let new_apply = arena.alloc(Loc::at(loc_expr.region, Expr::Apply(unwrapped_function, local_args, called_via)));
return init_unwrapped_err(arena, new_apply, maybe_def_pat, target); match maybe_def_pat {
Some(..) => {
return Err(EUnwrapped::UnwrappedDefExpr { loc_expr: new_apply, target });
}
None => {
return init_unwrapped_err(arena, new_apply, maybe_def_pat, target);
}
}
} }
// function is another expression // function is another expression
@ -357,7 +364,11 @@ pub fn unwrap_suffixed_expression_if_then_else_help<'a>(
maybe_def_pat: Option<&'a Loc<Pattern<'a>>>, maybe_def_pat: Option<&'a Loc<Pattern<'a>>>,
) -> Result<&'a Loc<Expr<'a>>, EUnwrapped<'a>> { ) -> Result<&'a Loc<Expr<'a>>, EUnwrapped<'a>> {
match loc_expr.value { match loc_expr.value {
Expr::If(if_thens, final_else_branch) => { Expr::If {
if_thens,
final_else: final_else_branch,
indented_else,
} => {
for (index, if_then) in if_thens.iter().enumerate() { for (index, if_then) in if_thens.iter().enumerate() {
let (current_if_then_statement, current_if_then_expression) = if_then; let (current_if_then_statement, current_if_then_expression) = if_then;
@ -376,10 +387,11 @@ pub fn unwrap_suffixed_expression_if_then_else_help<'a>(
let new_if = arena.alloc(Loc::at( let new_if = arena.alloc(Loc::at(
loc_expr.region, loc_expr.region,
Expr::If( Expr::If {
arena.alloc_slice_copy(new_if_thens.as_slice()), if_thens: arena.alloc_slice_copy(new_if_thens.as_slice()),
final_else_branch, final_else: final_else_branch,
), indented_else,
},
)); ));
return unwrap_suffixed_expression(arena, new_if, maybe_def_pat); return unwrap_suffixed_expression(arena, new_if, maybe_def_pat);
@ -411,10 +423,11 @@ pub fn unwrap_suffixed_expression_if_then_else_help<'a>(
let new_if = arena.alloc(Loc::at( let new_if = arena.alloc(Loc::at(
loc_expr.region, loc_expr.region,
Expr::If( Expr::If {
arena.alloc_slice_copy(new_if_thens.as_slice()), if_thens: arena.alloc_slice_copy(new_if_thens.as_slice()),
final_else_branch, final_else: final_else_branch,
), indented_else,
},
)); ));
return unwrap_suffixed_expression(arena, new_if, maybe_def_pat); return unwrap_suffixed_expression(arena, new_if, maybe_def_pat);
@ -439,10 +452,11 @@ pub fn unwrap_suffixed_expression_if_then_else_help<'a>(
let new_if = arena.alloc(Loc::at( let new_if = arena.alloc(Loc::at(
loc_expr.region, loc_expr.region,
Expr::If( Expr::If {
arena.alloc_slice_copy(new_if_thens.as_slice()), if_thens: arena.alloc_slice_copy(new_if_thens.as_slice()),
final_else_branch, final_else: final_else_branch,
), indented_else,
},
)); ));
return unwrap_suffixed_expression(arena, new_if, maybe_def_pat); return unwrap_suffixed_expression(arena, new_if, maybe_def_pat);
@ -465,10 +479,11 @@ pub fn unwrap_suffixed_expression_if_then_else_help<'a>(
let new_if = arena.alloc(Loc::at( let new_if = arena.alloc(Loc::at(
loc_expr.region, loc_expr.region,
Expr::If( Expr::If {
arena.alloc_slice_copy(new_if_thens.as_slice()), if_thens: arena.alloc_slice_copy(new_if_thens.as_slice()),
final_else_branch, final_else: final_else_branch,
), indented_else,
},
)); ));
let unwrapped_if_then = apply_try_function( let unwrapped_if_then = apply_try_function(
@ -494,10 +509,11 @@ pub fn unwrap_suffixed_expression_if_then_else_help<'a>(
let after_if = arena.alloc(Loc::at( let after_if = arena.alloc(Loc::at(
loc_expr.region, loc_expr.region,
Expr::If( Expr::If {
arena.alloc_slice_copy(after_if_thens.as_slice()), if_thens: arena.alloc_slice_copy(after_if_thens.as_slice()),
final_else_branch, final_else: final_else_branch,
), indented_else,
},
)); ));
let after_if_then = apply_try_function( let after_if_then = apply_try_function(
@ -512,7 +528,11 @@ pub fn unwrap_suffixed_expression_if_then_else_help<'a>(
let before_if_then = arena.alloc(Loc::at( let before_if_then = arena.alloc(Loc::at(
loc_expr.region, loc_expr.region,
Expr::If(before, after_if_then), Expr::If {
if_thens: before,
final_else: after_if_then,
indented_else: false,
},
)); ));
return unwrap_suffixed_expression( return unwrap_suffixed_expression(
@ -532,7 +552,11 @@ pub fn unwrap_suffixed_expression_if_then_else_help<'a>(
Ok(unwrapped_final_else) => { Ok(unwrapped_final_else) => {
return Ok(arena.alloc(Loc::at( return Ok(arena.alloc(Loc::at(
loc_expr.region, loc_expr.region,
Expr::If(if_thens, unwrapped_final_else), Expr::If {
if_thens,
final_else: unwrapped_final_else,
indented_else,
},
))); )));
} }
Err(EUnwrapped::UnwrappedDefExpr { .. }) => { Err(EUnwrapped::UnwrappedDefExpr { .. }) => {
@ -556,7 +580,11 @@ pub fn unwrap_suffixed_expression_if_then_else_help<'a>(
let new_if = arena.alloc(Loc::at( let new_if = arena.alloc(Loc::at(
loc_expr.region, loc_expr.region,
Expr::If(if_thens, unwrapped_final_else), Expr::If {
if_thens,
final_else: unwrapped_final_else,
indented_else,
},
)); ));
return unwrap_suffixed_expression(arena, new_if, maybe_def_pat); return unwrap_suffixed_expression(arena, new_if, maybe_def_pat);

View file

@ -46,6 +46,24 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
let var = var_store.fresh(); let var = var_store.fresh();
let qualified_module_ids = PackageModuleIds::default(); let qualified_module_ids = PackageModuleIds::default();
let mut scope = Scope::new(
home,
"TestPath".into(),
IdentIds::default(),
Default::default(),
);
let dep_idents = IdentIds::exposed_builtins(0);
let mut env = Env::new(
arena,
expr_str,
home,
Path::new("Test.roc"),
&dep_idents,
&qualified_module_ids,
None,
);
// Desugar operators (convert them to Apply calls, taking into account // Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization. // operator precedence and associativity rules), before doing other canonicalization.
// //
@ -53,22 +71,8 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
// visited a BinOp node we'd recursively try to apply this to each of its nested // visited a BinOp node we'd recursively try to apply this to each of its nested
// operators, and then again on *their* nested operators, ultimately applying the // operators, and then again on *their* nested operators, ultimately applying the
// rules multiple times unnecessarily. // rules multiple times unnecessarily.
let loc_expr = desugar::desugar_expr( let loc_expr = desugar::desugar_expr(&mut env, &mut scope, &loc_expr);
arena,
&mut var_store,
&loc_expr,
expr_str,
&mut None,
arena.alloc("TestPath"),
&mut Default::default(),
);
let mut scope = Scope::new(
home,
"TestPath".into(),
IdentIds::default(),
Default::default(),
);
scope.add_alias( scope.add_alias(
Symbol::NUM_INT, Symbol::NUM_INT,
Region::zero(), Region::zero(),
@ -81,15 +85,6 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
roc_types::types::AliasKind::Structural, roc_types::types::AliasKind::Structural,
); );
let dep_idents = IdentIds::exposed_builtins(0);
let mut env = Env::new(
arena,
home,
Path::new("Test.roc"),
&dep_idents,
&qualified_module_ids,
None,
);
let (loc_expr, output) = canonicalize_expr( let (loc_expr, output) = canonicalize_expr(
&mut env, &mut env,
&mut var_store, &mut var_store,

View file

@ -1,6 +1,6 @@
--- ---
source: crates/compiler/can/tests/test_suffixed.rs source: crates/compiler/can/tests/test_suffixed.rs
assertion_line: 449 assertion_line: 463
expression: snapshot expression: snapshot
--- ---
Defs { Defs {
@ -44,7 +44,7 @@ Defs {
value_defs: [ value_defs: [
Body( Body(
@15-26 Identifier { @15-26 Identifier {
ident: "64", ident: "1",
}, },
@15-26 ParensAround( @15-26 ParensAround(
Defs( Defs(
@ -66,7 +66,7 @@ Defs {
value_defs: [ value_defs: [
Body( Body(
@20-25 Identifier { @20-25 Identifier {
ident: "63", ident: "0",
}, },
@20-25 Apply( @20-25 Apply(
@22-23 Var { @22-23 Var {
@ -101,14 +101,14 @@ Defs {
[ [
@20-25 Var { @20-25 Var {
module_name: "", module_name: "",
ident: "63", ident: "0",
}, },
], ],
Space, Space,
), ),
@20-25 Var { @20-25 Var {
module_name: "", module_name: "",
ident: "63", ident: "0",
}, },
), ),
), ),
@ -129,14 +129,14 @@ Defs {
[ [
@15-26 Var { @15-26 Var {
module_name: "", module_name: "",
ident: "64", ident: "1",
}, },
], ],
Space, Space,
), ),
@15-26 Var { @15-26 Var {
module_name: "", module_name: "",
ident: "64", ident: "1",
}, },
), ),
), ),

View file

@ -63,8 +63,8 @@ Defs {
), ),
], ],
}, },
@66-130 If( @66-130 If {
[ if_thens: [
( (
@69-70 Var { @69-70 Var {
module_name: "", module_name: "",
@ -76,11 +76,12 @@ Defs {
}, },
), ),
], ],
@128-130 Var { final_else: @128-130 Var {
module_name: "", module_name: "",
ident: "c", ident: "c",
}, },
), indented_else: false,
},
), ),
guard: None, guard: None,
}, },

View file

@ -136,8 +136,8 @@ Defs {
ident: "#!1_arg", ident: "#!1_arg",
}, },
], ],
@109-298 If( @109-298 If {
[ if_thens: [
( (
@112-122 Apply( @112-122 Apply(
@112-113 Var { @112-113 Var {
@ -244,7 +244,7 @@ Defs {
), ),
), ),
], ],
Apply( final_else: Apply(
Var { Var {
module_name: "Task", module_name: "Task",
ident: "await", ident: "await",
@ -269,8 +269,8 @@ Defs {
ident: "#!3_arg", ident: "#!3_arg",
}, },
], ],
@109-298 If( @109-298 If {
[ if_thens: [
( (
@187-209 ParensAround( @187-209 ParensAround(
Var { Var {
@ -366,7 +366,7 @@ Defs {
), ),
), ),
], ],
@283-298 Apply( final_else: @283-298 Apply(
@283-298 Var { @283-298 Var {
module_name: "", module_name: "",
ident: "line", ident: "line",
@ -380,12 +380,14 @@ Defs {
], ],
Space, Space,
), ),
), indented_else: false,
},
), ),
], ],
BangSuffix, BangSuffix,
), ),
), indented_else: false,
},
), ),
], ],
BangSuffix, BangSuffix,

View file

@ -101,8 +101,8 @@ Defs {
ident: "#!0_arg", ident: "#!0_arg",
}, },
], ],
@76-189 If( @76-189 If {
[ if_thens: [
( (
@79-87 Var { @79-87 Var {
module_name: "", module_name: "",
@ -124,7 +124,7 @@ Defs {
), ),
), ),
], ],
@125-132 Apply( final_else: @125-132 Apply(
@125-132 Var { @125-132 Var {
module_name: "Task", module_name: "Task",
ident: "await", ident: "await",
@ -140,8 +140,8 @@ Defs {
ident: "#!1_arg", ident: "#!1_arg",
}, },
], ],
@76-189 If( @76-189 If {
[ if_thens: [
( (
@125-132 Var { @125-132 Var {
module_name: "", module_name: "",
@ -163,7 +163,7 @@ Defs {
), ),
), ),
], ],
@178-189 Apply( final_else: @178-189 Apply(
@178-182 Var { @178-182 Var {
module_name: "", module_name: "",
ident: "line", ident: "line",
@ -177,12 +177,14 @@ Defs {
], ],
Space, Space,
), ),
), indented_else: false,
},
), ),
], ],
BangSuffix, BangSuffix,
), ),
), indented_else: false,
},
), ),
], ],
BangSuffix, BangSuffix,

View file

@ -0,0 +1,289 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
Index(2147483649),
Index(2147483650),
],
regions: [
@0-80,
@82-227,
@229-266,
],
space_before: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 2),
Slice(start = 2, length = 2),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 2, length = 0),
Slice(start = 4, length = 1),
],
spaces: [
Newline,
Newline,
Newline,
Newline,
Newline,
],
type_defs: [],
value_defs: [
Body(
@0-3 Identifier {
ident: "inc",
},
@6-80 Closure(
[
@7-8 Identifier {
ident: "i",
},
],
@16-80 If {
if_thens: [
(
@19-24 Apply(
@21-22 Var {
module_name: "Num",
ident: "isGt",
},
[
@19-20 Var {
module_name: "",
ident: "i",
},
@23-24 Num(
"2",
),
],
BinOp(
GreaterThan,
),
),
@38-52 Apply(
@38-41 Tag(
"Err",
),
[
@42-52 Tag(
"MaxReached",
),
],
Space,
),
),
],
final_else: @70-80 Apply(
@70-72 Tag(
"Ok",
),
[
@74-79 ParensAround(
Apply(
@76-77 Var {
module_name: "Num",
ident: "add",
},
[
@74-75 Var {
module_name: "",
ident: "i",
},
@78-79 Num(
"1",
),
],
BinOp(
Plus,
),
),
),
],
Space,
),
indented_else: false,
},
),
),
Expect {
condition: @93-227 Defs(
Defs {
tags: [
Index(2147483648),
Index(2147483649),
],
regions: [
@99-189,
@203-208,
],
space_before: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 1, length = 0),
],
spaces: [
Newline,
],
type_defs: [],
value_defs: [
Body(
@93-96 Identifier {
ident: "run",
},
@99-189 Closure(
[
@100-101 Identifier {
ident: "i",
},
],
@132-172 Apply(
@132-172 Var {
module_name: "Result",
ident: "try",
},
[
@132-152 Apply(
@132-152 Var {
module_name: "",
ident: "inc",
},
[
@132-133 Var {
module_name: "",
ident: "i",
},
],
BinOp(
Pizza,
),
),
@132-172 Closure(
[
@132-152 Identifier {
ident: "#!0_arg",
},
],
@132-172 Apply(
@132-172 Var {
module_name: "Result",
ident: "try",
},
[
@132-172 Apply(
@132-172 Var {
module_name: "",
ident: "inc",
},
[
@132-152 Var {
module_name: "",
ident: "#!0_arg",
},
],
BinOp(
Pizza,
),
),
@132-172 Closure(
[
@113-117 Identifier {
ident: "newi",
},
],
@182-189 Apply(
@182-184 Tag(
"Ok",
),
[
@185-189 Var {
module_name: "",
ident: "newi",
},
],
Space,
),
),
],
QuestionSuffix,
),
),
],
QuestionSuffix,
),
),
),
Body(
@194-200 Identifier {
ident: "result",
},
@203-208 Apply(
@203-206 Var {
module_name: "",
ident: "run",
},
[
@207-208 Num(
"0",
),
],
Space,
),
),
],
},
@213-227 Apply(
@220-222 Var {
module_name: "Bool",
ident: "isEq",
},
[
@213-219 Var {
module_name: "",
ident: "result",
},
@223-227 Apply(
@223-225 Tag(
"Ok",
),
[
@226-227 Num(
"2",
),
],
Space,
),
],
BinOp(
Equals,
),
),
),
preceding_comment: @82-82,
},
Body(
@229-233 Identifier {
ident: "main",
},
@240-266 Apply(
@240-266 Var {
module_name: "Stdout",
ident: "line",
},
[
@253-266 Str(
PlainLine(
"Hello world",
),
),
],
Space,
),
),
],
}

View file

@ -0,0 +1,75 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
Index(2147483649),
],
regions: [
@0-33,
@35-45,
],
space_before: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 2),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 2, length = 1),
],
spaces: [
Newline,
Newline,
Newline,
],
type_defs: [],
value_defs: [
AnnotatedBody {
ann_pattern: @0-3 Identifier {
ident: "run",
},
ann_type: @6-15 Apply(
"",
"Task",
[
@11-13 Record {
fields: [],
ext: None,
},
@14-15 Inferred,
],
),
lines_between: [
Newline,
],
body_pattern: @16-19 Identifier {
ident: "run",
},
body_expr: @22-33 Apply(
@22-33 Var {
module_name: "",
ident: "line",
},
[
@28-33 Str(
PlainLine(
"foo",
),
),
],
Space,
),
},
Body(
@35-39 Identifier {
ident: "main",
},
@42-45 Var {
module_name: "",
ident: "run",
},
),
],
}

View file

@ -1,6 +1,6 @@
--- ---
source: crates/compiler/can/tests/test_suffixed.rs source: crates/compiler/can/tests/test_suffixed.rs
assertion_line: 459 assertion_line: 473
expression: snapshot expression: snapshot
--- ---
Defs { Defs {
@ -8,7 +8,7 @@ Defs {
Index(2147483648), Index(2147483648),
], ],
regions: [ regions: [
@0-19, @0-51,
], ],
space_before: [ space_before: [
Slice(start = 0, length = 0), Slice(start = 0, length = 0),
@ -25,13 +25,13 @@ Defs {
@0-4 Identifier { @0-4 Identifier {
ident: "main", ident: "main",
}, },
@11-19 Defs( @11-51 Defs(
Defs { Defs {
tags: [ tags: [
Index(2147483648), Index(2147483648),
], ],
regions: [ regions: [
@11-12, @11-40,
], ],
space_before: [ space_before: [
Slice(start = 0, length = 0), Slice(start = 0, length = 0),
@ -43,36 +43,98 @@ Defs {
type_defs: [], type_defs: [],
value_defs: [ value_defs: [
Body( Body(
@11-12 Identifier { @11-40 Identifier {
ident: "63", ident: "1",
}, },
@11-12 Num( @11-40 Apply(
"1", @31-38 Var {
module_name: "Num",
ident: "add",
},
[
@11-23 Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@11-12,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@11-12 Identifier {
ident: "0",
},
@11-12 Num(
"1",
),
),
],
},
@11-23 LowLevelDbg(
(
"test.roc:2",
" ",
),
@11-12 Apply(
@11-12 Var {
module_name: "Inspect",
ident: "toStr",
},
[
@11-12 Var {
module_name: "",
ident: "0",
},
],
Space,
),
@11-12 Var {
module_name: "",
ident: "0",
},
),
),
@39-40 Num(
"2",
),
],
BinOp(
Pizza,
),
), ),
), ),
], ],
}, },
@11-19 LowLevelDbg( @11-51 LowLevelDbg(
( (
"test.roc:2", "test.roc:2",
" ", " main =\n 1\n ",
), ),
@11-12 Apply( @11-40 Apply(
@11-12 Var { @11-40 Var {
module_name: "Inspect", module_name: "Inspect",
ident: "toStr", ident: "toStr",
}, },
[ [
@11-12 Var { @11-40 Var {
module_name: "", module_name: "",
ident: "63", ident: "1",
}, },
], ],
Space, Space,
), ),
@11-12 Var { @11-40 Var {
module_name: "", module_name: "",
ident: "63", ident: "1",
}, },
), ),
), ),

View file

@ -19,7 +19,6 @@ mod test_can {
use roc_can::expr::{ClosureData, IntValue, Recursive}; use roc_can::expr::{ClosureData, IntValue, Recursive};
use roc_can::pattern::Pattern; use roc_can::pattern::Pattern;
use roc_module::called_via::CalledVia; use roc_module::called_via::CalledVia;
use roc_module::symbol::Symbol;
use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError}; use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError};
use roc_region::all::{Loc, Position, Region}; use roc_region::all::{Loc, Position, Region};
use roc_types::subs::Variable; use roc_types::subs::Variable;
@ -653,101 +652,6 @@ mod test_can {
} }
// RECORD BUILDERS // RECORD BUILDERS
#[test]
fn old_record_builder_desugar() {
let src = indoc!(
r#"
succeed = \_ -> crash "succeed"
apply = \_ -> crash "get"
d = 3
succeed {
a: 1,
b: <- apply "b",
c: <- apply "c",
d
}
"#
);
let arena = Bump::new();
let out = can_expr_with(&arena, test_home(), src);
assert_eq!(out.problems.len(), 0);
// Assert that we desugar to:
//
// (apply "c") ((apply "b") (succeed \b -> \c -> { a: 1, b, c, d }))
// (apply "c") ..
let (apply_c, c_to_b) = simplify_curried_call(&out.loc_expr.value);
assert_apply_call(apply_c, "c", &out.interns);
// (apply "b") ..
let (apply_b, b_to_succeed) = simplify_curried_call(c_to_b);
assert_apply_call(apply_b, "b", &out.interns);
// (succeed ..)
let (succeed, b_closure) = simplify_curried_call(b_to_succeed);
match succeed {
Var(sym, _) => assert_eq!(sym.as_str(&out.interns), "succeed"),
_ => panic!("Not calling succeed: {:?}", succeed),
}
// \b -> ..
let (b_sym, c_closure) = simplify_builder_closure(b_closure);
// \c -> ..
let (c_sym, c_body) = simplify_builder_closure(c_closure);
// { a: 1, b, c, d }
match c_body {
Record { fields, .. } => {
match get_field_expr(fields, "a") {
Num(_, num_str, _, _) => {
assert_eq!(num_str.to_string(), "1");
}
expr => panic!("a is not a Num: {:?}", expr),
}
assert_eq!(get_field_var_sym(fields, "b"), b_sym);
assert_eq!(get_field_var_sym(fields, "c"), c_sym);
assert_eq!(get_field_var_sym(fields, "d").as_str(&out.interns), "d");
}
_ => panic!("Closure body wasn't a Record: {:?}", c_body),
}
}
fn simplify_curried_call(expr: &Expr) -> (&Expr, &Expr) {
match expr {
LetNonRec(_, loc_expr) => simplify_curried_call(&loc_expr.value),
Call(fun, args, _) => (&fun.1.value, &args[0].1.value),
_ => panic!("Final Expr is not a Call: {:?}", expr),
}
}
fn assert_apply_call(expr: &Expr, expected: &str, interns: &roc_module::symbol::Interns) {
match simplify_curried_call(expr) {
(Var(sym, _), Str(val)) => {
assert_eq!(sym.as_str(interns), "apply");
assert_eq!(val.to_string(), expected);
}
call => panic!("Not a valid (get {}) call: {:?}", expected, call),
};
}
fn simplify_builder_closure(expr: &Expr) -> (Symbol, &Expr) {
use roc_can::pattern::Pattern::*;
match expr {
Closure(closure) => match &closure.arguments[0].2.value {
Identifier(sym) => (*sym, &closure.loc_body.value),
pattern => panic!("Not an identifier pattern: {:?}", pattern),
},
_ => panic!("Not a closure: {:?}", expr),
}
}
fn get_field_expr<'a>( fn get_field_expr<'a>(
fields: &'a roc_collections::SendMap<roc_module::ident::Lowercase, roc_can::expr::Field>, fields: &'a roc_collections::SendMap<roc_module::ident::Lowercase, roc_can::expr::Field>,
@ -769,97 +673,7 @@ mod test_can {
} }
#[test] #[test]
fn old_record_builder_field_names_do_not_shadow() { fn record_builder_desugar() {
let src = indoc!(
r#"
succeed = \_ -> crash "succeed"
parse = \_ -> crash "parse"
number = "42"
succeed {
number: <- parse number,
raw: number,
}
"#
);
let arena = Bump::new();
let out = can_expr_with(&arena, test_home(), src);
assert_eq!(out.problems.len(), 0);
let (_, number_to_succeed) = simplify_curried_call(&out.loc_expr.value);
let (_, number_closure) = simplify_curried_call(number_to_succeed);
let (apply_number_sym, record) = simplify_builder_closure(number_closure);
match record {
Record { fields, .. } => {
assert_eq!(get_field_var_sym(fields, "number"), apply_number_sym);
match get_field_expr(fields, "raw") {
Var(number_sym, _) => {
assert_ne!(number_sym.ident_id(), apply_number_sym.ident_id());
assert_eq!(number_sym.as_str(&out.interns), "number")
}
expr => panic!("a is not a Num: {:?}", expr),
}
}
_ => panic!("Closure body wasn't a Record: {:?}", record),
}
}
#[test]
fn multiple_old_record_builders_error() {
let src = indoc!(
r#"
succeed
{ a: <- apply "a" }
{ b: <- apply "b" }
"#
);
let arena = Bump::new();
let CanExprOut {
problems, loc_expr, ..
} = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 1);
assert!(problems.iter().all(|problem| matches!(
problem,
Problem::RuntimeError(roc_problem::can::RuntimeError::MultipleOldRecordBuilders { .. })
)));
assert!(matches!(
loc_expr.value,
Expr::RuntimeError(roc_problem::can::RuntimeError::MultipleOldRecordBuilders { .. })
));
}
#[test]
fn hanging_old_record_builder() {
let src = indoc!(
r#"
{ a: <- apply "a" }
"#
);
let arena = Bump::new();
let CanExprOut {
problems, loc_expr, ..
} = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 1);
assert!(problems.iter().all(|problem| matches!(
problem,
Problem::RuntimeError(roc_problem::can::RuntimeError::UnappliedOldRecordBuilder { .. })
)));
assert!(matches!(
loc_expr.value,
Expr::RuntimeError(roc_problem::can::RuntimeError::UnappliedOldRecordBuilder { .. })
));
}
#[test]
fn new_record_builder_desugar() {
let src = indoc!( let src = indoc!(
r#" r#"
map2 = \a, b, combine -> combine a b map2 = \a, b, combine -> combine a b

View file

@ -6,25 +6,39 @@ mod suffixed_tests {
use bumpalo::Bump; use bumpalo::Bump;
use insta::assert_snapshot; use insta::assert_snapshot;
use roc_can::desugar::desugar_defs_node_values; use roc_can::desugar::desugar_defs_node_values;
use roc_can::env::Env;
use roc_can::scope::Scope;
use roc_module::symbol::{IdentIds, ModuleIds, PackageModuleIds};
use roc_parse::test_helpers::parse_defs_with; use roc_parse::test_helpers::parse_defs_with;
use roc_types::subs::VarStore; use std::path::Path;
macro_rules! run_test { macro_rules! run_test {
($src:expr) => {{ ($src:expr) => {{
let arena = &Bump::new(); let arena = &Bump::new();
let mut var_store = VarStore::default(); let home = ModuleIds::default().get_or_insert(&"Test".into());
let mut defs = parse_defs_with(arena, indoc!($src)).unwrap();
desugar_defs_node_values( let mut scope = Scope::new(
arena, home,
&mut var_store, "TestPath".into(),
&mut defs, IdentIds::default(),
$src, Default::default(),
&mut None,
"test.roc",
true,
&mut Default::default(),
); );
let dep_idents = IdentIds::exposed_builtins(0);
let qualified_module_ids = PackageModuleIds::default();
let mut env = Env::new(
arena,
$src,
home,
Path::new("test.roc"),
&dep_idents,
&qualified_module_ids,
None,
);
let mut defs = parse_defs_with(arena, indoc!($src)).unwrap();
desugar_defs_node_values(&mut env, &mut scope, &mut defs, true);
let snapshot = format!("{:#?}", &defs); let snapshot = format!("{:#?}", &defs);
println!("{}", snapshot); println!("{}", snapshot);
assert_snapshot!(snapshot); assert_snapshot!(snapshot);
@ -454,6 +468,19 @@ mod suffixed_tests {
); );
} }
#[test]
fn pizza_dbg() {
run_test!(
r#"
main =
1
|> dbg
|> Num.add 2
|> dbg
"#
)
}
#[test] #[test]
fn apply_argument_single() { fn apply_argument_single() {
run_test!( run_test!(
@ -579,6 +606,44 @@ mod suffixed_tests {
"## "##
); );
} }
#[test]
fn issue_7081() {
run_test!(
r##"
inc = \i ->
if i > 2 then
Err MaxReached
else
Ok (i + 1)
expect
run = \i ->
newi =
i
|> inc?
|> inc?
Ok newi
result = run 0
result == Ok 2
main =
Stdout.line! "Hello world"
"##
);
}
#[test]
fn issue_7103() {
run_test!(
r##"
run : Task {} _
run = line! "foo"
main = run
"##
);
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -5917,9 +5917,9 @@
"dev": true "dev": true
}, },
"node_modules/body-parser": { "node_modules/body-parser": {
"version": "1.20.2", "version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"bytes": "3.1.2", "bytes": "3.1.2",
@ -5930,7 +5930,7 @@
"http-errors": "2.0.0", "http-errors": "2.0.0",
"iconv-lite": "0.4.24", "iconv-lite": "0.4.24",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"qs": "6.11.0", "qs": "6.13.0",
"raw-body": "2.5.2", "raw-body": "2.5.2",
"type-is": "~1.6.18", "type-is": "~1.6.18",
"unpipe": "1.0.0" "unpipe": "1.0.0"
@ -6091,13 +6091,19 @@
} }
}, },
"node_modules/call-bind": { "node_modules/call-bind": {
"version": "1.0.2", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"function-bind": "^1.1.1", "es-define-property": "^1.0.0",
"get-intrinsic": "^1.0.2" "es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"set-function-length": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@ -7249,6 +7255,23 @@
"node": ">= 10" "node": ">= 10"
} }
}, },
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dev": true,
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/define-lazy-prop": { "node_modules/define-lazy-prop": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@ -7597,9 +7620,9 @@
} }
}, },
"node_modules/encodeurl": { "node_modules/encodeurl": {
"version": "1.0.2", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
@ -7704,6 +7727,27 @@
"integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==",
"dev": true "dev": true
}, },
"node_modules/es-define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
"dev": true,
"dependencies": {
"get-intrinsic": "^1.2.4"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-module-lexer": { "node_modules/es-module-lexer": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz",
@ -8646,37 +8690,37 @@
} }
}, },
"node_modules/express": { "node_modules/express": {
"version": "4.19.2", "version": "4.21.0",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"accepts": "~1.3.8", "accepts": "~1.3.8",
"array-flatten": "1.1.1", "array-flatten": "1.1.1",
"body-parser": "1.20.2", "body-parser": "1.20.3",
"content-disposition": "0.5.4", "content-disposition": "0.5.4",
"content-type": "~1.0.4", "content-type": "~1.0.4",
"cookie": "0.6.0", "cookie": "0.6.0",
"cookie-signature": "1.0.6", "cookie-signature": "1.0.6",
"debug": "2.6.9", "debug": "2.6.9",
"depd": "2.0.0", "depd": "2.0.0",
"encodeurl": "~1.0.2", "encodeurl": "~2.0.0",
"escape-html": "~1.0.3", "escape-html": "~1.0.3",
"etag": "~1.8.1", "etag": "~1.8.1",
"finalhandler": "1.2.0", "finalhandler": "1.3.1",
"fresh": "0.5.2", "fresh": "0.5.2",
"http-errors": "2.0.0", "http-errors": "2.0.0",
"merge-descriptors": "1.0.1", "merge-descriptors": "1.0.3",
"methods": "~1.1.2", "methods": "~1.1.2",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"path-to-regexp": "0.1.7", "path-to-regexp": "0.1.10",
"proxy-addr": "~2.0.7", "proxy-addr": "~2.0.7",
"qs": "6.11.0", "qs": "6.13.0",
"range-parser": "~1.2.1", "range-parser": "~1.2.1",
"safe-buffer": "5.2.1", "safe-buffer": "5.2.1",
"send": "0.18.0", "send": "0.19.0",
"serve-static": "1.15.0", "serve-static": "1.16.2",
"setprototypeof": "1.2.0", "setprototypeof": "1.2.0",
"statuses": "2.0.1", "statuses": "2.0.1",
"type-is": "~1.6.18", "type-is": "~1.6.18",
@ -8883,13 +8927,13 @@
} }
}, },
"node_modules/finalhandler": { "node_modules/finalhandler": {
"version": "1.2.0", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"debug": "2.6.9", "debug": "2.6.9",
"encodeurl": "~1.0.2", "encodeurl": "~2.0.0",
"escape-html": "~1.0.3", "escape-html": "~1.0.3",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
@ -9249,10 +9293,13 @@
} }
}, },
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.1", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true "dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
}, },
"node_modules/function.prototype.name": { "node_modules/function.prototype.name": {
"version": "1.1.5", "version": "1.1.5",
@ -9300,15 +9347,19 @@
} }
}, },
"node_modules/get-intrinsic": { "node_modules/get-intrinsic": {
"version": "1.2.1", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"function-bind": "^1.1.1", "es-errors": "^1.3.0",
"has": "^1.0.3", "function-bind": "^1.1.2",
"has-proto": "^1.0.1", "has-proto": "^1.0.1",
"has-symbols": "^1.0.3" "has-symbols": "^1.0.3",
"hasown": "^2.0.0"
},
"engines": {
"node": ">= 0.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@ -9590,12 +9641,12 @@
} }
}, },
"node_modules/has-property-descriptors": { "node_modules/has-property-descriptors": {
"version": "1.0.0", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"get-intrinsic": "^1.1.1" "es-define-property": "^1.0.0"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@ -9640,6 +9691,18 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/he": { "node_modules/he": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@ -13153,10 +13216,13 @@
} }
}, },
"node_modules/merge-descriptors": { "node_modules/merge-descriptors": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"dev": true "dev": true,
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
}, },
"node_modules/merge-stream": { "node_modules/merge-stream": {
"version": "2.0.0", "version": "2.0.0",
@ -13538,10 +13604,13 @@
} }
}, },
"node_modules/object-inspect": { "node_modules/object-inspect": {
"version": "1.12.3", "version": "1.13.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
"dev": true, "dev": true,
"engines": {
"node": ">= 0.4"
},
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
@ -13889,9 +13958,9 @@
"dev": true "dev": true
}, },
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "0.1.7", "version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
"dev": true "dev": true
}, },
"node_modules/path-type": { "node_modules/path-type": {
@ -15501,12 +15570,12 @@
} }
}, },
"node_modules/qs": { "node_modules/qs": {
"version": "6.11.0", "version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"side-channel": "^1.0.4" "side-channel": "^1.0.6"
}, },
"engines": { "engines": {
"node": ">=0.6" "node": ">=0.6"
@ -16265,9 +16334,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "2.79.1", "version": "2.79.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz",
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
"dev": true, "dev": true,
"bin": { "bin": {
"rollup": "dist/bin/rollup" "rollup": "dist/bin/rollup"
@ -16560,9 +16629,9 @@
"dev": true "dev": true
}, },
"node_modules/send": { "node_modules/send": {
"version": "0.18.0", "version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"debug": "2.6.9", "debug": "2.6.9",
@ -16598,6 +16667,15 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true "dev": true
}, },
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"dev": true,
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/ms": { "node_modules/send/node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -16692,20 +16770,37 @@
} }
}, },
"node_modules/serve-static": { "node_modules/serve-static": {
"version": "1.15.0", "version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"encodeurl": "~1.0.2", "encodeurl": "~2.0.0",
"escape-html": "~1.0.3", "escape-html": "~1.0.3",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"send": "0.18.0" "send": "0.19.0"
}, },
"engines": { "engines": {
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"dev": true,
"dependencies": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/setprototypeof": { "node_modules/setprototypeof": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@ -16743,14 +16838,18 @@
} }
}, },
"node_modules/side-channel": { "node_modules/side-channel": {
"version": "1.0.4", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"call-bind": "^1.0.0", "call-bind": "^1.0.7",
"get-intrinsic": "^1.0.2", "es-errors": "^1.3.0",
"object-inspect": "^1.9.0" "get-intrinsic": "^1.2.4",
"object-inspect": "^1.13.1"
},
"engines": {
"node": ">= 0.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"

View file

@ -5,7 +5,7 @@ use crate::{
}; };
use roc_parse::ast::{ use roc_parse::ast::{
AbilityImpls, AssignedField, Collection, Expr, ExtractSpaces, ImplementsAbilities, AbilityImpls, AssignedField, Collection, Expr, ExtractSpaces, ImplementsAbilities,
ImplementsAbility, ImplementsClause, OldRecordBuilderField, Tag, TypeAnnotation, TypeHeader, ImplementsAbility, ImplementsClause, Tag, TypeAnnotation, TypeHeader,
}; };
use roc_parse::ident::UppercaseIdent; use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc; use roc_region::all::Loc;
@ -524,101 +524,6 @@ fn format_assigned_field_help<T>(
} }
} }
impl<'a> Formattable for OldRecordBuilderField<'a> {
fn is_multiline(&self) -> bool {
is_multiline_record_builder_field_help(self)
}
fn format_with_options(&self, buf: &mut Buf, _parens: Parens, newlines: Newlines, indent: u16) {
// we abuse the `Newlines` type to decide between multiline or single-line layout
format_record_builder_field_help(self, buf, indent, newlines == Newlines::Yes);
}
}
fn is_multiline_record_builder_field_help(afield: &OldRecordBuilderField<'_>) -> bool {
use self::OldRecordBuilderField::*;
match afield {
Value(_, spaces, ann) => !spaces.is_empty() || ann.value.is_multiline(),
ApplyValue(_, colon_spaces, arrow_spaces, ann) => {
!colon_spaces.is_empty() || !arrow_spaces.is_empty() || ann.value.is_multiline()
}
LabelOnly(_) => false,
SpaceBefore(_, _) | SpaceAfter(_, _) => true,
Malformed(text) => text.chars().any(|c| c == '\n'),
}
}
fn format_record_builder_field_help(
zelf: &OldRecordBuilderField,
buf: &mut Buf,
indent: u16,
is_multiline: bool,
) {
use self::OldRecordBuilderField::*;
match zelf {
Value(name, spaces, ann) => {
if is_multiline {
buf.newline();
}
buf.indent(indent);
buf.push_str(name.value);
if !spaces.is_empty() {
fmt_spaces(buf, spaces.iter(), indent);
}
buf.push(':');
buf.spaces(1);
ann.value.format(buf, indent);
}
ApplyValue(name, colon_spaces, arrow_spaces, ann) => {
if is_multiline {
buf.newline();
buf.indent(indent);
}
buf.push_str(name.value);
if !colon_spaces.is_empty() {
fmt_spaces(buf, colon_spaces.iter(), indent);
}
buf.push(':');
buf.spaces(1);
if !arrow_spaces.is_empty() {
fmt_spaces(buf, arrow_spaces.iter(), indent);
}
buf.push_str("<-");
buf.spaces(1);
ann.value.format(buf, indent);
}
LabelOnly(name) => {
if is_multiline {
buf.newline();
buf.indent(indent);
}
buf.push_str(name.value);
}
SpaceBefore(sub_field, spaces) => {
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
format_record_builder_field_help(sub_field, buf, indent, is_multiline);
}
SpaceAfter(sub_field, spaces) => {
format_record_builder_field_help(sub_field, buf, indent, is_multiline);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
}
Malformed(raw) => {
buf.push_str(raw);
}
}
}
impl<'a> Formattable for Tag<'a> { impl<'a> Formattable for Tag<'a> {
fn is_multiline(&self) -> bool { fn is_multiline(&self) -> bool {
use self::Tag::*; use self::Tag::*;

View file

@ -10,7 +10,7 @@ use crate::Buf;
use roc_module::called_via::{self, BinOp}; use roc_module::called_via::{self, BinOp};
use roc_parse::ast::{ use roc_parse::ast::{
is_expr_suffixed, AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces, is_expr_suffixed, AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces,
OldRecordBuilderField, Pattern, TryTarget, WhenBranch, Pattern, TryTarget, WhenBranch,
}; };
use roc_parse::ast::{StrLiteral, StrSegment}; use roc_parse::ast::{StrLiteral, StrSegment};
use roc_parse::ident::Accessor; use roc_parse::ident::Accessor;
@ -71,7 +71,11 @@ impl<'a> Formattable for Expr<'a> {
"LowLevelDbg should only exist after desugaring, not during formatting" "LowLevelDbg should only exist after desugaring, not during formatting"
), ),
If(branches, final_else) => { If {
if_thens: branches,
final_else,
..
} => {
final_else.is_multiline() final_else.is_multiline()
|| branches || branches
.iter() .iter()
@ -87,8 +91,6 @@ impl<'a> Formattable for Expr<'a> {
| PrecedenceConflict(roc_parse::ast::PrecedenceConflict { | PrecedenceConflict(roc_parse::ast::PrecedenceConflict {
expr: loc_subexpr, .. expr: loc_subexpr, ..
}) })
| MultipleOldRecordBuilders(loc_subexpr)
| UnappliedOldRecordBuilder(loc_subexpr)
| EmptyRecordBuilder(loc_subexpr) | EmptyRecordBuilder(loc_subexpr)
| SingleFieldRecordBuilder(loc_subexpr) | SingleFieldRecordBuilder(loc_subexpr)
| OptionalFieldInRecordBuilder(_, loc_subexpr) => loc_subexpr.is_multiline(), | OptionalFieldInRecordBuilder(_, loc_subexpr) => loc_subexpr.is_multiline(),
@ -114,7 +116,6 @@ impl<'a> Formattable for Expr<'a> {
Record(fields) => is_collection_multiline(fields), Record(fields) => is_collection_multiline(fields),
Tuple(fields) => is_collection_multiline(fields), Tuple(fields) => is_collection_multiline(fields),
RecordUpdate { fields, .. } => is_collection_multiline(fields), RecordUpdate { fields, .. } => is_collection_multiline(fields),
OldRecordBuilder(fields) => is_collection_multiline(fields),
RecordBuilder { fields, .. } => is_collection_multiline(fields), RecordBuilder { fields, .. } => is_collection_multiline(fields),
} }
} }
@ -240,10 +241,7 @@ impl<'a> Formattable for Expr<'a> {
a.extract_spaces().item.is_multiline() a.extract_spaces().item.is_multiline()
&& matches!( && matches!(
a.value.extract_spaces().item, a.value.extract_spaces().item,
Expr::Tuple(_) Expr::Tuple(_) | Expr::List(_) | Expr::Record(_)
| Expr::List(_)
| Expr::Record(_)
| Expr::OldRecordBuilder(_)
) )
&& a.extract_spaces().before == [CommentOrNewline::Newline] && a.extract_spaces().before == [CommentOrNewline::Newline]
}) })
@ -388,16 +386,6 @@ impl<'a> Formattable for Expr<'a> {
assigned_field_to_space_before, assigned_field_to_space_before,
); );
} }
OldRecordBuilder(fields) => {
fmt_record_like(
buf,
None,
*fields,
indent,
format_record_builder_field_multiline,
record_builder_field_to_space_before,
);
}
Closure(loc_patterns, loc_ret) => { Closure(loc_patterns, loc_ret) => {
fmt_closure(buf, loc_patterns, loc_ret, indent); fmt_closure(buf, loc_patterns, loc_ret, indent);
} }
@ -464,8 +452,19 @@ impl<'a> Formattable for Expr<'a> {
LowLevelDbg(_, _, _) => unreachable!( LowLevelDbg(_, _, _) => unreachable!(
"LowLevelDbg should only exist after desugaring, not during formatting" "LowLevelDbg should only exist after desugaring, not during formatting"
), ),
If(branches, final_else) => { If {
fmt_if(buf, branches, final_else, self.is_multiline(), indent); if_thens: branches,
final_else,
indented_else,
} => {
fmt_if(
buf,
branches,
final_else,
self.is_multiline(),
*indented_else,
indent,
);
} }
When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent), When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent),
Tuple(items) => fmt_collection(buf, indent, Braces::Round, *items, Newlines::No), Tuple(items) => fmt_collection(buf, indent, Braces::Round, *items, Newlines::No),
@ -548,8 +547,6 @@ impl<'a> Formattable for Expr<'a> {
} }
MalformedClosure => {} MalformedClosure => {}
PrecedenceConflict { .. } => {} PrecedenceConflict { .. } => {}
MultipleOldRecordBuilders { .. } => {}
UnappliedOldRecordBuilder { .. } => {}
EmptyRecordBuilder { .. } => {} EmptyRecordBuilder { .. } => {}
SingleFieldRecordBuilder { .. } => {} SingleFieldRecordBuilder { .. } => {}
OptionalFieldInRecordBuilder(_, _) => {} OptionalFieldInRecordBuilder(_, _) => {}
@ -610,11 +607,7 @@ pub(crate) fn format_sq_literal(buf: &mut Buf, s: &str) {
fn is_outdentable(expr: &Expr) -> bool { fn is_outdentable(expr: &Expr) -> bool {
matches!( matches!(
expr.extract_spaces().item, expr.extract_spaces().item,
Expr::Tuple(_) Expr::Tuple(_) | Expr::List(_) | Expr::Record(_) | Expr::Closure(..)
| Expr::List(_)
| Expr::Record(_)
| Expr::OldRecordBuilder(_)
| Expr::Closure(..)
) )
} }
@ -1076,6 +1069,7 @@ fn fmt_if<'a>(
branches: &'a [(Loc<Expr<'a>>, Loc<Expr<'a>>)], branches: &'a [(Loc<Expr<'a>>, Loc<Expr<'a>>)],
final_else: &'a Loc<Expr<'a>>, final_else: &'a Loc<Expr<'a>>,
is_multiline: bool, is_multiline: bool,
indented_else: bool,
indent: u16, indent: u16,
) { ) {
// let is_multiline_then = loc_then.is_multiline(); // let is_multiline_then = loc_then.is_multiline();
@ -1208,16 +1202,22 @@ fn fmt_if<'a>(
} }
} }
buf.indent(indent); if indented_else {
if is_multiline { buf.indent(indent + INDENT);
buf.push_str("else");
buf.newline();
buf.newline();
} else if is_multiline {
buf.indent(indent);
buf.push_str("else"); buf.push_str("else");
buf.newline(); buf.newline();
} else { } else {
buf.indent(indent);
buf.push_str(" else"); buf.push_str(" else");
buf.spaces(1); buf.spaces(1);
} }
let indent = if indented_else { indent } else { return_indent };
final_else.format(buf, return_indent); final_else.format(buf, indent);
} }
fn fmt_closure<'a>( fn fmt_closure<'a>(
@ -1622,113 +1622,6 @@ fn assigned_field_to_space_before<'a, T>(
} }
} }
fn format_record_builder_field_multiline(
buf: &mut Buf,
field: &OldRecordBuilderField,
indent: u16,
separator_prefix: &str,
) {
use self::OldRecordBuilderField::*;
match field {
Value(name, spaces, ann) => {
buf.newline();
buf.indent(indent);
buf.push_str(name.value);
if !spaces.is_empty() {
fmt_spaces(buf, spaces.iter(), indent);
buf.indent(indent);
}
buf.push_str(separator_prefix);
buf.push_str(":");
if ann.value.is_multiline() {
buf.newline();
ann.value.format(buf, indent + INDENT);
} else {
buf.spaces(1);
ann.value.format(buf, indent);
}
buf.push(',');
}
ApplyValue(name, colon_spaces, arrow_spaces, ann) => {
buf.newline();
buf.indent(indent);
buf.push_str(name.value);
if !colon_spaces.is_empty() {
fmt_spaces(buf, colon_spaces.iter(), indent);
buf.indent(indent);
}
buf.push_str(separator_prefix);
buf.push(':');
buf.spaces(1);
if !arrow_spaces.is_empty() {
fmt_spaces(buf, arrow_spaces.iter(), indent);
buf.indent(indent + INDENT);
}
buf.push_str("<-");
if ann.value.is_multiline() {
buf.newline();
ann.value.format(buf, indent + INDENT);
} else {
buf.spaces(1);
ann.value.format(buf, indent);
}
buf.push(',');
}
LabelOnly(name) => {
buf.newline();
buf.indent(indent);
buf.push_str(name.value);
buf.push(',');
}
SpaceBefore(sub_field, _spaces) => {
// We have something like that:
// ```
// # comment
// field,
// ```
// we'd like to preserve this
format_record_builder_field_multiline(buf, sub_field, indent, separator_prefix);
}
SpaceAfter(sub_field, spaces) => {
// We have something like that:
// ```
// field # comment
// , otherfield
// ```
// we'd like to transform it into:
// ```
// field,
// # comment
// otherfield
// ```
format_record_builder_field_multiline(buf, sub_field, indent, separator_prefix);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, indent);
}
Malformed(raw) => {
buf.push_str(raw);
}
}
}
fn record_builder_field_to_space_before<'a>(
field: &'a OldRecordBuilderField<'a>,
) -> Option<(&OldRecordBuilderField<'a>, &'a [CommentOrNewline<'a>])> {
match field {
OldRecordBuilderField::SpaceBefore(sub_field, spaces) => Some((sub_field, spaces)),
_ => None,
}
}
fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool { fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool {
match expr { match expr {
Expr::BinOps(left_side, _) => { Expr::BinOps(left_side, _) => {
@ -1753,7 +1646,7 @@ fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool {
| BinOp::Pizza => true, | BinOp::Pizza => true,
}) })
} }
Expr::If(_, _) => true, Expr::If { .. } => true,
Expr::SpaceBefore(e, _) => sub_expr_requests_parens(e), Expr::SpaceBefore(e, _) => sub_expr_requests_parens(e),
Expr::SpaceAfter(e, _) => sub_expr_requests_parens(e), Expr::SpaceAfter(e, _) => sub_expr_requests_parens(e),
_ => false, _ => false,

View file

@ -2915,130 +2915,108 @@ fn list_literal<'a, 'ctx>(
let list_length = elems.len(); let list_length = elems.len();
let list_length_intval = env.ptr_int().const_int(list_length as _, false); let list_length_intval = env.ptr_int().const_int(list_length as _, false);
// TODO re-enable, currently causes morphic segfaults because it tries to update let is_refcounted = layout_interner.contains_refcounted(element_layout);
// constants in-place... let is_all_constant = elems.iter().all(|e| e.is_literal());
// if element_type.is_int_type() {
if false { if is_all_constant && !is_refcounted {
let element_type = element_type.into_int_type(); // Build a global literal in-place instead of GEPing and storing individual elements.
// Exceptions:
// - Anything that is refcounted has nested pointers,
// and nested pointers in globals will break the surgical linker.
// Ignore such cases for now.
let element_width = layout_interner.stack_size(element_layout); let element_width = layout_interner.stack_size(element_layout);
let size = list_length * element_width as usize;
let alignment = layout_interner let alignment = layout_interner
.alignment_bytes(element_layout) .alignment_bytes(element_layout)
.max(env.target.ptr_width() as u32); .max(env.target.ptr_width() as u32);
let mut is_all_constant = true; let refcount_slot_bytes = alignment as usize;
let zero_elements =
(env.target.ptr_width() as u8 as f64 / element_width as f64).ceil() as usize;
// runtime-evaluated elements let refcount_slot_elements =
let mut runtime_evaluated_elements = Vec::with_capacity_in(list_length, env.arena); (refcount_slot_bytes as f64 / element_width as f64).ceil() as usize;
// set up a global that contains all the literal elements of the array let data_bytes = list_length * element_width as usize;
// any variables or expressions are represented as `undef`
let global = {
let mut global_elements = Vec::with_capacity_in(list_length, env.arena);
// Add zero bytes that represent the refcount assert!(refcount_slot_elements > 0);
//
// - if all elements are const, then we store the whole list as a constant. let global = if element_type.is_int_type() {
// It then needs a refcount before the first element. let element_type = element_type.into_int_type();
// - but if the list is not all constants, then we will just copy the constant values, let mut bytes = Vec::with_capacity_in(refcount_slot_elements + data_bytes, env.arena);
// and we do not need that refcount at the start
// // Fill the refcount slot with nulls
// In the latter case, we won't store the zeros in the globals for _ in 0..(refcount_slot_elements) {
// (we slice them off again below) bytes.push(element_type.const_zero());
for _ in 0..zero_elements {
global_elements.push(element_type.const_zero());
} }
// Copy the elements from the list literal into the array // Copy the elements from the list literal into the array
for (index, element) in elems.iter().enumerate() { for element in elems.iter() {
match element { let literal = element.get_literal().expect("is_all_constant is true");
ListLiteralElement::Literal(literal) => { let val = build_exp_literal(env, layout_interner, element_layout, &literal);
let val = build_exp_literal(env, layout_interner, element_layout, literal); bytes.push(val.into_int_value());
global_elements.push(val.into_int_value());
}
ListLiteralElement::Symbol(symbol) => {
let val = scope.load_symbol(symbol);
// here we'd like to furthermore check for intval.is_const().
// if all elements are const for LLVM, we could make the array a constant.
// BUT morphic does not know about this, and could allow us to modify that
// array in-place. That would cause a segfault. So, we'll have to find
// constants ourselves and cannot lean on LLVM here.
is_all_constant = false;
runtime_evaluated_elements.push((index, val));
global_elements.push(element_type.get_undef());
}
};
} }
let const_elements = if is_all_constant { let typ = element_type.array_type(bytes.len() as u32);
global_elements.into_bump_slice()
} else {
&global_elements[zero_elements..]
};
// use None for the address space (e.g. Const does not work)
let typ = element_type.array_type(const_elements.len() as u32);
let global = env.module.add_global(typ, None, "roc__list_literal"); let global = env.module.add_global(typ, None, "roc__list_literal");
global.set_constant(true); global.set_initializer(&element_type.const_array(bytes.into_bump_slice()));
global.set_alignment(alignment); global
global.set_unnamed_addr(true); } else if element_type.is_float_type() {
global.set_linkage(inkwell::module::Linkage::Private); let element_type = element_type.into_float_type();
let mut bytes = Vec::with_capacity_in(refcount_slot_elements + data_bytes, env.arena);
global.set_initializer(&element_type.const_array(const_elements)); // Fill the refcount slot with nulls
global.as_pointer_value() for _ in 0..(refcount_slot_elements) {
}; bytes.push(element_type.const_zero());
if is_all_constant {
// all elements are constants, so we can use the memory in the constants section directly
// here we make a pointer to the first actual element (skipping the 0 bytes that
// represent the refcount)
let zero = env.ptr_int().const_zero();
let offset = env.ptr_int().const_int(zero_elements as _, false);
let ptr = unsafe {
env.builder.new_build_in_bounds_gep(
element_type,
global,
&[zero, offset],
"first_element_pointer",
)
};
super::build_list::store_list(env, ptr, list_length_intval).into()
} else {
// some of our elements are non-constant, so we must allocate space on the heap
let ptr = allocate_list(env, layout_interner, element_layout, list_length_intval);
// then, copy the relevant segment from the constant section into the heap
env.builder
.build_memcpy(
ptr,
alignment,
global,
alignment,
env.ptr_int().const_int(size as _, false),
)
.unwrap();
// then replace the `undef`s with the values that we evaluate at runtime
for (index, val) in runtime_evaluated_elements {
let index_val = ctx.i64_type().const_int(index as u64, false);
let elem_ptr = unsafe {
builder.new_build_in_bounds_gep(element_type, ptr, &[index_val], "index")
};
builder.new_build_store(elem_ptr, val);
} }
super::build_list::store_list(env, ptr, list_length_intval).into() // Copy the elements from the list literal into the array
} for element in elems.iter() {
let literal = element.get_literal().expect("is_all_constant is true");
let val = build_exp_literal(env, layout_interner, element_layout, &literal);
bytes.push(val.into_float_value());
}
let typ = element_type.array_type(bytes.len() as u32);
let global = env.module.add_global(typ, None, "roc__list_literal");
global.set_initializer(&element_type.const_array(bytes.into_bump_slice()));
global
} else {
internal_error!("unexpected element type: {:?}", element_type);
};
global.set_constant(true);
global.set_alignment(alignment);
global.set_unnamed_addr(true);
global.set_linkage(inkwell::module::Linkage::Private);
// Refcount the global itself in a new allocation.
// Necessary because morphic MAY attempt to update the global in-place, which is
// illegal if the global is in the read-only section.
let with_rc_ptr = global.as_pointer_value();
let const_data_ptr = unsafe {
env.builder.new_build_in_bounds_gep(
element_type,
with_rc_ptr,
&[env
.ptr_int()
.const_int(refcount_slot_elements as u64, false)],
"get_data_ptr",
)
};
let data_ptr = allocate_list(env, layout_interner, element_layout, list_length_intval);
let byte_size = env
.ptr_int()
.const_int(list_length as u64 * element_width as u64, false);
builder
.build_memcpy(data_ptr, alignment, const_data_ptr, alignment, byte_size)
.unwrap();
super::build_list::store_list(env, data_ptr, list_length_intval).into()
} else { } else {
let ptr = allocate_list(env, layout_interner, element_layout, list_length_intval); let ptr = allocate_list(env, layout_interner, element_layout, list_length_intval);

View file

@ -282,16 +282,9 @@ pub(crate) struct RocUnion<'ctx> {
tag_type: Option<TagType>, tag_type: Option<TagType>,
} }
fn is_multiple_of(big: u32, small: u32) -> bool {
match small {
0 => true, // 0 is a multiple of all n, because n * 0 = 0
n => big % n == 0,
}
}
impl<'ctx> RocUnion<'ctx> { impl<'ctx> RocUnion<'ctx> {
pub const TAG_ID_INDEX: u32 = 2; pub const TAG_ID_INDEX: u32 = 1;
pub const TAG_DATA_INDEX: u32 = 1; pub const TAG_DATA_INDEX: u32 = 0;
fn new( fn new(
context: &'ctx Context, context: &'ctx Context,
@ -301,45 +294,24 @@ impl<'ctx> RocUnion<'ctx> {
) -> Self { ) -> Self {
let bytes = round_up_to_alignment(data_width, data_align); let bytes = round_up_to_alignment(data_width, data_align);
let byte_array_type = if is_multiple_of(bytes, 8) && is_multiple_of(data_align, 8) { let align_type = alignment_type(context, data_align);
context let byte_array_type = align_type
.i64_type() .array_type(bytes / data_align)
.array_type(bytes / 8)
.as_basic_type_enum()
} else {
context.i8_type().array_type(bytes).as_basic_type_enum()
};
let alignment_array_type = alignment_type(context, data_align)
.array_type(0)
.as_basic_type_enum(); .as_basic_type_enum();
let struct_type = if let Some(tag_type) = tag_type { let struct_type = if let Some(tag_type) = tag_type {
let tag_width = match tag_type {
TagType::I8 => 1,
TagType::I16 => 2,
};
let tag_padding = round_up_to_alignment(tag_width, data_align) - tag_width;
let tag_padding_type = context
.i8_type()
.array_type(tag_padding)
.as_basic_type_enum();
context.struct_type( context.struct_type(
&[ &[
alignment_array_type,
byte_array_type, byte_array_type,
match tag_type { match tag_type {
TagType::I8 => context.i8_type().into(), TagType::I8 => context.i8_type().into(),
TagType::I16 => context.i16_type().into(), TagType::I16 => context.i16_type().into(),
}, },
tag_padding_type,
], ],
false, false,
) )
} else { } else {
context.struct_type(&[alignment_array_type, byte_array_type], false) context.struct_type(&[byte_array_type], false)
}; };
Self { Self {

View file

@ -161,23 +161,6 @@ pub fn can_expr_with<'a>(
// ensure the Test module is accessible in our tests // ensure the Test module is accessible in our tests
module_ids.get_or_insert(&PQModuleName::Unqualified("Test".into())); module_ids.get_or_insert(&PQModuleName::Unqualified("Test".into()));
// Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization.
//
// If we did this *during* canonicalization, then each time we
// visited a BinOp node we'd recursively try to apply this to each of its nested
// operators, and then again on *their* nested operators, ultimately applying the
// rules multiple times unnecessarily.
let loc_expr = desugar::desugar_expr(
arena,
&mut var_store,
&loc_expr,
expr_str,
&mut None,
arena.alloc("TestPath"),
&mut Default::default(),
);
let mut scope = Scope::new( let mut scope = Scope::new(
home, home,
"TestPath".into(), "TestPath".into(),
@ -188,12 +171,23 @@ pub fn can_expr_with<'a>(
let dep_idents = IdentIds::exposed_builtins(0); let dep_idents = IdentIds::exposed_builtins(0);
let mut env = Env::new( let mut env = Env::new(
arena, arena,
expr_str,
home, home,
Path::new("Test.roc"), Path::new("Test.roc"),
&dep_idents, &dep_idents,
&module_ids, &module_ids,
None, None,
); );
// Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization.
//
// If we did this *during* canonicalization, then each time we
// visited a BinOp node we'd recursively try to apply this to each of its nested
// operators, and then again on *their* nested operators, ultimately applying the
// rules multiple times unnecessarily.
let loc_expr = desugar::desugar_expr(&mut env, &mut scope, &loc_expr);
let (loc_expr, output) = canonicalize_expr( let (loc_expr, output) = canonicalize_expr(
&mut env, &mut env,
&mut var_store, &mut var_store,

View file

@ -4299,8 +4299,9 @@ mod test_reporting {
4 f :: I64 4 f :: I64
^^ ^^
I have no specific suggestion for this operator, see TODO for the full I have no specific suggestion for this operator, see
list of operators in Roc. https://www.roc-lang.org/tutorial#operator-desugaring-table for the
full list of operators in Roc.
"# "#
); );
@ -4339,6 +4340,14 @@ mod test_reporting {
However, I already saw the final expression in that series of However, I already saw the final expression in that series of
definitions. definitions.
Tip: An expression like `4`, `"hello"`, or `functionCall MyThing` is
like `return 4` in other programming languages. To me, it seems like
you did `return 4` followed by more code in the lines after, that code
would never be executed!
Tip: If you are working with `Task`, this error can happen if you
forgot a `!` somewhere.
"### "###
); );
@ -4972,30 +4981,6 @@ mod test_reporting {
"### "###
); );
test_report!(
record_builder_in_module_params,
indoc!(
r"
import Menu {
echo,
name: <- applyName
}
"
),@r###"
OLD-STYLE RECORD BUILDER IN MODULE PARAMS in ...r_in_module_params/Test.roc
I was partway through parsing module params, but I got stuck here:
4 import Menu {
5 echo,
6 name: <- applyName
^^^^^^^^^^^^^^^^^^
This looks like an old-style record builder field, but those are not
allowed in module params.
"###
);
test_report!( test_report!(
record_update_in_module_params, record_update_in_module_params,
indoc!( indoc!(
@ -6068,8 +6053,9 @@ All branches in an `if` must have the same type!
5 5 ** 3 5 5 ** 3
^^ ^^
I have no specific suggestion for this operator, see TODO for the full I have no specific suggestion for this operator, see
list of operators in Roc. https://www.roc-lang.org/tutorial#operator-desugaring-table for the
full list of operators in Roc.
"# "#
); );
@ -10661,163 +10647,6 @@ All branches in an `if` must have the same type!
// Record Builders // Record Builders
test_report!(
optional_field_in_old_record_builder,
indoc!(
r#"
{
a: <- apply "a",
b,
c ? "optional"
}
"#
),
@r#"
BAD OLD-STYLE RECORD BUILDER in ...nal_field_in_old_record_builder/Test.roc
I am partway through parsing a record builder, and I found an optional
field:
1 app "test" provides [main] to "./platform"
2
3 main =
4 {
5 a: <- apply "a",
6 b,
7 c ? "optional"
^^^^^^^^^^^^^^
Optional fields can only appear when you destructure a record.
"#
);
test_report!(
record_update_old_builder,
indoc!(
r#"
{ rec &
a: <- apply "a",
b: 3
}
"#
),
@r#"
BAD RECORD UPDATE in tmp/record_update_old_builder/Test.roc
I am partway through parsing a record update, and I found an old-style
record builder field:
1 app "test" provides [main] to "./platform"
2
3 main =
4 { rec &
5 a: <- apply "a",
^^^^^^^^^^^^^^^
Old-style record builders cannot be updated like records.
"#
);
test_report!(
multiple_old_record_builders,
indoc!(
r#"
succeed
{ a: <- apply "a" }
{ b: <- apply "b" }
"#
),
@r#"
MULTIPLE OLD-STYLE RECORD BUILDERS in /code/proj/Main.roc
This function is applied to multiple old-style record builders:
4> succeed
5> { a: <- apply "a" }
6> { b: <- apply "b" }
Note: Functions can only take at most one old-style record builder!
Tip: You can combine them or apply them separately.
"#
);
test_report!(
unapplied_old_record_builder,
indoc!(
r#"
{ a: <- apply "a" }
"#
),
@r#"
UNAPPLIED OLD-STYLE RECORD BUILDER in /code/proj/Main.roc
This old-style record builder was not applied to a function:
4 { a: <- apply "a" }
^^^^^^^^^^^^^^^^^^^
However, we need a function to construct the record.
Note: Functions must be applied directly. The pipe operator (|>) cannot be used.
"#
);
test_report!(
old_record_builder_apply_non_function,
indoc!(
r#"
succeed = \_ -> crash ""
succeed {
a: <- "a",
}
"#
),
@r#"
TOO MANY ARGS in /code/proj/Main.roc
This value is not a function, but it was given 1 argument:
7 a: <- "a",
^^^
Tip: Remove <- to assign the field directly.
"#
);
// Skipping test because opaque types defined in the same module
// do not fail with the special opaque type error
//
// test_report!(
// record_builder_apply_opaque,
// indoc!(
// r#"
// succeed = \_ -> crash ""
// Decode := {}
// get : Str -> Decode
// get = \_ -> @Decode {}
// succeed {
// a: <- get "a",
// # missing |> apply ^
// }
// "#
// ),
// @r#"
// ── TOO MANY ARGS in /code/proj/Main.roc ────────────────────────────────────────
// This value is an opaque type, so it cannot be called with an argument:
// 12│ a: <- get "a",
// ^^^^^^^
// Hint: Did you mean to apply it to a function first?
// "#
// );
test_report!( test_report!(
empty_record_builder, empty_record_builder,
indoc!( indoc!(
@ -14521,7 +14350,7 @@ All branches in an `if` must have the same type!
4 1 + dbg + 2 4 1 + dbg + 2
^^^ ^^^
This `63` value is a: This value is a:
{} {}
@ -14555,7 +14384,7 @@ All branches in an `if` must have the same type!
4 1 + dbg "" "" + 2 4 1 + dbg "" "" + 2
^^^^^^^^^ ^^^^^^^^^
This `63` value is a: This value is a:
{} {}

View file

@ -784,7 +784,7 @@ impl<'a> State<'a> {
number_of_workers: usize, number_of_workers: usize,
exec_mode: ExecutionMode, exec_mode: ExecutionMode,
) -> Self { ) -> Self {
let cache_dir = roc_packaging::cache::roc_cache_dir(); let cache_dir = roc_packaging::cache::roc_cache_packages_dir();
let dependencies = Dependencies::new(exec_mode.goal_phase()); let dependencies = Dependencies::new(exec_mode.goal_phase());
Self { Self {
@ -1665,7 +1665,7 @@ fn state_thread_step<'a>(
Ok(ControlFlow::Break(LoadResult::Monomorphized(monomorphized))) Ok(ControlFlow::Break(LoadResult::Monomorphized(monomorphized)))
} }
Msg::FailedToReadFile { filename, error } => { Msg::FailedToReadFile { filename, error } => {
let buf = to_file_problem_report_string(filename, error); let buf = to_file_problem_report_string(filename, error, true);
Err(LoadingProblem::FormattedReport(buf)) Err(LoadingProblem::FormattedReport(buf))
} }
@ -1839,7 +1839,7 @@ pub fn report_loading_problem(
} }
LoadingProblem::FormattedReport(report) => report, LoadingProblem::FormattedReport(report) => report,
LoadingProblem::FileProblem { filename, error } => { LoadingProblem::FileProblem { filename, error } => {
to_file_problem_report_string(filename, error) to_file_problem_report_string(filename, error, true)
} }
LoadingProblem::NoPlatformPackage { LoadingProblem::NoPlatformPackage {
filename, filename,

View file

@ -75,10 +75,6 @@ pub enum CalledVia {
/// e.g. "$(first) $(last)" is transformed into Str.concat (Str.concat first " ") last. /// e.g. "$(first) $(last)" is transformed into Str.concat (Str.concat first " ") last.
StringInterpolation, StringInterpolation,
/// This call is the result of desugaring an old style Record Builder field.
/// e.g. succeed { a <- get "a" } is transformed into (get "a") (succeed \a -> { a })
OldRecordBuilder,
/// This call is the result of desugaring a map2-based Record Builder field. e.g. /// This call is the result of desugaring a map2-based Record Builder field. e.g.
/// ```roc /// ```roc
/// { Task.parallel <- /// { Task.parallel <-

View file

@ -123,6 +123,10 @@ impl Symbol {
.any(|(_, (s, _))| *s == self) .any(|(_, (s, _))| *s == self)
} }
pub fn is_generated(self, interns: &Interns) -> bool {
self.ident_ids(interns).is_generated_id(self.ident_id())
}
pub fn module_string<'a>(&self, interns: &'a Interns) -> &'a ModuleName { pub fn module_string<'a>(&self, interns: &'a Interns) -> &'a ModuleName {
interns interns
.module_ids .module_ids
@ -137,24 +141,15 @@ impl Symbol {
} }
pub fn as_str(self, interns: &Interns) -> &str { pub fn as_str(self, interns: &Interns) -> &str {
let ident_ids = interns self.ident_ids(interns)
.all_ident_ids .get_name(self.ident_id())
.get(&self.module_id())
.unwrap_or_else(|| { .unwrap_or_else(|| {
internal_error!( internal_error!(
"ident_string could not find IdentIds for module {:?} in {:?}", "ident_string's IdentIds did not contain an entry for {} in module {:?}",
self.module_id(), self.ident_id().0,
interns self.module_id()
) )
}); })
ident_ids.get_name(self.ident_id()).unwrap_or_else(|| {
internal_error!(
"ident_string's IdentIds did not contain an entry for {} in module {:?}",
self.ident_id().0,
self.module_id()
)
})
} }
pub const fn as_u64(self) -> u64 { pub const fn as_u64(self) -> u64 {
@ -186,6 +181,19 @@ impl Symbol {
pub fn contains(self, needle: &str) -> bool { pub fn contains(self, needle: &str) -> bool {
format!("{self:?}").contains(needle) format!("{self:?}").contains(needle)
} }
fn ident_ids(self, interns: &Interns) -> &IdentIds {
interns
.all_ident_ids
.get(&self.module_id())
.unwrap_or_else(|| {
internal_error!(
"ident_string could not find IdentIds for module {:?} in {:?}",
self.module_id(),
interns
)
})
}
} }
/// Rather than displaying as this: /// Rather than displaying as this:
@ -438,13 +446,6 @@ impl fmt::Debug for ModuleId {
} }
} }
/// pf.Task
/// 1. build mapping from short name to package
/// 2. when adding new modules from package we need to register them in some other map (this module id goes with short name) (shortname, module-name) -> moduleId
/// 3. pass this around to other modules getting headers parsed. when parsing interfaces we need to use this map to reference shortnames
/// 4. throw away short names. stash the module id in the can env under the resolved module name
/// 5. test:
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PackageQualified<'a, T> { pub enum PackageQualified<'a, T> {
Unqualified(T), Unqualified(T),
@ -707,6 +708,12 @@ impl IdentIds {
IdentId(self.interner.insert_index_str() as u32) IdentId(self.interner.insert_index_str() as u32)
} }
pub fn is_generated_id(&self, id: IdentId) -> bool {
self.interner
.try_get(id.0 as usize)
.map_or(false, |str| str.starts_with(|c: char| c.is_ascii_digit()))
}
#[inline(always)] #[inline(always)]
pub fn get_id(&self, ident_name: &str) -> Option<IdentId> { pub fn get_id(&self, ident_name: &str) -> Option<IdentId> {
self.interner self.interner
@ -1373,6 +1380,8 @@ define_builtins! {
46 STR_REPLACE_FIRST: "replaceFirst" 46 STR_REPLACE_FIRST: "replaceFirst"
47 STR_REPLACE_LAST: "replaceLast" 47 STR_REPLACE_LAST: "replaceLast"
48 STR_RELEASE_EXCESS_CAPACITY: "releaseExcessCapacity" 48 STR_RELEASE_EXCESS_CAPACITY: "releaseExcessCapacity"
49 STR_DROP_PREFIX: "dropPrefix"
50 STR_DROP_SUFFIX: "dropSuffix"
} }
6 LIST: "List" => { 6 LIST: "List" => {
0 LIST_LIST: "List" exposed_apply_type=true // the List.List type alias 0 LIST_LIST: "List" exposed_apply_type=true // the List.List type alias

View file

@ -396,7 +396,7 @@ impl<'state, 'a> State<'state, 'a> {
None => unreachable!( None => unreachable!(
"\n\tNo borrow signature for {name:?} layout.\n\n\t\ "\n\tNo borrow signature for {name:?} layout.\n\n\t\
Tip 1: This can happen when you call a function with less arguments than it expects.\n\t\ Tip 1: This can happen when you call a function with less arguments than it expects.\n\t\
Like `Arg.list!` instead of `Arg.list! {{}}`.\n\t Like `Arg.list!` instead of `Arg.list! {{}}`.\n\t\
Tip 2: `roc check yourfile.roc` can sometimes give you a helpful error. Tip 2: `roc check yourfile.roc` can sometimes give you a helpful error.
" "
) )

View file

@ -49,6 +49,17 @@ impl<'a> ListLiteralElement<'a> {
_ => None, _ => None,
} }
} }
pub fn get_literal(&self) -> Option<Literal<'a>> {
match self {
Self::Literal(l) => Some(*l),
_ => None,
}
}
pub fn is_literal(&self) -> bool {
matches!(self, Self::Literal(_))
}
} }
pub enum NumLiteral { pub enum NumLiteral {

View file

@ -456,13 +456,6 @@ pub enum Expr<'a> {
Tuple(Collection<'a, &'a Loc<Expr<'a>>>), Tuple(Collection<'a, &'a Loc<Expr<'a>>>),
// Record Builders
/// Applicative record builders, e.g.
/// build {
/// foo: <- getData Foo,
/// bar: <- getData Bar,
/// }
OldRecordBuilder(Collection<'a, Loc<OldRecordBuilderField<'a>>>),
/// Mapper-based record builders, e.g. /// Mapper-based record builders, e.g.
/// { Task.parallel <- /// { Task.parallel <-
/// foo: Task.getData Foo, /// foo: Task.getData Foo,
@ -512,7 +505,11 @@ pub enum Expr<'a> {
UnaryOp(&'a Loc<Expr<'a>>, Loc<UnaryOp>), UnaryOp(&'a Loc<Expr<'a>>, Loc<UnaryOp>),
// Conditionals // Conditionals
If(&'a [(Loc<Expr<'a>>, Loc<Expr<'a>>)], &'a Loc<Expr<'a>>), If {
if_thens: &'a [(Loc<Expr<'a>>, Loc<Expr<'a>>)],
final_else: &'a Loc<Expr<'a>>,
indented_else: bool,
},
When( When(
/// The condition /// The condition
&'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>,
@ -537,8 +534,6 @@ pub enum Expr<'a> {
// Both operators were non-associative, e.g. (True == False == False). // Both operators were non-associative, e.g. (True == False == False).
// We should tell the author to disambiguate by grouping them with parens. // We should tell the author to disambiguate by grouping them with parens.
PrecedenceConflict(&'a PrecedenceConflict<'a>), PrecedenceConflict(&'a PrecedenceConflict<'a>),
MultipleOldRecordBuilders(&'a Loc<Expr<'a>>),
UnappliedOldRecordBuilder(&'a Loc<Expr<'a>>),
EmptyRecordBuilder(&'a Loc<Expr<'a>>), EmptyRecordBuilder(&'a Loc<Expr<'a>>),
SingleFieldRecordBuilder(&'a Loc<Expr<'a>>), SingleFieldRecordBuilder(&'a Loc<Expr<'a>>),
OptionalFieldInRecordBuilder(&'a Loc<&'a str>, &'a Loc<Expr<'a>>), OptionalFieldInRecordBuilder(&'a Loc<&'a str>, &'a Loc<Expr<'a>>),
@ -608,7 +603,11 @@ pub fn is_expr_suffixed(expr: &Expr) -> bool {
} }
// expression in a if-then-else, `if isOk! then "ok" else doSomething!` // expression in a if-then-else, `if isOk! then "ok" else doSomething!`
Expr::If(if_thens, final_else) => { Expr::If {
if_thens,
final_else,
..
} => {
let any_if_thens_suffixed = if_thens.iter().any(|(if_then, else_expr)| { let any_if_thens_suffixed = if_thens.iter().any(|(if_then, else_expr)| {
is_expr_suffixed(&if_then.value) || is_expr_suffixed(&else_expr.value) is_expr_suffixed(&if_then.value) || is_expr_suffixed(&else_expr.value)
}); });
@ -655,9 +654,6 @@ pub fn is_expr_suffixed(expr: &Expr) -> bool {
.iter() .iter()
.any(|field| is_assigned_value_suffixed(&field.value)), .any(|field| is_assigned_value_suffixed(&field.value)),
Expr::Tuple(items) => items.iter().any(|x| is_expr_suffixed(&x.value)), Expr::Tuple(items) => items.iter().any(|x| is_expr_suffixed(&x.value)),
Expr::OldRecordBuilder(items) => items
.iter()
.any(|rbf| is_record_builder_field_suffixed(&rbf.value)),
Expr::RecordBuilder { mapper: _, fields } => fields Expr::RecordBuilder { mapper: _, fields } => fields
.iter() .iter()
.any(|field| is_assigned_value_suffixed(&field.value)), .any(|field| is_assigned_value_suffixed(&field.value)),
@ -680,8 +676,6 @@ pub fn is_expr_suffixed(expr: &Expr) -> bool {
Expr::MalformedClosure => false, Expr::MalformedClosure => false,
Expr::MalformedSuffixed(_) => false, Expr::MalformedSuffixed(_) => false,
Expr::PrecedenceConflict(_) => false, Expr::PrecedenceConflict(_) => false,
Expr::MultipleOldRecordBuilders(_) => false,
Expr::UnappliedOldRecordBuilder(_) => false,
Expr::EmptyRecordBuilder(_) => false, Expr::EmptyRecordBuilder(_) => false,
Expr::SingleFieldRecordBuilder(_) => false, Expr::SingleFieldRecordBuilder(_) => false,
Expr::OptionalFieldInRecordBuilder(_, _) => false, Expr::OptionalFieldInRecordBuilder(_, _) => false,
@ -709,17 +703,6 @@ fn is_assigned_value_suffixed<'a>(value: &AssignedField<'a, Expr<'a>>) -> bool {
} }
} }
fn is_record_builder_field_suffixed(field: &OldRecordBuilderField<'_>) -> bool {
match field {
OldRecordBuilderField::Value(_, _, a) => is_expr_suffixed(&a.value),
OldRecordBuilderField::ApplyValue(_, _, _, a) => is_expr_suffixed(&a.value),
OldRecordBuilderField::LabelOnly(_) => false,
OldRecordBuilderField::SpaceBefore(a, _) => is_record_builder_field_suffixed(a),
OldRecordBuilderField::SpaceAfter(a, _) => is_record_builder_field_suffixed(a),
OldRecordBuilderField::Malformed(_) => false,
}
}
pub fn split_around<T>(items: &[T], target: usize) -> (&[T], &[T]) { pub fn split_around<T>(items: &[T], target: usize) -> (&[T], &[T]) {
let (before, rest) = items.split_at(target); let (before, rest) = items.split_at(target);
let after = &rest[1..]; let after = &rest[1..];
@ -927,26 +910,6 @@ impl<'a, 'b> RecursiveValueDefIter<'a, 'b> {
expr_stack.push(&loc_expr.value); expr_stack.push(&loc_expr.value);
} }
} }
OldRecordBuilder(fields) => {
expr_stack.reserve(fields.len());
for loc_record_builder_field in fields.items {
let mut current_field = loc_record_builder_field.value;
loop {
use OldRecordBuilderField::*;
match current_field {
Value(_, _, loc_val) => break expr_stack.push(&loc_val.value),
ApplyValue(_, _, _, loc_val) => {
break expr_stack.push(&loc_val.value)
}
SpaceBefore(next_field, _) => current_field = *next_field,
SpaceAfter(next_field, _) => current_field = *next_field,
LabelOnly(_) | Malformed(_) => break,
}
}
}
}
RecordBuilder { RecordBuilder {
mapper: map2, mapper: map2,
fields, fields,
@ -993,14 +956,18 @@ impl<'a, 'b> RecursiveValueDefIter<'a, 'b> {
expr_stack.push(&expr.value); expr_stack.push(&expr.value);
} }
UnaryOp(expr, _) => expr_stack.push(&expr.value), UnaryOp(expr, _) => expr_stack.push(&expr.value),
If(ifs, alternate) => { If {
expr_stack.reserve(ifs.len() * 2 + 1); if_thens,
final_else,
..
} => {
expr_stack.reserve(if_thens.len() * 2 + 1);
for (condition, consequent) in ifs.iter() { for (condition, consequent) in if_thens.iter() {
expr_stack.push(&condition.value); expr_stack.push(&condition.value);
expr_stack.push(&consequent.value); expr_stack.push(&consequent.value);
} }
expr_stack.push(&alternate.value); expr_stack.push(&final_else.value);
} }
When(condition, branches) => { When(condition, branches) => {
expr_stack.reserve(branches.len() + 1); expr_stack.reserve(branches.len() + 1);
@ -1027,9 +994,7 @@ impl<'a, 'b> RecursiveValueDefIter<'a, 'b> {
| SpaceAfter(expr, _) | SpaceAfter(expr, _)
| ParensAround(expr) => expr_stack.push(expr), | ParensAround(expr) => expr_stack.push(expr),
MultipleOldRecordBuilders(loc_expr) EmptyRecordBuilder(loc_expr)
| UnappliedOldRecordBuilder(loc_expr)
| EmptyRecordBuilder(loc_expr)
| SingleFieldRecordBuilder(loc_expr) | SingleFieldRecordBuilder(loc_expr)
| OptionalFieldInRecordBuilder(_, loc_expr) => expr_stack.push(&loc_expr.value), | OptionalFieldInRecordBuilder(_, loc_expr) => expr_stack.push(&loc_expr.value),
@ -1655,30 +1620,6 @@ impl<'a, Val> AssignedField<'a, Val> {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum OldRecordBuilderField<'a> {
// A field with a value, e.g. `{ name: "blah" }`
Value(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Expr<'a>>),
// A field with a function we can apply to build part of the record, e.g. `{ name: <- apply getName }`
ApplyValue(
Loc<&'a str>,
&'a [CommentOrNewline<'a>],
&'a [CommentOrNewline<'a>],
&'a Loc<Expr<'a>>,
),
// A label with no value, e.g. `{ name }` (this is sugar for { name: name })
LabelOnly(Loc<&'a str>),
// We preserve this for the formatter; canonicalization ignores it.
SpaceBefore(&'a OldRecordBuilderField<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a OldRecordBuilderField<'a>, &'a [CommentOrNewline<'a>]),
/// A malformed assigned field, which will code gen to a runtime error
Malformed(&'a str),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CommentOrNewline<'a> { pub enum CommentOrNewline<'a> {
Newline, Newline,
@ -2213,15 +2154,6 @@ impl<'a, Val> Spaceable<'a> for AssignedField<'a, Val> {
} }
} }
impl<'a> Spaceable<'a> for OldRecordBuilderField<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
OldRecordBuilderField::SpaceBefore(self, spaces)
}
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
OldRecordBuilderField::SpaceAfter(self, spaces)
}
}
impl<'a> Spaceable<'a> for Tag<'a> { impl<'a> Spaceable<'a> for Tag<'a> {
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self { fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
Tag::SpaceBefore(self, spaces) Tag::SpaceBefore(self, spaces)
@ -2524,7 +2456,6 @@ impl<'a> Malformed for Expr<'a> {
Record(items) => items.is_malformed(), Record(items) => items.is_malformed(),
Tuple(items) => items.is_malformed(), Tuple(items) => items.is_malformed(),
OldRecordBuilder(items) => items.is_malformed(),
RecordBuilder { mapper: map2, fields } => map2.is_malformed() || fields.is_malformed(), RecordBuilder { mapper: map2, fields } => map2.is_malformed() || fields.is_malformed(),
Closure(args, body) => args.iter().any(|arg| arg.is_malformed()) || body.is_malformed(), Closure(args, body) => args.iter().any(|arg| arg.is_malformed()) || body.is_malformed(),
@ -2537,7 +2468,7 @@ impl<'a> Malformed for Expr<'a> {
Apply(func, args, _) => func.is_malformed() || args.iter().any(|arg| arg.is_malformed()), Apply(func, args, _) => func.is_malformed() || args.iter().any(|arg| arg.is_malformed()),
BinOps(firsts, last) => firsts.iter().any(|(expr, _)| expr.is_malformed()) || last.is_malformed(), BinOps(firsts, last) => firsts.iter().any(|(expr, _)| expr.is_malformed()) || last.is_malformed(),
UnaryOp(expr, _) => expr.is_malformed(), UnaryOp(expr, _) => expr.is_malformed(),
If(chain, els) => chain.iter().any(|(cond, body)| cond.is_malformed() || body.is_malformed()) || els.is_malformed(), If { if_thens, final_else, ..} => if_thens.iter().any(|(cond, body)| cond.is_malformed() || body.is_malformed()) || final_else.is_malformed(),
When(cond, branches) => cond.is_malformed() || branches.iter().any(|branch| branch.is_malformed()), When(cond, branches) => cond.is_malformed() || branches.iter().any(|branch| branch.is_malformed()),
SpaceBefore(expr, _) | SpaceBefore(expr, _) |
@ -2548,8 +2479,6 @@ impl<'a> Malformed for Expr<'a> {
MalformedClosure | MalformedClosure |
MalformedSuffixed(..) | MalformedSuffixed(..) |
PrecedenceConflict(_) | PrecedenceConflict(_) |
MultipleOldRecordBuilders(_) |
UnappliedOldRecordBuilder(_) |
EmptyRecordBuilder(_) | EmptyRecordBuilder(_) |
SingleFieldRecordBuilder(_) | SingleFieldRecordBuilder(_) |
OptionalFieldInRecordBuilder(_, _) => true, OptionalFieldInRecordBuilder(_, _) => true,
@ -2629,19 +2558,6 @@ impl<'a, T: Malformed> Malformed for AssignedField<'a, T> {
} }
} }
impl<'a> Malformed for OldRecordBuilderField<'a> {
fn is_malformed(&self) -> bool {
match self {
OldRecordBuilderField::Value(_, _, expr)
| OldRecordBuilderField::ApplyValue(_, _, _, expr) => expr.is_malformed(),
OldRecordBuilderField::LabelOnly(_) => false,
OldRecordBuilderField::SpaceBefore(field, _)
| OldRecordBuilderField::SpaceAfter(field, _) => field.is_malformed(),
OldRecordBuilderField::Malformed(_) => true,
}
}
}
impl<'a> Malformed for Pattern<'a> { impl<'a> Malformed for Pattern<'a> {
fn is_malformed(&self) -> bool { fn is_malformed(&self) -> bool {
use Pattern::*; use Pattern::*;

View file

@ -2,8 +2,8 @@ use crate::ast::{
is_expr_suffixed, AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces, is_expr_suffixed, AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces,
Implements, ImplementsAbilities, ImportAlias, ImportAsKeyword, ImportExposingKeyword, Implements, ImplementsAbilities, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport, ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport,
ModuleImportParams, OldRecordBuilderField, Pattern, Spaceable, Spaced, Spaces, SpacesBefore, ModuleImportParams, Pattern, Spaceable, Spaced, Spaces, SpacesBefore, TryTarget,
TryTarget, TypeAnnotation, TypeDef, TypeHeader, ValueDef, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
}; };
use crate::blankspace::{ use crate::blankspace::{
loc_space0_e, require_newline_or_eof, space0_after_e, space0_around_ee, space0_before_e, loc_space0_e, require_newline_or_eof, space0_after_e, space0_around_ee, space0_before_e,
@ -925,15 +925,11 @@ fn import_params<'a>() -> impl Parser<'a, ModuleImportParams<'a>, EImportParams<
.fields .fields
.map_items_result(arena, |loc_field| { .map_items_result(arena, |loc_field| {
match loc_field.value.to_assigned_field(arena) { match loc_field.value.to_assigned_field(arena) {
Ok(AssignedField::IgnoredValue(_, _, _)) => Err(( AssignedField::IgnoredValue(_, _, _) => Err((
MadeProgress, MadeProgress,
EImportParams::RecordIgnoredFieldFound(loc_field.region), EImportParams::RecordIgnoredFieldFound(loc_field.region),
)), )),
Ok(field) => Ok(Loc::at(loc_field.region, field)), field => Ok(Loc::at(loc_field.region, field)),
Err(FoundApplyValue) => Err((
MadeProgress,
EImportParams::RecordApplyFound(loc_field.region),
)),
} }
})?; })?;
@ -2170,7 +2166,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
| Expr::Backpassing(_, _, _) | Expr::Backpassing(_, _, _)
| Expr::BinOps { .. } | Expr::BinOps { .. }
| Expr::Defs(_, _) | Expr::Defs(_, _)
| Expr::If(_, _) | Expr::If { .. }
| Expr::When(_, _) | Expr::When(_, _)
| Expr::Expect(_, _) | Expr::Expect(_, _)
| Expr::Dbg | Expr::Dbg
@ -2179,8 +2175,6 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
| Expr::MalformedClosure | Expr::MalformedClosure
| Expr::MalformedSuffixed(..) | Expr::MalformedSuffixed(..)
| Expr::PrecedenceConflict { .. } | Expr::PrecedenceConflict { .. }
| Expr::MultipleOldRecordBuilders { .. }
| Expr::UnappliedOldRecordBuilder { .. }
| Expr::EmptyRecordBuilder(_) | Expr::EmptyRecordBuilder(_)
| Expr::SingleFieldRecordBuilder(_) | Expr::SingleFieldRecordBuilder(_)
| Expr::OptionalFieldInRecordBuilder(_, _) | Expr::OptionalFieldInRecordBuilder(_, _)
@ -2189,7 +2183,6 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
| Expr::UnaryOp(_, _) | Expr::UnaryOp(_, _)
| Expr::TrySuffix { .. } | Expr::TrySuffix { .. }
| Expr::Crash | Expr::Crash
| Expr::OldRecordBuilder(..)
| Expr::RecordBuilder { .. } => return Err(()), | Expr::RecordBuilder { .. } => return Err(()),
Expr::Str(string) => Pattern::StrLiteral(string), Expr::Str(string) => Pattern::StrLiteral(string),
@ -2704,6 +2697,8 @@ fn if_expr_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EIf<
let (_, _, state) = let (_, _, state) =
parser::keyword(keyword::IF, EIf::If).parse(arena, state, min_indent)?; parser::keyword(keyword::IF, EIf::If).parse(arena, state, min_indent)?;
let if_indent = state.line_indent();
let mut branches = Vec::with_capacity_in(1, arena); let mut branches = Vec::with_capacity_in(1, arena);
let mut loop_state = state; let mut loop_state = state;
@ -2730,16 +2725,37 @@ fn if_expr_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EIf<
} }
}; };
let (_, else_branch, state) = parse_block( let else_indent = state_final_else.line_indent();
let indented_else = else_indent > if_indent;
let min_indent = if !indented_else {
else_indent + 1
} else {
if_indent
};
let (_, loc_first_space, state_final_else) =
loc_space0_e(EIf::IndentElseBranch).parse(arena, state_final_else, min_indent)?;
let allow_defs = !loc_first_space.value.is_empty();
// use parse_block_inner so we can set min_indent
let (_, else_branch, state) = parse_block_inner(
options, options,
arena, arena,
state_final_else, state_final_else,
true, min_indent,
EIf::IndentElseBranch, EIf::IndentElseBranch,
EIf::ElseBranch, EIf::ElseBranch,
loc_first_space,
allow_defs,
)?; )?;
let expr = Expr::If(branches.into_bump_slice(), arena.alloc(else_branch)); let expr = Expr::If {
if_thens: branches.into_bump_slice(),
final_else: arena.alloc(else_branch),
indented_else,
};
Ok((MadeProgress, expr, state)) Ok((MadeProgress, expr, state))
} }
@ -3367,38 +3383,12 @@ pub enum RecordField<'a> {
LabelOnly(Loc<&'a str>), LabelOnly(Loc<&'a str>),
SpaceBefore(&'a RecordField<'a>, &'a [CommentOrNewline<'a>]), SpaceBefore(&'a RecordField<'a>, &'a [CommentOrNewline<'a>]),
SpaceAfter(&'a RecordField<'a>, &'a [CommentOrNewline<'a>]), SpaceAfter(&'a RecordField<'a>, &'a [CommentOrNewline<'a>]),
ApplyValue(
Loc<&'a str>,
&'a [CommentOrNewline<'a>],
&'a [CommentOrNewline<'a>],
&'a Loc<Expr<'a>>,
),
} }
#[derive(Debug)] #[derive(Debug)]
pub struct FoundApplyValue; pub struct FoundApplyValue;
#[derive(Debug)]
pub enum NotOldBuilderFieldValue {
FoundOptionalValue,
FoundIgnoredValue,
}
impl<'a> RecordField<'a> { impl<'a> RecordField<'a> {
fn is_apply_value(&self) -> bool {
let mut current = self;
loop {
match current {
RecordField::ApplyValue(_, _, _, _) => break true,
RecordField::SpaceBefore(field, _) | RecordField::SpaceAfter(field, _) => {
current = *field;
}
_ => break false,
}
}
}
fn is_ignored_value(&self) -> bool { fn is_ignored_value(&self) -> bool {
let mut current = self; let mut current = self;
@ -3413,74 +3403,34 @@ impl<'a> RecordField<'a> {
} }
} }
pub fn to_assigned_field( pub fn to_assigned_field(self, arena: &'a Bump) -> AssignedField<'a, Expr<'a>> {
self,
arena: &'a Bump,
) -> Result<AssignedField<'a, Expr<'a>>, FoundApplyValue> {
use AssignedField::*; use AssignedField::*;
match self { match self {
RecordField::RequiredValue(loc_label, spaces, loc_expr) => { RecordField::RequiredValue(loc_label, spaces, loc_expr) => {
Ok(RequiredValue(loc_label, spaces, loc_expr)) RequiredValue(loc_label, spaces, loc_expr)
} }
RecordField::OptionalValue(loc_label, spaces, loc_expr) => { RecordField::OptionalValue(loc_label, spaces, loc_expr) => {
Ok(OptionalValue(loc_label, spaces, loc_expr)) OptionalValue(loc_label, spaces, loc_expr)
} }
RecordField::IgnoredValue(loc_label, spaces, loc_expr) => { RecordField::IgnoredValue(loc_label, spaces, loc_expr) => {
Ok(IgnoredValue(loc_label, spaces, loc_expr)) IgnoredValue(loc_label, spaces, loc_expr)
} }
RecordField::LabelOnly(loc_label) => Ok(LabelOnly(loc_label)), RecordField::LabelOnly(loc_label) => LabelOnly(loc_label),
RecordField::ApplyValue(_, _, _, _) => Err(FoundApplyValue),
RecordField::SpaceBefore(field, spaces) => { RecordField::SpaceBefore(field, spaces) => {
let assigned_field = field.to_assigned_field(arena)?; let assigned_field = field.to_assigned_field(arena);
Ok(SpaceBefore(arena.alloc(assigned_field), spaces)) SpaceBefore(arena.alloc(assigned_field), spaces)
} }
RecordField::SpaceAfter(field, spaces) => { RecordField::SpaceAfter(field, spaces) => {
let assigned_field = field.to_assigned_field(arena)?; let assigned_field = field.to_assigned_field(arena);
Ok(SpaceAfter(arena.alloc(assigned_field), spaces)) SpaceAfter(arena.alloc(assigned_field), spaces)
}
}
}
fn to_builder_field(
self,
arena: &'a Bump,
) -> Result<OldRecordBuilderField<'a>, NotOldBuilderFieldValue> {
use OldRecordBuilderField::*;
match self {
RecordField::RequiredValue(loc_label, spaces, loc_expr) => {
Ok(Value(loc_label, spaces, loc_expr))
}
RecordField::OptionalValue(_, _, _) => Err(NotOldBuilderFieldValue::FoundOptionalValue),
RecordField::IgnoredValue(_, _, _) => Err(NotOldBuilderFieldValue::FoundIgnoredValue),
RecordField::LabelOnly(loc_label) => Ok(LabelOnly(loc_label)),
RecordField::ApplyValue(loc_label, colon_spaces, arrow_spaces, loc_expr) => {
Ok(ApplyValue(loc_label, colon_spaces, arrow_spaces, loc_expr))
}
RecordField::SpaceBefore(field, spaces) => {
let builder_field = field.to_builder_field(arena)?;
Ok(SpaceBefore(arena.alloc(builder_field), spaces))
}
RecordField::SpaceAfter(field, spaces) => {
let builder_field = field.to_builder_field(arena)?;
Ok(SpaceAfter(arena.alloc(builder_field), spaces))
} }
} }
} }
@ -3534,14 +3484,10 @@ pub fn record_field<'a>() -> impl Parser<'a, RecordField<'a>, ERecord<'a>> {
match field_data { match field_data {
Either::First((loc_label, (spaces, opt_loc_val))) => { Either::First((loc_label, (spaces, opt_loc_val))) => {
match opt_loc_val { match opt_loc_val {
Some(Either::First((_, RecordFieldExpr::Value(loc_val)))) => { Some(Either::First((_, loc_val))) => {
RequiredValue(loc_label, spaces, arena.alloc(loc_val)) RequiredValue(loc_label, spaces, arena.alloc(loc_val))
} }
Some(Either::First((_, RecordFieldExpr::Apply(arrow_spaces, loc_val)))) => {
ApplyValue(loc_label, spaces, arrow_spaces, arena.alloc(loc_val))
}
Some(Either::Second((_, loc_val))) => { Some(Either::Second((_, loc_val))) => {
OptionalValue(loc_label, spaces, arena.alloc(loc_val)) OptionalValue(loc_label, spaces, arena.alloc(loc_val))
} }
@ -3568,34 +3514,17 @@ pub fn record_field<'a>() -> impl Parser<'a, RecordField<'a>, ERecord<'a>> {
) )
} }
enum RecordFieldExpr<'a> { fn record_field_expr<'a>() -> impl Parser<'a, Loc<Expr<'a>>, ERecord<'a>> {
Apply(&'a [CommentOrNewline<'a>], Loc<Expr<'a>>),
Value(Loc<Expr<'a>>),
}
fn record_field_expr<'a>() -> impl Parser<'a, RecordFieldExpr<'a>, ERecord<'a>> {
map_with_arena( map_with_arena(
and( and(spaces(), specialize_err_ref(ERecord::Expr, loc_expr(false))),
spaces(), |arena: &'a bumpalo::Bump, (spaces, loc_expr)| {
either( if spaces.is_empty() {
and( loc_expr
two_bytes(b'<', b'-', ERecord::Arrow), } else {
spaces_before(specialize_err_ref(ERecord::Expr, loc_expr(false))), arena
), .alloc(loc_expr.value)
specialize_err_ref(ERecord::Expr, loc_expr(false)), .with_spaces_before(spaces, loc_expr.region)
), }
),
|arena: &'a bumpalo::Bump, (spaces, either)| match either {
Either::First((_, loc_expr)) => RecordFieldExpr::Apply(spaces, loc_expr),
Either::Second(loc_expr) => RecordFieldExpr::Value({
if spaces.is_empty() {
loc_expr
} else {
arena
.alloc(loc_expr.value)
.with_spaces_before(spaces, loc_expr.region)
}
}),
}, },
) )
} }
@ -3663,13 +3592,11 @@ fn record_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
record_update_help(arena, update, record.fields) record_update_help(arena, update, record.fields)
} }
Some((mapper, RecordHelpPrefix::Mapper)) => { Some((mapper, RecordHelpPrefix::Mapper)) => {
new_record_builder_help(arena, mapper, record.fields) record_builder_help(arena, mapper, record.fields)
} }
None => { None => {
let special_field_found = record.fields.iter().find_map(|field| { let special_field_found = record.fields.iter().find_map(|field| {
if field.value.is_apply_value() { if field.value.is_ignored_value() {
Some(old_record_builder_help(arena, record.fields))
} else if field.value.is_ignored_value() {
Some(Err(EExpr::RecordUpdateIgnoredField(field.region))) Some(Err(EExpr::RecordUpdateIgnoredField(field.region)))
} else { } else {
None None
@ -3678,7 +3605,7 @@ fn record_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
special_field_found.unwrap_or_else(|| { special_field_found.unwrap_or_else(|| {
let fields = record.fields.map_items(arena, |loc_field| { let fields = record.fields.map_items(arena, |loc_field| {
loc_field.map(|field| field.to_assigned_field(arena).unwrap()) loc_field.map(|field| field.to_assigned_field(arena))
}); });
Ok(Expr::Record(fields)) Ok(Expr::Record(fields))
@ -3705,14 +3632,13 @@ fn record_update_help<'a>(
) -> Result<Expr<'a>, EExpr<'a>> { ) -> Result<Expr<'a>, EExpr<'a>> {
let result = fields.map_items_result(arena, |loc_field| { let result = fields.map_items_result(arena, |loc_field| {
match loc_field.value.to_assigned_field(arena) { match loc_field.value.to_assigned_field(arena) {
Ok(AssignedField::IgnoredValue(_, _, _)) => { AssignedField::IgnoredValue(_, _, _) => {
Err(EExpr::RecordUpdateIgnoredField(loc_field.region)) Err(EExpr::RecordUpdateIgnoredField(loc_field.region))
} }
Ok(builder_field) => Ok(Loc { builder_field => Ok(Loc {
region: loc_field.region, region: loc_field.region,
value: builder_field, value: builder_field,
}), }),
Err(FoundApplyValue) => Err(EExpr::RecordUpdateOldBuilderField(loc_field.region)),
} }
}); });
@ -3722,19 +3648,18 @@ fn record_update_help<'a>(
}) })
} }
fn new_record_builder_help<'a>( fn record_builder_help<'a>(
arena: &'a Bump, arena: &'a Bump,
mapper: Loc<Expr<'a>>, mapper: Loc<Expr<'a>>,
fields: Collection<'a, Loc<RecordField<'a>>>, fields: Collection<'a, Loc<RecordField<'a>>>,
) -> Result<Expr<'a>, EExpr<'a>> { ) -> Result<Expr<'a>, EExpr<'a>> {
let result = fields.map_items_result(arena, |loc_field| { let result = fields.map_items_result(arena, |loc_field| {
match loc_field.value.to_assigned_field(arena) { let builder_field = loc_field.value.to_assigned_field(arena);
Ok(builder_field) => Ok(Loc {
region: loc_field.region, Ok(Loc {
value: builder_field, region: loc_field.region,
}), value: builder_field,
Err(FoundApplyValue) => Err(EExpr::RecordBuilderOldBuilderField(loc_field.region)), })
}
}); });
result.map(|fields| Expr::RecordBuilder { result.map(|fields| Expr::RecordBuilder {
@ -3743,28 +3668,6 @@ fn new_record_builder_help<'a>(
}) })
} }
fn old_record_builder_help<'a>(
arena: &'a Bump,
fields: Collection<'a, Loc<RecordField<'a>>>,
) -> Result<Expr<'a>, EExpr<'a>> {
let result = fields.map_items_result(arena, |loc_field| {
match loc_field.value.to_builder_field(arena) {
Ok(builder_field) => Ok(Loc {
region: loc_field.region,
value: builder_field,
}),
Err(NotOldBuilderFieldValue::FoundOptionalValue) => {
Err(EExpr::OptionalValueInOldRecordBuilder(loc_field.region))
}
Err(NotOldBuilderFieldValue::FoundIgnoredValue) => {
Err(EExpr::IgnoredValueInOldRecordBuilder(loc_field.region))
}
}
});
result.map(Expr::OldRecordBuilder)
}
fn apply_expr_access_chain<'a>( fn apply_expr_access_chain<'a>(
arena: &'a Bump, arena: &'a Bump,
value: Expr<'a>, value: Expr<'a>,

View file

@ -1258,13 +1258,13 @@ pub struct PlatformHeader<'a> {
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub enum ImportsEntry<'a> { pub enum ImportsEntry<'a> {
/// e.g. `Task` or `Task.{ Task, after }` /// e.g. `Hello` or `Hello exposing [hello]` see roc-lang.org/examples/MultipleRocFiles/README.html
Module( Module(
ModuleName<'a>, ModuleName<'a>,
Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
), ),
/// e.g. `pf.Task` or `pf.Task.{ after }` or `pf.{ Task.{ Task, after } }` /// e.g. `pf.Stdout` or `pf.Stdout exposing [line]`
Package( Package(
&'a str, &'a str,
ModuleName<'a>, ModuleName<'a>,

View file

@ -1,905 +0,0 @@
use crate::ast::{Collection, CommentOrNewline, Defs, Header, Module, Spaced, Spaces};
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::expr::merge_spaces;
use crate::header::{
package_entry, package_name, AppHeader, ExposedName, ExposesKeyword, HostedHeader,
ImportsCollection, ImportsEntry, ImportsKeyword, ImportsKeywordItem, Keyword, KeywordItem,
ModuleHeader, ModuleName, ModuleParams, PackageEntry, PackageHeader, PackagesKeyword,
PlatformHeader, PlatformRequires, ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword,
TypedIdent,
};
use crate::ident::{self, lowercase_ident, unqualified_ident, UppercaseIdent};
use crate::parser::Progress::{self, *};
use crate::parser::{
and, backtrackable, byte, collection_trailing_sep_e, increment_min_indent, loc, map,
map_with_arena, optional, reset_min_indent, skip_first, skip_second, specialize_err, succeed,
two_bytes, zero_or_more, EExposes, EHeader, EImports, EPackages, EParams, EProvides, ERequires,
ETypedIdent, Parser, SourceError, SpaceProblem, SyntaxError,
};
use crate::pattern::record_pattern_fields;
use crate::state::State;
use crate::string_literal::{self, parse_str_literal};
use crate::type_annotation;
use roc_region::all::{Loc, Position, Region};
fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> {
|_arena, state: State<'a>, _min_indent: u32| {
if state.has_reached_end() {
Ok((NoProgress, (), state))
} else {
Err((NoProgress, SyntaxError::NotEndOfFile(state.pos())))
}
}
}
pub fn parse_module_defs<'a>(
arena: &'a bumpalo::Bump,
state: State<'a>,
defs: Defs<'a>,
) -> Result<Defs<'a>, SyntaxError<'a>> {
let min_indent = 0;
match crate::expr::parse_top_level_defs(arena, state.clone(), defs) {
Ok((_, defs, state)) => match end_of_file().parse(arena, state, min_indent) {
Ok(_) => Ok(defs),
Err((_, fail)) => Err(fail),
},
Err((_, fail)) => Err(SyntaxError::Expr(fail, state.pos())),
}
}
pub fn parse_header<'a>(
arena: &'a bumpalo::Bump,
state: State<'a>,
) -> Result<(Module<'a>, State<'a>), SourceError<'a, EHeader<'a>>> {
let min_indent = 0;
match header().parse(arena, state.clone(), min_indent) {
Ok((_, module, state)) => Ok((module, state)),
Err((_, fail)) => Err(SourceError::new(fail, &state)),
}
}
pub fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
use crate::parser::keyword;
record!(Module {
comments: space0_e(EHeader::IndentStart),
header: one_of![
map(
skip_first(
keyword("module", EHeader::Start),
increment_min_indent(module_header())
),
Header::Module
),
map(
skip_first(
keyword("interface", EHeader::Start),
increment_min_indent(interface_header())
),
Header::Module
),
map(
skip_first(
keyword("app", EHeader::Start),
increment_min_indent(one_of![app_header(), old_app_header()])
),
Header::App
),
map(
skip_first(
keyword("package", EHeader::Start),
increment_min_indent(one_of![package_header(), old_package_header()])
),
Header::Package
),
map(
skip_first(
keyword("platform", EHeader::Start),
increment_min_indent(platform_header())
),
Header::Platform
),
map(
skip_first(
keyword("hosted", EHeader::Start),
increment_min_indent(hosted_header())
),
Header::Hosted
),
]
})
}
#[inline(always)]
fn module_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> {
record!(ModuleHeader {
after_keyword: space0_e(EHeader::IndentStart),
params: optional(specialize_err(EHeader::Params, module_params())),
exposes: specialize_err(EHeader::Exposes, exposes_list()),
interface_imports: succeed(None)
})
.trace("module_header")
}
fn module_params<'a>() -> impl Parser<'a, ModuleParams<'a>, EParams<'a>> {
record!(ModuleParams {
params: specialize_err(EParams::Pattern, record_pattern_fields()),
before_arrow: skip_second(
space0_e(EParams::BeforeArrow),
loc(two_bytes(b'-', b'>', EParams::Arrow))
),
after_arrow: space0_e(EParams::AfterArrow),
})
}
// TODO does this need to be a macro?
macro_rules! merge_n_spaces {
($arena:expr, $($slice:expr),*) => {
{
let mut merged = bumpalo::collections::Vec::with_capacity_in(0 $(+ $slice.len())*, $arena);
$(merged.extend_from_slice($slice);)*
merged.into_bump_slice()
}
};
}
/// Parse old interface headers so we can format them into module headers
#[inline(always)]
fn interface_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> {
let after_keyword = map_with_arena(
and(
skip_second(
space0_e(EHeader::IndentStart),
loc(module_name_help(EHeader::ModuleName)),
),
specialize_err(EHeader::Exposes, exposes_kw()),
),
|arena: &'a bumpalo::Bump,
(before_name, kw): (&'a [CommentOrNewline<'a>], Spaces<'a, ExposesKeyword>)| {
merge_n_spaces!(arena, before_name, kw.before, kw.after)
},
);
record!(ModuleHeader {
after_keyword: after_keyword,
params: succeed(None),
exposes: specialize_err(EHeader::Exposes, exposes_list()).trace("exposes_list"),
interface_imports: map(
specialize_err(EHeader::Imports, imports()),
imports_none_if_empty
)
.trace("imports"),
})
.trace("interface_header")
}
fn imports_none_if_empty(value: ImportsKeywordItem<'_>) -> Option<ImportsKeywordItem<'_>> {
if value.item.is_empty() {
None
} else {
Some(value)
}
}
#[inline(always)]
fn hosted_header<'a>() -> impl Parser<'a, HostedHeader<'a>, EHeader<'a>> {
record!(HostedHeader {
before_name: space0_e(EHeader::IndentStart),
name: loc(module_name_help(EHeader::ModuleName)),
exposes: specialize_err(EHeader::Exposes, exposes_values_kw()),
imports: specialize_err(EHeader::Imports, imports()),
})
.trace("hosted_header")
}
fn chomp_module_name(buffer: &[u8]) -> Result<&str, Progress> {
use encode_unicode::CharExt;
let mut chomped = 0;
if let Ok((first_letter, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if first_letter.is_uppercase() {
chomped += width;
} else {
return Err(Progress::NoProgress);
}
}
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
// After the first character, only these are allowed:
//
// * Unicode alphabetic chars - you might include `鹏` if that's clear to your readers
// * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric()
// * A '.' separating module parts
if ch.is_alphabetic() || ch.is_ascii_digit() {
chomped += width;
} else if ch == '.' {
chomped += width;
if let Ok((first_letter, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if first_letter.is_uppercase() {
chomped += width;
} else if first_letter == '{' {
// the .{ starting a `Foo.{ bar, baz }` importing clauses
chomped -= width;
break;
} else {
return Err(Progress::MadeProgress);
}
}
} else {
// we're done
break;
}
}
let name = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
Ok(name)
}
#[inline(always)]
fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>, ()> {
|_, mut state: State<'a>, _min_indent: u32| match chomp_module_name(state.bytes()) {
Ok(name) => {
let width = name.len();
state = state.advance(width);
Ok((MadeProgress, ModuleName::new(name), state))
}
Err(progress) => Err((progress, ())),
}
}
#[inline(always)]
fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
record!(AppHeader {
before_provides: space0_e(EHeader::IndentStart),
provides: specialize_err(EHeader::Exposes, exposes_list()),
before_packages: space0_e(EHeader::IndentStart),
packages: specialize_err(EHeader::Packages, loc(packages_collection())),
old_imports: succeed(None),
old_provides_to_new_package: succeed(None),
})
.trace("app_header")
}
struct OldAppHeader<'a> {
pub before_name: &'a [CommentOrNewline<'a>],
pub packages: Option<Loc<OldAppPackages<'a>>>,
pub imports: Option<KeywordItem<'a, ImportsKeyword, ImportsCollection<'a>>>,
pub provides: ProvidesTo<'a>,
}
type OldAppPackages<'a> =
KeywordItem<'a, PackagesKeyword, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>;
#[inline(always)]
fn old_app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
let old = record!(OldAppHeader {
before_name: skip_second(
space0_e(EHeader::IndentStart),
loc(crate::parser::specialize_err(
EHeader::AppName,
string_literal::parse_str_literal()
))
),
packages: optional(specialize_err(EHeader::Packages, loc(packages()))),
imports: optional(specialize_err(EHeader::Imports, imports())),
provides: specialize_err(EHeader::Provides, provides_to()),
});
map_with_arena(old, |arena: &'a bumpalo::Bump, old: OldAppHeader<'a>| {
let mut before_packages: &'a [CommentOrNewline] = &[];
let packages = match old.packages {
Some(packages) => {
before_packages = merge_spaces(
arena,
packages.value.keyword.before,
packages.value.keyword.after,
);
if let To::ExistingPackage(platform_shorthand) = old.provides.to.value {
packages.map(|coll| {
coll.item.map_items(arena, |loc_spaced_pkg| {
if loc_spaced_pkg.value.item().shorthand == platform_shorthand {
loc_spaced_pkg.map(|spaced_pkg| {
spaced_pkg.map(arena, |pkg| {
let mut new_pkg = *pkg;
new_pkg.platform_marker = Some(merge_spaces(
arena,
old.provides.to_keyword.before,
old.provides.to_keyword.after,
));
new_pkg
})
})
} else {
*loc_spaced_pkg
}
})
})
} else {
packages.map(|kw| kw.item)
}
}
None => Loc {
region: Region::zero(),
value: Collection::empty(),
},
};
let provides = match old.provides.types {
Some(types) => {
let mut combined_items = bumpalo::collections::Vec::with_capacity_in(
old.provides.entries.items.len() + types.items.len(),
arena,
);
combined_items.extend_from_slice(old.provides.entries.items);
for loc_spaced_type_ident in types.items {
combined_items.push(loc_spaced_type_ident.map(|spaced_type_ident| {
spaced_type_ident.map(arena, |type_ident| {
ExposedName::new(From::from(*type_ident))
})
}));
}
let value_comments = old.provides.entries.final_comments();
let type_comments = types.final_comments();
let mut combined_comments = bumpalo::collections::Vec::with_capacity_in(
value_comments.len() + type_comments.len(),
arena,
);
combined_comments.extend_from_slice(value_comments);
combined_comments.extend_from_slice(type_comments);
Collection::with_items_and_comments(
arena,
combined_items.into_bump_slice(),
combined_comments.into_bump_slice(),
)
}
None => old.provides.entries,
};
AppHeader {
before_provides: merge_spaces(
arena,
old.before_name,
old.provides.provides_keyword.before,
),
provides,
before_packages: merge_spaces(
arena,
before_packages,
old.provides.provides_keyword.after,
),
packages,
old_imports: old.imports.and_then(imports_none_if_empty),
old_provides_to_new_package: match old.provides.to.value {
To::NewPackage(new_pkg) => Some(new_pkg),
To::ExistingPackage(_) => None,
},
}
})
}
#[inline(always)]
fn package_header<'a>() -> impl Parser<'a, PackageHeader<'a>, EHeader<'a>> {
record!(PackageHeader {
before_exposes: space0_e(EHeader::IndentStart),
exposes: specialize_err(EHeader::Exposes, exposes_module_collection()),
before_packages: space0_e(EHeader::IndentStart),
packages: specialize_err(EHeader::Packages, loc(packages_collection())),
})
.trace("package_header")
}
#[derive(Debug, Clone, PartialEq)]
struct OldPackageHeader<'a> {
before_name: &'a [CommentOrNewline<'a>],
exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>>,
packages:
Loc<KeywordItem<'a, PackagesKeyword, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>>,
}
#[inline(always)]
fn old_package_header<'a>() -> impl Parser<'a, PackageHeader<'a>, EHeader<'a>> {
map_with_arena(
record!(OldPackageHeader {
before_name: skip_second(
space0_e(EHeader::IndentStart),
specialize_err(EHeader::PackageName, package_name())
),
exposes: specialize_err(EHeader::Exposes, exposes_modules()),
packages: specialize_err(EHeader::Packages, loc(packages())),
}),
|arena: &'a bumpalo::Bump, old: OldPackageHeader<'a>| {
let before_exposes = merge_n_spaces!(
arena,
old.before_name,
old.exposes.keyword.before,
old.exposes.keyword.after
);
let before_packages = merge_spaces(
arena,
old.packages.value.keyword.before,
old.packages.value.keyword.after,
);
PackageHeader {
before_exposes,
exposes: old.exposes.item,
before_packages,
packages: old.packages.map(|kw| kw.item),
}
},
)
.trace("old_package_header")
}
#[inline(always)]
fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> {
record!(PlatformHeader {
before_name: space0_e(EHeader::IndentStart),
name: loc(specialize_err(EHeader::PlatformName, package_name())),
requires: specialize_err(EHeader::Requires, requires()),
exposes: specialize_err(EHeader::Exposes, exposes_modules()),
packages: specialize_err(EHeader::Packages, packages()),
imports: specialize_err(EHeader::Imports, imports()),
provides: specialize_err(EHeader::Provides, provides_exposed()),
})
.trace("platform_header")
}
fn provides_to_package<'a>() -> impl Parser<'a, To<'a>, EProvides<'a>> {
one_of![
specialize_err(
|_, pos| EProvides::Identifier(pos),
map(lowercase_ident(), To::ExistingPackage)
),
specialize_err(EProvides::Package, map(package_name(), To::NewPackage))
]
}
#[inline(always)]
fn provides_to<'a>() -> impl Parser<'a, ProvidesTo<'a>, EProvides<'a>> {
record!(ProvidesTo {
provides_keyword: spaces_around_keyword(
ProvidesKeyword,
EProvides::Provides,
EProvides::IndentProvides,
EProvides::IndentListStart
),
entries: collection_trailing_sep_e(
byte(b'[', EProvides::ListStart),
exposes_entry(EProvides::Identifier),
byte(b',', EProvides::ListEnd),
byte(b']', EProvides::ListEnd),
Spaced::SpaceBefore
),
types: optional(backtrackable(provides_types())),
to_keyword: spaces_around_keyword(
ToKeyword,
EProvides::To,
EProvides::IndentTo,
EProvides::IndentListStart
),
to: loc(provides_to_package()),
})
.trace("provides_to")
}
fn provides_exposed<'a>() -> impl Parser<
'a,
KeywordItem<'a, ProvidesKeyword, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>,
EProvides<'a>,
> {
record!(KeywordItem {
keyword: spaces_around_keyword(
ProvidesKeyword,
EProvides::Provides,
EProvides::IndentProvides,
EProvides::IndentListStart
),
item: collection_trailing_sep_e(
byte(b'[', EProvides::ListStart),
exposes_entry(EProvides::Identifier),
byte(b',', EProvides::ListEnd),
byte(b']', EProvides::ListEnd),
Spaced::SpaceBefore
),
})
}
#[inline(always)]
fn provides_types<'a>(
) -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, UppercaseIdent<'a>>>>, EProvides<'a>> {
skip_first(
// We only support spaces here, not newlines, because this is not intended
// to be the design forever. Someday it will hopefully work like Elm,
// where platform authors can provide functions like Browser.sandbox which
// present an API based on ordinary-looking type variables.
zero_or_more(byte(
b' ',
// HACK: If this errors, EProvides::Provides is not an accurate reflection
// of what went wrong. However, this is both skipped and zero_or_more,
// so this error should never be visible to anyone in practice!
EProvides::Provides,
)),
collection_trailing_sep_e(
byte(b'{', EProvides::ListStart),
provides_type_entry(EProvides::Identifier),
byte(b',', EProvides::ListEnd),
byte(b'}', EProvides::ListEnd),
Spaced::SpaceBefore,
),
)
}
fn provides_type_entry<'a, F, E>(
to_expectation: F,
) -> impl Parser<'a, Loc<Spaced<'a, UppercaseIdent<'a>>>, E>
where
F: Fn(Position) -> E,
F: Copy,
E: 'a,
{
loc(map(
specialize_err(move |_, pos| to_expectation(pos), ident::uppercase()),
Spaced::Item,
))
}
fn exposes_entry<'a, F, E>(
to_expectation: F,
) -> impl Parser<'a, Loc<Spaced<'a, ExposedName<'a>>>, E>
where
F: Fn(Position) -> E,
F: Copy,
E: 'a,
{
loc(map(
specialize_err(move |_, pos| to_expectation(pos), unqualified_ident()),
|n| Spaced::Item(ExposedName::new(n)),
))
}
#[inline(always)]
fn requires<'a>(
) -> impl Parser<'a, KeywordItem<'a, RequiresKeyword, PlatformRequires<'a>>, ERequires<'a>> {
record!(KeywordItem {
keyword: spaces_around_keyword(
RequiresKeyword,
ERequires::Requires,
ERequires::IndentRequires,
ERequires::IndentListStart
),
item: platform_requires(),
})
}
#[inline(always)]
fn platform_requires<'a>() -> impl Parser<'a, PlatformRequires<'a>, ERequires<'a>> {
record!(PlatformRequires {
rigids: skip_second(requires_rigids(), space0_e(ERequires::ListStart)),
signature: requires_typed_ident()
})
}
#[inline(always)]
fn requires_rigids<'a>(
) -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, UppercaseIdent<'a>>>>, ERequires<'a>> {
collection_trailing_sep_e(
byte(b'{', ERequires::ListStart),
specialize_err(
|_, pos| ERequires::Rigid(pos),
loc(map(ident::uppercase(), Spaced::Item)),
),
byte(b',', ERequires::ListEnd),
byte(b'}', ERequires::ListEnd),
Spaced::SpaceBefore,
)
}
#[inline(always)]
fn requires_typed_ident<'a>() -> impl Parser<'a, Loc<Spaced<'a, TypedIdent<'a>>>, ERequires<'a>> {
skip_first(
byte(b'{', ERequires::ListStart),
skip_second(
reset_min_indent(space0_around_ee(
specialize_err(ERequires::TypedIdent, loc(typed_ident())),
ERequires::ListStart,
ERequires::ListEnd,
)),
byte(b'}', ERequires::ListStart),
),
)
}
#[inline(always)]
fn exposes_values_kw<'a>() -> impl Parser<
'a,
KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>,
EExposes,
> {
record!(KeywordItem {
keyword: exposes_kw(),
item: exposes_list()
})
}
#[inline(always)]
fn exposes_kw<'a>() -> impl Parser<'a, Spaces<'a, ExposesKeyword>, EExposes> {
spaces_around_keyword(
ExposesKeyword,
EExposes::Exposes,
EExposes::IndentExposes,
EExposes::IndentListStart,
)
}
#[inline(always)]
fn exposes_list<'a>() -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>, EExposes>
{
collection_trailing_sep_e(
byte(b'[', EExposes::ListStart),
exposes_entry(EExposes::Identifier),
byte(b',', EExposes::ListEnd),
byte(b']', EExposes::ListEnd),
Spaced::SpaceBefore,
)
}
pub fn spaces_around_keyword<'a, K: Keyword, E>(
keyword_item: K,
expectation: fn(Position) -> E,
indent_problem1: fn(Position) -> E,
indent_problem2: fn(Position) -> E,
) -> impl Parser<'a, Spaces<'a, K>, E>
where
E: 'a + SpaceProblem,
{
map(
and(
skip_second(
// parse any leading space before the keyword
backtrackable(space0_e(indent_problem1)),
// parse the keyword
crate::parser::keyword(K::KEYWORD, expectation),
),
// parse the trailing space
space0_e(indent_problem2),
),
move |(before, after)| Spaces {
before,
item: keyword_item,
after,
},
)
}
#[inline(always)]
fn exposes_modules<'a>() -> impl Parser<
'a,
KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>>,
EExposes,
> {
record!(KeywordItem {
keyword: spaces_around_keyword(
ExposesKeyword,
EExposes::Exposes,
EExposes::IndentExposes,
EExposes::IndentListStart
),
item: exposes_module_collection(),
})
}
fn exposes_module_collection<'a>(
) -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>, EExposes> {
collection_trailing_sep_e(
byte(b'[', EExposes::ListStart),
exposes_module(EExposes::Identifier),
byte(b',', EExposes::ListEnd),
byte(b']', EExposes::ListEnd),
Spaced::SpaceBefore,
)
}
fn exposes_module<'a, F, E>(
to_expectation: F,
) -> impl Parser<'a, Loc<Spaced<'a, ModuleName<'a>>>, E>
where
F: Fn(Position) -> E,
F: Copy,
E: 'a,
{
loc(map(
specialize_err(move |_, pos| to_expectation(pos), module_name()),
Spaced::Item,
))
}
#[inline(always)]
fn packages<'a>() -> impl Parser<
'a,
KeywordItem<'a, PackagesKeyword, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>,
EPackages<'a>,
> {
record!(KeywordItem {
keyword: packages_kw(),
item: packages_collection()
})
}
#[inline(always)]
fn packages_kw<'a>() -> impl Parser<'a, Spaces<'a, PackagesKeyword>, EPackages<'a>> {
spaces_around_keyword(
PackagesKeyword,
EPackages::Packages,
EPackages::IndentPackages,
EPackages::IndentListStart,
)
}
#[inline(always)]
fn packages_collection<'a>(
) -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>, EPackages<'a>> {
collection_trailing_sep_e(
byte(b'{', EPackages::ListStart),
specialize_err(EPackages::PackageEntry, loc(package_entry())),
byte(b',', EPackages::ListEnd),
byte(b'}', EPackages::ListEnd),
Spaced::SpaceBefore,
)
}
fn imports<'a>() -> impl Parser<
'a,
KeywordItem<'a, ImportsKeyword, Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>>,
EImports,
> {
record!(KeywordItem {
keyword: spaces_around_keyword(
ImportsKeyword,
EImports::Imports,
EImports::IndentImports,
EImports::IndentListStart
),
item: collection_trailing_sep_e(
byte(b'[', EImports::ListStart),
loc(imports_entry()),
byte(b',', EImports::ListEnd),
byte(b']', EImports::ListEnd),
Spaced::SpaceBefore
)
})
.trace("imports")
}
#[inline(always)]
pub fn typed_ident<'a>() -> impl Parser<'a, Spaced<'a, TypedIdent<'a>>, ETypedIdent<'a>> {
// e.g.
//
// printLine : Str -> Task {} *
map(
and(
and(
loc(specialize_err(
|_, pos| ETypedIdent::Identifier(pos),
lowercase_ident(),
)),
space0_e(ETypedIdent::IndentHasType),
),
skip_first(
byte(b':', ETypedIdent::HasType),
space0_before_e(
specialize_err(
ETypedIdent::Type,
reset_min_indent(type_annotation::located(true)),
),
ETypedIdent::IndentType,
),
),
),
|((ident, spaces_before_colon), ann)| {
Spaced::Item(TypedIdent {
ident,
spaces_before_colon,
ann,
})
},
)
}
fn shortname<'a>() -> impl Parser<'a, &'a str, EImports> {
specialize_err(|_, pos| EImports::Shorthand(pos), lowercase_ident())
}
pub fn module_name_help<'a, F, E>(to_expectation: F) -> impl Parser<'a, ModuleName<'a>, E>
where
F: Fn(Position) -> E,
E: 'a,
F: 'a,
{
specialize_err(move |_, pos| to_expectation(pos), module_name())
}
#[inline(always)]
fn imports_entry<'a>() -> impl Parser<'a, Spaced<'a, ImportsEntry<'a>>, EImports> {
type Temp<'a> = (
(Option<&'a str>, ModuleName<'a>),
Option<Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>,
);
let spaced_import = |((opt_shortname, module_name), opt_values): Temp<'a>| {
let exposed_values = opt_values.unwrap_or_else(Collection::empty);
let entry = match opt_shortname {
Some(shortname) => ImportsEntry::Package(shortname, module_name, exposed_values),
None => ImportsEntry::Module(module_name, exposed_values),
};
Spaced::Item(entry)
};
one_of!(
map(
and(
and(
// e.g. `pf.`
optional(backtrackable(skip_second(
shortname(),
byte(b'.', EImports::ShorthandDot)
))),
// e.g. `Task`
module_name_help(EImports::ModuleName)
),
// e.g. `.{ Task, after}`
optional(skip_first(
byte(b'.', EImports::ExposingDot),
collection_trailing_sep_e(
byte(b'{', EImports::SetStart),
exposes_entry(EImports::Identifier),
byte(b',', EImports::SetEnd),
byte(b'}', EImports::SetEnd),
Spaced::SpaceBefore
)
))
),
spaced_import
)
.trace("normal_import"),
map(
and(
and(
// e.g. "filename"
// TODO: str literal allows for multiline strings. We probably don't want that for file names.
specialize_err(|_, pos| EImports::StrLiteral(pos), parse_str_literal()),
// e.g. as
and(
and(
space0_e(EImports::AsKeyword),
two_bytes(b'a', b's', EImports::AsKeyword)
),
space0_e(EImports::AsKeyword)
)
),
// e.g. file : Str
specialize_err(|_, pos| EImports::TypedIdent(pos), typed_ident())
),
|((file_name, _), typed_ident)| {
// TODO: look at blacking block strings during parsing.
Spaced::Item(ImportsEntry::IngestedFile(file_name, typed_ident))
}
)
.trace("ingest_file_import")
)
.trace("imports_entry")
}

View file

@ -8,9 +8,9 @@ use crate::{
AbilityImpls, AbilityMember, AssignedField, Collection, Defs, Expr, FullAst, Header, AbilityImpls, AbilityMember, AssignedField, Collection, Defs, Expr, FullAst, Header,
Implements, ImplementsAbilities, ImplementsAbility, ImplementsClause, ImportAlias, Implements, ImplementsAbilities, ImplementsAbility, ImplementsClause, ImportAlias,
ImportAsKeyword, ImportExposingKeyword, ImportedModuleName, IngestedFileAnnotation, ImportAsKeyword, ImportExposingKeyword, ImportedModuleName, IngestedFileAnnotation,
IngestedFileImport, ModuleImport, ModuleImportParams, OldRecordBuilderField, Pattern, IngestedFileImport, ModuleImport, ModuleImportParams, Pattern, PatternAs, Spaced, Spaces,
PatternAs, Spaced, Spaces, SpacesBefore, StrLiteral, StrSegment, Tag, TypeAnnotation, SpacesBefore, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
TypeDef, TypeHeader, ValueDef, WhenBranch, WhenBranch,
}, },
header::{ header::{
AppHeader, ExposedName, ExposesKeyword, HostedHeader, ImportsEntry, ImportsKeyword, AppHeader, ExposedName, ExposesKeyword, HostedHeader, ImportsEntry, ImportsKeyword,
@ -562,30 +562,6 @@ impl<'a, T: Normalize<'a> + Copy + std::fmt::Debug> Normalize<'a> for AssignedFi
} }
} }
impl<'a> Normalize<'a> for OldRecordBuilderField<'a> {
fn normalize(&self, arena: &'a Bump) -> Self {
match *self {
OldRecordBuilderField::Value(a, _, c) => OldRecordBuilderField::Value(
a.normalize(arena),
&[],
arena.alloc(c.normalize(arena)),
),
OldRecordBuilderField::ApplyValue(a, _, _, c) => OldRecordBuilderField::ApplyValue(
a.normalize(arena),
&[],
&[],
arena.alloc(c.normalize(arena)),
),
OldRecordBuilderField::LabelOnly(a) => {
OldRecordBuilderField::LabelOnly(a.normalize(arena))
}
OldRecordBuilderField::Malformed(a) => OldRecordBuilderField::Malformed(a),
OldRecordBuilderField::SpaceBefore(a, _) => a.normalize(arena),
OldRecordBuilderField::SpaceAfter(a, _) => a.normalize(arena),
}
}
}
impl<'a> Normalize<'a> for StrLiteral<'a> { impl<'a> Normalize<'a> for StrLiteral<'a> {
fn normalize(&self, arena: &'a Bump) -> Self { fn normalize(&self, arena: &'a Bump) -> Self {
match *self { match *self {
@ -738,7 +714,6 @@ impl<'a> Normalize<'a> for Expr<'a> {
fields: fields.normalize(arena), fields: fields.normalize(arena),
}, },
Expr::Record(a) => Expr::Record(a.normalize(arena)), Expr::Record(a) => Expr::Record(a.normalize(arena)),
Expr::OldRecordBuilder(a) => Expr::OldRecordBuilder(a.normalize(arena)),
Expr::RecordBuilder { mapper, fields } => Expr::RecordBuilder { Expr::RecordBuilder { mapper, fields } => Expr::RecordBuilder {
mapper: arena.alloc(mapper.normalize(arena)), mapper: arena.alloc(mapper.normalize(arena)),
fields: fields.normalize(arena), fields: fields.normalize(arena),
@ -796,7 +771,15 @@ impl<'a> Normalize<'a> for Expr<'a> {
Expr::UnaryOp(a, b) => { Expr::UnaryOp(a, b) => {
Expr::UnaryOp(arena.alloc(a.normalize(arena)), b.normalize(arena)) Expr::UnaryOp(arena.alloc(a.normalize(arena)), b.normalize(arena))
} }
Expr::If(a, b) => Expr::If(a.normalize(arena), arena.alloc(b.normalize(arena))), Expr::If {
if_thens,
final_else,
indented_else,
} => Expr::If {
if_thens: if_thens.normalize(arena),
final_else: arena.alloc(final_else.normalize(arena)),
indented_else,
},
Expr::When(a, b) => Expr::When(arena.alloc(a.normalize(arena)), b.normalize(arena)), Expr::When(a, b) => Expr::When(arena.alloc(a.normalize(arena)), b.normalize(arena)),
Expr::ParensAround(a) => { Expr::ParensAround(a) => {
// The formatter can remove redundant parentheses, so also remove these when normalizing for comparison. // The formatter can remove redundant parentheses, so also remove these when normalizing for comparison.
@ -809,12 +792,6 @@ impl<'a> Normalize<'a> for Expr<'a> {
Expr::SpaceBefore(a, _) => a.normalize(arena), Expr::SpaceBefore(a, _) => a.normalize(arena),
Expr::SpaceAfter(a, _) => a.normalize(arena), Expr::SpaceAfter(a, _) => a.normalize(arena),
Expr::SingleQuote(a) => Expr::Num(a), Expr::SingleQuote(a) => Expr::Num(a),
Expr::MultipleOldRecordBuilders(a) => {
Expr::MultipleOldRecordBuilders(arena.alloc(a.normalize(arena)))
}
Expr::UnappliedOldRecordBuilder(a) => {
Expr::UnappliedOldRecordBuilder(arena.alloc(a.normalize(arena)))
}
Expr::EmptyRecordBuilder(a) => { Expr::EmptyRecordBuilder(a) => {
Expr::EmptyRecordBuilder(arena.alloc(a.normalize(arena))) Expr::EmptyRecordBuilder(arena.alloc(a.normalize(arena)))
} }
@ -1084,12 +1061,6 @@ impl<'a> Normalize<'a> for EExpr<'a> {
EExpr::Record(inner_err, _pos) => { EExpr::Record(inner_err, _pos) => {
EExpr::Record(inner_err.normalize(arena), Position::zero()) EExpr::Record(inner_err.normalize(arena), Position::zero())
} }
EExpr::OptionalValueInOldRecordBuilder(_pos) => {
EExpr::OptionalValueInOldRecordBuilder(Region::zero())
}
EExpr::IgnoredValueInOldRecordBuilder(_pos) => {
EExpr::OptionalValueInOldRecordBuilder(Region::zero())
}
EExpr::Str(inner_err, _pos) => EExpr::Str(inner_err.normalize(arena), Position::zero()), EExpr::Str(inner_err, _pos) => EExpr::Str(inner_err.normalize(arena), Position::zero()),
EExpr::Number(inner_err, _pos) => EExpr::Number(inner_err.clone(), Position::zero()), EExpr::Number(inner_err, _pos) => EExpr::Number(inner_err.clone(), Position::zero()),
EExpr::List(inner_err, _pos) => { EExpr::List(inner_err, _pos) => {
@ -1323,7 +1294,6 @@ impl<'a> Normalize<'a> for EImportParams<'a> {
EImportParams::Record(inner_err.normalize(arena), Position::zero()) EImportParams::Record(inner_err.normalize(arena), Position::zero())
} }
EImportParams::RecordUpdateFound(_) => EImportParams::RecordUpdateFound(Region::zero()), EImportParams::RecordUpdateFound(_) => EImportParams::RecordUpdateFound(Region::zero()),
EImportParams::RecordApplyFound(_) => EImportParams::RecordApplyFound(Region::zero()),
EImportParams::RecordIgnoredFieldFound(_) => { EImportParams::RecordIgnoredFieldFound(_) => {
EImportParams::RecordIgnoredFieldFound(Region::zero()) EImportParams::RecordIgnoredFieldFound(Region::zero())
} }

View file

@ -344,8 +344,6 @@ pub enum EExpr<'a> {
InParens(EInParens<'a>, Position), InParens(EInParens<'a>, Position),
Record(ERecord<'a>, Position), Record(ERecord<'a>, Position),
OptionalValueInOldRecordBuilder(Region),
IgnoredValueInOldRecordBuilder(Region),
RecordUpdateOldBuilderField(Region), RecordUpdateOldBuilderField(Region),
RecordUpdateIgnoredField(Region), RecordUpdateIgnoredField(Region),
RecordBuilderOldBuilderField(Region), RecordBuilderOldBuilderField(Region),
@ -551,7 +549,6 @@ pub enum EImportParams<'a> {
Record(ERecord<'a>, Position), Record(ERecord<'a>, Position),
RecordUpdateFound(Region), RecordUpdateFound(Region),
RecordBuilderFound(Region), RecordBuilderFound(Region),
RecordApplyFound(Region),
RecordIgnoredFieldFound(Region), RecordIgnoredFieldFound(Region),
Space(BadInputError, Position), Space(BadInputError, Position),
} }

View file

@ -5,7 +5,7 @@ use crate::ast::{
use crate::blankspace::{ use crate::blankspace::{
space0_around_ee, space0_before_e, space0_before_optional_after, space0_e, space0_around_ee, space0_before_e, space0_before_optional_after, space0_e,
}; };
use crate::expr::{record_field, FoundApplyValue}; use crate::expr::record_field;
use crate::ident::{lowercase_ident, lowercase_ident_keyword_e}; use crate::ident::{lowercase_ident, lowercase_ident_keyword_e};
use crate::keyword; use crate::keyword;
use crate::parser::{ use crate::parser::{
@ -565,11 +565,10 @@ fn parse_implements_ability<'a>() -> impl Parser<'a, ImplementsAbility<'a>, ETyp
fn ability_impl_field<'a>() -> impl Parser<'a, AssignedField<'a, Expr<'a>>, ERecord<'a>> { fn ability_impl_field<'a>() -> impl Parser<'a, AssignedField<'a, Expr<'a>>, ERecord<'a>> {
then(record_field(), move |arena, state, _, field| { then(record_field(), move |arena, state, _, field| {
match field.to_assigned_field(arena) { match field.to_assigned_field(arena) {
Ok(AssignedField::IgnoredValue(_, _, _)) => { AssignedField::IgnoredValue(_, _, _) => {
Err((MadeProgress, ERecord::Field(state.pos()))) Err((MadeProgress, ERecord::Field(state.pos())))
} }
Ok(assigned_field) => Ok((MadeProgress, assigned_field, state)), assigned_field => Ok((MadeProgress, assigned_field, state)),
Err(FoundApplyValue) => Err((MadeProgress, ERecord::Field(state.pos()))),
} }
}) })
} }

View file

@ -427,8 +427,6 @@ impl Problem {
| Problem::RuntimeError(RuntimeError::EmptySingleQuote(region)) | Problem::RuntimeError(RuntimeError::EmptySingleQuote(region))
| Problem::RuntimeError(RuntimeError::MultipleCharsInSingleQuote(region)) | Problem::RuntimeError(RuntimeError::MultipleCharsInSingleQuote(region))
| Problem::RuntimeError(RuntimeError::DegenerateBranch(region)) | Problem::RuntimeError(RuntimeError::DegenerateBranch(region))
| Problem::RuntimeError(RuntimeError::MultipleOldRecordBuilders(region))
| Problem::RuntimeError(RuntimeError::UnappliedOldRecordBuilder(region))
| Problem::RuntimeError(RuntimeError::EmptyRecordBuilder(region)) | Problem::RuntimeError(RuntimeError::EmptyRecordBuilder(region))
| Problem::RuntimeError(RuntimeError::SingleFieldRecordBuilder(region)) | Problem::RuntimeError(RuntimeError::SingleFieldRecordBuilder(region))
| Problem::RuntimeError(RuntimeError::OptionalFieldInRecordBuilder { | Problem::RuntimeError(RuntimeError::OptionalFieldInRecordBuilder {
@ -686,9 +684,6 @@ pub enum RuntimeError {
DegenerateBranch(Region), DegenerateBranch(Region),
MultipleOldRecordBuilders(Region),
UnappliedOldRecordBuilder(Region),
EmptyRecordBuilder(Region), EmptyRecordBuilder(Region),
SingleFieldRecordBuilder(Region), SingleFieldRecordBuilder(Region),
OptionalFieldInRecordBuilder { OptionalFieldInRecordBuilder {
@ -739,8 +734,6 @@ impl RuntimeError {
| RuntimeError::DegenerateBranch(region) | RuntimeError::DegenerateBranch(region)
| RuntimeError::InvalidInterpolation(region) | RuntimeError::InvalidInterpolation(region)
| RuntimeError::InvalidHexadecimal(region) | RuntimeError::InvalidHexadecimal(region)
| RuntimeError::MultipleOldRecordBuilders(region)
| RuntimeError::UnappliedOldRecordBuilder(region)
| RuntimeError::EmptyRecordBuilder(region) | RuntimeError::EmptyRecordBuilder(region)
| RuntimeError::SingleFieldRecordBuilder(region) | RuntimeError::SingleFieldRecordBuilder(region)
| RuntimeError::OptionalFieldInRecordBuilder { | RuntimeError::OptionalFieldInRecordBuilder {

View file

@ -547,6 +547,11 @@ fn list_drop_at() {
RocList::from_slice(&[2, 3]), RocList::from_slice(&[2, 3]),
RocList<i64> RocList<i64>
); );
assert_evals_to!(
"List.dropAt [1, 2, 3] 1",
RocList::from_slice(&[1, 3]),
RocList<i64>
);
assert_evals_to!( assert_evals_to!(
"List.dropAt [0, 0, 0] 3", "List.dropAt [0, 0, 0] 3",
RocList::from_slice(&[0, 0, 0]), RocList::from_slice(&[0, 0, 0]),

View file

@ -1988,3 +1988,75 @@ fn str_contains_self() {
bool bool
); );
} }
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn str_drop_prefix() {
assert_evals_to!(
r#"
Str.dropPrefix "" "foo"
"#,
RocStr::from(""),
RocStr
);
assert_evals_to!(
r#"
Str.dropPrefix "bar" "foo"
"#,
RocStr::from("bar"),
RocStr
);
assert_evals_to!(
r#"
Str.dropPrefix "foobar" "foo"
"#,
RocStr::from("bar"),
RocStr
);
assert_evals_to!(
r#"
Str.dropPrefix "fooBarThisIsDefinitelyAReallyLongAndNotaShortString" "foo"
"#,
RocStr::from("BarThisIsDefinitelyAReallyLongAndNotaShortString"),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn str_drop_suffix() {
assert_evals_to!(
r#"
Str.dropSuffix "" "foo"
"#,
RocStr::from(""),
RocStr
);
assert_evals_to!(
r#"
Str.dropSuffix "bar" "foo"
"#,
RocStr::from("bar"),
RocStr
);
assert_evals_to!(
r#"
Str.dropSuffix "barfoo" "foo"
"#,
RocStr::from("bar"),
RocStr
);
assert_evals_to!(
r#"
Str.dropSuffix "BarThisIsDefinitelyAReallyLongAndNotaShortStringfoo" "foo"
"#,
RocStr::from("BarThisIsDefinitelyAReallyLongAndNotaShortString"),
RocStr
);
}

View file

@ -41,8 +41,8 @@ procedure Num.51 (#Attr.2, #Attr.3):
ret Num.281; ret Num.281;
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.238 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.248 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.238; ret Str.248;
procedure Test.1 (Test.5): procedure Test.1 (Test.5):
ret Test.5; ret Test.5;

View file

@ -43,14 +43,14 @@ procedure Num.96 (#Attr.2):
ret Num.282; ret Num.282;
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.236 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.246 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.236; ret Str.246;
procedure Test.0 (): procedure Test.0 ():
let Test.4 : I64 = 1i64; let Test.5 : I64 = 1i64;
let Test.1 : I64 = 2i64; let Test.2 : I64 = 2i64;
let Test.2 : Str = CallByName Inspect.33 Test.1; let Test.3 : Str = CallByName Inspect.33 Test.2;
dbg Test.2; dbg Test.3;
dec Test.2; dec Test.3;
let Test.3 : I64 = CallByName Num.19 Test.4 Test.1; let Test.4 : I64 = CallByName Num.19 Test.5 Test.2;
ret Test.3; ret Test.4;

View file

@ -44,8 +44,8 @@ procedure Inspect.64 (Inspect.302):
ret Inspect.302; ret Inspect.302;
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.236 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.246 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.236; ret Str.246;
procedure Test.1 (): procedure Test.1 ():
let Test.4 : Str = ""; let Test.4 : Str = "";

View file

@ -40,19 +40,19 @@ procedure Inspect.64 (Inspect.302):
ret Inspect.302; ret Inspect.302;
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.238 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.248 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.238; ret Str.248;
procedure Test.0 (): procedure Test.0 ():
let Test.4 : Str = "Hello "; let Test.5 : Str = "Hello ";
let Test.1 : Str = "world"; let Test.2 : Str = "world";
inc Test.1; inc Test.2;
let Test.2 : Str = CallByName Inspect.33 Test.1; let Test.3 : Str = CallByName Inspect.33 Test.2;
dbg Test.2; dbg Test.3;
dec Test.2; dec Test.3;
let Test.7 : Str = "!"; let Test.8 : Str = "!";
let Test.5 : Str = CallByName Str.3 Test.1 Test.7; let Test.6 : Str = CallByName Str.3 Test.2 Test.8;
dec Test.7; dec Test.8;
let Test.3 : Str = CallByName Str.3 Test.4 Test.5; let Test.4 : Str = CallByName Str.3 Test.5 Test.6;
dec Test.5; dec Test.6;
ret Test.3; ret Test.4;

View file

@ -39,18 +39,18 @@ procedure Num.96 (#Attr.2):
ret Num.281; ret Num.281;
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.236 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.246 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.236; ret Str.246;
procedure Test.0 (): procedure Test.0 ():
let Test.3 : I64 = 1i64; let Test.6 : I64 = 1i64;
let Test.4 : Str = CallByName Inspect.33 Test.3; let Test.7 : Str = CallByName Inspect.33 Test.6;
dbg Test.4; dbg Test.7;
dec Test.4; dec Test.7;
let Test.5 : Str = CallByName Inspect.33 Test.3; let Test.8 : Str = CallByName Inspect.33 Test.6;
dbg Test.5; dbg Test.8;
dec Test.5; dec Test.8;
let Test.6 : Str = CallByName Inspect.33 Test.3; let Test.9 : Str = CallByName Inspect.33 Test.6;
dbg Test.6; dbg Test.9;
dec Test.6; dec Test.9;
ret Test.3; ret Test.6;

View file

@ -40,8 +40,8 @@ procedure Inspect.64 (Inspect.302):
ret Inspect.302; ret Inspect.302;
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.236 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.246 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.236; ret Str.246;
procedure Test.0 (): procedure Test.0 ():
let Test.3 : Str = ""; let Test.3 : Str = "";

View file

@ -1,29 +1,29 @@
procedure Dict.1 (Dict.730): procedure Dict.1 (Dict.731):
let Dict.739 : List {U32, U32} = Array []; let Dict.740 : List {U32, U32} = Array [];
let Dict.740 : List {[], []} = Array []; let Dict.741 : List {[], []} = Array [];
let Dict.741 : U64 = 0i64; let Dict.742 : U64 = 0i64;
let Dict.51 : Float32 = CallByName Dict.51; let Dict.51 : Float32 = CallByName Dict.51;
let Dict.52 : U8 = CallByName Dict.52; let Dict.52 : U8 = CallByName Dict.52;
let Dict.738 : {List {U32, U32}, List {[], []}, U64, Float32, U8} = Struct {Dict.739, Dict.740, Dict.741, Dict.51, Dict.52}; let Dict.739 : {List {U32, U32}, List {[], []}, U64, Float32, U8} = Struct {Dict.740, Dict.741, Dict.742, Dict.51, Dict.52};
ret Dict.739;
procedure Dict.4 (Dict.737):
let Dict.163 : List {[], []} = StructAtIndex 1 Dict.737;
let #Derived_gen.0 : List {U32, U32} = StructAtIndex 0 Dict.737;
dec #Derived_gen.0;
let Dict.738 : U64 = CallByName List.6 Dict.163;
dec Dict.163;
ret Dict.738; ret Dict.738;
procedure Dict.4 (Dict.736):
let Dict.163 : List {[], []} = StructAtIndex 1 Dict.736;
let #Derived_gen.0 : List {U32, U32} = StructAtIndex 0 Dict.736;
dec #Derived_gen.0;
let Dict.737 : U64 = CallByName List.6 Dict.163;
dec Dict.163;
ret Dict.737;
procedure Dict.51 (): procedure Dict.51 ():
let Dict.745 : Float32 = 0.8f64; let Dict.746 : Float32 = 0.8f64;
ret Dict.745; ret Dict.746;
procedure Dict.52 (): procedure Dict.52 ():
let Dict.743 : U8 = 64i64; let Dict.744 : U8 = 64i64;
let Dict.744 : U8 = 3i64; let Dict.745 : U8 = 3i64;
let Dict.742 : U8 = CallByName Num.75 Dict.743 Dict.744; let Dict.743 : U8 = CallByName Num.75 Dict.744 Dict.745;
ret Dict.742; ret Dict.743;
procedure List.6 (#Attr.2): procedure List.6 (#Attr.2):
let List.625 : U64 = lowlevel ListLenU64 #Attr.2; let List.625 : U64 = lowlevel ListLenU64 #Attr.2;

View file

@ -164,32 +164,32 @@ procedure Num.96 (#Attr.2):
ret Num.285; ret Num.285;
procedure Str.12 (#Attr.2): procedure Str.12 (#Attr.2):
let Str.248 : List U8 = lowlevel StrToUtf8 #Attr.2; let Str.258 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.248; ret Str.258;
procedure Str.36 (#Attr.2): procedure Str.36 (#Attr.2):
let Str.249 : U64 = lowlevel StrCountUtf8Bytes #Attr.2; let Str.259 : U64 = lowlevel StrCountUtf8Bytes #Attr.2;
ret Str.249; ret Str.259;
procedure Str.43 (#Attr.2): procedure Str.43 (#Attr.2):
let Str.243 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8 #Attr.2; let Str.253 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8 #Attr.2;
ret Str.243; ret Str.253;
procedure Str.9 (Str.71): procedure Str.9 (Str.73):
let Str.72 : {U64, Str, Int1, U8} = CallByName Str.43 Str.71; let Str.74 : {U64, Str, Int1, U8} = CallByName Str.43 Str.73;
let Str.240 : Int1 = StructAtIndex 2 Str.72; let Str.250 : Int1 = StructAtIndex 2 Str.74;
if Str.240 then if Str.250 then
let Str.242 : Str = StructAtIndex 1 Str.72; let Str.252 : Str = StructAtIndex 1 Str.74;
let Str.241 : [C {U64, U8}, C Str] = TagId(1) Str.242; let Str.251 : [C {U64, U8}, C Str] = TagId(1) Str.252;
ret Str.241; ret Str.251;
else else
let Str.238 : U8 = StructAtIndex 3 Str.72; let Str.248 : U8 = StructAtIndex 3 Str.74;
let Str.239 : U64 = StructAtIndex 0 Str.72; let Str.249 : U64 = StructAtIndex 0 Str.74;
let #Derived_gen.45 : Str = StructAtIndex 1 Str.72; let #Derived_gen.45 : Str = StructAtIndex 1 Str.74;
dec #Derived_gen.45; dec #Derived_gen.45;
let Str.237 : {U64, U8} = Struct {Str.239, Str.238}; let Str.247 : {U64, U8} = Struct {Str.249, Str.248};
let Str.236 : [C {U64, U8}, C Str] = TagId(0) Str.237; let Str.246 : [C {U64, U8}, C Str] = TagId(0) Str.247;
ret Str.236; ret Str.246;
procedure Test.20 (Test.56): procedure Test.20 (Test.56):
let Test.325 : Str = CallByName Encode.23 Test.56; let Test.325 : Str = CallByName Encode.23 Test.56;

View file

@ -105,32 +105,32 @@ procedure Num.96 (#Attr.2):
ret Num.281; ret Num.281;
procedure Str.12 (#Attr.2): procedure Str.12 (#Attr.2):
let Str.245 : List U8 = lowlevel StrToUtf8 #Attr.2; let Str.255 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.245; ret Str.255;
procedure Str.36 (#Attr.2): procedure Str.36 (#Attr.2):
let Str.246 : U64 = lowlevel StrCountUtf8Bytes #Attr.2; let Str.256 : U64 = lowlevel StrCountUtf8Bytes #Attr.2;
ret Str.246; ret Str.256;
procedure Str.43 (#Attr.2): procedure Str.43 (#Attr.2):
let Str.243 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8 #Attr.2; let Str.253 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8 #Attr.2;
ret Str.243; ret Str.253;
procedure Str.9 (Str.71): procedure Str.9 (Str.73):
let Str.72 : {U64, Str, Int1, U8} = CallByName Str.43 Str.71; let Str.74 : {U64, Str, Int1, U8} = CallByName Str.43 Str.73;
let Str.240 : Int1 = StructAtIndex 2 Str.72; let Str.250 : Int1 = StructAtIndex 2 Str.74;
if Str.240 then if Str.250 then
let Str.242 : Str = StructAtIndex 1 Str.72; let Str.252 : Str = StructAtIndex 1 Str.74;
let Str.241 : [C {U64, U8}, C Str] = TagId(1) Str.242; let Str.251 : [C {U64, U8}, C Str] = TagId(1) Str.252;
ret Str.241; ret Str.251;
else else
let Str.238 : U8 = StructAtIndex 3 Str.72; let Str.248 : U8 = StructAtIndex 3 Str.74;
let Str.239 : U64 = StructAtIndex 0 Str.72; let Str.249 : U64 = StructAtIndex 0 Str.74;
let #Derived_gen.24 : Str = StructAtIndex 1 Str.72; let #Derived_gen.24 : Str = StructAtIndex 1 Str.74;
dec #Derived_gen.24; dec #Derived_gen.24;
let Str.237 : {U64, U8} = Struct {Str.239, Str.238}; let Str.247 : {U64, U8} = Struct {Str.249, Str.248};
let Str.236 : [C {U64, U8}, C Str] = TagId(0) Str.237; let Str.246 : [C {U64, U8}, C Str] = TagId(0) Str.247;
ret Str.236; ret Str.246;
procedure Test.20 (Test.56): procedure Test.20 (Test.56):
let Test.292 : Str = CallByName Encode.23 Test.56; let Test.292 : Str = CallByName Encode.23 Test.56;

View file

@ -112,32 +112,32 @@ procedure Num.96 (#Attr.2):
ret Num.281; ret Num.281;
procedure Str.12 (#Attr.2): procedure Str.12 (#Attr.2):
let Str.245 : List U8 = lowlevel StrToUtf8 #Attr.2; let Str.255 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.245; ret Str.255;
procedure Str.36 (#Attr.2): procedure Str.36 (#Attr.2):
let Str.246 : U64 = lowlevel StrCountUtf8Bytes #Attr.2; let Str.256 : U64 = lowlevel StrCountUtf8Bytes #Attr.2;
ret Str.246; ret Str.256;
procedure Str.43 (#Attr.2): procedure Str.43 (#Attr.2):
let Str.243 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8 #Attr.2; let Str.253 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8 #Attr.2;
ret Str.243; ret Str.253;
procedure Str.9 (Str.71): procedure Str.9 (Str.73):
let Str.72 : {U64, Str, Int1, U8} = CallByName Str.43 Str.71; let Str.74 : {U64, Str, Int1, U8} = CallByName Str.43 Str.73;
let Str.240 : Int1 = StructAtIndex 2 Str.72; let Str.250 : Int1 = StructAtIndex 2 Str.74;
if Str.240 then if Str.250 then
let Str.242 : Str = StructAtIndex 1 Str.72; let Str.252 : Str = StructAtIndex 1 Str.74;
let Str.241 : [C {U64, U8}, C Str] = TagId(1) Str.242; let Str.251 : [C {U64, U8}, C Str] = TagId(1) Str.252;
ret Str.241; ret Str.251;
else else
let Str.238 : U8 = StructAtIndex 3 Str.72; let Str.248 : U8 = StructAtIndex 3 Str.74;
let Str.239 : U64 = StructAtIndex 0 Str.72; let Str.249 : U64 = StructAtIndex 0 Str.74;
let #Derived_gen.28 : Str = StructAtIndex 1 Str.72; let #Derived_gen.28 : Str = StructAtIndex 1 Str.74;
dec #Derived_gen.28; dec #Derived_gen.28;
let Str.237 : {U64, U8} = Struct {Str.239, Str.238}; let Str.247 : {U64, U8} = Struct {Str.249, Str.248};
let Str.236 : [C {U64, U8}, C Str] = TagId(0) Str.237; let Str.246 : [C {U64, U8}, C Str] = TagId(0) Str.247;
ret Str.236; ret Str.246;
procedure Test.20 (Test.56): procedure Test.20 (Test.56):
let Test.296 : Str = CallByName Encode.23 Test.56; let Test.296 : Str = CallByName Encode.23 Test.56;

View file

@ -38,32 +38,32 @@ procedure Num.96 (#Attr.2):
ret Num.281; ret Num.281;
procedure Str.12 (#Attr.2): procedure Str.12 (#Attr.2):
let Str.245 : List U8 = lowlevel StrToUtf8 #Attr.2; let Str.255 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.245; ret Str.255;
procedure Str.36 (#Attr.2): procedure Str.36 (#Attr.2):
let Str.246 : U64 = lowlevel StrCountUtf8Bytes #Attr.2; let Str.256 : U64 = lowlevel StrCountUtf8Bytes #Attr.2;
ret Str.246; ret Str.256;
procedure Str.43 (#Attr.2): procedure Str.43 (#Attr.2):
let Str.243 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8 #Attr.2; let Str.253 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8 #Attr.2;
ret Str.243; ret Str.253;
procedure Str.9 (Str.71): procedure Str.9 (Str.73):
let Str.72 : {U64, Str, Int1, U8} = CallByName Str.43 Str.71; let Str.74 : {U64, Str, Int1, U8} = CallByName Str.43 Str.73;
let Str.240 : Int1 = StructAtIndex 2 Str.72; let Str.250 : Int1 = StructAtIndex 2 Str.74;
if Str.240 then if Str.250 then
let Str.242 : Str = StructAtIndex 1 Str.72; let Str.252 : Str = StructAtIndex 1 Str.74;
let Str.241 : [C {U64, U8}, C Str] = TagId(1) Str.242; let Str.251 : [C {U64, U8}, C Str] = TagId(1) Str.252;
ret Str.241; ret Str.251;
else else
let Str.238 : U8 = StructAtIndex 3 Str.72; let Str.248 : U8 = StructAtIndex 3 Str.74;
let Str.239 : U64 = StructAtIndex 0 Str.72; let Str.249 : U64 = StructAtIndex 0 Str.74;
let #Derived_gen.3 : Str = StructAtIndex 1 Str.72; let #Derived_gen.3 : Str = StructAtIndex 1 Str.74;
dec #Derived_gen.3; dec #Derived_gen.3;
let Str.237 : {U64, U8} = Struct {Str.239, Str.238}; let Str.247 : {U64, U8} = Struct {Str.249, Str.248};
let Str.236 : [C {U64, U8}, C Str] = TagId(0) Str.237; let Str.246 : [C {U64, U8}, C Str] = TagId(0) Str.247;
ret Str.236; ret Str.246;
procedure Test.20 (Test.56): procedure Test.20 (Test.56):
let Test.259 : Str = CallByName Encode.23 Test.56; let Test.259 : Str = CallByName Encode.23 Test.56;

View file

@ -110,32 +110,32 @@ procedure Num.96 (#Attr.2):
ret Num.281; ret Num.281;
procedure Str.12 (#Attr.2): procedure Str.12 (#Attr.2):
let Str.245 : List U8 = lowlevel StrToUtf8 #Attr.2; let Str.255 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.245; ret Str.255;
procedure Str.36 (#Attr.2): procedure Str.36 (#Attr.2):
let Str.246 : U64 = lowlevel StrCountUtf8Bytes #Attr.2; let Str.256 : U64 = lowlevel StrCountUtf8Bytes #Attr.2;
ret Str.246; ret Str.256;
procedure Str.43 (#Attr.2): procedure Str.43 (#Attr.2):
let Str.243 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8 #Attr.2; let Str.253 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8 #Attr.2;
ret Str.243; ret Str.253;
procedure Str.9 (Str.71): procedure Str.9 (Str.73):
let Str.72 : {U64, Str, Int1, U8} = CallByName Str.43 Str.71; let Str.74 : {U64, Str, Int1, U8} = CallByName Str.43 Str.73;
let Str.240 : Int1 = StructAtIndex 2 Str.72; let Str.250 : Int1 = StructAtIndex 2 Str.74;
if Str.240 then if Str.250 then
let Str.242 : Str = StructAtIndex 1 Str.72; let Str.252 : Str = StructAtIndex 1 Str.74;
let Str.241 : [C {U64, U8}, C Str] = TagId(1) Str.242; let Str.251 : [C {U64, U8}, C Str] = TagId(1) Str.252;
ret Str.241; ret Str.251;
else else
let Str.238 : U8 = StructAtIndex 3 Str.72; let Str.248 : U8 = StructAtIndex 3 Str.74;
let Str.239 : U64 = StructAtIndex 0 Str.72; let Str.249 : U64 = StructAtIndex 0 Str.74;
let #Derived_gen.27 : Str = StructAtIndex 1 Str.72; let #Derived_gen.27 : Str = StructAtIndex 1 Str.74;
dec #Derived_gen.27; dec #Derived_gen.27;
let Str.237 : {U64, U8} = Struct {Str.239, Str.238}; let Str.247 : {U64, U8} = Struct {Str.249, Str.248};
let Str.236 : [C {U64, U8}, C Str] = TagId(0) Str.237; let Str.246 : [C {U64, U8}, C Str] = TagId(0) Str.247;
ret Str.236; ret Str.246;
procedure Test.20 (Test.56): procedure Test.20 (Test.56):
let Test.297 : Str = CallByName Encode.23 Test.56; let Test.297 : Str = CallByName Encode.23 Test.56;

View file

@ -113,32 +113,32 @@ procedure Num.96 (#Attr.2):
ret Num.281; ret Num.281;
procedure Str.12 (#Attr.2): procedure Str.12 (#Attr.2):
let Str.245 : List U8 = lowlevel StrToUtf8 #Attr.2; let Str.255 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.245; ret Str.255;
procedure Str.36 (#Attr.2): procedure Str.36 (#Attr.2):
let Str.246 : U64 = lowlevel StrCountUtf8Bytes #Attr.2; let Str.256 : U64 = lowlevel StrCountUtf8Bytes #Attr.2;
ret Str.246; ret Str.256;
procedure Str.43 (#Attr.2): procedure Str.43 (#Attr.2):
let Str.243 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8 #Attr.2; let Str.253 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8 #Attr.2;
ret Str.243; ret Str.253;
procedure Str.9 (Str.71): procedure Str.9 (Str.73):
let Str.72 : {U64, Str, Int1, U8} = CallByName Str.43 Str.71; let Str.74 : {U64, Str, Int1, U8} = CallByName Str.43 Str.73;
let Str.240 : Int1 = StructAtIndex 2 Str.72; let Str.250 : Int1 = StructAtIndex 2 Str.74;
if Str.240 then if Str.250 then
let Str.242 : Str = StructAtIndex 1 Str.72; let Str.252 : Str = StructAtIndex 1 Str.74;
let Str.241 : [C {U64, U8}, C Str] = TagId(1) Str.242; let Str.251 : [C {U64, U8}, C Str] = TagId(1) Str.252;
ret Str.241; ret Str.251;
else else
let Str.238 : U8 = StructAtIndex 3 Str.72; let Str.248 : U8 = StructAtIndex 3 Str.74;
let Str.239 : U64 = StructAtIndex 0 Str.72; let Str.249 : U64 = StructAtIndex 0 Str.74;
let #Derived_gen.28 : Str = StructAtIndex 1 Str.72; let #Derived_gen.28 : Str = StructAtIndex 1 Str.74;
dec #Derived_gen.28; dec #Derived_gen.28;
let Str.237 : {U64, U8} = Struct {Str.239, Str.238}; let Str.247 : {U64, U8} = Struct {Str.249, Str.248};
let Str.236 : [C {U64, U8}, C Str] = TagId(0) Str.237; let Str.246 : [C {U64, U8}, C Str] = TagId(0) Str.247;
ret Str.236; ret Str.246;
procedure Test.20 (Test.56): procedure Test.20 (Test.56):
let Test.301 : Str = CallByName Encode.23 Test.56; let Test.301 : Str = CallByName Encode.23 Test.56;

File diff suppressed because it is too large Load diff

View file

@ -178,8 +178,8 @@ procedure Num.96 (#Attr.2):
ret Num.281; ret Num.281;
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.236 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.246 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.236; ret Str.246;
procedure Test.0 (): procedure Test.0 ():
let Test.2 : List I64 = Array [1i64, 2i64, 3i64]; let Test.2 : List I64 = Array [1i64, 2i64, 3i64];

View file

@ -292,8 +292,8 @@ procedure Num.51 (#Attr.2, #Attr.3):
ret Num.283; ret Num.283;
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.237 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.247 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.237; ret Str.247;
procedure Test.0 (): procedure Test.0 ():
let Test.4 : Str = "bar"; let Test.4 : Str = "bar";

View file

@ -209,8 +209,8 @@ procedure Num.96 (#Attr.2):
ret Num.282; ret Num.282;
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.236 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.246 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.236; ret Str.246;
procedure Test.0 (): procedure Test.0 ():
let Test.3 : Decimal = 3dec; let Test.3 : Decimal = 3dec;

View file

@ -179,8 +179,8 @@ procedure Num.51 (#Attr.2, #Attr.3):
ret Num.281; ret Num.281;
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.236 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.246 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.236; ret Str.246;
procedure Test.0 (): procedure Test.0 ():
let Test.3 : Str = "foo"; let Test.3 : Str = "foo";

View file

@ -186,8 +186,8 @@ procedure Num.51 (#Attr.2, #Attr.3):
ret Num.281; ret Num.281;
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.236 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.246 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.236; ret Str.246;
procedure Test.0 (): procedure Test.0 ():
let Test.3 : Str = "foo"; let Test.3 : Str = "foo";

View file

@ -40,8 +40,8 @@ procedure Inspect.64 (Inspect.302):
ret Inspect.302; ret Inspect.302;
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.236 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.246 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.236; ret Str.246;
procedure Test.0 (): procedure Test.0 ():
let Test.2 : Str = "abc"; let Test.2 : Str = "abc";

View file

@ -179,8 +179,8 @@ procedure Num.51 (#Attr.2, #Attr.3):
ret Num.281; ret Num.281;
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.236 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.246 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.236; ret Str.246;
procedure Test.0 (): procedure Test.0 ():
let Test.4 : Str = "foo"; let Test.4 : Str = "foo";

View file

@ -182,8 +182,8 @@ procedure Num.51 (#Attr.2, #Attr.3):
ret Num.281; ret Num.281;
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.236 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.246 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.236; ret Str.246;
procedure Test.0 (): procedure Test.0 ():
let Test.5 : Str = "foo"; let Test.5 : Str = "foo";

View file

@ -45,27 +45,27 @@ procedure Num.22 (#Attr.2, #Attr.3):
let Num.281 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; let Num.281 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.281; ret Num.281;
procedure Str.27 (Str.82): procedure Str.27 (Str.84):
let Str.236 : [C Int1, C I64] = CallByName Str.64 Str.82; let Str.246 : [C Int1, C I64] = CallByName Str.66 Str.84;
ret Str.236; ret Str.246;
procedure Str.42 (#Attr.2): procedure Str.42 (#Attr.2):
let Str.244 : {I64, U8} = lowlevel StrToNum #Attr.2; let Str.254 : {I64, U8} = lowlevel StrToNum #Attr.2;
ret Str.244; ret Str.254;
procedure Str.64 (Str.189): procedure Str.66 (Str.191):
let Str.190 : {I64, U8} = CallByName Str.42 Str.189; let Str.192 : {I64, U8} = CallByName Str.42 Str.191;
let Str.242 : U8 = StructAtIndex 1 Str.190; let Str.252 : U8 = StructAtIndex 1 Str.192;
let Str.243 : U8 = 0i64; let Str.253 : U8 = 0i64;
let Str.239 : Int1 = CallByName Bool.11 Str.242 Str.243; let Str.249 : Int1 = CallByName Bool.11 Str.252 Str.253;
if Str.239 then if Str.249 then
let Str.241 : I64 = StructAtIndex 0 Str.190; let Str.251 : I64 = StructAtIndex 0 Str.192;
let Str.240 : [C Int1, C I64] = TagId(1) Str.241; let Str.250 : [C Int1, C I64] = TagId(1) Str.251;
ret Str.240; ret Str.250;
else else
let Str.238 : Int1 = false; let Str.248 : Int1 = false;
let Str.237 : [C Int1, C I64] = TagId(0) Str.238; let Str.247 : [C Int1, C I64] = TagId(0) Str.248;
ret Str.237; ret Str.247;
procedure Test.0 (): procedure Test.0 ():
let Test.3 : Int1 = CallByName Bool.2; let Test.3 : Int1 = CallByName Bool.2;

View file

@ -19,30 +19,30 @@ procedure Decode.26 (Decode.109, Decode.110):
ret Decode.126; ret Decode.126;
procedure Str.12 (#Attr.2): procedure Str.12 (#Attr.2):
let Str.245 : List U8 = lowlevel StrToUtf8 #Attr.2; let Str.255 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.245; ret Str.255;
procedure Str.27 (Str.82): procedure Str.27 (Str.84):
let Str.236 : [C {}, C I64] = CallByName Str.64 Str.82; let Str.246 : [C {}, C I64] = CallByName Str.66 Str.84;
ret Str.236; ret Str.246;
procedure Str.42 (#Attr.2): procedure Str.42 (#Attr.2):
let Str.244 : {I64, U8} = lowlevel StrToNum #Attr.2; let Str.254 : {I64, U8} = lowlevel StrToNum #Attr.2;
ret Str.244; ret Str.254;
procedure Str.64 (Str.189): procedure Str.66 (Str.191):
let Str.190 : {I64, U8} = CallByName Str.42 Str.189; let Str.192 : {I64, U8} = CallByName Str.42 Str.191;
let Str.242 : U8 = StructAtIndex 1 Str.190; let Str.252 : U8 = StructAtIndex 1 Str.192;
let Str.243 : U8 = 0i64; let Str.253 : U8 = 0i64;
let Str.239 : Int1 = CallByName Bool.11 Str.242 Str.243; let Str.249 : Int1 = CallByName Bool.11 Str.252 Str.253;
if Str.239 then if Str.249 then
let Str.241 : I64 = StructAtIndex 0 Str.190; let Str.251 : I64 = StructAtIndex 0 Str.192;
let Str.240 : [C {}, C I64] = TagId(1) Str.241; let Str.250 : [C {}, C I64] = TagId(1) Str.251;
ret Str.240; ret Str.250;
else else
let Str.238 : {} = Struct {}; let Str.248 : {} = Struct {};
let Str.237 : [C {}, C I64] = TagId(0) Str.238; let Str.247 : [C {}, C I64] = TagId(0) Str.248;
ret Str.237; ret Str.247;
procedure Test.103 (): procedure Test.103 ():
let Test.101 : [C Str, C {List U8, I64}] = CallByName Test.19; let Test.101 : [C Str, C {List U8, I64}] = CallByName Test.19;

View file

@ -1,6 +1,6 @@
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.236 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.246 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.236; ret Str.246;
procedure Test.1 (Test.5): procedure Test.1 (Test.5):
let Test.16 : [C {}, C U64, C Str] = TagId(0) Test.5; let Test.16 : [C {}, C U64, C Str] = TagId(0) Test.5;

View file

@ -71,12 +71,12 @@ procedure Num.51 (#Attr.2, #Attr.3):
ret Num.283; ret Num.283;
procedure Str.16 (#Attr.2, #Attr.3): procedure Str.16 (#Attr.2, #Attr.3):
let Str.236 : Str = lowlevel StrRepeat #Attr.2 #Attr.3; let Str.246 : Str = lowlevel StrRepeat #Attr.2 #Attr.3;
ret Str.236; ret Str.246;
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.237 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.247 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.237; ret Str.247;
procedure Test.1 (): procedure Test.1 ():
let Test.21 : Str = "lllllllllllllllllllllooooooooooong"; let Test.21 : Str = "lllllllllllllllllllllooooooooooong";

View file

@ -70,8 +70,8 @@ procedure Num.51 (#Attr.2, #Attr.3):
ret Num.283; ret Num.283;
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.237 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.247 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.237; ret Str.247;
procedure Test.1 (): procedure Test.1 ():
let Test.21 : Str = "lllllllllllllllllllllooooooooooong"; let Test.21 : Str = "lllllllllllllllllllllooooooooooong";

View file

@ -0,0 +1,59 @@
procedure Inspect.278 (Inspect.279, Inspect.277):
let Inspect.318 : Str = CallByName Num.96 Inspect.277;
let Inspect.317 : Str = CallByName Inspect.63 Inspect.279 Inspect.318;
dec Inspect.318;
ret Inspect.317;
procedure Inspect.30 (Inspect.147):
ret Inspect.147;
procedure Inspect.33 (Inspect.152):
let Inspect.322 : Str = CallByName Inspect.5 Inspect.152;
let Inspect.321 : Str = CallByName Inspect.64 Inspect.322;
ret Inspect.321;
procedure Inspect.39 (Inspect.301):
let Inspect.311 : Str = "";
ret Inspect.311;
procedure Inspect.5 (Inspect.150):
let Inspect.312 : I64 = CallByName Inspect.57 Inspect.150;
let Inspect.309 : {} = Struct {};
let Inspect.308 : Str = CallByName Inspect.39 Inspect.309;
let Inspect.307 : Str = CallByName Inspect.278 Inspect.308 Inspect.312;
ret Inspect.307;
procedure Inspect.57 (Inspect.277):
let Inspect.313 : I64 = CallByName Inspect.30 Inspect.277;
ret Inspect.313;
procedure Inspect.63 (Inspect.300, Inspect.296):
let Inspect.320 : Str = CallByName Str.3 Inspect.300 Inspect.296;
ret Inspect.320;
procedure Inspect.64 (Inspect.302):
ret Inspect.302;
procedure Num.19 (#Attr.2, #Attr.3):
let Num.281 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.281;
procedure Num.96 (#Attr.2):
let Num.282 : Str = lowlevel NumToStr #Attr.2;
ret Num.282;
procedure Str.3 (#Attr.2, #Attr.3):
let Str.246 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.246;
procedure Test.0 ():
let Test.4 : I64 = 1i64;
let Test.5 : Str = CallByName Inspect.33 Test.4;
dbg Test.5;
dec Test.5;
let Test.9 : I64 = 2i64;
let Test.3 : I64 = CallByName Num.19 Test.4 Test.9;
let Test.6 : Str = CallByName Inspect.33 Test.3;
dbg Test.6;
dec Test.6;
ret Test.3;

View file

@ -3,8 +3,8 @@ procedure Bool.11 (#Attr.2, #Attr.3):
ret Bool.23; ret Bool.23;
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.237 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.247 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.237; ret Str.247;
procedure Test.2 (Test.7): procedure Test.2 (Test.7):
let Test.24 : Str = ".trace(\""; let Test.24 : Str = ".trace(\"";

View file

@ -3,8 +3,8 @@ procedure Num.20 (#Attr.2, #Attr.3):
ret Num.281; ret Num.281;
procedure Str.3 (#Attr.2, #Attr.3): procedure Str.3 (#Attr.2, #Attr.3):
let Str.238 : Str = lowlevel StrConcat #Attr.2 #Attr.3; let Str.248 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.238; ret Str.248;
procedure Test.11 (Test.29, #Attr.12): procedure Test.11 (Test.29, #Attr.12):
let Test.32 : {} = UnionAtIndex (Id 0) (Index 0) #Attr.12; let Test.32 : {} = UnionAtIndex (Id 0) (Index 0) #Attr.12;

View file

@ -99,12 +99,12 @@ procedure Num.96 (#Attr.2):
ret Num.281; ret Num.281;
procedure Str.12 (#Attr.2): procedure Str.12 (#Attr.2):
let Str.237 : List U8 = lowlevel StrToUtf8 #Attr.2; let Str.247 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.237; ret Str.247;
procedure Str.36 (#Attr.2): procedure Str.36 (#Attr.2):
let Str.238 : U64 = lowlevel StrCountUtf8Bytes #Attr.2; let Str.248 : U64 = lowlevel StrCountUtf8Bytes #Attr.2;
ret Str.238; ret Str.248;
procedure Test.20 (Test.58): procedure Test.20 (Test.58):
let Test.295 : Str = CallByName Encode.23 Test.58; let Test.295 : Str = CallByName Encode.23 Test.58;

View file

@ -192,12 +192,12 @@ procedure Num.96 (#Attr.2):
ret Num.285; ret Num.285;
procedure Str.12 (#Attr.2): procedure Str.12 (#Attr.2):
let Str.240 : List U8 = lowlevel StrToUtf8 #Attr.2; let Str.250 : List U8 = lowlevel StrToUtf8 #Attr.2;
ret Str.240; ret Str.250;
procedure Str.36 (#Attr.2): procedure Str.36 (#Attr.2):
let Str.241 : U64 = lowlevel StrCountUtf8Bytes #Attr.2; let Str.251 : U64 = lowlevel StrCountUtf8Bytes #Attr.2;
ret Str.241; ret Str.251;
procedure Test.20 (Test.58): procedure Test.20 (Test.58):
inc Test.58; inc Test.58;

View file

@ -3295,6 +3295,18 @@ fn dbg_inside_string() {
) )
} }
#[mono_test]
fn pizza_dbg() {
indoc!(
r#"
1
|> dbg
|> Num.add 2
|> dbg
"#
)
}
#[mono_test] #[mono_test]
fn linked_list_reverse() { fn linked_list_reverse() {
indoc!( indoc!(

View file

@ -0,0 +1 @@
Expr(If(IndentElseBranch(@31), @0), @0)

View file

@ -0,0 +1,4 @@
if thing then
whatever
else
something better

View file

@ -1,6 +1,6 @@
SpaceAfter( SpaceAfter(
If( If {
[ if_thens: [
( (
@3-6 Var { @3-6 Var {
module_name: "", module_name: "",
@ -60,7 +60,7 @@ SpaceAfter(
), ),
), ),
], ],
@49-50 SpaceBefore( final_else: @49-50 SpaceBefore(
Var { Var {
module_name: "", module_name: "",
ident: "c", ident: "c",
@ -71,7 +71,8 @@ SpaceAfter(
), ),
], ],
), ),
), indented_else: false,
},
[ [
LineComment( LineComment(
" 4", " 4",

View file

@ -1,6 +1,6 @@
SpaceAfter( SpaceAfter(
If( If {
[ if_thens: [
( (
@3-5 Var { @3-5 Var {
module_name: "", module_name: "",
@ -40,7 +40,7 @@ SpaceAfter(
), ),
), ),
], ],
@42-43 SpaceBefore( final_else: @42-43 SpaceBefore(
Num( Num(
"3", "3",
), ),
@ -48,7 +48,8 @@ SpaceAfter(
Newline, Newline,
], ],
), ),
), indented_else: false,
},
[ [
Newline, Newline,
], ],

View file

@ -0,0 +1,16 @@
SpaceAfter(
BinOps(
[
(
@0-1 Num(
"1",
),
@2-4 Pizza,
),
],
@5-8 Dbg,
),
[
Newline,
],
)

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