Merge remote-tracking branch 'remote/main' into builtin-task

This commit is contained in:
Luke Boswell 2024-07-19 19:51:50 +10:00
commit b489c44b19
No known key found for this signature in database
GPG key ID: F6DB3C9DB47377B0
262 changed files with 11354 additions and 5821 deletions

View file

@ -1,5 +1,5 @@
on:
# pull_request:
#pull_request:
workflow_dispatch:
# this cancels workflows currently in progress if you start a new one
@ -11,7 +11,7 @@ env:
# use .tar.gz for quick testing
ARCHIVE_FORMAT: .tar.br
# Make a new basic-cli git tag and set it here before starting this workflow
RELEASE_TAG: 0.9.1
RELEASE_TAG: 0.12.0
jobs:
prepare:
@ -240,6 +240,8 @@ jobs:
mkdir platform
# move all files to platform dir
find . -maxdepth 1 -type f -exec mv {} platform/ \;
# move all dirs (except roc_nightly) to platform dir
find . -maxdepth 1 -type d ! -name 'platform' ! -name 'roc_nightly' -exec mv {} platform/ \;
mkdir temp-basic-cli
cd temp-basic-cli

View file

@ -30,7 +30,7 @@ jobs:
run: cargo run --locked --release format --check crates/compiler/builtins/roc
- name: ensure there are no unused dependencies
run: cargo +nightly-2023-12-21 udeps --all-targets
run: cargo +nightly-2024-02-03 udeps --all-targets
- name: zig wasm tests
run: cd crates/compiler/builtins/bitcode && ./run-wasm-tests.sh

View file

@ -29,8 +29,8 @@ jobs:
- name: zig version
run: zig version
- name: install rust nightly 1.76.0
run: rustup install nightly-2023-12-21
- name: install rust nightly 1.77.0
run: rustup install nightly-2024-02-03
- name: set up llvm 16
run: |

View file

@ -37,8 +37,8 @@ jobs:
cd crates\compiler\builtins\bitcode\
zig build test
- name: install rust nightly 1.76.0
run: rustup install nightly-2023-12-21
- name: install rust nightly 1.77.0
run: rustup install nightly-2024-02-03
- name: set up llvm 16
run: |

1
.gitignore vendored
View file

@ -25,6 +25,7 @@
target
generated-docs
zig-cache
.zig-cache
.direnv
.envrc
*.rs.bk

16
Cargo.lock generated
View file

@ -941,6 +941,13 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fs"
version = "0.0.1"
dependencies = [
"widestring",
]
[[package]]
name = "fs_at"
version = "0.1.10"
@ -2335,6 +2342,7 @@ dependencies = [
"bitvec",
"bumpalo",
"indoc",
"insta",
"pretty_assertions",
"roc_collections",
"roc_error_macros",
@ -2344,7 +2352,6 @@ dependencies = [
"roc_problem",
"roc_region",
"roc_serialize",
"roc_test_utils",
"roc_types",
"static_assertions",
"ven_pretty",
@ -2828,6 +2835,7 @@ dependencies = [
"bitvec",
"bumpalo",
"hashbrown",
"indoc",
"parking_lot",
"roc_builtins",
"roc_can",
@ -4551,6 +4559,12 @@ version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
[[package]]
name = "widestring"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
[[package]]
name = "winapi"
version = "0.3.9"

View file

@ -1,40 +1,41 @@
[workspace]
members = [
"crates/compiler/*",
"crates/vendor/*",
"crates/glue",
"crates/cli",
"crates/cli_utils",
"crates/highlight",
"crates/error_macros",
"crates/reporting",
"crates/packaging",
"crates/repl_cli",
"crates/repl_eval",
"crates/repl_test",
"crates/repl_ui",
"crates/repl_wasm",
"crates/repl_expect",
"crates/roc_std",
"crates/test_utils",
"crates/test_utils_dir",
"crates/valgrind",
"crates/tracing",
"crates/utils/*",
"crates/docs",
"crates/docs_cli",
"crates/linker",
"crates/wasi-libc-sys",
"crates/wasm_module",
"crates/wasm_interp",
"crates/language_server",
"crates/compiler/*",
"crates/vendor/*",
"crates/fs",
"crates/glue",
"crates/cli",
"crates/cli_utils",
"crates/highlight",
"crates/error_macros",
"crates/reporting",
"crates/packaging",
"crates/repl_cli",
"crates/repl_eval",
"crates/repl_test",
"crates/repl_ui",
"crates/repl_wasm",
"crates/repl_expect",
"crates/roc_std",
"crates/test_utils",
"crates/test_utils_dir",
"crates/valgrind",
"crates/tracing",
"crates/utils/*",
"crates/docs",
"crates/docs_cli",
"crates/linker",
"crates/wasi-libc-sys",
"crates/wasm_module",
"crates/wasm_interp",
"crates/language_server",
]
exclude = [
"ci/benchmarks/bench-runner",
"ci/repl_basic_test",
# Examples sometimes have Rust hosts in their platforms. The compiler should ignore those.
"examples",
"ci/benchmarks/bench-runner",
"ci/repl_basic_test",
# Examples sometimes have Rust hosts in their platforms. The compiler should ignore those.
"examples",
]
# Needed to be able to run `cargo run -p roc_cli --no-default-features` -
# see www/build.sh for more.
@ -69,7 +70,9 @@ version = "0.0.1"
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# This way, GitHub Actions works and nobody's builds get broken.
# TODO: Switch this back to roc-lang/inkwell once it is updated
inkwell = { git = "https://github.com/roc-lang/inkwell", branch = "inkwell-llvm-16", features = ["llvm16-0"] }
inkwell = { git = "https://github.com/roc-lang/inkwell", branch = "inkwell-llvm-16", features = [
"llvm16-0",
] }
arrayvec = "0.7.2" # update roc_std/Cargo.toml on change
backtrace = "0.3.67"
@ -78,18 +81,27 @@ bincode = "1.3.3"
bitflags = "1.3.2"
bitvec = "1.0.1"
blake3 = "1.3.3"
brotli = "3.3.4" # used for decompressing tarballs over HTTPS, if the server supports brotli
brotli = "3.3.4" # used for decompressing tarballs over HTTPS, if the server supports brotli
bumpalo = { version = "3.12.0", features = ["collections"] }
bytemuck = { version = "1.13.1", features = ["derive"] }
capstone = { version = "0.11.0", default-features = false }
cgmath = "0.18.0"
chrono = "0.4.26"
clap = { version = "4.2.7", default-features = false, features = ["std", "color", "suggestions", "help", "usage", "error-context"] }
clap = { version = "4.2.7", default-features = false, features = [
"std",
"color",
"suggestions",
"help",
"usage",
"error-context",
] }
colored = "2.0.0"
console_error_panic_hook = "0.1.7"
const_format = { version = "0.2.30", features = ["const_generics"] }
copypasta = "0.8.2"
criterion = { git = "https://github.com/Anton-4/criterion.rs", features = ["html_reports"], rev = "30ea0c5" }
criterion = { git = "https://github.com/Anton-4/criterion.rs", features = [
"html_reports",
], rev = "30ea0c5" }
criterion-perf-events = { git = "https://github.com/Anton-4/criterion-perf-events", rev = "0f38c3e" }
crossbeam = "0.8.2"
dircpy = "0.3.14"
@ -102,7 +114,12 @@ fs_extra = "1.3.0"
futures = "0.3.26"
glyph_brush = "0.7.7"
hashbrown = { version = "0.14.3" }
iced-x86 = { version = "1.18.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] }
iced-x86 = { version = "1.18.0", default-features = false, features = [
"std",
"decoder",
"op_code_info",
"instr_info",
] }
im = "15.1.0"
im-rc = "15.1.0"
indexmap = "2.1.0"
@ -138,12 +155,17 @@ quote = "1.0.23"
rand = "0.8.5"
regex = "1.7.1"
remove_dir_all = "0.8.1"
reqwest = { version = "0.11.23", default-features = false, features = ["blocking", "rustls-tls"] } # default-features=false removes libopenssl as a dependency on Linux, which might not be available!
reqwest = { version = "0.11.23", default-features = false, features = [
"blocking",
"rustls-tls",
] } # default-features=false removes libopenssl as a dependency on Linux, which might not be available!
rlimit = "0.9.1"
rustyline = { git = "https://github.com/roc-lang/rustyline", rev = "e74333c" }
rustyline-derive = { git = "https://github.com/roc-lang/rustyline", rev = "e74333c" }
schemars = "0.8.12"
serde = { version = "1.0.153", features = ["derive"] } # update roc_std/Cargo.toml on change
serde = { version = "1.0.153", features = [
"derive",
] } # update roc_std/Cargo.toml on change
serde-xml-rs = "0.6.0"
serde_json = "1.0.94" # update roc_std/Cargo.toml on change
serial_test = "1.0.0"
@ -191,7 +213,7 @@ debug = true
[profile.release-with-lto]
inherits = "release"
lto = "thin" # TODO: We could consider full here since this is only used for packaged release on github.
lto = "thin" # TODO: We could consider full here since this is only used for packaged release on github.
[profile.debug-full]
inherits = "dev"

View file

@ -1,6 +1,6 @@
VERSION 0.6
FROM rust:1.76.0-slim-buster # make sure to update rust-toolchain.toml too so that everything uses the same rust version
FROM rust:1.77.2-slim-buster # make sure to update rust-toolchain.toml too so that everything uses the same rust version
WORKDIR /earthbuild
prep-debian:

View file

@ -256,7 +256,7 @@ mod tests {
use std::io::Write;
use tempfile::{tempdir, TempDir};
const FORMATTED_ROC: &str = r#"app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" }
const FORMATTED_ROC: &str = r#"app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }
import pf.Stdout
import pf.Task
@ -264,7 +264,7 @@ import pf.Task
main =
Stdout.line! "I'm a Roc application!""#;
const UNFORMATTED_ROC: &str = r#"app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" }
const UNFORMATTED_ROC: &str = r#"app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }
import pf.Stdout

View file

@ -13,15 +13,18 @@ use roc_build::program::{
handle_error_module, handle_loading_problem, standard_load_config, BuildFileError,
BuildOrdering, BuiltFile, CodeGenBackend, CodeGenOptions, DEFAULT_ROC_FILENAME,
};
#[cfg(not(windows))]
use roc_collections::MutMap;
use roc_error_macros::{internal_error, user_error};
use roc_gen_dev::AssemblyBackendMode;
use roc_gen_llvm::llvm::build::LlvmBackendMode;
use roc_load::{ExpectMetadata, Threading};
#[cfg(not(windows))]
use roc_module::symbol::ModuleId;
use roc_mono::ir::OptLevel;
use roc_packaging::cache::RocCacheDir;
use roc_packaging::tarball::Compression;
#[cfg(not(windows))]
use roc_reporting::report::ANSI_STYLE_CODES;
use roc_target::{Architecture, Target};
use std::env;
@ -31,7 +34,9 @@ use std::mem::ManuallyDrop;
use std::os::raw::{c_char, c_int};
use std::path::{Path, PathBuf};
use std::process;
use std::time::{Duration, Instant};
#[cfg(not(windows))]
use std::time::Duration;
use std::time::Instant;
use strum::IntoEnumIterator;
#[cfg(not(target_os = "linux"))]
use tempfile::TempDir;
@ -468,6 +473,7 @@ pub fn test(_matches: &ArgMatches, _target: Target) -> io::Result<i32> {
todo!("running tests does not work on windows right now")
}
#[cfg(not(windows))]
struct ModuleTestResults {
module_id: ModuleId,
failed_count: usize,
@ -516,8 +522,7 @@ pub fn test(matches: &ArgMatches, target: Target) -> io::Result<i32> {
}
let arena = &arena;
// TODO may need to determine this dynamically based on dev builds.
let function_kind = FunctionKind::LambdaSet;
let function_kind = FunctionKind::from_env();
let opt_main_path = matches.get_one::<PathBuf>(FLAG_MAIN);
@ -647,6 +652,7 @@ pub fn test(matches: &ArgMatches, target: Target) -> io::Result<i32> {
}
}
#[cfg(not(windows))]
fn print_test_results(
module_test_results: ModuleTestResults,
sources: &MutMap<ModuleId, (PathBuf, Box<str>)>,
@ -666,6 +672,7 @@ fn print_test_results(
println!("\n{module_name}:\n {test_summary_str}",);
}
#[cfg(not(windows))]
fn test_summary(failed_count: usize, passed_count: usize, tests_duration: Duration) -> String {
let failed_color = if failed_count == 0 {
ANSI_STYLE_CODES.green
@ -1157,6 +1164,11 @@ unsafe fn roc_run_native_fast(
enum ExecutableFile {
#[cfg(target_os = "linux")]
MemFd(libc::c_int, PathBuf),
// We store the TempDir in the onDisk variant alongside the path to the executable,
// so that the TempDir doesn't get dropped until after we're done with the path.
// If we didn't do that, then the tempdir would potentially get deleted by the
// TempDir's Drop impl before the file had been executed.
#[allow(dead_code)]
#[cfg(not(target_os = "linux"))]
OnDisk(TempDir, PathBuf),
}
@ -1322,6 +1334,7 @@ fn roc_run_executable_file_path(binary_bytes: &[u8]) -> std::io::Result<Executab
let mut file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.mode(0o777) // create the file as executable
.open(&app_path_buf)?;

View file

@ -126,7 +126,7 @@ fn main() -> io::Result<()> {
.get_one::<String>(FLAG_TARGET)
.and_then(|s| Target::from_str(s).ok())
.unwrap_or_default();
let function_kind = FunctionKind::LambdaSet;
let function_kind = FunctionKind::from_env();
roc_linker::generate_stub_lib(
input_path,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),

View file

@ -1,65 +0,0 @@
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" }
import pf.Stdout
import pf.Arg
main =
args = Arg.list!
parser =
divCmd =
Arg.succeed (\dividend -> \divisor -> Div (Num.toF64 dividend) (Num.toF64 divisor))
|> Arg.withParser
(
Arg.i64Option {
long: "dividend",
short: "n",
help: "the number to divide; corresponds to a numerator",
}
)
|> Arg.withParser
(
Arg.i64Option {
long: "divisor",
short: "d",
help: "the number to divide by; corresponds to a denominator",
}
)
|> Arg.subCommand "div"
logCmd =
Arg.succeed (\base -> \num -> Log (Num.toF64 base) (Num.toF64 num))
|> Arg.withParser
(
Arg.i64Option {
long: "base",
short: "b",
help: "base of the logarithm",
}
)
|> Arg.withParser
(
Arg.i64Option {
long: "num",
help: "the number to take the logarithm of",
}
)
|> Arg.subCommand "log"
Arg.choice [divCmd, logCmd]
|> Arg.program { name: "args-example", help: "A calculator example of the CLI platform argument parser" }
when Arg.parseFormatted parser args is
Ok cmd ->
runCmd cmd
|> Num.toStr
|> Stdout.line
Err helpMenuErr ->
Task.err (Exit 1 "unable to parse args: $(Inspect.toStr helpMenuErr)")
runCmd = \cmd ->
when cmd is
Div n d -> n / d
Log b n ->
# log_b(n) = log_x(n) / log_x(b) for all x
runCmd (Div (Num.log n) (Num.log b))

View file

@ -0,0 +1,22 @@
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }
import pf.Stdout
import pf.Task exposing [Task]
main =
multipleIn =
{ sequential <-
a: Task.ok 123,
b: Task.ok "abc",
c: Task.ok [123],
d: Task.ok ["abc"],
e: Task.ok (Dict.single "a" "b"),
}!
Stdout.line! "For multiple tasks: $(Inspect.toStr multipleIn)"
sequential : Task a err, Task b err, (a, b -> c) -> Task c err
sequential = \firstTask, secondTask, mapper ->
first = firstTask!
second = secondTask!
Task.ok (mapper first second)

View file

@ -1,4 +1,4 @@
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" }
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }
import pf.Stdin
import pf.Stdout

View file

@ -1,4 +1,4 @@
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" }
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }
import pf.Stdin
import pf.Stdout

View file

@ -1,4 +1,4 @@
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" }
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }
import pf.Stdout
import pf.Stderr

View file

@ -1,24 +1,25 @@
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" }
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }
import pf.Stdout
import pf.File
import pf.Path
import pf.Env
import pf.Dir
main : Task {} [Exit I32 Str]_
main =
path = Path.fromStr "out.txt"
pathStr = "out.txt"
task =
cwd = Env.cwd!
Stdout.line! "cwd: $(Path.display cwd)"
dirEntries = Dir.list! cwd
cwdPath = Env.cwd!
cwdStr = Path.display cwdPath
Stdout.line! "Current working directory: $(cwdStr)"
dirEntries = Path.listDir! cwdPath
contentsStr = Str.joinWith (List.map dirEntries Path.display) "\n "
Stdout.line! "Directory contents:\n $(contentsStr)\n"
Stdout.line! "Writing a string to out.txt"
File.writeUtf8! path "a string!"
contents = File.readUtf8! path
File.writeUtf8! pathStr "a string!"
contents = File.readUtf8! pathStr
Stdout.line! "I read the file back. Its contents: \"$(contents)\""
when Task.result! task is

View file

@ -1,4 +1,4 @@
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" }
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }
import pf.Stdin
import pf.Stdout

View file

@ -1,4 +1,4 @@
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" }
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }
import pf.Http
import pf.Stdout

View file

@ -1,10 +1,11 @@
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" }
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }
import pf.Stdout
import "ingested-file.roc" as license
import "test-file.txt" as testFile
main =
license
# Due to the functions we apply on testFile, it will be inferred as a List U8.
testFile
|> List.map Num.toU64
|> List.sum
|> Num.toStr

View file

@ -1,11 +1,11 @@
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" }
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }
import pf.Stdout
import "ingested-file.roc" as license : _ # A type hole can also be used here.
import "test-file.txt" as testFile : _ # the _ is optional
main =
# Due to how license is used, it will be a List U8.
license
# Due to the functions we apply on testFile, it will be inferred as a List U8.
testFile
|> List.map Num.toU64
|> List.sum
|> Num.toStr

View file

@ -1,4 +1,4 @@
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" }
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }
import pf.Stdout
import "ingested-file.roc" as ownCode : Str

View file

@ -0,0 +1,86 @@
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br",
}
import pf.Stdout
import pf.Task exposing [Task]
main =
file = strParam { name: "file" }
argParser =
{ cliBuild <-
file,
count: numParam { name: "count" },
doubled: numParam { name: "doubled" }
|> cliMap \d -> d * 2,
}
args = ["parse-args", "file.txt", "5", "7"]
when argParser |> parseArgs args is
Ok data -> Stdout.line "Success: $(Inspect.toStr data)"
Err (FailedToParse message) -> Stdout.line "Failed: $(message)"
ArgParseErr : [NoMoreArgs, InvalidParam ParamConfig]
ParamConfig : {
name : Str,
type : [Num, Str],
}
ArgParser out : {
params : List ParamConfig,
parser : List Str -> Result (out, List Str) ArgParseErr,
}
strParam : { name : Str } -> ArgParser Str
strParam = \{ name } ->
parser = \args ->
when args is
[] -> Err NoMoreArgs
[first, .. as rest] -> Ok (first, rest)
{ params: [{ name, type: Str }], parser }
numParam : { name : Str } -> ArgParser U64
numParam = \{ name } ->
param = { name, type: Num }
parser = \args ->
when args is
[] -> Err NoMoreArgs
[first, .. as rest] ->
when Str.toU64 first is
Ok num -> Ok (num, rest)
Err InvalidNumStr -> Err (InvalidParam param)
{ params: [param], parser }
cliMap : ArgParser a, (a -> b) -> ArgParser b
cliMap = \{ params, parser }, mapper -> {
params,
parser: \args ->
(data, afterData) <- parser args
|> Result.try
Ok (mapper data, afterData),
}
cliBuild : ArgParser a, ArgParser b, (a, b -> c) -> ArgParser c
cliBuild = \firstWeaver, secondWeaver, combine ->
allParams = List.concat firstWeaver.params secondWeaver.params
combinedParser = \args ->
(firstValue, afterFirst) <- firstWeaver.parser args
|> Result.try
(secondValue, afterSecond) <- secondWeaver.parser afterFirst
|> Result.try
Ok (combine firstValue secondValue, afterSecond)
{ params: allParams, parser: combinedParser }
parseArgs : ArgParser a, List Str -> Result a [FailedToParse Str]
parseArgs = \{ params: _, parser }, args ->
when parser (List.dropFirst args 1) is
Ok (data, []) -> Ok data
Ok (_data, extraArgs) -> Err (FailedToParse "Got $(List.len extraArgs |> Inspect.toStr) extra args")
Err NoMoreArgs -> Err (FailedToParse "I needed more args")
Err (InvalidParam param) -> Err (FailedToParse "Parameter '$(param.name)' needed a $(Inspect.toStr param.type)")

View file

@ -1,5 +1,5 @@
app [main] {
cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br",
cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br",
parser: "https://github.com/lukewilliamboswell/roc-parser/releases/download/0.5.2/9VrPjwfQQ1QeSL3CfmWr2Pr9DESdDIXy97pwpuq84Ck.tar.br",
}

View file

@ -1,13 +1,13 @@
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br",
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br",
parser: "https://github.com/lukewilliamboswell/roc-parser/releases/download/0.5.2/9VrPjwfQQ1QeSL3CfmWr2Pr9DESdDIXy97pwpuq84Ck.tar.br",
}
import pf.Stdout
import pf.Stderr
import parser.Core exposing [Parser, map, keep]
import parser.Core exposing [map, keep]
import parser.String exposing [strFromUtf8]
import parser.CSV exposing [CSV]
import parser.CSV
input : Str
input = "Airplane!,1980,\"Robert Hays,Julie Hagerty\"\r\nCaddyshack,1980,\"Chevy Chase,Rodney Dangerfield,Ted Knight,Michael O'Keefe,Bill Murray\""

View file

@ -0,0 +1 @@
Used by ingested-file-bytes.roc and ingested-file-bytes-no-ann.roc

View file

@ -79,6 +79,8 @@ mod cli_run {
#[derive(Debug, PartialEq, Eq)]
enum Arg<'a> {
ExamplePath(&'a str),
// allow because we may need PlainText in the future
#[allow(dead_code)]
PlainText(&'a str),
}
@ -799,39 +801,6 @@ mod cli_run {
)
}
#[test]
#[ignore = "currently broken in basic-cli platform"]
#[cfg_attr(windows, ignore = "missing __udivdi3 and some other symbols")]
#[serial(cli_platform)]
fn cli_args() {
test_roc_app(
"examples/cli",
"argsBROKEN.roc",
&[],
&[
Arg::PlainText("log"),
Arg::PlainText("-b"),
Arg::PlainText("3"),
Arg::PlainText("--num"),
Arg::PlainText("81"),
],
&[],
"4\n",
UseValgrind::No,
TestCliCommands::Run,
)
}
// TODO: remove in favor of cli_args once mono bugs are resolved in investigation
#[test]
#[cfg_attr(windows, ignore = "missing __udivdi3 and some other symbols")]
#[serial(cli_platform)]
fn cli_args_check() {
let path = file_path_from_root("crates/cli/tests/cli", "argsBROKEN.roc");
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
assert!(out.status.success());
}
// TODO: write a new test once mono bugs are resolved in investigation
#[test]
#[cfg(not(debug_assertions))] // https://github.com/roc-lang/roc/issues/4806
@ -988,6 +957,38 @@ mod cli_run {
)
}
#[test]
#[serial(cli_platform)]
#[cfg_attr(windows, ignore)]
fn combine_tasks_with_record_builder() {
test_roc_app(
"crates/cli/tests/cli",
"combine-tasks.roc",
&[],
&[],
&[],
"For multiple tasks: {a: 123, b: \"abc\", c: [123], d: [\"abc\"], e: {\"a\": \"b\"}}\n",
UseValgrind::No,
TestCliCommands::Run,
)
}
#[test]
#[serial(cli_platform)]
#[cfg_attr(windows, ignore)]
fn parse_args_with_record_builder() {
test_roc_app(
"crates/cli/tests/cli",
"parse-args.roc",
&[],
&[],
&[],
"Success: {count: 5, doubled: 14, file: \"file.txt\"}\n",
UseValgrind::No,
TestCliCommands::Run,
)
}
#[test]
#[serial(cli_platform)]
#[cfg_attr(windows, ignore)]
@ -998,7 +999,7 @@ mod cli_run {
&[],
&[],
&[],
"27101\n",
"6239\n",
UseValgrind::No,
TestCliCommands::Run,
)
@ -1013,7 +1014,7 @@ mod cli_run {
&[],
&[],
&[],
"27101\n",
"6239\n",
UseValgrind::No,
TestCliCommands::Run,
)

View file

@ -351,6 +351,7 @@ struct ValgrindOutput {
#[derive(Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
#[allow(dead_code)] // Some fields are unused but this allows for easy deserialization of the xml.
enum ValgrindField {
ProtocolVersion(isize),
ProtocolTool(String),

View file

@ -863,7 +863,6 @@ fn call_spec<'a>(
let closure_env = env.symbols[&passed_function.captured_environment];
let return_layout = &passed_function.return_layout;
let argument_layouts = passed_function.argument_layouts;
macro_rules! call_function {
@ -879,30 +878,6 @@ fn call_spec<'a>(
}
match op {
ListMap { xs } => {
let list = env.symbols[xs];
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let input_bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let element = builder.add_bag_get(block, input_bag)?;
let new_element = call_function!(builder, block, [element]);
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type =
layout_spec(env, builder, interner, interner.get_repr(*return_layout))?;
let state_layout = LayoutRepr::Builtin(Builtin::List(*return_layout));
let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListSortWith { xs } => {
let list = env.symbols[xs];
@ -928,109 +903,6 @@ fn call_spec<'a>(
add_loop(builder, block, state_type, init_state, loop_body)
}
ListMap2 { xs, ys } => {
let list1 = env.symbols[xs];
let list2 = env.symbols[ys];
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let input_bag_1 =
builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let input_bag_2 =
builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?;
let element_1 = builder.add_bag_get(block, input_bag_1)?;
let element_2 = builder.add_bag_get(block, input_bag_2)?;
let new_element = call_function!(builder, block, [element_1, element_2]);
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type =
layout_spec(env, builder, interner, interner.get_repr(*return_layout))?;
let state_layout = LayoutRepr::Builtin(Builtin::List(*return_layout));
let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListMap3 { xs, ys, zs } => {
let list1 = env.symbols[xs];
let list2 = env.symbols[ys];
let list3 = env.symbols[zs];
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let input_bag_1 =
builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let input_bag_2 =
builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?;
let input_bag_3 =
builder.add_get_tuple_field(block, list3, LIST_BAG_INDEX)?;
let element_1 = builder.add_bag_get(block, input_bag_1)?;
let element_2 = builder.add_bag_get(block, input_bag_2)?;
let element_3 = builder.add_bag_get(block, input_bag_3)?;
let new_element =
call_function!(builder, block, [element_1, element_2, element_3]);
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type =
layout_spec(env, builder, interner, interner.get_repr(*return_layout))?;
let state_layout = LayoutRepr::Builtin(Builtin::List(*return_layout));
let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListMap4 { xs, ys, zs, ws } => {
let list1 = env.symbols[xs];
let list2 = env.symbols[ys];
let list3 = env.symbols[zs];
let list4 = env.symbols[ws];
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let input_bag_1 =
builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let input_bag_2 =
builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?;
let input_bag_3 =
builder.add_get_tuple_field(block, list3, LIST_BAG_INDEX)?;
let input_bag_4 =
builder.add_get_tuple_field(block, list4, LIST_BAG_INDEX)?;
let element_1 = builder.add_bag_get(block, input_bag_1)?;
let element_2 = builder.add_bag_get(block, input_bag_2)?;
let element_3 = builder.add_bag_get(block, input_bag_3)?;
let element_4 = builder.add_bag_get(block, input_bag_4)?;
let new_element = call_function!(
builder,
block,
[element_1, element_2, element_3, element_4]
);
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type =
layout_spec(env, builder, interner, interner.get_repr(*return_layout))?;
let state_layout = LayoutRepr::Builtin(Builtin::List(*return_layout));
let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
}
}
}

View file

@ -540,7 +540,7 @@ pub fn rebuild_host(
// on windows, we need the nightly toolchain so we can use `-Z export-executable-symbols`
// using `+nightly` only works when running cargo through rustup
let mut cmd = rustup();
cmd.args(["run", "nightly-2023-12-21", "cargo"]);
cmd.args(["run", "nightly-2024-02-03", "cargo"]);
cmd
} else {
@ -1105,6 +1105,10 @@ fn link_macos(
.args(input_paths)
.args(extra_link_flags());
if get_xcode_version() >= 15.0 {
ld_command.arg("-ld_classic");
}
let sdk_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib";
if Path::new(sdk_path).exists() {
ld_command.arg(format!("-L{sdk_path}"));
@ -1187,6 +1191,24 @@ fn get_macos_version() -> String {
.join(".")
}
fn get_xcode_version() -> f32 {
let mut cmd = Command::new("xcodebuild");
cmd.arg("-version");
debug_print_command(&cmd);
cmd.output()
.map_err(|_| ())
.and_then(|out| String::from_utf8(out.stdout).map_err(|_| ()))
.and_then(|str| {
str.split_whitespace()
.nth(1)
.map(|s| s.to_string())
.ok_or(())
})
.and_then(|version| version.parse::<f32>().map_err(|_| ()))
.unwrap_or(0.0)
}
fn link_wasm32(
_target: Target,
output_path: PathBuf,

View file

@ -22,7 +22,6 @@ use std::ffi::OsStr;
use std::ops::Deref;
use std::{
path::{Path, PathBuf},
thread::JoinHandle,
time::{Duration, Instant},
};
@ -696,20 +695,9 @@ pub fn standard_load_config(
BuildOrdering::AlwaysBuild => ExecutionMode::Executable,
};
// UNSTABLE(lambda-erasure)
let function_kind = if cfg!(debug_assertions) {
if std::env::var("EXPERIMENTAL_ROC_ERASE").is_ok() {
FunctionKind::Erased
} else {
FunctionKind::LambdaSet
}
} else {
FunctionKind::LambdaSet
};
LoadConfig {
target,
function_kind,
function_kind: FunctionKind::from_env(),
render: RenderTarget::ColorTerminal,
palette: DEFAULT_PALETTE,
threading,
@ -915,11 +903,6 @@ fn build_loaded_file<'a>(
let problems = report_problems_monomorphized(&mut loaded);
let loaded = loaded;
enum HostRebuildTiming {
BeforeApp(u128),
ConcurrentWithApp(JoinHandle<u128>),
}
let opt_rebuild_timing = if let Some(rebuild_thread) = rebuild_thread {
if linking_strategy == LinkingStrategy::Additive {
let rebuild_duration = rebuild_thread
@ -930,9 +913,9 @@ fn build_loaded_file<'a>(
println!("Finished rebuilding the platform in {rebuild_duration} ms\n");
}
Some(HostRebuildTiming::BeforeApp(rebuild_duration))
None
} else {
Some(HostRebuildTiming::ConcurrentWithApp(rebuild_thread))
Some(rebuild_thread)
}
} else {
None
@ -977,7 +960,7 @@ fn build_loaded_file<'a>(
);
}
if let Some(HostRebuildTiming::ConcurrentWithApp(thread)) = opt_rebuild_timing {
if let Some(thread) = opt_rebuild_timing {
let rebuild_duration = thread.join().expect("Failed to (re)build platform.");
if emit_timings && !is_platform_prebuilt {
@ -1208,8 +1191,7 @@ pub fn check_file<'a>(
let load_config = LoadConfig {
target,
// TODO: we may not want this for just checking.
function_kind: FunctionKind::LambdaSet,
function_kind: FunctionKind::from_env(),
// TODO: expose this from CLI?
render: RenderTarget::ColorTerminal,
palette: DEFAULT_PALETTE,

View file

@ -98,7 +98,7 @@ fn generateObjectFile(
const obj_file = obj.getEmittedBin();
var suffix =
const suffix =
if (target.os_tag == .windows)
"obj"
else

View file

@ -84,12 +84,12 @@ pub const RocList = extern struct {
return true;
}
pub fn fromSlice(comptime T: type, slice: []const T) RocList {
pub fn fromSlice(comptime T: type, slice: []const T, elements_refcounted: bool) RocList {
if (slice.len == 0) {
return RocList.empty();
}
var list = allocate(@alignOf(T), slice.len, @sizeOf(T));
const list = allocate(@alignOf(T), slice.len, @sizeOf(T), elements_refcounted);
if (slice.len > 0) {
const dest = list.bytes orelse unreachable;
@ -107,7 +107,7 @@ pub const RocList = extern struct {
// The pointer is to just after the refcount.
// For big lists, it just returns their bytes pointer.
// For seamless slices, it returns the pointer stored in capacity_or_alloc_ptr.
pub fn getAllocationPtr(self: RocList) ?[*]u8 {
pub fn getAllocationDataPtr(self: RocList) ?[*]u8 {
const list_alloc_ptr = @intFromPtr(self.bytes);
const slice_alloc_ptr = self.capacity_or_alloc_ptr << 1;
const slice_mask = self.seamlessSliceMask();
@ -115,9 +115,60 @@ pub const RocList = extern struct {
return @as(?[*]u8, @ptrFromInt(alloc_ptr));
}
pub fn decref(self: RocList, alignment: u32) void {
// This function is only valid if the list has refcounted elements.
fn getAllocationElementCount(self: RocList) usize {
if (self.isSeamlessSlice()) {
// Seamless slices always refer to an underlying allocation.
const alloc_ptr = self.getAllocationDataPtr() orelse unreachable;
// - 1 is refcount.
// - 2 is size on heap.
const ptr = @as([*]usize, @ptrCast(@alignCast(alloc_ptr))) - 2;
return ptr[0];
} else {
return self.length;
}
}
// This needs to be called when creating seamless slices from unique list.
// It will put the allocation size on the heap to enable the seamless slice to free the underlying allocation.
fn setAllocationElementCount(self: RocList, elements_refcounted: bool) void {
if (elements_refcounted and !self.isSeamlessSlice()) {
// - 1 is refcount.
// - 2 is size on heap.
const ptr = @as([*]usize, @alignCast(@ptrCast(self.getAllocationDataPtr()))) - 2;
ptr[0] = self.length;
}
}
pub fn incref(self: RocList, amount: isize, elements_refcounted: bool) void {
// If the list is unique and not a seamless slice, the length needs to be store on the heap if the elements are refcounted.
if (elements_refcounted and self.isUnique() and !self.isSeamlessSlice()) {
if (self.getAllocationDataPtr()) |source| {
// - 1 is refcount.
// - 2 is size on heap.
const ptr = @as([*]usize, @alignCast(@ptrCast(source))) - 2;
ptr[0] = self.length;
}
}
utils.increfDataPtrC(self.getAllocationDataPtr(), amount);
}
pub fn decref(self: RocList, alignment: u32, element_width: usize, elements_refcounted: bool, dec: Dec) void {
// If unique, decref will free the list. Before that happens, all elements must be decremented.
if (elements_refcounted and self.isUnique()) {
if (self.getAllocationDataPtr()) |source| {
const count = self.getAllocationElementCount();
var i: usize = 0;
while (i < count) : (i += 1) {
const element = source + i * element_width;
dec(element);
}
}
}
// We use the raw capacity to ensure we always decrement the refcount of seamless slices.
utils.decref(self.getAllocationPtr(), self.capacity_or_alloc_ptr, alignment);
utils.decref(self.getAllocationDataPtr(), self.capacity_or_alloc_ptr, alignment, elements_refcounted);
}
pub fn elements(self: RocList, comptime T: type) ?[*]T {
@ -134,7 +185,7 @@ pub const RocList = extern struct {
return utils.REFCOUNT_ONE;
}
const ptr: [*]usize = @as([*]usize, @ptrCast(@alignCast(self.bytes)));
const ptr: [*]usize = @as([*]usize, @ptrCast(@alignCast(self.getAllocationDataPtr())));
return (ptr - 1)[0];
}
@ -142,15 +193,22 @@ pub const RocList = extern struct {
return self.refcountMachine() - utils.REFCOUNT_ONE + 1;
}
pub fn makeUniqueExtra(self: RocList, alignment: u32, element_width: usize, update_mode: UpdateMode) RocList {
pub fn makeUniqueExtra(self: RocList, alignment: u32, element_width: usize, elements_refcounted: bool, dec: Dec, update_mode: UpdateMode) RocList {
if (update_mode == .InPlace) {
return self;
} else {
return self.makeUnique(alignment, element_width);
return self.makeUnique(alignment, element_width, elements_refcounted, dec);
}
}
pub fn makeUnique(self: RocList, alignment: u32, element_width: usize) RocList {
pub fn makeUnique(
self: RocList,
alignment: u32,
element_width: usize,
elements_refcounted: bool,
inc: Inc,
dec: Dec,
) RocList {
if (self.isUnique()) {
return self;
}
@ -158,12 +216,12 @@ pub const RocList = extern struct {
if (self.isEmpty()) {
// Empty is not necessarily unique on it's own.
// The list could have capacity and be shared.
self.decref(alignment);
self.decref(alignment, element_width, elements_refcounted, dec);
return RocList.empty();
}
// unfortunately, we have to clone
var new_list = RocList.allocate(alignment, self.length, element_width);
const new_list = RocList.allocate(alignment, self.length, element_width, elements_refcounted);
var old_bytes: [*]u8 = @as([*]u8, @ptrCast(self.bytes));
var new_bytes: [*]u8 = @as([*]u8, @ptrCast(new_list.bytes));
@ -171,8 +229,15 @@ pub const RocList = extern struct {
const number_of_bytes = self.len() * element_width;
@memcpy(new_bytes[0..number_of_bytes], old_bytes[0..number_of_bytes]);
// NOTE we fuse an increment of all keys/values with a decrement of the input list.
self.decref(alignment);
// Increment refcount of all elements now in a new list.
if (elements_refcounted) {
var i: usize = 0;
while (i < self.len()) : (i += 1) {
inc(new_bytes + i * element_width);
}
}
self.decref(alignment, element_width, elements_refcounted, dec);
return new_list;
}
@ -181,6 +246,7 @@ pub const RocList = extern struct {
alignment: u32,
length: usize,
element_width: usize,
elements_refcounted: bool,
) RocList {
if (length == 0) {
return empty();
@ -189,7 +255,7 @@ pub const RocList = extern struct {
const capacity = utils.calculateCapacity(0, length, element_width);
const data_bytes = capacity * element_width;
return RocList{
.bytes = utils.allocateWithRefcount(data_bytes, alignment),
.bytes = utils.allocateWithRefcount(data_bytes, alignment, elements_refcounted),
.length = length,
.capacity_or_alloc_ptr = capacity,
};
@ -199,6 +265,7 @@ pub const RocList = extern struct {
alignment: u32,
length: usize,
element_width: usize,
elements_refcounted: bool,
) RocList {
if (length == 0) {
return empty();
@ -206,7 +273,7 @@ pub const RocList = extern struct {
const data_bytes = length * element_width;
return RocList{
.bytes = utils.allocateWithRefcount(data_bytes, alignment),
.bytes = utils.allocateWithRefcount(data_bytes, alignment, elements_refcounted),
.length = length,
.capacity_or_alloc_ptr = length,
};
@ -217,6 +284,8 @@ pub const RocList = extern struct {
alignment: u32,
new_length: usize,
element_width: usize,
elements_refcounted: bool,
inc: Inc,
) RocList {
if (self.bytes) |source_ptr| {
if (self.isUnique() and !self.isSeamlessSlice()) {
@ -225,13 +294,13 @@ pub const RocList = extern struct {
return RocList{ .bytes = self.bytes, .length = new_length, .capacity_or_alloc_ptr = capacity };
} else {
const new_capacity = utils.calculateCapacity(capacity, new_length, element_width);
const new_source = utils.unsafeReallocate(source_ptr, alignment, capacity, new_capacity, element_width);
const new_source = utils.unsafeReallocate(source_ptr, alignment, capacity, new_capacity, element_width, elements_refcounted);
return RocList{ .bytes = new_source, .length = new_length, .capacity_or_alloc_ptr = new_capacity };
}
}
return self.reallocateFresh(alignment, new_length, element_width);
return self.reallocateFresh(alignment, new_length, element_width, elements_refcounted, inc);
}
return RocList.allocate(alignment, new_length, element_width);
return RocList.allocate(alignment, new_length, element_width, elements_refcounted);
}
/// reallocate by explicitly making a new allocation and copying elements over
@ -240,244 +309,52 @@ pub const RocList = extern struct {
alignment: u32,
new_length: usize,
element_width: usize,
elements_refcounted: bool,
inc: Inc,
) RocList {
const old_length = self.length;
const result = RocList.allocate(alignment, new_length, element_width);
const result = RocList.allocate(alignment, new_length, element_width, elements_refcounted);
// transfer the memory
if (self.bytes) |source_ptr| {
// transfer the memory
const dest_ptr = result.bytes orelse unreachable;
@memcpy(dest_ptr[0..(old_length * element_width)], source_ptr[0..(old_length * element_width)]);
@memset(dest_ptr[(old_length * element_width)..(new_length * element_width)], 0);
// Increment refcount of all elements now in a new list.
if (elements_refcounted) {
var i: usize = 0;
while (i < old_length) : (i += 1) {
inc(dest_ptr + i * element_width);
}
}
}
self.decref(alignment);
// Calls utils.decref directly to avoid decrementing the refcount of elements.
utils.decref(self.getAllocationDataPtr(), self.capacity_or_alloc_ptr, alignment, elements_refcounted);
return result;
}
};
const Caller0 = *const fn (?[*]u8, ?[*]u8) callconv(.C) void;
const Caller1 = *const fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller2 = *const fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller3 = *const fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller4 = *const fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
pub fn listMap(
list: RocList,
caller: Caller1,
data: Opaque,
inc_n_data: IncN,
data_is_owned: bool,
alignment: u32,
old_element_width: usize,
new_element_width: usize,
) callconv(.C) RocList {
if (list.bytes) |source_ptr| {
const size = list.len();
var i: usize = 0;
const output = RocList.allocate(alignment, size, new_element_width);
const target_ptr = output.bytes orelse unreachable;
if (data_is_owned) {
inc_n_data(data, size);
}
while (i < size) : (i += 1) {
caller(data, source_ptr + (i * old_element_width), target_ptr + (i * new_element_width));
}
return output;
} else {
return RocList.empty();
}
pub fn listIncref(list: RocList, amount: isize, elements_refcounted: bool) callconv(.C) void {
list.incref(amount, elements_refcounted);
}
fn decrementTail(list: RocList, start_index: usize, element_width: usize, dec: Dec) void {
if (list.bytes) |source| {
var i = start_index;
while (i < list.len()) : (i += 1) {
const element = source + i * element_width;
dec(element);
}
}
}
pub fn listMap2(
list1: RocList,
list2: RocList,
caller: Caller2,
data: Opaque,
inc_n_data: IncN,
data_is_owned: bool,
alignment: u32,
a_width: usize,
b_width: usize,
c_width: usize,
dec_a: Dec,
dec_b: Dec,
) callconv(.C) RocList {
const output_length = @min(list1.len(), list2.len());
// if the lists don't have equal length, we must consume the remaining elements
// In this case we consume by (recursively) decrementing the elements
decrementTail(list1, output_length, a_width, dec_a);
decrementTail(list2, output_length, b_width, dec_b);
if (data_is_owned) {
inc_n_data(data, output_length);
}
if (list1.bytes) |source_a| {
if (list2.bytes) |source_b| {
const output = RocList.allocate(alignment, output_length, c_width);
const target_ptr = output.bytes orelse unreachable;
var i: usize = 0;
while (i < output_length) : (i += 1) {
const element_a = source_a + i * a_width;
const element_b = source_b + i * b_width;
const target = target_ptr + i * c_width;
caller(data, element_a, element_b, target);
}
return output;
} else {
return RocList.empty();
}
} else {
return RocList.empty();
}
}
pub fn listMap3(
list1: RocList,
list2: RocList,
list3: RocList,
caller: Caller3,
data: Opaque,
inc_n_data: IncN,
data_is_owned: bool,
alignment: u32,
a_width: usize,
b_width: usize,
c_width: usize,
d_width: usize,
dec_a: Dec,
dec_b: Dec,
dec_c: Dec,
) callconv(.C) RocList {
const smaller_length = @min(list1.len(), list2.len());
const output_length = @min(smaller_length, list3.len());
decrementTail(list1, output_length, a_width, dec_a);
decrementTail(list2, output_length, b_width, dec_b);
decrementTail(list3, output_length, c_width, dec_c);
if (data_is_owned) {
inc_n_data(data, output_length);
}
if (list1.bytes) |source_a| {
if (list2.bytes) |source_b| {
if (list3.bytes) |source_c| {
const output = RocList.allocate(alignment, output_length, d_width);
const target_ptr = output.bytes orelse unreachable;
var i: usize = 0;
while (i < output_length) : (i += 1) {
const element_a = source_a + i * a_width;
const element_b = source_b + i * b_width;
const element_c = source_c + i * c_width;
const target = target_ptr + i * d_width;
caller(data, element_a, element_b, element_c, target);
}
return output;
} else {
return RocList.empty();
}
} else {
return RocList.empty();
}
} else {
return RocList.empty();
}
}
pub fn listMap4(
list1: RocList,
list2: RocList,
list3: RocList,
list4: RocList,
caller: Caller4,
data: Opaque,
inc_n_data: IncN,
data_is_owned: bool,
alignment: u32,
a_width: usize,
b_width: usize,
c_width: usize,
d_width: usize,
e_width: usize,
dec_a: Dec,
dec_b: Dec,
dec_c: Dec,
dec_d: Dec,
) callconv(.C) RocList {
const output_length = @min(@min(list1.len(), list2.len()), @min(list3.len(), list4.len()));
decrementTail(list1, output_length, a_width, dec_a);
decrementTail(list2, output_length, b_width, dec_b);
decrementTail(list3, output_length, c_width, dec_c);
decrementTail(list4, output_length, d_width, dec_d);
if (data_is_owned) {
inc_n_data(data, output_length);
}
if (list1.bytes) |source_a| {
if (list2.bytes) |source_b| {
if (list3.bytes) |source_c| {
if (list4.bytes) |source_d| {
const output = RocList.allocate(alignment, output_length, e_width);
const target_ptr = output.bytes orelse unreachable;
var i: usize = 0;
while (i < output_length) : (i += 1) {
const element_a = source_a + i * a_width;
const element_b = source_b + i * b_width;
const element_c = source_c + i * c_width;
const element_d = source_d + i * d_width;
const target = target_ptr + i * e_width;
caller(data, element_a, element_b, element_c, element_d, target);
}
return output;
} else {
return RocList.empty();
}
} else {
return RocList.empty();
}
} else {
return RocList.empty();
}
} else {
return RocList.empty();
}
pub fn listDecref(list: RocList, alignment: u32, element_width: usize, elements_refcounted: bool, dec: Dec) callconv(.C) void {
list.decref(alignment, element_width, elements_refcounted, dec);
}
pub fn listWithCapacity(
capacity: u64,
alignment: u32,
element_width: usize,
elements_refcounted: bool,
inc: Inc,
) callconv(.C) RocList {
return listReserve(RocList.empty(), alignment, capacity, element_width, .InPlace);
return listReserve(RocList.empty(), alignment, capacity, element_width, elements_refcounted, inc, .InPlace);
}
pub fn listReserve(
@ -485,6 +362,8 @@ pub fn listReserve(
alignment: u32,
spare: u64,
element_width: usize,
elements_refcounted: bool,
inc: Inc,
update_mode: UpdateMode,
) callconv(.C) RocList {
const original_len = list.len();
@ -497,7 +376,7 @@ pub fn listReserve(
// Make sure on 32-bit targets we don't accidentally wrap when we cast our U64 desired capacity to U32.
const reserve_size: u64 = @min(desired_cap, @as(u64, @intCast(std.math.maxInt(usize))));
var output = list.reallocate(alignment, @as(usize, @intCast(reserve_size)), element_width);
var output = list.reallocate(alignment, @as(usize, @intCast(reserve_size)), element_width, elements_refcounted, inc);
output.length = original_len;
return output;
}
@ -507,6 +386,9 @@ pub fn listReleaseExcessCapacity(
list: RocList,
alignment: u32,
element_width: usize,
elements_refcounted: bool,
inc: Inc,
dec: Dec,
update_mode: UpdateMode,
) callconv(.C) RocList {
const old_length = list.len();
@ -514,16 +396,27 @@ pub fn listReleaseExcessCapacity(
if ((update_mode == .InPlace or list.isUnique()) and list.capacity_or_alloc_ptr == old_length) {
return list;
} else if (old_length == 0) {
list.decref(alignment);
list.decref(alignment, element_width, elements_refcounted, dec);
return RocList.empty();
} else {
var output = RocList.allocateExact(alignment, old_length, element_width);
// TODO: This can be made more efficient, but has to work around the `decref`.
// If the list is unique, we can avoid incrementing and decrementing the live items.
// We can just decrement the dead elements and free the old list.
// This pattern is also like true in other locations like listConcat and listDropAt.
const output = RocList.allocateExact(alignment, old_length, element_width, elements_refcounted);
if (list.bytes) |source_ptr| {
const dest_ptr = output.bytes orelse unreachable;
@memcpy(dest_ptr[0..(old_length * element_width)], source_ptr[0..(old_length * element_width)]);
if (elements_refcounted) {
var i: usize = 0;
while (i < old_length) : (i += 1) {
const element = source_ptr + i * element_width;
inc(element);
}
}
}
list.decref(alignment);
list.decref(alignment, element_width, elements_refcounted, dec);
return output;
}
}
@ -547,15 +440,30 @@ pub fn listAppendUnsafe(
return output;
}
fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width: usize, update_mode: UpdateMode) callconv(.C) RocList {
const with_capacity = listReserve(list, alignment, 1, element_width, update_mode);
fn listAppend(
list: RocList,
alignment: u32,
element: Opaque,
element_width: usize,
elements_refcounted: bool,
inc: Inc,
update_mode: UpdateMode,
) callconv(.C) RocList {
const with_capacity = listReserve(list, alignment, 1, element_width, elements_refcounted, inc, update_mode);
return listAppendUnsafe(with_capacity, element, element_width);
}
pub fn listPrepend(list: RocList, alignment: u32, element: Opaque, element_width: usize) callconv(.C) RocList {
pub fn listPrepend(
list: RocList,
alignment: u32,
element: Opaque,
element_width: usize,
elements_refcounted: bool,
inc: Inc,
) callconv(.C) RocList {
const old_length = list.len();
// TODO: properly wire in update mode.
var with_capacity = listReserve(list, alignment, 1, element_width, .Immutable);
var with_capacity = listReserve(list, alignment, 1, element_width, elements_refcounted, inc, .Immutable);
with_capacity.length += 1;
// can't use one memcpy here because source and target overlap
@ -586,8 +494,15 @@ pub fn listSwap(
element_width: usize,
index_1: u64,
index_2: u64,
elements_refcounted: bool,
inc: Inc,
dec: Dec,
update_mode: UpdateMode,
) callconv(.C) RocList {
// Early exit to avoid swapping the same element.
if (index_1 == index_2)
return list;
const size = @as(u64, @intCast(list.len()));
if (index_1 == index_2 or index_1 >= size or index_2 >= size) {
// Either one index was out of bounds, or both indices were the same; just return
@ -598,7 +513,7 @@ pub fn listSwap(
if (update_mode == .InPlace) {
break :blk list;
} else {
break :blk list.makeUnique(alignment, element_width);
break :blk list.makeUnique(alignment, element_width, elements_refcounted, inc, dec);
}
};
@ -616,26 +531,30 @@ pub fn listSublist(
list: RocList,
alignment: u32,
element_width: usize,
elements_refcounted: bool,
start_u64: u64,
len_u64: u64,
dec: Dec,
) callconv(.C) RocList {
const size = list.len();
if (size == 0 or start_u64 >= @as(u64, @intCast(size))) {
// Decrement the reference counts of all elements.
if (list.bytes) |source_ptr| {
var i: usize = 0;
while (i < size) : (i += 1) {
const element = source_ptr + i * element_width;
dec(element);
}
}
if (size == 0 or len_u64 == 0 or start_u64 >= @as(u64, @intCast(size))) {
if (list.isUnique()) {
// Decrement the reference counts of all elements.
if (list.bytes) |source_ptr| {
if (elements_refcounted) {
var i: usize = 0;
while (i < size) : (i += 1) {
const element = source_ptr + i * element_width;
dec(element);
}
}
}
var output = list;
output.length = 0;
return output;
}
list.decref(alignment);
list.decref(alignment, element_width, elements_refcounted, dec);
return RocList.empty();
}
@ -643,7 +562,6 @@ pub fn listSublist(
// This cast is lossless because we would have early-returned already
// if `start_u64` were greater than `size`, and `size` fits in usize.
const start: usize = @intCast(start_u64);
const drop_start_len = start;
// (size - start) can't overflow because we would have early-returned already
// if `start` were greater than `size`.
@ -654,32 +572,25 @@ pub fn listSublist(
// than something that fit in usize.
const keep_len = @as(usize, @intCast(@min(len_u64, @as(u64, @intCast(size_minus_start)))));
// This can't overflow because if len > size_minus_start,
// then keep_len == size_minus_start and this will be 0.
// Alternatively, if len <= size_minus_start, then keep_len will
// be equal to len, meaning keep_len <= size_minus_start too,
// which in turn means this won't overflow.
const drop_end_len = size_minus_start - keep_len;
// Decrement the reference counts of elements before `start`.
var i: usize = 0;
while (i < drop_start_len) : (i += 1) {
const element = source_ptr + i * element_width;
dec(element);
}
// Decrement the reference counts of elements after `start + keep_len`.
i = 0;
while (i < drop_end_len) : (i += 1) {
const element = source_ptr + (start + keep_len + i) * element_width;
dec(element);
}
if (start == 0 and list.isUnique()) {
// The list is unique, we actually have to decrement refcounts to elements we aren't keeping around.
// Decrement the reference counts of elements after `start + keep_len`.
if (elements_refcounted) {
const drop_end_len = size_minus_start - keep_len;
var i: usize = 0;
while (i < drop_end_len) : (i += 1) {
const element = source_ptr + (start + keep_len + i) * element_width;
dec(element);
}
}
var output = list;
output.length = keep_len;
return output;
} else {
if (list.isUnique()) {
list.setAllocationElementCount(elements_refcounted);
}
const list_alloc_ptr = (@intFromPtr(source_ptr) >> 1) | SEAMLESS_SLICE_BIT;
const slice_alloc_ptr = list.capacity_or_alloc_ptr;
const slice_mask = list.seamlessSliceMask();
@ -699,7 +610,9 @@ pub fn listDropAt(
list: RocList,
alignment: u32,
element_width: usize,
elements_refcounted: bool,
drop_index_u64: u64,
inc: Inc,
dec: Dec,
) callconv(.C) RocList {
const size = list.len();
@ -708,11 +621,11 @@ pub fn listDropAt(
// For simplicity, do this by calling listSublist.
// In the future, we can test if it is faster to manually inline the important parts here.
if (drop_index_u64 == 0) {
return listSublist(list, alignment, element_width, 1, size -| 1, dec);
return listSublist(list, alignment, element_width, elements_refcounted, 1, size -| 1, dec);
} else if (drop_index_u64 == size_u64 - 1) { // It's fine if (size - 1) wraps on size == 0 here,
// because if size is 0 then it's always fine for this branch to be taken; no
// matter what drop_index was, we're size == 0, so empty list will always be returned.
return listSublist(list, alignment, element_width, 0, size -| 1, dec);
return listSublist(list, alignment, element_width, elements_refcounted, 0, size -| 1, dec);
}
if (list.bytes) |source_ptr| {
@ -724,15 +637,17 @@ pub fn listDropAt(
// were >= than `size`, and we know `size` fits in usize.
const drop_index: usize = @intCast(drop_index_u64);
const element = source_ptr + drop_index * element_width;
dec(element);
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);
list.decref(alignment, element_width, elements_refcounted, dec);
return RocList.empty();
}
@ -751,7 +666,7 @@ pub fn listDropAt(
return new_list;
}
const output = RocList.allocate(alignment, size - 1, element_width);
const output = RocList.allocate(alignment, size - 1, element_width, elements_refcounted);
const target_ptr = output.bytes orelse unreachable;
const head_size = drop_index * element_width;
@ -762,7 +677,15 @@ pub fn listDropAt(
const tail_size = (size - drop_index - 1) * element_width;
@memcpy(tail_target[0..tail_size], tail_source[0..tail_size]);
list.decref(alignment);
if (elements_refcounted) {
var i: usize = 0;
while (i < output.len()) : (i += 1) {
const cloned_elem = target_ptr + i * element_width;
inc(cloned_elem);
}
}
list.decref(alignment, element_width, elements_refcounted, dec);
return output;
} else {
@ -812,8 +735,11 @@ pub fn listSortWith(
data_is_owned: bool,
alignment: u32,
element_width: usize,
elements_refcounted: bool,
inc: Inc,
dec: Dec,
) callconv(.C) RocList {
var list = input.makeUnique(alignment, element_width);
var list = input.makeUnique(alignment, element_width, elements_refcounted, inc, dec);
if (data_is_owned) {
inc_n_data(data, list.len());
@ -845,7 +771,7 @@ fn swap(width_initial: usize, p1: [*]u8, p2: [*]u8) void {
var ptr2 = p2;
var buffer_actual: [threshold]u8 = undefined;
var buffer: [*]u8 = buffer_actual[0..];
const buffer: [*]u8 = buffer_actual[0..];
while (true) {
if (width < threshold) {
@ -863,43 +789,60 @@ fn swap(width_initial: usize, p1: [*]u8, p2: [*]u8) void {
}
fn swapElements(source_ptr: [*]u8, element_width: usize, index_1: usize, index_2: usize) void {
var element_at_i = source_ptr + (index_1 * element_width);
var element_at_j = source_ptr + (index_2 * element_width);
const element_at_i = source_ptr + (index_1 * element_width);
const element_at_j = source_ptr + (index_2 * element_width);
return swap(element_width, element_at_i, element_at_j);
}
pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_width: usize) callconv(.C) RocList {
pub fn listConcat(
list_a: RocList,
list_b: RocList,
alignment: u32,
element_width: usize,
elements_refcounted: bool,
inc: Inc,
dec: Dec,
) callconv(.C) RocList {
// NOTE we always use list_a! because it is owned, we must consume it, and it may have unused capacity
if (list_b.isEmpty()) {
if (list_a.getCapacity() == 0) {
// a could be a seamless slice, so we still need to decref.
list_a.decref(alignment);
list_a.decref(alignment, element_width, elements_refcounted, dec);
return list_b;
} else {
// we must consume this list. Even though it has no elements, it could still have capacity
list_b.decref(alignment);
list_b.decref(alignment, element_width, elements_refcounted, dec);
return list_a;
}
} else if (list_a.isUnique()) {
const total_length: usize = list_a.len() + list_b.len();
const resized_list_a = list_a.reallocate(alignment, total_length, element_width);
const resized_list_a = list_a.reallocate(alignment, total_length, element_width, elements_refcounted, inc);
// These must exist, otherwise, the lists would have been empty.
const source_a = resized_list_a.bytes orelse unreachable;
const source_b = list_b.bytes orelse unreachable;
@memcpy(source_a[(list_a.len() * element_width)..(total_length * element_width)], source_b[0..(list_b.len() * element_width)]);
// Increment refcount of all cloned elements.
if (elements_refcounted) {
var i: usize = 0;
while (i < list_b.len()) : (i += 1) {
const cloned_elem = source_b + i * element_width;
inc(cloned_elem);
}
}
// decrement list b.
list_b.decref(alignment);
list_b.decref(alignment, element_width, elements_refcounted, dec);
return resized_list_a;
} else if (list_b.isUnique()) {
const total_length: usize = list_a.len() + list_b.len();
const resized_list_b = list_b.reallocate(alignment, total_length, element_width);
const resized_list_b = list_b.reallocate(alignment, total_length, element_width, elements_refcounted, inc);
// These must exist, otherwise, the lists would have been empty.
const source_a = list_a.bytes orelse unreachable;
@ -913,14 +856,23 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
mem.copyBackwards(u8, source_b[byte_count_a .. byte_count_a + byte_count_b], source_b[0..byte_count_b]);
@memcpy(source_b[0..byte_count_a], source_a[0..byte_count_a]);
// Increment refcount of all cloned elements.
if (elements_refcounted) {
var i: usize = 0;
while (i < list_a.len()) : (i += 1) {
const cloned_elem = source_a + i * element_width;
inc(cloned_elem);
}
}
// decrement list a.
list_a.decref(alignment);
list_a.decref(alignment, element_width, elements_refcounted, dec);
return resized_list_b;
}
const total_length: usize = list_a.len() + list_b.len();
const output = RocList.allocate(alignment, total_length, element_width);
const output = RocList.allocate(alignment, total_length, element_width, elements_refcounted);
// These must exist, otherwise, the lists would have been empty.
const target = output.bytes orelse unreachable;
@ -930,9 +882,23 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
@memcpy(target[0..(list_a.len() * element_width)], source_a[0..(list_a.len() * element_width)]);
@memcpy(target[(list_a.len() * element_width)..(total_length * element_width)], source_b[0..(list_b.len() * element_width)]);
// Increment refcount of all cloned elements.
if (elements_refcounted) {
var i: usize = 0;
while (i < list_a.len()) : (i += 1) {
const cloned_elem = source_a + i * element_width;
inc(cloned_elem);
}
i = 0;
while (i < list_b.len()) : (i += 1) {
const cloned_elem = source_b + i * element_width;
inc(cloned_elem);
}
}
// decrement list a and b.
list_a.decref(alignment);
list_b.decref(alignment);
list_a.decref(alignment, element_width, elements_refcounted, dec);
list_b.decref(alignment, element_width, elements_refcounted, dec);
return output;
}
@ -960,6 +926,9 @@ pub fn listReplace(
index: u64,
element: Opaque,
element_width: usize,
elements_refcounted: bool,
inc: Inc,
dec: Dec,
out_element: ?[*]u8,
) callconv(.C) RocList {
// INVARIANT: bounds checking happens on the roc side
@ -969,7 +938,8 @@ pub fn listReplace(
// so we don't do a bounds check here. Hence, the list is also non-empty,
// because inserting into an empty list is always out of bounds,
// and it's always safe to cast index to usize.
return listReplaceInPlaceHelp(list.makeUnique(alignment, element_width), @as(usize, @intCast(index)), element, element_width, out_element);
// because inserting into an empty list is always out of bounds
return listReplaceInPlaceHelp(list.makeUnique(alignment, element_width, elements_refcounted, inc, dec), @as(usize, @intCast(index)), element, element_width, out_element);
}
inline fn listReplaceInPlaceHelp(
@ -1001,8 +971,11 @@ pub fn listClone(
list: RocList,
alignment: u32,
element_width: usize,
elements_refcounted: bool,
inc: Inc,
dec: Dec,
) callconv(.C) RocList {
return list.makeUnique(alignment, element_width);
return list.makeUnique(alignment, element_width, elements_refcounted, inc, dec);
}
pub fn listCapacity(
@ -1014,23 +987,25 @@ pub fn listCapacity(
pub fn listAllocationPtr(
list: RocList,
) callconv(.C) ?[*]u8 {
return list.getAllocationPtr();
return list.getAllocationDataPtr();
}
fn rcNone(_: ?[*]u8) callconv(.C) void {}
test "listConcat: non-unique with unique overlapping" {
var nonUnique = RocList.fromSlice(u8, ([_]u8{1})[0..]);
var bytes: [*]u8 = @as([*]u8, @ptrCast(nonUnique.bytes));
var nonUnique = RocList.fromSlice(u8, ([_]u8{1})[0..], false);
const bytes: [*]u8 = @as([*]u8, @ptrCast(nonUnique.bytes));
const ptr_width = @sizeOf(usize);
const refcount_ptr = @as([*]isize, @ptrCast(@as([*]align(ptr_width) u8, @alignCast(bytes)) - ptr_width));
utils.increfRcPtrC(&refcount_ptr[0], 1);
defer nonUnique.decref(@sizeOf(u8)); // listConcat will dec the other refcount
defer nonUnique.decref(@alignOf(u8), @sizeOf(u8), false, rcNone); // listConcat will dec the other refcount
var unique = RocList.fromSlice(u8, ([_]u8{ 2, 3, 4 })[0..]);
defer unique.decref(@sizeOf(u8));
var unique = RocList.fromSlice(u8, ([_]u8{ 2, 3, 4 })[0..], false);
defer unique.decref(@alignOf(u8), @sizeOf(u8), false, rcNone);
var concatted = listConcat(nonUnique, unique, 1, 1);
var wanted = RocList.fromSlice(u8, ([_]u8{ 1, 2, 3, 4 })[0..]);
defer wanted.decref(@sizeOf(u8));
var concatted = listConcat(nonUnique, unique, 1, 1, false, rcNone, rcNone);
var wanted = RocList.fromSlice(u8, ([_]u8{ 1, 2, 3, 4 })[0..], false);
defer wanted.decref(@alignOf(u8), @sizeOf(u8), false, rcNone);
try expect(concatted.eql(wanted));
}
@ -1045,7 +1020,7 @@ pub fn listConcatUtf8(
const combined_length = list.len() + string.len();
// List U8 has alignment 1 and element_width 1
var result = list.reallocate(1, combined_length, 1);
const result = list.reallocate(1, combined_length, 1, false, &rcNone);
// We just allocated combined_length, which is > 0 because string.len() > 0
var bytes = result.bytes orelse unreachable;
@memcpy(bytes[list.len()..combined_length], string.asU8ptr()[0..string.len()]);
@ -1055,13 +1030,13 @@ pub fn listConcatUtf8(
}
test "listConcatUtf8" {
const list = RocList.fromSlice(u8, &[_]u8{ 1, 2, 3, 4 });
defer list.decref(1);
const list = RocList.fromSlice(u8, &[_]u8{ 1, 2, 3, 4 }, false);
defer list.decref(1, 1, false, &rcNone);
const string_bytes = "🐦";
const string = str.RocStr.init(string_bytes, string_bytes.len);
defer string.decref();
const ret = listConcatUtf8(list, string);
const expected = RocList.fromSlice(u8, &[_]u8{ 1, 2, 3, 4, 240, 159, 144, 166 });
defer expected.decref(1);
const expected = RocList.fromSlice(u8, &[_]u8{ 1, 2, 3, 4, 240, 159, 144, 166 }, false);
defer expected.decref(1, 1, false, &rcNone);
try expect(ret.eql(expected));
}

View file

@ -65,10 +65,6 @@ comptime {
const list = @import("list.zig");
comptime {
exportListFn(list.listMap, "map");
exportListFn(list.listMap2, "map2");
exportListFn(list.listMap3, "map3");
exportListFn(list.listMap4, "map4");
exportListFn(list.listAppendUnsafe, "append_unsafe");
exportListFn(list.listReserve, "reserve");
exportListFn(list.listPrepend, "prepend");
@ -86,6 +82,8 @@ comptime {
exportListFn(list.listAllocationPtr, "allocation_ptr");
exportListFn(list.listReleaseExcessCapacity, "release_excess_capacity");
exportListFn(list.listConcatUtf8, "concat_utf8");
exportListFn(list.listIncref, "incref");
exportListFn(list.listDecref, "decref");
}
// Num Module

View file

@ -96,7 +96,7 @@ pub const RocStr = extern struct {
}
fn allocateBig(length: usize, capacity: usize) RocStr {
const first_element = utils.allocateWithRefcount(capacity, @sizeOf(usize));
const first_element = utils.allocateWithRefcount(capacity, @sizeOf(usize), false);
return RocStr{
.bytes = first_element,
@ -172,7 +172,7 @@ pub const RocStr = extern struct {
pub fn decref(self: RocStr) void {
if (!self.isSmallStr()) {
utils.decref(self.getAllocationPtr(), self.capacity_or_alloc_ptr, RocStr.alignment);
utils.decref(self.getAllocationPtr(), self.capacity_or_alloc_ptr, RocStr.alignment, false);
}
}
@ -247,6 +247,7 @@ pub const RocStr = extern struct {
old_capacity,
new_capacity,
element_width,
false,
);
return RocStr{ .bytes = new_source, .length = new_length, .capacity_or_alloc_ptr = new_capacity };
@ -600,7 +601,7 @@ fn strFromFloatHelp(comptime T: type, float: T) RocStr {
// Str.split
pub fn strSplit(string: RocStr, delimiter: RocStr) callconv(.C) RocList {
const segment_count = countSegments(string, delimiter);
const list = RocList.allocate(@alignOf(RocStr), segment_count, @sizeOf(RocStr));
const list = RocList.allocate(@alignOf(RocStr), segment_count, @sizeOf(RocStr), true);
if (list.bytes) |bytes| {
const strings = @as([*]RocStr, @ptrCast(@alignCast(bytes)));
@ -1427,7 +1428,7 @@ inline fn strToBytes(arg: RocStr) RocList {
if (length == 0) {
return RocList.empty();
} else if (arg.isSmallStr()) {
const ptr = utils.allocateWithRefcount(length, RocStr.alignment);
const ptr = utils.allocateWithRefcount(length, RocStr.alignment, false);
@memcpy(ptr[0..length], arg.asU8ptr()[0..length]);
@ -1457,7 +1458,7 @@ pub fn fromUtf8(
update_mode: UpdateMode,
) FromUtf8Result {
if (list.len() == 0) {
list.decref(1); // Alignment 1 for List U8
list.decref(@alignOf(u8), @sizeOf(u8), false, rcNone);
return FromUtf8Result{
.is_ok = true,
.string = RocStr.empty(),
@ -1479,7 +1480,7 @@ pub fn fromUtf8(
} else {
const temp = errorToProblem(bytes);
list.decref(1); // Alignment 1 for List U8
list.decref(@alignOf(u8), @sizeOf(u8), false, rcNone);
return FromUtf8Result{
.is_ok = false,
@ -1603,7 +1604,7 @@ fn expectOk(result: FromUtf8Result) !void {
}
fn sliceHelp(bytes: [*]const u8, length: usize) RocList {
var list = RocList.allocate(RocStr.alignment, length, @sizeOf(u8));
var list = RocList.allocate(RocStr.alignment, length, @sizeOf(u8), false);
var list_bytes = list.bytes orelse unreachable;
@memcpy(list_bytes[0..length], bytes[0..length]);
list.length = length;
@ -1971,6 +1972,13 @@ fn countTrailingWhitespaceBytes(string: RocStr) usize {
return byte_count;
}
fn rcNone(_: ?[*]u8) callconv(.C) void {}
fn decStr(ptr: ?[*]u8) callconv(.C) void {
const str_ptr = @as(*RocStr, @ptrCast(@alignCast(ptr orelse unreachable)));
str_ptr.decref();
}
/// A backwards version of Utf8View from std.unicode
const ReverseUtf8View = struct {
bytes: []const u8,

View file

@ -219,6 +219,7 @@ pub fn increfRcPtrC(ptr_to_refcount: *isize, amount: isize) callconv(.C) void {
pub fn decrefRcPtrC(
bytes_or_null: ?[*]isize,
alignment: u32,
elements_refcounted: bool,
) callconv(.C) void {
// IMPORTANT: bytes_or_null is this case is expected to be a pointer to the refcount
// (NOT the start of the data, or the start of the allocation)
@ -226,22 +227,24 @@ pub fn decrefRcPtrC(
// this is of course unsafe, but we trust what we get from the llvm side
var bytes = @as([*]isize, @ptrCast(bytes_or_null));
return @call(.always_inline, decref_ptr_to_refcount, .{ bytes, alignment });
return @call(.always_inline, decref_ptr_to_refcount, .{ bytes, alignment, elements_refcounted });
}
pub fn decrefCheckNullC(
bytes_or_null: ?[*]u8,
alignment: u32,
elements_refcounted: bool,
) callconv(.C) void {
if (bytes_or_null) |bytes| {
const isizes: [*]isize = @as([*]isize, @ptrCast(@alignCast(bytes)));
return @call(.always_inline, decref_ptr_to_refcount, .{ isizes - 1, alignment });
return @call(.always_inline, decref_ptr_to_refcount, .{ isizes - 1, alignment, elements_refcounted });
}
}
pub fn decrefDataPtrC(
bytes_or_null: ?[*]u8,
alignment: u32,
elements_refcounted: bool,
) callconv(.C) void {
var bytes = bytes_or_null orelse return;
@ -252,7 +255,7 @@ pub fn decrefDataPtrC(
const isizes: [*]isize = @as([*]isize, @ptrFromInt(unmasked_ptr));
const rc_ptr = isizes - 1;
return decrefRcPtrC(rc_ptr, alignment);
return decrefRcPtrC(rc_ptr, alignment, elements_refcounted);
}
pub fn increfDataPtrC(
@ -273,6 +276,7 @@ pub fn increfDataPtrC(
pub fn freeDataPtrC(
bytes_or_null: ?[*]u8,
alignment: u32,
elements_refcounted: bool,
) callconv(.C) void {
var bytes = bytes_or_null orelse return;
@ -283,21 +287,23 @@ pub fn freeDataPtrC(
const isizes: [*]isize = @as([*]isize, @ptrFromInt(masked_ptr));
// we always store the refcount right before the data
return freeRcPtrC(isizes - 1, alignment);
return freeRcPtrC(isizes - 1, alignment, elements_refcounted);
}
pub fn freeRcPtrC(
bytes_or_null: ?[*]isize,
alignment: u32,
elements_refcounted: bool,
) callconv(.C) void {
var bytes = bytes_or_null orelse return;
return free_ptr_to_refcount(bytes, alignment);
return free_ptr_to_refcount(bytes, alignment, elements_refcounted);
}
pub fn decref(
bytes_or_null: ?[*]u8,
data_bytes: usize,
alignment: u32,
elements_refcounted: bool,
) void {
if (data_bytes == 0) {
return;
@ -307,15 +313,18 @@ pub fn decref(
const isizes: [*]isize = @as([*]isize, @ptrCast(@alignCast(bytes)));
decref_ptr_to_refcount(isizes - 1, alignment);
decref_ptr_to_refcount(isizes - 1, alignment, elements_refcounted);
}
inline fn free_ptr_to_refcount(
refcount_ptr: [*]isize,
alignment: u32,
elements_refcounted: bool,
) void {
if (RC_TYPE == Refcount.none) return;
const extra_bytes = @max(alignment, @sizeOf(usize));
const ptr_width = @sizeOf(usize);
const required_space: usize = if (elements_refcounted) (2 * ptr_width) else ptr_width;
const extra_bytes = @max(required_space, alignment);
const allocation_ptr = @as([*]u8, @ptrCast(refcount_ptr)) - (extra_bytes - @sizeOf(usize));
// NOTE: we don't even check whether the refcount is "infinity" here!
@ -328,7 +337,8 @@ inline fn free_ptr_to_refcount(
inline fn decref_ptr_to_refcount(
refcount_ptr: [*]isize,
alignment: u32,
element_alignment: u32,
elements_refcounted: bool,
) void {
if (RC_TYPE == Refcount.none) return;
@ -336,6 +346,10 @@ inline fn decref_ptr_to_refcount(
std.debug.print("| decrement {*}: ", .{refcount_ptr});
}
// Due to RC alignmen tmust take into acount pointer size.
const ptr_width = @sizeOf(usize);
const alignment = @max(ptr_width, element_alignment);
// Ensure that the refcount is not whole program lifetime.
const refcount: isize = refcount_ptr[0];
if (refcount != REFCOUNT_MAX_ISIZE) {
@ -353,13 +367,13 @@ inline fn decref_ptr_to_refcount(
}
if (refcount == REFCOUNT_ONE_ISIZE) {
free_ptr_to_refcount(refcount_ptr, alignment);
free_ptr_to_refcount(refcount_ptr, alignment, elements_refcounted);
}
},
Refcount.atomic => {
var last = @atomicRmw(isize, &refcount_ptr[0], std.builtin.AtomicRmwOp.Sub, 1, Monotonic);
if (last == REFCOUNT_ONE_ISIZE) {
free_ptr_to_refcount(refcount_ptr, alignment);
free_ptr_to_refcount(refcount_ptr, alignment, elements_refcounted);
}
},
Refcount.none => unreachable,
@ -438,17 +452,23 @@ pub inline fn calculateCapacity(
pub fn allocateWithRefcountC(
data_bytes: usize,
element_alignment: u32,
elements_refcounted: bool,
) callconv(.C) [*]u8 {
return allocateWithRefcount(data_bytes, element_alignment);
return allocateWithRefcount(data_bytes, element_alignment, elements_refcounted);
}
pub fn allocateWithRefcount(
data_bytes: usize,
element_alignment: u32,
elements_refcounted: bool,
) [*]u8 {
// If the element type is refcounted, we need to also allocate space to store the element count on the heap.
// This is used so that a seamless slice can de-allocate the underlying list type.
const ptr_width = @sizeOf(usize);
const alignment = @max(ptr_width, element_alignment);
const length = alignment + data_bytes;
const required_space: usize = if (elements_refcounted) (2 * ptr_width) else ptr_width;
const extra_bytes = @max(required_space, element_alignment);
const length = extra_bytes + data_bytes;
var new_bytes: [*]u8 = alloc(length, alignment) orelse unreachable;
@ -456,7 +476,7 @@ pub fn allocateWithRefcount(
std.debug.print("+ allocated {*} ({} bytes with alignment {})\n", .{ new_bytes, data_bytes, alignment });
}
const data_ptr = new_bytes + alignment;
const data_ptr = new_bytes + extra_bytes;
const refcount_ptr = @as([*]usize, @ptrCast(@as([*]align(ptr_width) u8, @alignCast(data_ptr)) - ptr_width));
refcount_ptr[0] = if (RC_TYPE == Refcount.none) REFCOUNT_MAX_ISIZE else REFCOUNT_ONE;
@ -474,11 +494,14 @@ pub fn unsafeReallocate(
old_length: usize,
new_length: usize,
element_width: usize,
elements_refcounted: bool,
) [*]u8 {
const align_width: usize = @max(alignment, @sizeOf(usize));
const ptr_width: usize = @sizeOf(usize);
const required_space: usize = if (elements_refcounted) (2 * ptr_width) else ptr_width;
const extra_bytes = @max(required_space, alignment);
const old_width = align_width + old_length * element_width;
const new_width = align_width + new_length * element_width;
const old_width = extra_bytes + old_length * element_width;
const new_width = extra_bytes + new_length * element_width;
if (old_width >= new_width) {
return source_ptr;
@ -486,10 +509,10 @@ pub fn unsafeReallocate(
// TODO handle out of memory
// NOTE realloc will dealloc the original allocation
const old_allocation = source_ptr - align_width;
const old_allocation = source_ptr - extra_bytes;
const new_allocation = realloc(old_allocation, new_width, old_width, alignment);
const new_source = @as([*]u8, @ptrCast(new_allocation)) + align_width;
const new_source = @as([*]u8, @ptrCast(new_allocation)) + extra_bytes;
return new_source;
}

View file

@ -746,6 +746,15 @@ keepErrs = \list, toResult ->
## expect List.map ["", "a", "bc"] Str.isEmpty == [Bool.true, Bool.false, Bool.false]
## ```
map : List a, (a -> b) -> List b
map = \list, mapper ->
# TODO: allow checking the refcounting and running the map inplace.
# Preferably allow it even if the types are different (must be same size with padding though).
length = List.len list
List.walk
list
(List.withCapacity length)
\state, elem ->
List.appendUnsafe state (mapper elem)
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
@ -757,16 +766,56 @@ map : List a, (a -> b) -> List b
## zipped = List.map2 ["a", "b", "c"] [1, 2, 3] Pair
## ```
map2 : List a, List b, (a, b -> c) -> List c
map2 = \listA, listB, mapper ->
length = Num.min (List.len listA) (List.len listB)
map2Help listA listB (List.withCapacity length) mapper 0 length
map2Help : List a, List b, List c, (a, b -> c), U64, U64 -> List c
map2Help = \listA, listB, out, mapper, index, length ->
if index < length then
mapped = mapper (List.getUnsafe listA index) (List.getUnsafe listB index)
map2Help listA listB (List.appendUnsafe out mapped) mapper (Num.addWrap index 1) length
else
out
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
map3 : List a, List b, List c, (a, b, c -> d) -> List d
map3 = \listA, listB, listC, mapper ->
length = Num.min
(Num.min (List.len listA) (List.len listB))
(List.len listC)
map3Help listA listB listC (List.withCapacity length) mapper 0 length
map3Help : List a, List b, List c, List d, (a, b, c -> d), U64, U64 -> List d
map3Help = \listA, listB, listC, out, mapper, index, length ->
if index < length then
mapped = mapper (List.getUnsafe listA index) (List.getUnsafe listB index) (List.getUnsafe listC index)
map3Help listA listB listC (List.appendUnsafe out mapped) mapper (Num.addWrap index 1) length
else
out
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
map4 = \listA, listB, listC, listD, mapper ->
length = Num.min
(Num.min (List.len listA) (List.len listB))
(Num.min (List.len listC) (List.len listD))
map4Help listA listB listC listD (List.withCapacity length) mapper 0 length
map4Help : List a, List b, List c, List d, List e, (a, b, c, d -> e), U64, U64 -> List e
map4Help = \listA, listB, listC, listD, out, mapper, index, length ->
if index < length then
mapped = mapper (List.getUnsafe listA index) (List.getUnsafe listB index) (List.getUnsafe listC index) (List.getUnsafe listD index)
map4Help listA listB listC listD (List.append out mapped) mapper (Num.addWrap index 1) length
else
out
## This works like [List.map], except it also passes the index
## of the element to the conversion function.

View file

@ -391,6 +391,8 @@ pub const LIST_CAPACITY: &str = "roc_builtins.list.capacity";
pub const LIST_ALLOCATION_PTR: &str = "roc_builtins.list.allocation_ptr";
pub const LIST_RELEASE_EXCESS_CAPACITY: &str = "roc_builtins.list.release_excess_capacity";
pub const LIST_CONCAT_UTF8: &str = "roc_builtins.list.concat_utf8";
pub const LIST_INCREF: &str = "roc_builtins.list.incref";
pub const LIST_DECREF: &str = "roc_builtins.list.decref";
pub const DEC_ABS: &str = "roc_builtins.dec.abs";
pub const DEC_ACOS: &str = "roc_builtins.dec.acos";

View file

@ -17,7 +17,6 @@ roc_problem = { path = "../problem" }
roc_region = { path = "../region" }
roc_serialize = { path = "../serialize" }
roc_types = { path = "../types" }
roc_test_utils = { path = "../../test_utils" }
ven_pretty = { path = "../../vendor/pretty" }
@ -27,4 +26,5 @@ static_assertions.workspace = true
[dev-dependencies]
indoc.workspace = true
insta.workspace = true
pretty_assertions.workspace = true

View file

@ -968,19 +968,10 @@ fn can_annotation_help(
);
if tags.is_empty() {
match ext {
Some(_) => {
// just `a` does not mean the same as `[]`, so even
// if there are no fields, still make this a `TagUnion`,
// not an EmptyTagUnion
Type::TagUnion(
Default::default(),
TypeExtension::from_type(ext_type, is_implicit_openness),
)
}
None => Type::EmptyTagUnion,
}
Type::TagUnion(
Default::default(),
TypeExtension::from_type(ext_type, is_implicit_openness),
)
} else {
let mut tag_types = can_tags(
env,
@ -1173,6 +1164,7 @@ fn can_extension_type(
local_aliases,
references,
);
if valid_extension_type(shallow_dealias_with_scope(scope, &ext_type)) {
if matches!(loc_ann.extract_spaces().item, TypeAnnotation::Wildcard)
&& matches!(ext_problem_kind, ExtensionTypeKind::TagUnion)

View file

@ -91,6 +91,8 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
LowLevel::RefCountIncDataPtr => unimplemented!(),
LowLevel::RefCountDecDataPtr=> unimplemented!(),
LowLevel::RefCountIsUnique => unimplemented!(),
LowLevel::ListIncref => unimplemented!(),
LowLevel::ListDecref => unimplemented!(),
LowLevel::SetJmp => unimplemented!(),
LowLevel::LongJmp => unimplemented!(),
@ -140,10 +142,6 @@ map_symbol_to_lowlevel_and_arity! {
ListGetUnsafe; LIST_GET_UNSAFE; 2,
ListReplaceUnsafe; LIST_REPLACE_UNSAFE; 3,
ListConcat; LIST_CONCAT; 2,
ListMap; LIST_MAP; 2,
ListMap2; LIST_MAP2; 3,
ListMap3; LIST_MAP3; 4,
ListMap4; LIST_MAP4; 5,
ListSortWith; LIST_SORT_WITH; 2,
ListSublist; LIST_SUBLIST_LOWLEVEL; 3,
ListDropAt; LIST_DROP_AT; 2,

View file

@ -150,11 +150,7 @@ impl ExpectsOrDbgs {
#[derive(Debug, Clone)]
enum PendingValueDef<'a> {
/// A standalone annotation with no body
AnnotationOnly(
&'a Loc<ast::Pattern<'a>>,
Loc<Pattern>,
&'a Loc<ast::TypeAnnotation<'a>>,
),
AnnotationOnly(Loc<Pattern>, &'a Loc<ast::TypeAnnotation<'a>>),
/// A body with no type annotation
Body(Loc<Pattern>, &'a Loc<ast::Expr<'a>>),
/// A body with a type annotation
@ -175,7 +171,7 @@ enum PendingValueDef<'a> {
impl PendingValueDef<'_> {
fn loc_pattern(&self) -> &Loc<Pattern> {
match self {
PendingValueDef::AnnotationOnly(_, loc_pattern, _) => loc_pattern,
PendingValueDef::AnnotationOnly(loc_pattern, _) => loc_pattern,
PendingValueDef::Body(loc_pattern, _) => loc_pattern,
PendingValueDef::TypedBody(_, loc_pattern, _, _) => loc_pattern,
PendingValueDef::IngestedFile(loc_pattern, _, _) => loc_pattern,
@ -2208,7 +2204,7 @@ fn canonicalize_pending_value_def<'a>(
let pending_abilities_in_scope = &Default::default();
let output = match pending_def {
AnnotationOnly(_, loc_can_pattern, loc_ann) => {
AnnotationOnly(loc_can_pattern, loc_ann) => {
// Make types for the body expr, even if we won't end up having a body.
let expr_var = var_store.fresh();
let mut vars_by_symbol = SendMap::default();
@ -2939,7 +2935,6 @@ fn to_pending_value_def<'a>(
);
PendingValue::Def(PendingValueDef::AnnotationOnly(
loc_pattern,
loc_can_pattern,
loc_ann,
))

View file

@ -9,7 +9,7 @@ use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::ModuleName;
use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::{
AssignedField, Collection, ModuleImportParams, Pattern, RecordBuilderField, StrLiteral,
AssignedField, Collection, ModuleImportParams, OldRecordBuilderField, Pattern, StrLiteral,
StrSegment, ValueDef, WhenBranch,
};
use roc_region::all::{LineInfo, Loc, Region};
@ -300,8 +300,11 @@ pub fn desugar_expr<'a>(
| MalformedClosure
| MalformedSuffixed(..)
| PrecedenceConflict { .. }
| MultipleRecordBuilders { .. }
| UnappliedRecordBuilder { .. }
| MultipleOldRecordBuilders(_)
| UnappliedOldRecordBuilder(_)
| EmptyRecordBuilder(_)
| SingleFieldRecordBuilder(_)
| OptionalFieldInRecordBuilder { .. }
| Tag(_)
| OpaqueRef(_)
| Crash => loc_expr,
@ -485,10 +488,236 @@ pub fn desugar_expr<'a>(
}
}
}
RecordBuilder(_) => arena.alloc(Loc {
value: UnappliedRecordBuilder(loc_expr),
OldRecordBuilder(_) => arena.alloc(Loc {
value: UnappliedOldRecordBuilder(loc_expr),
region: loc_expr.region,
}),
RecordBuilder { mapper, fields } => {
// NOTE the `mapper` is always a `Var { .. }`, we only desugar it to get rid of
// any spaces before/after
let new_mapper = desugar_expr(arena, mapper, src, line_info, module_path);
if fields.is_empty() {
return arena.alloc(Loc {
value: EmptyRecordBuilder(loc_expr),
region: loc_expr.region,
});
} else if fields.len() == 1 {
return arena.alloc(Loc {
value: SingleFieldRecordBuilder(loc_expr),
region: loc_expr.region,
});
}
let mut field_names = Vec::with_capacity_in(fields.len(), arena);
let mut field_vals = Vec::with_capacity_in(fields.len(), arena);
for field in fields.items {
match desugar_field(arena, &field.value, src, line_info, module_path) {
AssignedField::RequiredValue(loc_name, _, loc_val) => {
field_names.push(loc_name);
field_vals.push(loc_val);
}
AssignedField::LabelOnly(loc_name) => {
field_names.push(loc_name);
field_vals.push(arena.alloc(Loc {
region: loc_name.region,
value: Expr::Var {
module_name: "",
ident: loc_name.value,
},
}));
}
AssignedField::OptionalValue(loc_name, _, loc_val) => {
return arena.alloc(Loc {
region: loc_expr.region,
value: OptionalFieldInRecordBuilder(arena.alloc(loc_name), loc_val),
});
}
AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => {
unreachable!("Should have been desugared in `desugar_field`")
}
AssignedField::Malformed(_name) => {}
}
}
let closure_arg_from_field = |field: Loc<&'a str>| Loc {
region: field.region,
value: Pattern::Identifier {
ident: arena.alloc_str(&format!("#{}", field.value)),
},
};
let combiner_closure_in_region = |region| {
let closure_body = Tuple(Collection::with_items(
Vec::from_iter_in(
[
&*arena.alloc(Loc::at(
region,
Expr::Var {
module_name: "",
ident: "#record_builder_closure_arg_a",
},
)),
&*arena.alloc(Loc::at(
region,
Expr::Var {
module_name: "",
ident: "#record_builder_closure_arg_b",
},
)),
],
arena,
)
.into_bump_slice(),
));
arena.alloc(Loc::at(
region,
Closure(
arena.alloc_slice_copy(&[
Loc::at(
region,
Pattern::Identifier {
ident: "#record_builder_closure_arg_a",
},
),
Loc::at(
region,
Pattern::Identifier {
ident: "#record_builder_closure_arg_b",
},
),
]),
arena.alloc(Loc::at(region, closure_body)),
),
))
};
let closure_args = {
if field_names.len() == 2 {
arena.alloc_slice_copy(&[
closure_arg_from_field(field_names[0]),
closure_arg_from_field(field_names[1]),
])
} else {
let second_to_last_arg =
closure_arg_from_field(field_names[field_names.len() - 2]);
let last_arg = closure_arg_from_field(field_names[field_names.len() - 1]);
let mut second_arg = Pattern::Tuple(Collection::with_items(
arena.alloc_slice_copy(&[second_to_last_arg, last_arg]),
));
let mut second_arg_region =
Region::span_across(&second_to_last_arg.region, &last_arg.region);
for index in (1..(field_names.len() - 2)).rev() {
second_arg =
Pattern::Tuple(Collection::with_items(arena.alloc_slice_copy(&[
closure_arg_from_field(field_names[index]),
Loc::at(second_arg_region, second_arg),
])));
second_arg_region =
Region::span_across(&field_names[index].region, &second_arg_region);
}
arena.alloc_slice_copy(&[
closure_arg_from_field(field_names[0]),
Loc::at(second_arg_region, second_arg),
])
}
};
let record_val = Record(Collection::with_items(
Vec::from_iter_in(
field_names.iter().map(|field_name| {
Loc::at(
field_name.region,
AssignedField::RequiredValue(
Loc::at(field_name.region, field_name.value),
&[],
arena.alloc(Loc::at(
field_name.region,
Expr::Var {
module_name: "",
ident: arena.alloc_str(&format!("#{}", field_name.value)),
},
)),
),
)
}),
arena,
)
.into_bump_slice(),
));
let record_combiner_closure = arena.alloc(Loc {
region: loc_expr.region,
value: Closure(
closure_args,
arena.alloc(Loc::at(loc_expr.region, record_val)),
),
});
if field_names.len() == 2 {
return arena.alloc(Loc {
region: loc_expr.region,
value: Apply(
new_mapper,
arena.alloc_slice_copy(&[
field_vals[0],
field_vals[1],
record_combiner_closure,
]),
CalledVia::RecordBuilder,
),
});
}
let mut inner_combined = arena.alloc(Loc {
region: Region::span_across(
&field_vals[field_names.len() - 2].region,
&field_vals[field_names.len() - 1].region,
),
value: Apply(
new_mapper,
arena.alloc_slice_copy(&[
field_vals[field_names.len() - 2],
field_vals[field_names.len() - 1],
combiner_closure_in_region(loc_expr.region),
]),
CalledVia::RecordBuilder,
),
});
for index in (1..(field_names.len() - 2)).rev() {
inner_combined = arena.alloc(Loc {
region: Region::span_across(&field_vals[index].region, &inner_combined.region),
value: Apply(
new_mapper,
arena.alloc_slice_copy(&[
field_vals[index],
inner_combined,
combiner_closure_in_region(loc_expr.region),
]),
CalledVia::RecordBuilder,
),
});
}
arena.alloc(Loc {
region: loc_expr.region,
value: Apply(
new_mapper,
arena.alloc_slice_copy(&[
field_vals[0],
inner_combined,
record_combiner_closure,
]),
CalledVia::RecordBuilder,
),
})
}
BinOps(lefts, right) => desugar_bin_ops(
arena,
loc_expr.region,
@ -513,15 +742,15 @@ pub fn desugar_expr<'a>(
let mut current = loc_arg.value;
let arg = loop {
match current {
RecordBuilder(fields) => {
OldRecordBuilder(fields) => {
if builder_apply_exprs.is_some() {
return arena.alloc(Loc {
value: MultipleRecordBuilders(loc_expr),
value: MultipleOldRecordBuilders(loc_expr),
region: loc_expr.region,
});
}
let builder_arg = record_builder_arg(arena, loc_arg.region, fields);
let builder_arg = old_record_builder_arg(arena, loc_arg.region, fields);
builder_apply_exprs = Some(builder_arg.apply_exprs);
break builder_arg.closure;
@ -557,7 +786,7 @@ pub fn desugar_expr<'a>(
let args = std::slice::from_ref(arena.alloc(apply));
apply = arena.alloc(Loc {
value: Apply(desugared_expr, args, CalledVia::RecordBuilder),
value: Apply(desugared_expr, args, CalledVia::OldRecordBuilder),
region: loc_expr.region,
});
}
@ -1007,16 +1236,16 @@ fn desugar_pattern<'a>(
}
}
struct RecordBuilderArg<'a> {
struct OldRecordBuilderArg<'a> {
closure: &'a Loc<Expr<'a>>,
apply_exprs: Vec<'a, &'a Loc<Expr<'a>>>,
}
fn record_builder_arg<'a>(
fn old_record_builder_arg<'a>(
arena: &'a Bump,
region: Region,
fields: Collection<'a, Loc<RecordBuilderField<'a>>>,
) -> RecordBuilderArg<'a> {
fields: Collection<'a, Loc<OldRecordBuilderField<'a>>>,
) -> OldRecordBuilderArg<'a> {
let mut record_fields = Vec::with_capacity_in(fields.len(), arena);
let mut apply_exprs = Vec::with_capacity_in(fields.len(), arena);
let mut apply_field_names = Vec::with_capacity_in(fields.len(), arena);
@ -1028,10 +1257,10 @@ fn record_builder_arg<'a>(
let new_field = loop {
match current {
RecordBuilderField::Value(label, spaces, expr) => {
OldRecordBuilderField::Value(label, spaces, expr) => {
break AssignedField::RequiredValue(label, spaces, expr)
}
RecordBuilderField::ApplyValue(label, _, _, expr) => {
OldRecordBuilderField::ApplyValue(label, _, _, expr) => {
apply_field_names.push(label);
apply_exprs.push(expr);
@ -1045,14 +1274,14 @@ fn record_builder_arg<'a>(
break AssignedField::RequiredValue(label, &[], var);
}
RecordBuilderField::LabelOnly(label) => break AssignedField::LabelOnly(label),
RecordBuilderField::SpaceBefore(sub_field, _) => {
OldRecordBuilderField::LabelOnly(label) => break AssignedField::LabelOnly(label),
OldRecordBuilderField::SpaceBefore(sub_field, _) => {
current = *sub_field;
}
RecordBuilderField::SpaceAfter(sub_field, _) => {
OldRecordBuilderField::SpaceAfter(sub_field, _) => {
current = *sub_field;
}
RecordBuilderField::Malformed(malformed) => {
OldRecordBuilderField::Malformed(malformed) => {
break AssignedField::Malformed(malformed)
}
}
@ -1092,7 +1321,7 @@ fn record_builder_arg<'a>(
});
}
RecordBuilderArg {
OldRecordBuilderArg {
closure: body,
apply_exprs,
}

View file

@ -1021,8 +1021,11 @@ pub fn canonicalize_expr<'a>(
can_defs_with_return(env, var_store, inner_scope, env.arena.alloc(defs), loc_ret)
})
}
ast::Expr::RecordBuilder(_) => {
internal_error!("RecordBuilder should have been desugared by now")
ast::Expr::OldRecordBuilder(_) => {
internal_error!("Old record builder should have been desugared by now")
}
ast::Expr::RecordBuilder { .. } => {
internal_error!("New record builder should have been desugared by now")
}
ast::Expr::Backpassing(_, _, _) => {
internal_error!("Backpassing should have been desugared by now")
@ -1340,18 +1343,43 @@ pub fn canonicalize_expr<'a>(
use roc_problem::can::RuntimeError::*;
(RuntimeError(MalformedSuffixed(region)), Output::default())
}
ast::Expr::MultipleRecordBuilders(sub_expr) => {
ast::Expr::MultipleOldRecordBuilders(sub_expr) => {
use roc_problem::can::RuntimeError::*;
let problem = MultipleRecordBuilders(sub_expr.region);
let problem = MultipleOldRecordBuilders(sub_expr.region);
env.problem(Problem::RuntimeError(problem.clone()));
(RuntimeError(problem), Output::default())
}
ast::Expr::UnappliedRecordBuilder(sub_expr) => {
ast::Expr::UnappliedOldRecordBuilder(sub_expr) => {
use roc_problem::can::RuntimeError::*;
let problem = UnappliedRecordBuilder(sub_expr.region);
let problem = UnappliedOldRecordBuilder(sub_expr.region);
env.problem(Problem::RuntimeError(problem.clone()));
(RuntimeError(problem), Output::default())
}
ast::Expr::EmptyRecordBuilder(sub_expr) => {
use roc_problem::can::RuntimeError::*;
let problem = EmptyRecordBuilder(sub_expr.region);
env.problem(Problem::RuntimeError(problem.clone()));
(RuntimeError(problem), Output::default())
}
ast::Expr::SingleFieldRecordBuilder(sub_expr) => {
use roc_problem::can::RuntimeError::*;
let problem = SingleFieldRecordBuilder(sub_expr.region);
env.problem(Problem::RuntimeError(problem.clone()));
(RuntimeError(problem), Output::default())
}
ast::Expr::OptionalFieldInRecordBuilder(loc_name, loc_value) => {
use roc_problem::can::RuntimeError::*;
let sub_region = Region::span_across(&loc_name.region, &loc_value.region);
let problem = OptionalFieldInRecordBuilder {record: region, field: sub_region };
env.problem(Problem::RuntimeError(problem.clone()));
(RuntimeError(problem), Output::default())
@ -2414,9 +2442,12 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
ast::Expr::Tuple(fields) => fields
.iter()
.all(|loc_field| is_valid_interpolation(&loc_field.value)),
ast::Expr::MultipleRecordBuilders(loc_expr)
| ast::Expr::MalformedSuffixed(loc_expr)
| ast::Expr::UnappliedRecordBuilder(loc_expr)
ast::Expr::MalformedSuffixed(loc_expr)
| ast::Expr::MultipleOldRecordBuilders(loc_expr)
| ast::Expr::UnappliedOldRecordBuilder(loc_expr)
| ast::Expr::EmptyRecordBuilder(loc_expr)
| ast::Expr::SingleFieldRecordBuilder(loc_expr)
| ast::Expr::OptionalFieldInRecordBuilder(_, loc_expr)
| ast::Expr::PrecedenceConflict(PrecedenceConflict { expr: loc_expr, .. })
| ast::Expr::UnaryOp(loc_expr, _)
| ast::Expr::Closure(_, loc_expr) => is_valid_interpolation(&loc_expr.value),
@ -2458,24 +2489,39 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
| ast::AssignedField::SpaceAfter(_, _) => false,
})
}
ast::Expr::RecordBuilder(fields) => fields.iter().all(|loc_field| match loc_field.value {
ast::RecordBuilderField::Value(_label, comments, loc_expr) => {
comments.is_empty() && is_valid_interpolation(&loc_expr.value)
}
ast::RecordBuilderField::ApplyValue(
_label,
comments_before,
comments_after,
loc_expr,
) => {
comments_before.is_empty()
&& comments_after.is_empty()
&& is_valid_interpolation(&loc_expr.value)
}
ast::RecordBuilderField::Malformed(_) | ast::RecordBuilderField::LabelOnly(_) => true,
ast::RecordBuilderField::SpaceBefore(_, _)
| ast::RecordBuilderField::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 } => {
is_valid_interpolation(&mapper.value)
&& fields.iter().all(|loc_field| match loc_field.value {
ast::AssignedField::RequiredValue(_label, loc_comments, loc_val)
| ast::AssignedField::OptionalValue(_label, loc_comments, loc_val) => {
loc_comments.is_empty() && is_valid_interpolation(&loc_val.value)
}
ast::AssignedField::Malformed(_) | ast::AssignedField::LabelOnly(_) => true,
ast::AssignedField::SpaceBefore(_, _)
| ast::AssignedField::SpaceAfter(_, _) => false,
})
}
}
}

View file

@ -12,30 +12,18 @@ use std::cell::Cell;
thread_local! {
// we use a thread_local here so that tests consistently give the same pattern
static SUFFIXED_ANSWER_COUNTER: Cell<usize> = Cell::new(0);
static SUFFIXED_ANSWER_COUNTER: Cell<usize> = const { Cell::new(0) };
}
/// Provide an intermediate answer expression and pattern when unwrapping a
/// (sub) expression
///
/// e.g. `x = foo (bar!)` unwraps to `x = Task.await (bar) \#!a0 -> foo #!a0`
fn next_suffixed_answer_pattern(arena: &Bump) -> (Expr, Pattern) {
// Use the thread-local counter
/// Generates a unique identifier, useful for intermediate items during desugaring.
fn next_unique_suffixed_ident() -> String {
SUFFIXED_ANSWER_COUNTER.with(|counter| {
let count = counter.get();
counter.set(count + 1);
let answer_ident = arena.alloc(format!("#!a{}", count));
(
Expr::Var {
module_name: "",
ident: answer_ident,
},
Pattern::Identifier {
ident: answer_ident.as_str(),
},
)
// # is used as prefix because it's impossible for code authors to define names like this.
// This makes it easy to see this identifier was created by the compiler.
format!("#!a{}", count)
})
}
@ -69,9 +57,23 @@ fn init_unwrapped_err<'a>(
Err(EUnwrapped::UnwrappedDefExpr(unwrapped_expr))
}
None => {
let (answer_var, answer_pat) = next_suffixed_answer_pattern(arena);
let sub_new = arena.alloc(Loc::at(unwrapped_expr.region, answer_var));
let sub_pat = arena.alloc(Loc::at(unwrapped_expr.region, answer_pat));
// Provide an intermediate answer expression and pattern when unwrapping a
// (sub) expression.
// e.g. `x = foo (bar!)` unwraps to `x = Task.await (bar) \#!a0 -> foo #!a0`
let answer_ident = arena.alloc(next_unique_suffixed_ident());
let sub_new = arena.alloc(Loc::at(
unwrapped_expr.region,
Expr::Var {
module_name: "",
ident: answer_ident,
},
));
let sub_pat = arena.alloc(Loc::at(
unwrapped_expr.region,
Pattern::Identifier {
ident: answer_ident,
},
));
Err(EUnwrapped::UnwrappedSubExpr {
sub_arg: unwrapped_expr,
@ -819,12 +821,6 @@ pub fn apply_task_await<'a>(
loc_pat: &'a Loc<Pattern<'a>>,
loc_new: &'a Loc<Expr<'a>>,
) -> &'a Loc<Expr<'a>> {
// If the pattern and the new are the same then we don't need to unwrap anything
// e.g. `Task.await foo \{} -> Task.ok {}` is the same as `foo`
if is_matching_empty_record(loc_pat, loc_new) {
return loc_arg;
}
// If the pattern and the new are matching answers then we don't need to unwrap anything
// e.g. `Task.await foo \#!a1 -> Task.ok #!a1` is the same as `foo`
if is_matching_intermediate_answer(loc_pat, loc_new) {
@ -872,26 +868,6 @@ fn extract_wrapped_task_ok_value<'a>(loc_expr: &'a Loc<Expr<'a>>) -> Option<&'a
}
}
fn is_matching_empty_record<'a>(
loc_pat: &'a Loc<Pattern<'a>>,
loc_expr: &'a Loc<Expr<'a>>,
) -> bool {
let is_empty_record = match extract_wrapped_task_ok_value(loc_expr) {
Some(task_expr) => match task_expr.value {
Expr::Record(collection) => collection.is_empty(),
_ => false,
},
None => false,
};
let is_pattern_empty_record = match loc_pat.value {
Pattern::RecordDestructure(collection) => collection.is_empty(),
_ => false,
};
is_empty_record && is_pattern_empty_record
}
pub fn is_matching_intermediate_answer<'a>(
loc_pat: &'a Loc<Pattern<'a>>,
loc_new: &'a Loc<Expr<'a>>,

View file

@ -0,0 +1,113 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-28,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@15-22 Apply(
@15-22 Var {
module_name: "Task",
ident: "await",
},
[
@17-18 Var {
module_name: "",
ident: "a",
},
@15-22 Closure(
[
@17-18 Identifier {
ident: "#!a0",
},
],
@15-22 Apply(
@15-22 Var {
module_name: "Task",
ident: "await",
},
[
@20-21 Var {
module_name: "",
ident: "x",
},
@15-22 Closure(
[
@20-21 Identifier {
ident: "#!a1",
},
],
@15-22 Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@15-22,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@11-12 Identifier {
ident: "c",
},
@15-22 Apply(
@15-16 Var {
module_name: "",
ident: "b",
},
[
@17-18 Var {
module_name: "",
ident: "#!a0",
},
@20-21 Var {
module_name: "",
ident: "#!a1",
},
],
Space,
),
),
],
},
@27-28 Var {
module_name: "",
ident: "c",
},
),
),
],
BangSuffix,
),
),
],
BangSuffix,
),
),
],
}

View file

@ -0,0 +1,89 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-25,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@15-19 Apply(
@15-19 Var {
module_name: "Task",
ident: "await",
},
[
@17-18 Var {
module_name: "",
ident: "a",
},
@15-19 Closure(
[
@17-18 Identifier {
ident: "#!a0",
},
],
@15-19 Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@15-19,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@11-12 Identifier {
ident: "c",
},
@15-19 Apply(
@15-16 Var {
module_name: "",
ident: "b",
},
[
@17-18 Var {
module_name: "",
ident: "#!a0",
},
],
Space,
),
),
],
},
@24-25 Var {
module_name: "",
ident: "c",
},
),
),
],
BangSuffix,
),
),
],
}

View file

@ -0,0 +1,110 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-43,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@15-33 Apply(
@15-33 Var {
module_name: "Task",
ident: "await",
},
[
Apply(
Var {
module_name: "",
ident: "foo",
},
[
@25-32 Str(
PlainLine(
"hello",
),
),
],
Space,
),
@15-33 Closure(
[
Identifier {
ident: "#!a0",
},
],
@15-33 Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@15-33,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@11-12 Identifier {
ident: "x",
},
@15-33 Apply(
@15-18 Var {
module_name: "",
ident: "bar",
},
[
@20-32 ParensAround(
Var {
module_name: "",
ident: "#!a0",
},
),
],
Space,
),
),
],
},
@38-43 Apply(
@38-41 Var {
module_name: "",
ident: "baz",
},
[
@42-43 Var {
module_name: "",
ident: "x",
},
],
Space,
),
),
),
],
BangSuffix,
),
),
],
}

View file

@ -0,0 +1,111 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-45,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@15-35 Apply(
@15-35 Var {
module_name: "Task",
ident: "await",
},
[
Apply(
Var {
module_name: "",
ident: "foo",
},
[
@21-26 Str(
PlainLine(
"bar",
),
),
],
Space,
),
@15-35 Closure(
[
Identifier {
ident: "#!a0",
},
],
@15-35 Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@15-35,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@11-12 Identifier {
ident: "x",
},
@15-35 Apply(
@16-26 ParensAround(
Var {
module_name: "",
ident: "#!a0",
},
),
[
@28-35 Str(
PlainLine(
"hello",
),
),
],
Space,
),
),
],
},
@40-45 Apply(
@40-43 Var {
module_name: "",
ident: "baz",
},
[
@44-45 Var {
module_name: "",
ident: "x",
},
],
Space,
),
),
),
],
BangSuffix,
),
),
],
}

View file

@ -0,0 +1,91 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-28,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@15-22 Apply(
@15-22 Var {
module_name: "Task",
ident: "await",
},
[
@15-16 Var {
module_name: "",
ident: "a",
},
@15-22 Closure(
[
@15-16 Identifier {
ident: "#!a0",
},
],
@15-22 Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@15-22,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@11-12 Identifier {
ident: "c",
},
@15-22 Apply(
@21-22 Var {
module_name: "",
ident: "b",
},
[
@15-16 Var {
module_name: "",
ident: "#!a0",
},
],
BinOp(
Pizza,
),
),
),
],
},
@27-28 Var {
module_name: "",
ident: "c",
},
),
),
],
BangSuffix,
),
),
],
}

View file

@ -0,0 +1,59 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-26,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-15 Apply(
@11-15 Var {
module_name: "Task",
ident: "await",
},
[
@11-15 Var {
module_name: "",
ident: "foo",
},
@11-15 Closure(
[
@11-15 RecordDestructure(
[],
),
],
@21-26 Apply(
@21-23 Var {
module_name: "",
ident: "ok",
},
[
@24-26 Record(
[],
),
],
Space,
),
),
],
BangSuffix,
),
),
],
}

View file

@ -0,0 +1,92 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-42,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@16-35 Apply(
@16-35 Var {
module_name: "Task",
ident: "await",
},
[
Var {
module_name: "",
ident: "sayMultiple",
},
@16-35 Closure(
[
Identifier {
ident: "#!a0",
},
],
@16-35 Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@16-35,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@11-13 Identifier {
ident: "do",
},
@16-35 Apply(
@17-29 ParensAround(
Var {
module_name: "",
ident: "#!a0",
},
),
[
@31-35 Str(
PlainLine(
"hi",
),
),
],
Space,
),
),
],
},
@40-42 Var {
module_name: "",
ident: "do",
},
),
),
],
BangSuffix,
),
),
],
}

View file

@ -0,0 +1,116 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-69,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-69 Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@15-57,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@11-12 Identifier {
ident: "x",
},
@15-57 Closure(
[
@16-19 Identifier {
ident: "msg",
},
],
@31-42 Apply(
@31-42 Var {
module_name: "Task",
ident: "await",
},
[
@31-42 Apply(
@31-42 Var {
module_name: "",
ident: "line",
},
[
@31-34 Var {
module_name: "",
ident: "msg",
},
],
BinOp(
Pizza,
),
),
@31-42 Closure(
[
@31-42 RecordDestructure(
[],
),
],
@52-57 Apply(
@52-54 Var {
module_name: "",
ident: "ok",
},
[
@55-57 Record(
[],
),
],
Space,
),
),
],
BangSuffix,
),
),
),
],
},
@63-69 Apply(
@63-64 Var {
module_name: "",
ident: "x",
},
[
@65-69 Str(
PlainLine(
"hi",
),
),
],
Space,
),
),
),
],
}

View file

@ -0,0 +1,127 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-114,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-114 Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@39-101,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
AnnotatedBody {
ann_pattern: @11-12 Identifier {
ident: "x",
},
ann_type: @15-30 Function(
[
@15-18 Apply(
"",
"Str",
[],
),
],
@22-30 Apply(
"",
"Task",
[
@27-28 Inferred,
@29-30 Inferred,
],
),
),
comment: None,
body_pattern: @35-36 Identifier {
ident: "x",
},
body_expr: @39-101 Closure(
[
@40-43 Identifier {
ident: "msg",
},
],
@78-91 Apply(
@78-91 Var {
module_name: "Task",
ident: "await",
},
[
@78-91 Apply(
@78-91 Var {
module_name: "",
ident: "line",
},
[
@88-91 Var {
module_name: "",
ident: "msg",
},
],
Space,
),
@78-91 Closure(
[
@78-79 Identifier {
ident: "y",
},
],
@100-101 Var {
module_name: "",
ident: "y",
},
),
],
BangSuffix,
),
),
},
],
},
@107-114 Apply(
@107-108 Var {
module_name: "",
ident: "x",
},
[
@109-114 Str(
PlainLine(
"foo",
),
),
],
Space,
),
),
),
],
}

View file

@ -0,0 +1,194 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-143,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@12-143 Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@56-119,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
AnnotatedBody {
ann_pattern: @12-15 Identifier {
ident: "foo",
},
ann_type: @18-45 Function(
[
@18-21 Apply(
"",
"Str",
[],
),
@23-25 Record {
fields: [],
ext: None,
},
@27-30 Apply(
"",
"Str",
[],
),
],
@34-45 Apply(
"",
"Task",
[
@39-41 Record {
fields: [],
ext: None,
},
@42-45 Apply(
"",
"I32",
[],
),
],
),
),
comment: None,
body_pattern: @50-53 Identifier {
ident: "foo",
},
body_expr: @56-119 Closure(
[
@57-58 Identifier {
ident: "a",
},
@60-61 Underscore(
"",
),
@63-64 Identifier {
ident: "b",
},
],
@76-83 Apply(
@76-83 Var {
module_name: "Task",
ident: "await",
},
[
@76-83 Apply(
@76-83 Var {
module_name: "",
ident: "line",
},
[
@82-83 Var {
module_name: "",
ident: "a",
},
],
Space,
),
@76-83 Closure(
[
@76-83 RecordDestructure(
[],
),
],
@92-99 Apply(
@92-99 Var {
module_name: "Task",
ident: "await",
},
[
@92-99 Apply(
@92-99 Var {
module_name: "",
ident: "line",
},
[
@98-99 Var {
module_name: "",
ident: "b",
},
],
Space,
),
@92-99 Closure(
[
@92-99 RecordDestructure(
[],
),
],
@109-119 Apply(
@109-116 Var {
module_name: "Task",
ident: "ok",
},
[
@117-119 Record(
[],
),
],
Space,
),
),
],
BangSuffix,
),
),
],
BangSuffix,
),
),
},
],
},
@125-143 Apply(
@125-128 Var {
module_name: "",
ident: "foo",
},
[
@129-134 Str(
PlainLine(
"bar",
),
),
@135-137 Record(
[],
),
@138-143 Str(
PlainLine(
"baz",
),
),
],
Space,
),
),
),
],
}

View file

@ -0,0 +1,79 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-49,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@17-24 Apply(
@17-24 Var {
module_name: "Task",
ident: "await",
},
[
@17-24 Var {
module_name: "",
ident: "getFoo",
},
@17-24 Closure(
[
@11-14 Identifier {
ident: "foo",
},
],
@29-49 LowLevelDbg(
(
"test.roc:3",
" ",
),
@33-36 Apply(
@33-36 Var {
module_name: "Inspect",
ident: "toStr",
},
[
@33-36 Var {
module_name: "",
ident: "foo",
},
],
Space,
),
@41-49 Apply(
@41-49 Var {
module_name: "",
ident: "bar",
},
[
@46-49 Var {
module_name: "",
ident: "foo",
},
],
Space,
),
),
),
],
BangSuffix,
),
),
],
}

View file

@ -0,0 +1,70 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-24,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-24 Apply(
@11-24 Var {
module_name: "Task",
ident: "await",
},
[
@15-17 Var {
module_name: "",
ident: "a",
},
@11-24 Closure(
[
@15-17 Identifier {
ident: "#!a0",
},
],
@11-24 LowLevelDbg(
(
"test.roc:2",
"in",
),
@15-17 Apply(
@15-17 Var {
module_name: "Inspect",
ident: "toStr",
},
[
@15-17 Var {
module_name: "",
ident: "#!a0",
},
],
Space,
),
@23-24 Var {
module_name: "",
ident: "b",
},
),
),
],
BangSuffix,
),
),
],
}

View file

@ -0,0 +1,63 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-99,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-99 When(
@16-17 Var {
module_name: "",
ident: "a",
},
[
WhenBranch {
patterns: [
@29-30 NumLiteral(
"0",
),
],
value: @46-99 When(
@51-52 Var {
module_name: "",
ident: "b",
},
[
WhenBranch {
patterns: [
@72-73 NumLiteral(
"1",
),
],
value: @97-99 Var {
module_name: "",
ident: "c",
},
guard: None,
},
],
),
guard: None,
},
],
),
),
],
}

View file

@ -0,0 +1,145 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
Index(2147483649),
],
regions: [
@0-54,
@56-98,
],
space_before: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 2),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 2, length = 0),
],
spaces: [
Newline,
Newline,
],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-54 Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@15-20,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@11-12 Identifier {
ident: "a",
},
@15-20 Str(
PlainLine(
"Foo",
),
),
),
],
},
@11-54 Apply(
@11-54 Var {
module_name: "Task",
ident: "await",
},
[
@25-39 Apply(
@25-39 Var {
module_name: "Stdout",
ident: "line",
},
[
@38-39 Var {
module_name: "",
ident: "a",
},
],
Space,
),
@11-54 Closure(
[
@25-39 RecordDestructure(
[],
),
],
@45-54 Var {
module_name: "",
ident: "printBar",
},
),
],
BangSuffix,
),
),
),
Body(
@56-64 Identifier {
ident: "printBar",
},
@71-98 Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@75-80,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@71-72 Identifier {
ident: "b",
},
@75-80 Str(
PlainLine(
"Bar",
),
),
),
],
},
@85-98 Apply(
@85-96 Var {
module_name: "Stdout",
ident: "line",
},
[
@97-98 Var {
module_name: "",
ident: "b",
},
],
Space,
),
),
),
],
}

View file

@ -0,0 +1,101 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-158,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-158 When(
@16-17 Var {
module_name: "",
ident: "x",
},
[
WhenBranch {
patterns: [
@29-30 Tag(
"A",
),
],
value: @46-130 Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@50-52,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@46-47 Identifier {
ident: "y",
},
@50-52 Num(
"42",
),
),
],
},
@66-130 If(
[
(
@69-70 Var {
module_name: "",
ident: "a",
},
@92-94 Var {
module_name: "",
ident: "b",
},
),
],
@128-130 Var {
module_name: "",
ident: "c",
},
),
),
guard: None,
},
WhenBranch {
patterns: [
@139-140 Tag(
"B",
),
],
value: @156-158 Var {
module_name: "",
ident: "d",
},
guard: None,
},
],
),
),
],
}

View file

@ -0,0 +1,50 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-31,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-31 Expect(
@18-24 Apply(
@20-22 Var {
module_name: "Bool",
ident: "isEq",
},
[
@18-19 Num(
"1",
),
@23-24 Num(
"2",
),
],
BinOp(
Equals,
),
),
@29-31 Var {
module_name: "",
ident: "x",
},
),
),
],
}

View file

@ -0,0 +1,311 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-307,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-307 Defs(
Defs {
tags: [
Index(2147483648),
Index(2147483649),
Index(2147483650),
],
regions: [
@20-37,
@53-68,
@109-298,
],
space_before: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
Slice(start = 1, length = 1),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 1, length = 0),
Slice(start = 2, length = 0),
],
spaces: [
Newline,
Newline,
],
type_defs: [],
value_defs: [
Body(
@11-17 Identifier {
ident: "isTrue",
},
@20-37 Apply(
@20-27 Var {
module_name: "Task",
ident: "ok",
},
[
@28-37 Var {
module_name: "Bool",
ident: "true",
},
],
Space,
),
),
Body(
@42-50 Identifier {
ident: "isFalsey",
},
@53-68 Closure(
[
@54-55 Identifier {
ident: "x",
},
],
@59-68 Apply(
@59-66 Var {
module_name: "Task",
ident: "ok",
},
[
@67-68 Var {
module_name: "",
ident: "x",
},
],
Space,
),
),
),
AnnotatedBody {
ann_pattern: @73-76 Identifier {
ident: "msg",
},
ann_type: @79-90 Apply(
"",
"Task",
[
@84-86 Record {
fields: [],
ext: None,
},
@87-90 Apply(
"",
"I32",
[],
),
],
),
comment: None,
body_pattern: @95-98 Identifier {
ident: "msg",
},
body_expr: Apply(
Var {
module_name: "Task",
ident: "await",
},
[
Var {
module_name: "",
ident: "isTrue",
},
Closure(
[
Identifier {
ident: "#!a0",
},
],
@109-298 If(
[
(
@112-122 Apply(
@112-113 Var {
module_name: "Bool",
ident: "not",
},
[
@114-121 ParensAround(
Var {
module_name: "",
ident: "#!a0",
},
),
],
UnaryOp(
Not,
),
),
@140-152 Apply(
@140-152 Var {
module_name: "Task",
ident: "await",
},
[
@140-152 Apply(
@140-152 Var {
module_name: "",
ident: "line",
},
[
@146-152 Str(
PlainLine(
"fail",
),
),
],
Space,
),
@140-152 Closure(
[
@140-152 RecordDestructure(
[],
),
],
@165-170 Apply(
@165-168 Var {
module_name: "",
ident: "err",
},
[
@169-170 Num(
"1",
),
],
Space,
),
),
],
BangSuffix,
),
),
],
Apply(
Var {
module_name: "Task",
ident: "await",
},
[
Apply(
Var {
module_name: "",
ident: "isFalsey",
},
[
@198-208 Var {
module_name: "Bool",
ident: "false",
},
],
Space,
),
Closure(
[
Identifier {
ident: "#!a1",
},
],
@109-298 If(
[
(
@187-209 ParensAround(
Var {
module_name: "",
ident: "#!a1",
},
),
@227-239 Apply(
@227-239 Var {
module_name: "Task",
ident: "await",
},
[
@227-239 Apply(
@227-239 Var {
module_name: "",
ident: "line",
},
[
@233-239 Str(
PlainLine(
"nope",
),
),
],
Space,
),
@227-239 Closure(
[
@227-239 RecordDestructure(
[],
),
],
@252-257 Apply(
@252-254 Var {
module_name: "",
ident: "ok",
},
[
@255-257 Record(
[],
),
],
Space,
),
),
],
BangSuffix,
),
),
],
@283-298 Apply(
@283-298 Var {
module_name: "",
ident: "line",
},
[
@289-298 Str(
PlainLine(
"success",
),
),
],
Space,
),
),
),
],
BangSuffix,
),
),
),
],
BangSuffix,
),
},
],
},
@304-307 Var {
module_name: "",
ident: "msg",
},
),
),
],
}

View file

@ -0,0 +1,191 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-189,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-189 Defs(
Defs {
tags: [
Index(2147483648),
Index(2147483649),
],
regions: [
@20-37,
@52-70,
],
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(
@11-17 Identifier {
ident: "isTrue",
},
@20-37 Apply(
@20-27 Var {
module_name: "Task",
ident: "ok",
},
[
@28-37 Var {
module_name: "Bool",
ident: "true",
},
],
Space,
),
),
Body(
@42-49 Identifier {
ident: "isFalse",
},
@52-70 Apply(
@52-59 Var {
module_name: "Task",
ident: "ok",
},
[
@60-70 Var {
module_name: "Bool",
ident: "false",
},
],
Space,
),
),
],
},
@79-87 Apply(
@79-87 Var {
module_name: "Task",
ident: "await",
},
[
@79-87 Var {
module_name: "",
ident: "isFalse",
},
@79-87 Closure(
[
@79-87 Identifier {
ident: "#!a0",
},
],
@76-189 If(
[
(
@79-87 Var {
module_name: "",
ident: "#!a0",
},
@101-112 Apply(
@101-105 Var {
module_name: "",
ident: "line",
},
[
@106-112 Str(
PlainLine(
"fail",
),
),
],
Space,
),
),
],
@125-132 Apply(
@125-132 Var {
module_name: "Task",
ident: "await",
},
[
@125-132 Var {
module_name: "",
ident: "isTrue",
},
@125-132 Closure(
[
@125-132 Identifier {
ident: "#!a1",
},
],
@76-189 If(
[
(
@125-132 Var {
module_name: "",
ident: "#!a1",
},
@146-160 Apply(
@146-150 Var {
module_name: "",
ident: "line",
},
[
@151-160 Str(
PlainLine(
"success",
),
),
],
Space,
),
),
],
@178-189 Apply(
@178-182 Var {
module_name: "",
ident: "line",
},
[
@183-189 Str(
PlainLine(
"fail",
),
),
],
Space,
),
),
),
],
BangSuffix,
),
),
),
],
BangSuffix,
),
),
),
],
}

View file

@ -0,0 +1,88 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-26,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-26 Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@15-17,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@11-12 Identifier {
ident: "x",
},
@15-17 Num(
"42",
),
),
],
},
@11-26 Apply(
@11-26 Var {
module_name: "Task",
ident: "await",
},
[
@24-25 Var {
module_name: "",
ident: "b",
},
@11-26 Closure(
[
@24-25 Identifier {
ident: "#!a0",
},
],
@22-26 Apply(
@22-23 Var {
module_name: "",
ident: "a",
},
[
@24-25 Var {
module_name: "",
ident: "#!a0",
},
],
Space,
),
),
],
BangSuffix,
),
),
),
],
}

View file

@ -0,0 +1,71 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-33,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-15 Apply(
@11-15 Var {
module_name: "Task",
ident: "await",
},
[
@11-15 Var {
module_name: "",
ident: "foo",
},
@11-15 Closure(
[
@11-15 RecordDestructure(
[],
),
],
@20-24 Apply(
@20-24 Var {
module_name: "Task",
ident: "await",
},
[
@20-24 Var {
module_name: "",
ident: "bar",
},
@20-24 Closure(
[
@20-24 RecordDestructure(
[],
),
],
@29-33 Var {
module_name: "",
ident: "baz",
},
),
],
BangSuffix,
),
),
],
BangSuffix,
),
),
],
}

View file

@ -0,0 +1,49 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-26,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@0-26 Apply(
@0-26 Var {
module_name: "",
ident: "foo",
},
[
@12-17 Str(
PlainLine(
"bar",
),
),
@18-20 Record(
[],
),
@21-26 Str(
PlainLine(
"baz",
),
),
],
Space,
),
),
],
}

View file

@ -0,0 +1,101 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-72,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-23 Apply(
@11-23 Var {
module_name: "Task",
ident: "await",
},
[
@11-23 Apply(
@11-23 Var {
module_name: "",
ident: "line",
},
[
@17-23 Str(
PlainLine(
"Ahoy",
),
),
],
Space,
),
@11-23 Closure(
[
@11-23 RecordDestructure(
[],
),
],
@33-55 Apply(
@33-55 Var {
module_name: "Task",
ident: "await",
},
[
@33-55 Apply(
@33-55 Var {
module_name: "Stdout",
ident: "line",
},
[
@33-40 Str(
PlainLine(
"There",
),
),
],
BinOp(
Pizza,
),
),
@33-55 Closure(
[
@28-30 RecordDestructure(
[],
),
],
@62-72 Apply(
@62-69 Var {
module_name: "Task",
ident: "ok",
},
[
@70-72 Record(
[],
),
],
Space,
),
),
],
BangSuffix,
),
),
],
BangSuffix,
),
),
],
}

View file

@ -0,0 +1,99 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-51,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-51 Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@17-24,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@11-14 Identifier {
ident: "msg",
},
@17-24 Str(
PlainLine(
"hello",
),
),
),
],
},
@11-51 Apply(
@11-51 Var {
module_name: "Task",
ident: "await",
},
[
@29-41 Apply(
@29-41 Var {
module_name: "",
ident: "foo",
},
[
@38-41 Var {
module_name: "",
ident: "msg",
},
],
Space,
),
@11-51 Closure(
[
@29-30 Identifier {
ident: "x",
},
],
@46-51 Apply(
@46-49 Var {
module_name: "",
ident: "bar",
},
[
@50-51 Var {
module_name: "",
ident: "x",
},
],
Space,
),
),
],
BangSuffix,
),
),
),
],
}

View file

@ -0,0 +1,71 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-24,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-24 Apply(
@11-24 Var {
module_name: "Task",
ident: "await",
},
[
@11-16 Var {
module_name: "",
ident: "foo",
},
@11-24 Closure(
[
@11-16 Identifier {
ident: "#!a0",
},
],
@11-16 Apply(
@11-16 Var {
module_name: "Task",
ident: "await",
},
[
@11-16 Var {
module_name: "",
ident: "#!a0",
},
@11-16 Closure(
[
@11-16 RecordDestructure(
[],
),
],
@21-24 Var {
module_name: "",
ident: "bar",
},
),
],
BangSuffix,
),
),
],
BangSuffix,
),
),
],
}

View file

@ -0,0 +1,115 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-61,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@15-43 Apply(
@15-43 Var {
module_name: "Task",
ident: "await",
},
[
Apply(
Var {
module_name: "",
ident: "bar",
},
[
@26-29 Var {
module_name: "",
ident: "baz",
},
],
Space,
),
@15-43 Closure(
[
Identifier {
ident: "#!a0",
},
],
@15-43 Apply(
@15-43 Var {
module_name: "Task",
ident: "await",
},
[
@15-43 Apply(
@15-43 Var {
module_name: "",
ident: "foo",
},
[
@21-29 ParensAround(
Var {
module_name: "",
ident: "#!a0",
},
),
@32-42 ParensAround(
Apply(
@32-36 Var {
module_name: "",
ident: "blah",
},
[
@37-42 Var {
module_name: "",
ident: "stuff",
},
],
Space,
),
),
],
Space,
),
@15-43 Closure(
[
@11-12 Identifier {
ident: "z",
},
],
@48-61 Apply(
@48-59 Var {
module_name: "",
ident: "doSomething",
},
[
@60-61 Var {
module_name: "",
ident: "z",
},
],
Space,
),
),
],
BangSuffix,
),
),
],
BangSuffix,
),
),
],
}

View file

@ -0,0 +1,89 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-49,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-49 Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@23-42,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@11-12 Identifier {
ident: "x",
},
@27-29 Apply(
@27-29 Var {
module_name: "Task",
ident: "await",
},
[
@27-29 Var {
module_name: "",
ident: "b",
},
@27-29 Closure(
[
@23-24 Identifier {
ident: "a",
},
],
@38-42 Apply(
@38-42 Var {
module_name: "",
ident: "c",
},
[
@41-42 Var {
module_name: "",
ident: "a",
},
],
Space,
),
),
],
BangSuffix,
),
),
],
},
@48-49 Var {
module_name: "",
ident: "x",
},
),
),
],
}

View file

@ -0,0 +1,62 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-22,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-3 Identifier {
ident: "run",
},
@0-22 Apply(
@0-22 Var {
module_name: "Task",
ident: "await",
},
[
Var {
module_name: "",
ident: "nextMsg",
},
@0-22 Closure(
[
Identifier {
ident: "#!a0",
},
],
@0-22 Apply(
@0-22 Var {
module_name: "",
ident: "line",
},
[
@13-21 ParensAround(
Var {
module_name: "",
ident: "#!a0",
},
),
],
Space,
),
),
],
BangSuffix,
),
),
],
}

View file

@ -0,0 +1,87 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-73,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@11-56 Apply(
@11-56 Var {
module_name: "Task",
ident: "await",
},
[
@11-56 Apply(
@11-56 Var {
module_name: "",
ident: "line",
},
[
@11-44 Apply(
@26-36 Var {
module_name: "Str",
ident: "concat",
},
[
@11-18 Str(
PlainLine(
"hello",
),
),
@37-44 Str(
PlainLine(
"world",
),
),
],
BinOp(
Pizza,
),
),
],
BinOp(
Pizza,
),
),
@11-56 Closure(
[
@26-56 RecordDestructure(
[],
),
],
@63-73 Apply(
@63-70 Var {
module_name: "Task",
ident: "ok",
},
[
@71-73 Record(
[],
),
],
Space,
),
),
],
BangSuffix,
),
),
],
}

View file

@ -0,0 +1,95 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-67,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "copy",
},
@7-67 Closure(
[
@8-9 Identifier {
ident: "a",
},
@10-11 Identifier {
ident: "b",
},
],
@19-30 Apply(
@19-30 Var {
module_name: "Task",
ident: "await",
},
[
@19-30 Apply(
@19-30 Var {
module_name: "",
ident: "line",
},
[
@25-30 Str(
PlainLine(
"FOO",
),
),
],
Space,
),
@19-30 Closure(
[
@19-30 RecordDestructure(
[],
),
],
@36-67 Apply(
@36-67 Var {
module_name: "",
ident: "mapErr",
},
[
@36-48 Apply(
@36-43 Var {
module_name: "CMD",
ident: "new",
},
[
@44-48 Str(
PlainLine(
"cp",
),
),
],
Space,
),
@64-67 Tag(
"ERR",
),
],
BinOp(
Pizza,
),
),
),
],
BangSuffix,
),
),
),
],
}

View file

@ -0,0 +1,114 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-154,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@20-31 Apply(
@20-31 Var {
module_name: "Task",
ident: "await",
},
[
@20-31 Var {
module_name: "Stdin",
ident: "line",
},
@20-31 Closure(
[
@11-17 Identifier {
ident: "result",
},
],
@37-154 When(
@42-48 Var {
module_name: "",
ident: "result",
},
[
WhenBranch {
patterns: [
@60-63 Tag(
"End",
),
],
value: @79-89 Apply(
@79-86 Var {
module_name: "Task",
ident: "ok",
},
[
@87-89 Record(
[],
),
],
Space,
),
guard: None,
},
WhenBranch {
patterns: [
@99-109 Apply(
@99-104 Tag(
"Input",
),
[
@105-109 Identifier {
ident: "name",
},
],
),
],
value: @125-154 Apply(
@125-154 Var {
module_name: "Stdout",
ident: "line",
},
[
@138-154 Str(
Line(
[
Plaintext(
"Hello, ",
),
Interpolated(
@148-152 Var {
module_name: "",
ident: "name",
},
),
],
),
),
],
Space,
),
guard: None,
},
],
),
),
],
BangSuffix,
),
),
],
}

View file

@ -0,0 +1,104 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-45,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "main",
},
@15-19 Apply(
@15-19 Var {
module_name: "Task",
ident: "await",
},
[
@15-19 Var {
module_name: "",
ident: "foo",
},
@15-19 Closure(
[
@11-12 Identifier {
ident: "a",
},
],
@15-19 Apply(
@15-19 Var {
module_name: "Task",
ident: "await",
},
[
@24-33 Var {
module_name: "",
ident: "bar",
},
@15-19 Closure(
[
@24-33 Identifier {
ident: "#!a0",
},
],
@24-33 Apply(
@24-33 Var {
module_name: "Task",
ident: "await",
},
[
@24-33 Var {
module_name: "",
ident: "#!a0",
},
@24-33 Closure(
[
@24-25 Identifier {
ident: "b",
},
],
@38-45 Apply(
@38-41 Var {
module_name: "",
ident: "baz",
},
[
@42-43 Var {
module_name: "",
ident: "a",
},
@44-45 Var {
module_name: "",
ident: "b",
},
],
Space,
),
),
],
BangSuffix,
),
),
],
BangSuffix,
),
),
],
BangSuffix,
),
),
],
}

View file

@ -0,0 +1,127 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-120,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "list",
},
@11-120 Apply(
@11-120 Var {
module_name: "Task",
ident: "await",
},
[
@16-24 Var {
module_name: "",
ident: "getList",
},
@11-120 Closure(
[
@16-24 Identifier {
ident: "#!a1",
},
],
@11-120 When(
@16-24 Var {
module_name: "",
ident: "#!a1",
},
[
WhenBranch {
patterns: [
@36-38 List(
[],
),
],
value: @54-65 Apply(
@54-65 Var {
module_name: "Task",
ident: "await",
},
[
@54-65 Apply(
@54-65 Var {
module_name: "",
ident: "line",
},
[
@60-65 Str(
PlainLine(
"foo",
),
),
],
Space,
),
@54-65 Closure(
[
@54-65 RecordDestructure(
[],
),
],
@78-89 Apply(
@78-89 Var {
module_name: "",
ident: "line",
},
[
@84-89 Str(
PlainLine(
"bar",
),
),
],
Space,
),
),
],
BangSuffix,
),
guard: None,
},
WhenBranch {
patterns: [
@98-99 Underscore(
"",
),
],
value: @115-120 Apply(
@115-117 Var {
module_name: "",
ident: "ok",
},
[
@118-120 Record(
[],
),
],
Space,
),
guard: None,
},
],
),
),
],
BangSuffix,
),
),
],
}

View file

@ -0,0 +1,81 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
---
Defs {
tags: [
Index(2147483648),
],
regions: [
@0-74,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@0-4 Identifier {
ident: "list",
},
@11-74 Apply(
@11-74 Var {
module_name: "Task",
ident: "await",
},
[
@16-24 Var {
module_name: "",
ident: "getList",
},
@11-74 Closure(
[
@16-24 Identifier {
ident: "#!a0",
},
],
@11-74 When(
@16-24 Var {
module_name: "",
ident: "#!a0",
},
[
WhenBranch {
patterns: [
@36-38 List(
[],
),
],
value: @42-49 Str(
PlainLine(
"empty",
),
),
guard: None,
},
WhenBranch {
patterns: [
@58-59 Underscore(
"",
),
],
value: @63-74 Str(
PlainLine(
"non-empty",
),
),
guard: None,
},
],
),
),
],
BangSuffix,
),
),
],
}

View file

@ -17,9 +17,12 @@ mod test_can {
use core::panic;
use roc_can::expr::Expr::{self, *};
use roc_can::expr::{ClosureData, IntValue, Recursive};
use roc_can::pattern::Pattern;
use roc_module::called_via::CalledVia;
use roc_module::symbol::Symbol;
use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError};
use roc_region::all::{Position, Region};
use roc_region::all::{Loc, Position, Region};
use roc_types::subs::Variable;
use std::{f64, i64};
fn assert_can_runtime_error(input: &str, expected: RuntimeError) {
@ -651,7 +654,7 @@ mod test_can {
// RECORD BUILDERS
#[test]
fn record_builder_desugar() {
fn old_record_builder_desugar() {
let src = indoc!(
r#"
succeed = \_ -> crash "succeed"
@ -766,7 +769,7 @@ mod test_can {
}
#[test]
fn record_builder_field_names_do_not_shadow() {
fn old_record_builder_field_names_do_not_shadow() {
let src = indoc!(
r#"
succeed = \_ -> crash "succeed"
@ -806,7 +809,7 @@ mod test_can {
}
#[test]
fn multiple_record_builders_error() {
fn multiple_old_record_builders_error() {
let src = indoc!(
r#"
succeed
@ -822,17 +825,17 @@ mod test_can {
assert_eq!(problems.len(), 1);
assert!(problems.iter().all(|problem| matches!(
problem,
Problem::RuntimeError(roc_problem::can::RuntimeError::MultipleRecordBuilders { .. })
Problem::RuntimeError(roc_problem::can::RuntimeError::MultipleOldRecordBuilders { .. })
)));
assert!(matches!(
loc_expr.value,
Expr::RuntimeError(roc_problem::can::RuntimeError::MultipleRecordBuilders { .. })
Expr::RuntimeError(roc_problem::can::RuntimeError::MultipleOldRecordBuilders { .. })
));
}
#[test]
fn hanging_record_builder() {
fn hanging_old_record_builder() {
let src = indoc!(
r#"
{ a: <- apply "a" }
@ -846,15 +849,189 @@ mod test_can {
assert_eq!(problems.len(), 1);
assert!(problems.iter().all(|problem| matches!(
problem,
Problem::RuntimeError(roc_problem::can::RuntimeError::UnappliedRecordBuilder { .. })
Problem::RuntimeError(roc_problem::can::RuntimeError::UnappliedOldRecordBuilder { .. })
)));
assert!(matches!(
loc_expr.value,
Expr::RuntimeError(roc_problem::can::RuntimeError::UnappliedRecordBuilder { .. })
Expr::RuntimeError(roc_problem::can::RuntimeError::UnappliedOldRecordBuilder { .. })
));
}
#[test]
fn new_record_builder_desugar() {
let src = indoc!(
r#"
map2 = \a, b, combine -> combine a b
double = \n -> n * 2
c = 3
{ map2 <-
a: 1,
b: double 2,
c
}
"#
);
let arena = Bump::new();
let out = can_expr_with(&arena, test_home(), src);
assert_eq!(out.problems.len(), 0);
// Assert that we desugar to:
//
// map2
// (1)
// (map2
// (double 2)
// (c)
// (\#a, #b -> (#a, #b))
// )
// (\a, (b, c) -> { a: #a, b: #b, c: #c })
let first_map2_args = assert_func_call(
&out.loc_expr.value,
"map2",
CalledVia::RecordBuilder,
&out.interns,
);
let (first_arg, second_arg, third_arg) = match &first_map2_args[..] {
[first, second, third] => (&first.1.value, &second.1.value, &third.1.value),
_ => panic!("map2 didn't receive three arguments"),
};
assert_num_value(first_arg, 1);
let inner_map2_args =
assert_func_call(second_arg, "map2", CalledVia::RecordBuilder, &out.interns);
let (first_inner_arg, second_inner_arg, third_inner_arg) = match &inner_map2_args[..] {
[first, second, third] => (&first.1.value, &second.1.value, &third.1.value),
_ => panic!("inner map2 didn't receive three arguments"),
};
let double_args =
assert_func_call(first_inner_arg, "double", CalledVia::Space, &out.interns);
assert_eq!(double_args.len(), 1);
assert_num_value(&double_args[0].1.value, 2);
assert_var_usage(second_inner_arg, "c", &out.interns);
match third_inner_arg {
Expr::Closure(ClosureData {
arguments,
loc_body,
..
}) => {
assert_eq!(arguments.len(), 2);
assert_pattern_name(
&arguments[0].2.value,
"#record_builder_closure_arg_a",
&out.interns,
);
assert_pattern_name(
&arguments[1].2.value,
"#record_builder_closure_arg_b",
&out.interns,
);
match &loc_body.value {
Expr::Tuple { elems, .. } => {
assert_eq!(elems.len(), 2);
assert_var_usage(
&elems[0].1.value,
"#record_builder_closure_arg_a",
&out.interns,
);
assert_var_usage(
&elems[1].1.value,
"#record_builder_closure_arg_b",
&out.interns,
);
}
_ => panic!("Closure body was not a tuple"),
}
}
_ => panic!("inner map2's combiner was not a closure"),
}
match third_arg {
Expr::Closure(ClosureData {
arguments,
loc_body,
..
}) => {
assert_eq!(arguments.len(), 2);
assert_pattern_name(&arguments[0].2.value, "#a", &out.interns);
match &arguments[1].2.value {
Pattern::TupleDestructure { destructs, .. } => {
assert_eq!(destructs.len(), 2);
assert_pattern_name(&destructs[0].value.typ.1.value, "#b", &out.interns);
assert_pattern_name(&destructs[1].value.typ.1.value, "#c", &out.interns);
}
_ => panic!("Second arg to builder func was not a tuple destructure"),
}
match &loc_body.value {
Expr::Record { fields, .. } => {
assert_eq!(fields.len(), 3);
assert_eq!(get_field_var_sym(fields, "a").as_str(&out.interns), "#a");
assert_eq!(get_field_var_sym(fields, "b").as_str(&out.interns), "#b");
assert_eq!(get_field_var_sym(fields, "c").as_str(&out.interns), "#c");
}
_ => panic!("Closure body was not a tuple"),
}
}
_ => panic!("inner map2's combiner was not a closure"),
}
}
fn assert_num_value(expr: &Expr, num: usize) {
match expr {
Expr::Num(_, num_str, _, _) => {
assert_eq!(&**num_str, &num.to_string())
}
_ => panic!("Expr wasn't a Num with value {num}: {:?}", expr),
}
}
fn assert_var_usage(expr: &Expr, name: &str, interns: &roc_module::symbol::Interns) {
match expr {
Expr::Var(sym, _) => assert_eq!(sym.as_str(interns), name),
_ => panic!("Expr was not a variable usage: {:?}", expr),
}
}
fn assert_func_call(
expr: &Expr,
name: &str,
called_via: CalledVia,
interns: &roc_module::symbol::Interns,
) -> Vec<(Variable, Loc<Expr>)> {
match expr {
Expr::LetNonRec(_, loc_expr) => {
assert_func_call(&loc_expr.value, name, called_via, interns)
}
Expr::Call(fun, args, called) if called == &called_via => {
match &fun.1.value {
Expr::Var(sym, _) => assert_eq!(sym.as_str(interns), name),
_ => panic!("Builder didn't desugar with mapper at front"),
};
args.clone()
}
_ => panic!("Expr was not a RecordBuilder Call: {:?}", expr),
}
}
fn assert_pattern_name(pattern: &Pattern, name: &str, interns: &roc_module::symbol::Interns) {
match pattern {
Pattern::Identifier(sym) => assert_eq!(sym.as_str(interns), name),
_ => panic!("Pattern was not an identifier: {:?}", pattern),
}
}
// TAIL CALLS
fn get_closure(expr: &Expr, i: usize) -> roc_can::expr::Recursive {
match expr {

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@ use crate::{
};
use roc_parse::ast::{
AbilityImpls, AssignedField, Collection, Expr, ExtractSpaces, ImplementsAbilities,
ImplementsAbility, ImplementsClause, RecordBuilderField, Tag, TypeAnnotation, TypeHeader,
ImplementsAbility, ImplementsClause, OldRecordBuilderField, Tag, TypeAnnotation, TypeHeader,
};
use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc;
@ -505,7 +505,7 @@ fn format_assigned_field_help<T>(
}
}
impl<'a> Formattable for RecordBuilderField<'a> {
impl<'a> Formattable for OldRecordBuilderField<'a> {
fn is_multiline(&self) -> bool {
is_multiline_record_builder_field_help(self)
}
@ -516,8 +516,8 @@ impl<'a> Formattable for RecordBuilderField<'a> {
}
}
fn is_multiline_record_builder_field_help(afield: &RecordBuilderField<'_>) -> bool {
use self::RecordBuilderField::*;
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(),
@ -531,12 +531,12 @@ fn is_multiline_record_builder_field_help(afield: &RecordBuilderField<'_>) -> bo
}
fn format_record_builder_field_help(
zelf: &RecordBuilderField,
zelf: &OldRecordBuilderField,
buf: &mut Buf,
indent: u16,
is_multiline: bool,
) {
use self::RecordBuilderField::*;
use self::OldRecordBuilderField::*;
match zelf {
Value(name, spaces, ann) => {

View file

@ -10,7 +10,7 @@ use crate::Buf;
use roc_module::called_via::{self, BinOp};
use roc_parse::ast::{
is_expr_suffixed, AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces,
Pattern, RecordBuilderField, WhenBranch,
OldRecordBuilderField, Pattern, WhenBranch,
};
use roc_parse::ast::{StrLiteral, StrSegment};
use roc_parse::ident::Accessor;
@ -85,8 +85,11 @@ impl<'a> Formattable for Expr<'a> {
| PrecedenceConflict(roc_parse::ast::PrecedenceConflict {
expr: loc_subexpr, ..
})
| MultipleRecordBuilders(loc_subexpr)
| UnappliedRecordBuilder(loc_subexpr) => loc_subexpr.is_multiline(),
| MultipleOldRecordBuilders(loc_subexpr)
| UnappliedOldRecordBuilder(loc_subexpr)
| EmptyRecordBuilder(loc_subexpr)
| SingleFieldRecordBuilder(loc_subexpr)
| OptionalFieldInRecordBuilder(_, loc_subexpr) => loc_subexpr.is_multiline(),
ParensAround(subexpr) => subexpr.is_multiline(),
@ -109,7 +112,8 @@ impl<'a> Formattable for Expr<'a> {
Record(fields) => is_collection_multiline(fields),
Tuple(fields) => is_collection_multiline(fields),
RecordUpdate { fields, .. } => is_collection_multiline(fields),
RecordBuilder(fields) => is_collection_multiline(fields),
OldRecordBuilder(fields) => is_collection_multiline(fields),
RecordBuilder { fields, .. } => is_collection_multiline(fields),
}
}
@ -237,7 +241,7 @@ impl<'a> Formattable for Expr<'a> {
Expr::Tuple(_)
| Expr::List(_)
| Expr::Record(_)
| Expr::RecordBuilder(_)
| Expr::OldRecordBuilder(_)
)
&& a.extract_spaces().before == [CommentOrNewline::Newline]
})
@ -365,14 +369,24 @@ impl<'a> Formattable for Expr<'a> {
RecordUpdate { update, fields } => {
fmt_record_like(
buf,
Some(*update),
Some(RecordPrefix::Update(update)),
*fields,
indent,
format_assigned_field_multiline,
assigned_field_to_space_before,
);
}
RecordBuilder(fields) => {
RecordBuilder { mapper, fields } => {
fmt_record_like(
buf,
Some(RecordPrefix::Mapper(mapper)),
*fields,
indent,
format_assigned_field_multiline,
assigned_field_to_space_before,
);
}
OldRecordBuilder(fields) => {
fmt_record_like(
buf,
None,
@ -520,8 +534,11 @@ impl<'a> Formattable for Expr<'a> {
}
MalformedClosure => {}
PrecedenceConflict { .. } => {}
MultipleRecordBuilders { .. } => {}
UnappliedRecordBuilder { .. } => {}
MultipleOldRecordBuilders { .. } => {}
UnappliedOldRecordBuilder { .. } => {}
EmptyRecordBuilder { .. } => {}
SingleFieldRecordBuilder { .. } => {}
OptionalFieldInRecordBuilder(_, _) => {}
}
}
}
@ -582,7 +599,7 @@ fn is_outdentable(expr: &Expr) -> bool {
Expr::Tuple(_)
| Expr::List(_)
| Expr::Record(_)
| Expr::RecordBuilder(_)
| Expr::OldRecordBuilder(_)
| Expr::Closure(..)
)
}
@ -1366,9 +1383,14 @@ fn pattern_needs_parens_when_backpassing(pat: &Pattern) -> bool {
}
}
enum RecordPrefix<'a> {
Update(&'a Loc<Expr<'a>>),
Mapper(&'a Loc<Expr<'a>>),
}
fn fmt_record_like<'a, Field, Format, ToSpaceBefore>(
buf: &mut Buf,
update: Option<&'a Loc<Expr<'a>>>,
prefix: Option<RecordPrefix<'a>>,
fields: Collection<'a, Loc<Field>>,
indent: u16,
format_field_multiline: Format,
@ -1381,22 +1403,27 @@ fn fmt_record_like<'a, Field, Format, ToSpaceBefore>(
let loc_fields = fields.items;
let final_comments = fields.final_comments();
buf.indent(indent);
if loc_fields.is_empty() && final_comments.iter().all(|c| c.is_newline()) && update.is_none() {
if loc_fields.is_empty() && final_comments.iter().all(|c| c.is_newline()) && prefix.is_none() {
buf.push_str("{}");
} else {
buf.push('{');
match update {
match prefix {
None => {}
// We are presuming this to be a Var()
// If it wasnt a Var() we would not have made
// it this far. For example "{ 4 & hello = 9 }"
// doesnt make sense.
Some(record_var) => {
Some(RecordPrefix::Update(record_var)) => {
buf.spaces(1);
record_var.format(buf, indent);
buf.push_str(" &");
}
Some(RecordPrefix::Mapper(mapper_var)) => {
buf.spaces(1);
mapper_var.format(buf, indent);
buf.push_str(" <-");
}
}
let is_multiline = loc_fields.iter().any(|loc_field| loc_field.is_multiline())
@ -1554,11 +1581,11 @@ fn assigned_field_to_space_before<'a, T>(
fn format_record_builder_field_multiline(
buf: &mut Buf,
field: &RecordBuilderField,
field: &OldRecordBuilderField,
indent: u16,
separator_prefix: &str,
) {
use self::RecordBuilderField::*;
use self::OldRecordBuilderField::*;
match field {
Value(name, spaces, ann) => {
buf.newline();
@ -1651,10 +1678,10 @@ fn format_record_builder_field_multiline(
}
fn record_builder_field_to_space_before<'a>(
field: &'a RecordBuilderField<'a>,
) -> Option<(&RecordBuilderField<'a>, &'a [CommentOrNewline<'a>])> {
field: &'a OldRecordBuilderField<'a>,
) -> Option<(&OldRecordBuilderField<'a>, &'a [CommentOrNewline<'a>])> {
match field {
RecordBuilderField::SpaceBefore(sub_field, spaces) => Some((sub_field, spaces)),
OldRecordBuilderField::SpaceBefore(sub_field, spaces) => Some((sub_field, spaces)),
_ => None,
}
}

View file

@ -6,8 +6,8 @@ use roc_parse::{
AbilityImpls, AbilityMember, AssignedField, Collection, CommentOrNewline, Defs, Expr,
Header, Implements, ImplementsAbilities, ImplementsAbility, ImplementsClause, ImportAlias,
ImportAsKeyword, ImportExposingKeyword, ImportedModuleName, IngestedFileAnnotation,
IngestedFileImport, Module, ModuleImport, ModuleImportParams, Pattern, PatternAs,
RecordBuilderField, Spaced, Spaces, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef,
IngestedFileImport, Module, ModuleImport, ModuleImportParams, OldRecordBuilderField,
Pattern, PatternAs, Spaced, Spaces, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef,
TypeHeader, ValueDef, WhenBranch,
},
header::{
@ -715,26 +715,26 @@ impl<'a, T: RemoveSpaces<'a> + Copy + std::fmt::Debug> RemoveSpaces<'a> for Assi
}
}
impl<'a> RemoveSpaces<'a> for RecordBuilderField<'a> {
impl<'a> RemoveSpaces<'a> for OldRecordBuilderField<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
RecordBuilderField::Value(a, _, c) => RecordBuilderField::Value(
OldRecordBuilderField::Value(a, _, c) => OldRecordBuilderField::Value(
a.remove_spaces(arena),
&[],
arena.alloc(c.remove_spaces(arena)),
),
RecordBuilderField::ApplyValue(a, _, _, c) => RecordBuilderField::ApplyValue(
OldRecordBuilderField::ApplyValue(a, _, _, c) => OldRecordBuilderField::ApplyValue(
a.remove_spaces(arena),
&[],
&[],
arena.alloc(c.remove_spaces(arena)),
),
RecordBuilderField::LabelOnly(a) => {
RecordBuilderField::LabelOnly(a.remove_spaces(arena))
OldRecordBuilderField::LabelOnly(a) => {
OldRecordBuilderField::LabelOnly(a.remove_spaces(arena))
}
RecordBuilderField::Malformed(a) => RecordBuilderField::Malformed(a),
RecordBuilderField::SpaceBefore(a, _) => a.remove_spaces(arena),
RecordBuilderField::SpaceAfter(a, _) => a.remove_spaces(arena),
OldRecordBuilderField::Malformed(a) => OldRecordBuilderField::Malformed(a),
OldRecordBuilderField::SpaceBefore(a, _) => a.remove_spaces(arena),
OldRecordBuilderField::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
@ -788,7 +788,11 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
fields: fields.remove_spaces(arena),
},
Expr::Record(a) => Expr::Record(a.remove_spaces(arena)),
Expr::RecordBuilder(a) => Expr::RecordBuilder(a.remove_spaces(arena)),
Expr::OldRecordBuilder(a) => Expr::OldRecordBuilder(a.remove_spaces(arena)),
Expr::RecordBuilder { mapper, fields } => Expr::RecordBuilder {
mapper: arena.alloc(mapper.remove_spaces(arena)),
fields: fields.remove_spaces(arena),
},
Expr::Tuple(a) => Expr::Tuple(a.remove_spaces(arena)),
Expr::Var { module_name, ident } => Expr::Var { module_name, ident },
Expr::Underscore(a) => Expr::Underscore(a),
@ -855,8 +859,13 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
Expr::MalformedClosure => Expr::MalformedClosure,
Expr::MalformedSuffixed(a) => Expr::MalformedSuffixed(a),
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
Expr::MultipleRecordBuilders(a) => Expr::MultipleRecordBuilders(a),
Expr::UnappliedRecordBuilder(a) => Expr::UnappliedRecordBuilder(a),
Expr::MultipleOldRecordBuilders(a) => Expr::MultipleOldRecordBuilders(a),
Expr::UnappliedOldRecordBuilder(a) => Expr::UnappliedOldRecordBuilder(a),
Expr::EmptyRecordBuilder(a) => Expr::EmptyRecordBuilder(a),
Expr::SingleFieldRecordBuilder(a) => Expr::SingleFieldRecordBuilder(a),
Expr::OptionalFieldInRecordBuilder(name, a) => {
Expr::OptionalFieldInRecordBuilder(name, a)
}
Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
Expr::SingleQuote(a) => Expr::Num(a),

View file

@ -1082,6 +1082,8 @@ impl<
arg_layouts: &[InLayout<'a>],
ret_layout: &InLayout<'a>,
) {
debug_assert_eq!(args.len(), arg_layouts.len());
// Save used caller saved regs.
self.storage_manager
.push_used_caller_saved_regs_to_stack(&mut self.buf);
@ -2278,6 +2280,25 @@ impl<
refcount_proc_name
}
fn build_indirect_inc_n(&mut self, layout: InLayout<'a>) -> Symbol {
let ident_ids = self
.interns
.all_ident_ids
.get_mut(&self.env.module_id)
.unwrap();
let (refcount_proc_name, linker_data) = self.helper_proc_gen.gen_refcount_proc(
ident_ids,
self.layout_interner,
layout,
HelperOp::IndirectIncN,
);
self.helper_proc_symbols_mut().extend(linker_data);
refcount_proc_name
}
fn build_indirect_dec(&mut self, layout: InLayout<'a>) -> Symbol {
let ident_ids = self
.interns
@ -2310,17 +2331,6 @@ impl<
.unwrap();
let caller_proc = match higher_order.op {
HigherOrder::ListMap { .. }
| HigherOrder::ListMap2 { .. }
| HigherOrder::ListMap3 { .. }
| HigherOrder::ListMap4 { .. } => CallerProc::new_list_map(
self.env.arena,
self.env.module_id,
ident_ids,
self.layout_interner,
&higher_order.passed_function,
higher_order.closure_env_layout,
),
HigherOrder::ListSortWith { .. } => CallerProc::new_compare(
self.env.arena,
self.env.module_id,
@ -2348,7 +2358,7 @@ impl<
// function pointer to a function that takes a pointer, and increments
let inc_n_data = if let Some(closure_env_layout) = higher_order.closure_env_layout {
self.increment_fn_pointer(closure_env_layout)
self.increment_n_fn_pointer(closure_env_layout)
} else {
// null pointer
self.load_literal_i64(&Symbol::DEV_TMP, 0);
@ -2374,394 +2384,6 @@ impl<
let usize_ = Layout::U64;
match higher_order.op {
HigherOrder::ListMap { xs } => {
let old_element_layout = argument_layouts[0];
let new_element_layout = higher_order.passed_function.return_layout;
let input_list_layout = LayoutRepr::Builtin(Builtin::List(old_element_layout));
let input_list_in_layout = self
.layout_interner
.insert_direct_no_semantic(input_list_layout);
let alignment = self.debug_symbol("alignment");
let old_element_width = self.debug_symbol("old_element_width");
let new_element_width = self.debug_symbol("new_element_width");
self.load_layout_alignment(new_element_layout, alignment);
self.load_layout_stack_size(old_element_layout, old_element_width);
self.load_layout_stack_size(new_element_layout, new_element_width);
self.build_fn_pointer(&caller, caller_string);
// we pass a null pointer when the data is not owned. the zig code must not call this!
let data_is_owned = higher_order.closure_env_layout.is_some()
&& higher_order.passed_function.owns_captured_environment;
self.load_literal(
&Symbol::DEV_TMP2,
&Layout::BOOL,
&Literal::Bool(data_is_owned),
);
// list: RocList,
// caller: Caller1,
// data: Opaque,
// inc_n_data: IncN,
// data_is_owned: bool,
// alignment: u32,
// old_element_width: usize,
// new_element_width: usize,
let arguments = [
xs,
caller,
data,
inc_n_data,
Symbol::DEV_TMP2,
alignment,
old_element_width,
new_element_width,
];
let layouts = [
input_list_in_layout,
ptr,
ptr,
ptr,
Layout::BOOL,
Layout::U32,
usize_,
usize_,
];
self.build_fn_call_stack_return(
bitcode::LIST_MAP.to_string(),
&arguments,
&layouts,
ret_layout,
*dst,
);
self.free_symbol(&Symbol::DEV_TMP);
self.free_symbol(&Symbol::DEV_TMP2);
}
HigherOrder::ListMap2 { xs, ys } => {
let old_element_layout1 = argument_layouts[0];
let old_element_layout2 = argument_layouts[1];
let new_element_layout = higher_order.passed_function.return_layout;
let input_list_layout1 = LayoutRepr::Builtin(Builtin::List(old_element_layout1));
let input_list_in_layout1 = self
.layout_interner
.insert_direct_no_semantic(input_list_layout1);
let input_list_layout2 = LayoutRepr::Builtin(Builtin::List(old_element_layout2));
let input_list_in_layout2 = self
.layout_interner
.insert_direct_no_semantic(input_list_layout2);
let alignment = self.debug_symbol("alignment");
let old_element_width1 = self.debug_symbol("old_element_width1");
let old_element_width2 = self.debug_symbol("old_element_width2");
let new_element_width = self.debug_symbol("new_element_width");
self.load_layout_alignment(new_element_layout, alignment);
self.load_layout_stack_size(old_element_layout1, old_element_width1);
self.load_layout_stack_size(old_element_layout2, old_element_width2);
self.load_layout_stack_size(new_element_layout, new_element_width);
let dec1 = self.decrement_fn_pointer(old_element_layout1);
let dec2 = self.decrement_fn_pointer(old_element_layout2);
self.build_fn_pointer(&caller, caller_string);
// we pass a null pointer when the data is not owned. the zig code must not call this!
let data_is_owned = higher_order.closure_env_layout.is_some()
&& higher_order.passed_function.owns_captured_environment;
self.load_literal(
&Symbol::DEV_TMP2,
&Layout::BOOL,
&Literal::Bool(data_is_owned),
);
// list1: RocList,
// list2: RocList,
// caller: Caller1,
// data: Opaque,
// inc_n_data: IncN,
// data_is_owned: bool,
// alignment: u32,
// old_element_width1: usize,
// old_element_width2: usize,
// new_element_width: usize,
let arguments = [
xs,
ys,
caller,
data,
inc_n_data,
Symbol::DEV_TMP2,
alignment,
old_element_width1,
old_element_width2,
new_element_width,
dec1,
dec2,
];
let layouts = [
input_list_in_layout1,
input_list_in_layout2,
ptr,
ptr,
ptr,
Layout::BOOL,
Layout::U32,
usize_,
usize_,
usize_,
ptr, // dec1
ptr, // dec2
];
self.build_fn_call_stack_return(
bitcode::LIST_MAP2.to_string(),
&arguments,
&layouts,
ret_layout,
*dst,
);
self.free_symbol(&Symbol::DEV_TMP);
self.free_symbol(&Symbol::DEV_TMP2);
}
HigherOrder::ListMap3 { xs, ys, zs } => {
let old_element_layout1 = argument_layouts[0];
let old_element_layout2 = argument_layouts[1];
let old_element_layout3 = argument_layouts[2];
let new_element_layout = higher_order.passed_function.return_layout;
let input_list_layout1 = LayoutRepr::Builtin(Builtin::List(old_element_layout1));
let input_list_in_layout1 = self
.layout_interner
.insert_direct_no_semantic(input_list_layout1);
let input_list_layout2 = LayoutRepr::Builtin(Builtin::List(old_element_layout2));
let input_list_in_layout2 = self
.layout_interner
.insert_direct_no_semantic(input_list_layout2);
let input_list_layout3 = LayoutRepr::Builtin(Builtin::List(old_element_layout3));
let input_list_in_layout3 = self
.layout_interner
.insert_direct_no_semantic(input_list_layout3);
let alignment = self.debug_symbol("alignment");
let old_element_width1 = self.debug_symbol("old_element_width1");
let old_element_width2 = self.debug_symbol("old_element_width2");
let old_element_width3 = self.debug_symbol("old_element_width3");
let new_element_width = self.debug_symbol("new_element_width");
self.load_layout_alignment(new_element_layout, alignment);
self.load_layout_stack_size(old_element_layout1, old_element_width1);
self.load_layout_stack_size(old_element_layout2, old_element_width2);
self.load_layout_stack_size(old_element_layout3, old_element_width3);
self.load_layout_stack_size(new_element_layout, new_element_width);
let dec1 = self.decrement_fn_pointer(old_element_layout1);
let dec2 = self.decrement_fn_pointer(old_element_layout2);
let dec3 = self.decrement_fn_pointer(old_element_layout3);
self.build_fn_pointer(&caller, caller_string);
// we pass a null pointer when the data is not owned. the zig code must not call this!
let data_is_owned = higher_order.closure_env_layout.is_some()
&& higher_order.passed_function.owns_captured_environment;
self.load_literal(
&Symbol::DEV_TMP2,
&Layout::BOOL,
&Literal::Bool(data_is_owned),
);
// list1: RocList,
// list2: RocList,
// caller: Caller1,
// data: Opaque,
// inc_n_data: IncN,
// data_is_owned: bool,
// alignment: u32,
// old_element_width1: usize,
// old_element_width2: usize,
// new_element_width: usize,
let arguments = [
xs,
ys,
zs,
caller,
data,
inc_n_data,
Symbol::DEV_TMP2,
alignment,
old_element_width1,
old_element_width2,
old_element_width3,
new_element_width,
dec1,
dec2,
dec3,
];
let layouts = [
input_list_in_layout1,
input_list_in_layout2,
input_list_in_layout3,
ptr,
ptr,
ptr,
Layout::BOOL,
Layout::U32,
usize_, // old_element_width_1
usize_, // old_element_width_2
usize_, // old_element_width_3
usize_, // new_element_width
ptr, // dec1
ptr, // dec2
ptr, // dec3
];
self.build_fn_call_stack_return(
bitcode::LIST_MAP3.to_string(),
&arguments,
&layouts,
ret_layout,
*dst,
);
self.free_symbol(&Symbol::DEV_TMP);
self.free_symbol(&Symbol::DEV_TMP2);
}
HigherOrder::ListMap4 { xs, ys, zs, ws } => {
let old_element_layout1 = argument_layouts[0];
let old_element_layout2 = argument_layouts[1];
let old_element_layout3 = argument_layouts[2];
let old_element_layout4 = argument_layouts[3];
let new_element_layout = higher_order.passed_function.return_layout;
let input_list_layout1 = LayoutRepr::Builtin(Builtin::List(old_element_layout1));
let input_list_in_layout1 = self
.layout_interner
.insert_direct_no_semantic(input_list_layout1);
let input_list_layout2 = LayoutRepr::Builtin(Builtin::List(old_element_layout2));
let input_list_in_layout2 = self
.layout_interner
.insert_direct_no_semantic(input_list_layout2);
let input_list_layout3 = LayoutRepr::Builtin(Builtin::List(old_element_layout3));
let input_list_in_layout3 = self
.layout_interner
.insert_direct_no_semantic(input_list_layout3);
let input_list_layout4 = LayoutRepr::Builtin(Builtin::List(old_element_layout4));
let input_list_in_layout4 = self
.layout_interner
.insert_direct_no_semantic(input_list_layout4);
let alignment = self.debug_symbol("alignment");
let old_element_width1 = self.debug_symbol("old_element_width1");
let old_element_width2 = self.debug_symbol("old_element_width2");
let old_element_width3 = self.debug_symbol("old_element_width3");
let old_element_width4 = self.debug_symbol("old_element_width4");
let new_element_width = self.debug_symbol("new_element_width");
self.load_layout_alignment(new_element_layout, alignment);
self.load_layout_stack_size(old_element_layout1, old_element_width1);
self.load_layout_stack_size(old_element_layout2, old_element_width2);
self.load_layout_stack_size(old_element_layout3, old_element_width3);
self.load_layout_stack_size(old_element_layout4, old_element_width4);
self.load_layout_stack_size(new_element_layout, new_element_width);
let dec1 = self.decrement_fn_pointer(old_element_layout1);
let dec2 = self.decrement_fn_pointer(old_element_layout2);
let dec3 = self.decrement_fn_pointer(old_element_layout3);
let dec4 = self.decrement_fn_pointer(old_element_layout4);
self.build_fn_pointer(&caller, caller_string);
// we pass a null pointer when the data is not owned. the zig code must not call this!
let data_is_owned = higher_order.closure_env_layout.is_some()
&& higher_order.passed_function.owns_captured_environment;
self.load_literal(
&Symbol::DEV_TMP2,
&Layout::BOOL,
&Literal::Bool(data_is_owned),
);
let arguments = [
xs,
ys,
zs,
ws,
caller,
data,
inc_n_data,
Symbol::DEV_TMP2,
alignment,
old_element_width1,
old_element_width2,
old_element_width3,
old_element_width4,
new_element_width,
dec1,
dec2,
dec3,
dec4,
];
let layouts = [
input_list_in_layout1,
input_list_in_layout2,
input_list_in_layout3,
input_list_in_layout4,
ptr,
ptr,
ptr,
Layout::BOOL,
Layout::U32,
usize_, // old_element_width_1
usize_, // old_element_width_2
usize_, // old_element_width_3
usize_, // old_element_width_4
usize_, // new_element_width
ptr, // dec1
ptr, // dec2
ptr, // dec3
ptr, // dec4
];
self.build_fn_call_stack_return(
bitcode::LIST_MAP4.to_string(),
&arguments,
&layouts,
ret_layout,
*dst,
);
self.free_symbol(&Symbol::DEV_TMP);
self.free_symbol(&Symbol::DEV_TMP2);
}
HigherOrder::ListSortWith { xs } => {
let element_layout = argument_layouts[0];
@ -2788,6 +2410,12 @@ impl<
&Literal::Bool(data_is_owned),
);
// Load element_refcounted argument (bool).
self.load_layout_refcounted(element_layout, Symbol::DEV_TMP3);
let inc_elem_fn = self.increment_fn_pointer(element_layout);
let dec_elem_fn = self.decrement_fn_pointer(element_layout);
// input: RocList,
// caller: CompareFn,
// data: Opaque,
@ -2795,6 +2423,9 @@ impl<
// data_is_owned: bool,
// alignment: u32,
// element_width: usize,
// element_refcounted: bool,
// inc: Inc,
// dec: Dec,
let arguments = [
xs,
@ -2804,6 +2435,9 @@ impl<
Symbol::DEV_TMP2,
alignment,
element_width,
Symbol::DEV_TMP3,
inc_elem_fn,
dec_elem_fn,
];
let layouts = [
@ -2814,6 +2448,9 @@ impl<
Layout::BOOL,
Layout::U32,
usize_,
Layout::BOOL,
usize_,
usize_,
];
self.build_fn_call_stack_return(
@ -2826,6 +2463,7 @@ impl<
self.free_symbol(&Symbol::DEV_TMP);
self.free_symbol(&Symbol::DEV_TMP2);
self.free_symbol(&Symbol::DEV_TMP3);
}
}
}
@ -2852,6 +2490,12 @@ impl<
// Load element_width argument (usize).
self.load_layout_stack_size(elem_layout, Symbol::DEV_TMP2);
// Load element_refcounted argument (bool).
self.load_layout_refcounted(elem_layout, Symbol::DEV_TMP3);
let inc_elem_fn = self.increment_fn_pointer(elem_layout);
let dec_elem_fn = self.decrement_fn_pointer(elem_layout);
// Setup the return location.
let base_offset =
self.storage_manager
@ -2863,11 +2507,25 @@ impl<
Symbol::DEV_TMP,
// element_width
Symbol::DEV_TMP2,
// element_refcounted
Symbol::DEV_TMP3,
// inc
inc_elem_fn,
// dec
dec_elem_fn,
];
let usize_layout = Layout::U64;
let lowlevel_arg_layouts = [
ret_layout,
Layout::U32,
Layout::U64,
Layout::BOOL,
usize_layout,
usize_layout,
];
let lowlevel_arg_layouts = [ret_layout, Layout::U32, Layout::U64];
self.build_fn_call(
&Symbol::DEV_TMP3,
&Symbol::DEV_TMP4,
bitcode::LIST_CLONE.to_string(),
&lowlevel_args,
&lowlevel_arg_layouts,
@ -2875,17 +2533,18 @@ impl<
);
self.free_symbol(&Symbol::DEV_TMP);
self.free_symbol(&Symbol::DEV_TMP2);
self.free_symbol(&Symbol::DEV_TMP3);
// Copy from list to the output record.
self.storage_manager.copy_symbol_to_stack_offset(
self.layout_interner,
&mut self.buf,
base_offset,
&Symbol::DEV_TMP3,
&Symbol::DEV_TMP4,
&ret_layout,
);
self.free_symbol(&Symbol::DEV_TMP3);
self.free_symbol(&Symbol::DEV_TMP4);
}
fn build_list_with_capacity(
@ -2902,6 +2561,11 @@ impl<
// Load element_width argument (usize).
self.load_layout_stack_size(elem_layout, Symbol::DEV_TMP2);
// Load element_refcounted argument (bool).
self.load_layout_refcounted(elem_layout, Symbol::DEV_TMP3);
let inc_elem_fn = self.increment_fn_pointer(elem_layout);
// Setup the return location.
let base_offset =
self.storage_manager
@ -2913,11 +2577,22 @@ impl<
Symbol::DEV_TMP,
// element_width
Symbol::DEV_TMP2,
// element_refcounted
Symbol::DEV_TMP3,
// Inc element fn
inc_elem_fn,
];
let layout_usize = Layout::U64;
let lowlevel_arg_layouts = [
capacity_layout,
Layout::U32,
Layout::U64,
Layout::BOOL,
layout_usize,
];
let lowlevel_arg_layouts = [capacity_layout, Layout::U32, Layout::U64];
self.build_fn_call(
&Symbol::DEV_TMP3,
&Symbol::DEV_TMP4,
bitcode::LIST_WITH_CAPACITY.to_string(),
&lowlevel_args,
&lowlevel_arg_layouts,
@ -2925,17 +2600,18 @@ impl<
);
self.free_symbol(&Symbol::DEV_TMP);
self.free_symbol(&Symbol::DEV_TMP2);
self.free_symbol(&Symbol::DEV_TMP3);
// Copy from list to the output record.
self.storage_manager.copy_symbol_to_stack_offset(
self.layout_interner,
&mut self.buf,
base_offset,
&Symbol::DEV_TMP3,
&Symbol::DEV_TMP4,
ret_layout,
);
self.free_symbol(&Symbol::DEV_TMP3);
self.free_symbol(&Symbol::DEV_TMP4);
}
fn build_list_reserve(
@ -2959,12 +2635,15 @@ impl<
_ => unreachable!(),
};
self.load_layout_stack_size(element_layout, Symbol::DEV_TMP2);
self.load_layout_refcounted(element_layout, Symbol::DEV_TMP3);
let inc_elem_fn = self.increment_fn_pointer(element_layout);
// Load UpdateMode.Immutable argument (0u8)
let u8_layout = Layout::U8;
let update_mode = 0u8;
self.load_literal(
&Symbol::DEV_TMP3,
&Symbol::DEV_TMP4,
&u8_layout,
&Literal::Int((update_mode as i128).to_ne_bytes()),
);
@ -2982,20 +2661,26 @@ impl<
spare,
// element_width
Symbol::DEV_TMP2,
// update_mode
// element_refcounted
Symbol::DEV_TMP3,
// Inc element fn
inc_elem_fn,
// update_mode
Symbol::DEV_TMP4,
];
let usize_layout = Layout::U64;
let lowlevel_arg_layouts = [
list_layout,
Layout::U32,
spare_layout,
Layout::U64,
Layout::BOOL,
usize_layout,
u8_layout,
];
self.build_fn_call(
&Symbol::DEV_TMP4,
&Symbol::DEV_TMP5,
bitcode::LIST_RESERVE.to_string(),
&lowlevel_args,
&lowlevel_arg_layouts,
@ -3004,17 +2689,18 @@ impl<
self.free_symbol(&Symbol::DEV_TMP);
self.free_symbol(&Symbol::DEV_TMP2);
self.free_symbol(&Symbol::DEV_TMP3);
self.free_symbol(&Symbol::DEV_TMP4);
// Return list value from fn call
self.storage_manager.copy_symbol_to_stack_offset(
self.layout_interner,
&mut self.buf,
base_offset,
&Symbol::DEV_TMP4,
&Symbol::DEV_TMP5,
ret_layout,
);
self.free_symbol(&Symbol::DEV_TMP4);
self.free_symbol(&Symbol::DEV_TMP5);
}
fn build_list_append_unsafe(
@ -3155,6 +2841,12 @@ impl<
// Load the elements size.
self.load_layout_stack_size(elem_layout, Symbol::DEV_TMP3);
// Load element_refcounted argument (bool).
self.load_layout_refcounted(elem_layout, Symbol::DEV_TMP4);
let inc_elem_fn = self.increment_fn_pointer(elem_layout);
let dec_elem_fn = self.decrement_fn_pointer(elem_layout);
// Setup the return location.
let base_offset =
self.storage_manager
@ -3188,7 +2880,7 @@ impl<
// Load address of output element into register.
let reg = self
.storage_manager
.claim_general_reg(&mut self.buf, &Symbol::DEV_TMP4);
.claim_general_reg(&mut self.buf, &Symbol::DEV_TMP5);
ASM::add_reg64_reg64_imm32(&mut self.buf, reg, CC::BASE_PTR_REG, out_elem_offset);
let lowlevel_args = bumpalo::vec![
@ -3199,6 +2891,9 @@ impl<
Symbol::DEV_TMP2,
Symbol::DEV_TMP3,
Symbol::DEV_TMP4,
inc_elem_fn,
dec_elem_fn,
Symbol::DEV_TMP5,
];
let lowlevel_arg_layouts = [
list_layout,
@ -3206,11 +2901,15 @@ impl<
index_layout,
u64_layout,
u64_layout,
Layout::BOOL,
u64_layout,
u64_layout,
u64_layout,
];
let out = self.debug_symbol("out");
self.build_fn_call(
&Symbol::DEV_TMP5,
&out,
bitcode::LIST_REPLACE.to_string(),
&lowlevel_args,
&lowlevel_arg_layouts,
@ -3220,17 +2919,18 @@ impl<
self.free_symbol(&Symbol::DEV_TMP2);
self.free_symbol(&Symbol::DEV_TMP3);
self.free_symbol(&Symbol::DEV_TMP4);
self.free_symbol(&Symbol::DEV_TMP5);
// Copy from list to the output record.
self.storage_manager.copy_symbol_to_stack_offset(
self.layout_interner,
&mut self.buf,
out_list_offset,
&Symbol::DEV_TMP5,
&out,
&list_layout,
);
self.free_symbol(&Symbol::DEV_TMP5);
self.free_symbol(&out);
}
fn build_list_concat(
@ -3252,6 +2952,12 @@ impl<
// Load element_width argument (usize).
self.load_layout_stack_size(elem_layout, Symbol::DEV_TMP2);
// Load element_refcounted argument (bool).
self.load_layout_refcounted(elem_layout, Symbol::DEV_TMP3);
let inc_elem_fn = self.increment_fn_pointer(elem_layout);
let dec_elem_fn = self.decrement_fn_pointer(elem_layout);
// Setup the return location.
let base_offset =
self.storage_manager
@ -3265,11 +2971,23 @@ impl<
Symbol::DEV_TMP,
// element_width
Symbol::DEV_TMP2,
// element_refcounted
Symbol::DEV_TMP3,
inc_elem_fn,
dec_elem_fn,
];
let lowlevel_arg_layouts = [list_a_layout, list_b_layout, Layout::U32, Layout::U64];
let lowlevel_arg_layouts = [
list_a_layout,
list_b_layout,
Layout::U32,
Layout::U64,
Layout::BOOL,
Layout::U64,
Layout::U64,
];
self.build_fn_call(
&Symbol::DEV_TMP3,
&Symbol::DEV_TMP4,
bitcode::LIST_CONCAT.to_string(),
&lowlevel_args,
&lowlevel_arg_layouts,
@ -3278,17 +2996,18 @@ impl<
self.free_symbol(&Symbol::DEV_TMP);
self.free_symbol(&Symbol::DEV_TMP2);
self.free_symbol(&Symbol::DEV_TMP3);
// Return list value from fn call
self.storage_manager.copy_symbol_to_stack_offset(
self.layout_interner,
&mut self.buf,
base_offset,
&Symbol::DEV_TMP3,
&Symbol::DEV_TMP4,
ret_layout,
);
self.free_symbol(&Symbol::DEV_TMP3);
self.free_symbol(&Symbol::DEV_TMP4);
}
fn build_list_prepend(
@ -3320,6 +3039,11 @@ impl<
// Load element_witdh argument (usize).
self.load_layout_stack_size(elem_layout, Symbol::DEV_TMP3);
// Load element_refcounted argument (bool).
self.load_layout_refcounted(elem_layout, Symbol::DEV_TMP4);
let inc_elem_fn = self.increment_fn_pointer(elem_layout);
// Setup the return location.
let base_offset =
self.storage_manager
@ -3333,11 +3057,23 @@ impl<
Symbol::DEV_TMP2,
// element_width
Symbol::DEV_TMP3,
// element_refcounted
Symbol::DEV_TMP4,
// inc
inc_elem_fn,
];
let usize_layout = Layout::U64;
let lowlevel_arg_layouts = [
list_layout,
Layout::U32,
Layout::U64,
Layout::U64,
Layout::BOOL,
usize_layout,
];
let lowlevel_arg_layouts = [list_layout, Layout::U32, Layout::U64, Layout::U64];
self.build_fn_call(
&Symbol::DEV_TMP4,
&Symbol::DEV_TMP5,
bitcode::LIST_PREPEND.to_string(),
&lowlevel_args,
&lowlevel_arg_layouts,
@ -3346,17 +3082,18 @@ impl<
self.free_symbol(&Symbol::DEV_TMP);
self.free_symbol(&Symbol::DEV_TMP2);
self.free_symbol(&Symbol::DEV_TMP3);
self.free_symbol(&Symbol::DEV_TMP4);
// Return list value from fn call
self.storage_manager.copy_symbol_to_stack_offset(
self.layout_interner,
&mut self.buf,
base_offset,
&Symbol::DEV_TMP4,
&Symbol::DEV_TMP5,
ret_layout,
);
self.free_symbol(&Symbol::DEV_TMP4);
self.free_symbol(&Symbol::DEV_TMP5);
}
fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol) {
@ -3403,15 +3140,20 @@ impl<
let element_alignment_symbol = self.debug_symbol("element_alignment");
self.load_layout_alignment(*element_in_layout, element_alignment_symbol);
let element_refcounted = self.debug_symbol("element_refcounted");
self.load_layout_refcounted(*element_in_layout, element_refcounted);
let allocation_symbol = self.debug_symbol("list_allocation");
self.allocate_with_refcount(
allocation_symbol,
data_bytes_symbol,
element_alignment_symbol,
element_refcounted,
);
self.free_symbol(&data_bytes_symbol);
self.free_symbol(&element_alignment_symbol);
self.free_symbol(&element_refcounted);
enum Origin {
S(Symbol),
@ -3862,11 +3604,18 @@ impl<
match reuse {
None => {
// element_refcounted only applies to lists.
let element_refcounted = self.debug_symbol("element_refcounted");
self.load_literal(&element_refcounted, &Layout::BOOL, &Literal::Bool(false));
self.allocate_with_refcount(
allocation,
element_width_symbol,
element_alignment_symbol,
element_refcounted,
);
self.free_symbol(&element_refcounted);
}
Some(reuse) => {
self.allocate_with_refcount_if_null(allocation, reuse, element_layout);
@ -5180,12 +4929,13 @@ impl<
dst: Symbol,
data_bytes: Symbol,
element_alignment: Symbol,
element_refcounted: Symbol,
) {
self.build_fn_call(
&dst,
bitcode::UTILS_ALLOCATE_WITH_REFCOUNT.to_string(),
&[data_bytes, element_alignment],
&[Layout::U64, Layout::U32],
&[data_bytes, element_alignment, element_refcounted],
&[Layout::U64, Layout::U32, Layout::BOOL],
&Layout::U64,
);
}
@ -5217,10 +4967,15 @@ impl<
let element_alignment = self.debug_symbol("element_alignment");
self.load_layout_alignment(layout, element_alignment);
self.allocate_with_refcount(dst, data_bytes, element_alignment);
// element_refcounted only applies to lists.
let element_refcounted = self.debug_symbol("element_refcounted");
self.load_literal(&element_refcounted, &Layout::BOOL, &Literal::Bool(false));
self.allocate_with_refcount(dst, data_bytes, element_alignment, element_refcounted);
self.free_symbol(&data_bytes);
self.free_symbol(&element_alignment);
self.free_symbol(&element_refcounted);
let mut tmp = bumpalo::vec![in self.env.arena];
@ -5609,6 +5364,15 @@ impl<
self.load_literal(&symbol, &u32_layout, &alignment_literal);
}
/// Loads if the layout is refcounted (recursively checking) of `layout` into the given `symbol`
fn load_layout_refcounted(&mut self, layout: InLayout<'_>, symbol: Symbol) {
let u64_layout = Layout::BOOL;
let refcounted = self.layout_interner.contains_refcounted(layout);
let refcounted_literal = Literal::Bool(refcounted);
self.load_literal(&symbol, &u64_layout, &refcounted_literal);
}
/// Loads the stack size of `layout` into the given `symbol`
fn load_layout_stack_size(&mut self, layout: InLayout<'_>, symbol: Symbol) {
let u64_layout = Layout::U64;

View file

@ -111,6 +111,7 @@ struct ListArgument<'a> {
alignment: Symbol,
element_width: Symbol,
element_refcounted: Symbol,
}
// Track when a variable is last used (and hence when it can be disregarded). This is non-trivial
@ -408,10 +409,19 @@ trait Backend<'a> {
let element_width = self.debug_symbol("element_width");
self.load_literal_i64(&element_width, element_width_int as i64);
let element_refcounted = self.debug_symbol("element_refcounted");
let refcounted = self.interner().contains_refcounted(element_layout);
self.load_literal(
&element_refcounted,
&Layout::BOOL,
&Literal::Bool(refcounted),
);
ListArgument {
element_layout,
alignment,
element_width,
element_refcounted,
}
}
@ -435,6 +445,26 @@ trait Backend<'a> {
element_increment
}
fn increment_n_fn_pointer(&mut self, layout: InLayout<'a>) -> Symbol {
let box_layout = self
.interner_mut()
.insert_direct_no_semantic(LayoutRepr::Ptr(layout));
let element_increment = self.debug_symbol("element_increment_n");
let element_increment_symbol = self.build_indirect_inc_n(layout);
let element_increment_string = self.lambda_name_to_string(
LambdaName::no_niche(element_increment_symbol),
[box_layout, Layout::I64].into_iter(),
None,
Layout::UNIT,
);
self.build_fn_pointer(&element_increment, element_increment_string);
element_increment
}
fn decrement_fn_pointer(&mut self, layout: InLayout<'a>) -> Symbol {
let box_layout = self
.interner_mut()
@ -556,22 +586,29 @@ trait Backend<'a> {
let dst = Symbol::DEV_TMP;
let layout = *self.layout_map().get(symbol).unwrap();
debug_assert!(!matches!(self.interner().get_repr(layout), LayoutRepr::Builtin(Builtin::List(_))), "List are no longer safe to refcount through pointer alone. They must go through the zig bitcode functions");
let alignment_bytes = self.interner().allocation_alignment_bytes(layout);
let alignment = self.debug_symbol("alignment");
self.load_literal_i32(&alignment, alignment_bytes as i32);
// elems_refcounted (always false except for list which are refcounted differently)
let elems_refcounted = self.debug_symbol("elems_refcounted");
self.load_literal(&elems_refcounted, &Layout::BOOL, &Literal::Bool(false));
// NOTE: UTILS_FREE_DATA_PTR clears any tag id bits
self.build_fn_call(
&dst,
bitcode::UTILS_FREE_DATA_PTR.to_string(),
&[*symbol, alignment],
&[Layout::I64, Layout::I32],
&[*symbol, alignment, elems_refcounted],
&[Layout::I64, Layout::I32, Layout::BOOL],
&Layout::UNIT,
);
self.free_symbol(&dst);
self.free_symbol(&alignment);
self.free_symbol(&elems_refcounted);
self.build_stmt(layout_ids, following, ret_layout)
}
@ -784,6 +821,9 @@ trait Backend<'a> {
for arg in *arguments {
if let Some(layout) = layout_map.get(arg) {
arg_layouts.push(*layout);
} else if matches!(lowlevel, LowLevel::ListDecref) {
// The last arg of ListDecref has no layout. It is a proc symbol.
continue;
} else {
internal_error!("the argument, {:?}, has no know layout", arg);
}
@ -1883,6 +1923,7 @@ trait Backend<'a> {
list,
list_argument.alignment,
list_argument.element_width,
list_argument.element_refcounted,
start,
len,
self.decrement_fn_pointer(element_layout),
@ -1894,6 +1935,7 @@ trait Backend<'a> {
arg_layouts[0],
Layout::U32,
layout_usize,
Layout::BOOL,
Layout::U64,
Layout::U64,
layout_usize,
@ -1913,6 +1955,9 @@ trait Backend<'a> {
let update_mode = self.debug_symbol("update_mode");
self.load_literal_i8(&update_mode, UpdateMode::Immutable as i8);
let inc_elem_fn = self.increment_fn_pointer(list_argument.element_layout);
let dec_elem_fn = self.decrement_fn_pointer(list_argument.element_layout);
let layout_usize = Layout::U64;
// list: RocList,
@ -1920,6 +1965,9 @@ trait Backend<'a> {
// element_width: usize,
// index_1: u64,
// index_2: u64,
// element_refcounted: bool,
// inc: Inc
// dec: Dec
// update_mode: UpdateMode,
self.build_fn_call(
@ -1931,6 +1979,9 @@ trait Backend<'a> {
list_argument.element_width,
i,
j,
list_argument.element_refcounted,
inc_elem_fn,
dec_elem_fn,
update_mode,
],
&[
@ -1939,6 +1990,9 @@ trait Backend<'a> {
layout_usize,
Layout::U64,
Layout::U64,
Layout::BOOL,
layout_usize,
layout_usize,
Layout::U8,
],
ret_layout,
@ -1953,11 +2007,17 @@ trait Backend<'a> {
let update_mode = self.debug_symbol("update_mode");
self.load_literal_i8(&update_mode, UpdateMode::Immutable as i8);
let inc_elem_fn = self.increment_fn_pointer(list_argument.element_layout);
let dec_elem_fn = self.decrement_fn_pointer(list_argument.element_layout);
let layout_usize = Layout::U64;
// list: RocList,
// alignment: u32,
// element_width: usize,
// element_refcounted: bool,
// inc_elem_fn: Inc,
// dec_elem_fn: Dec,
// update_mode: UpdateMode,
self.build_fn_call(
@ -1967,9 +2027,88 @@ trait Backend<'a> {
list,
list_argument.alignment,
list_argument.element_width,
list_argument.element_refcounted,
inc_elem_fn,
dec_elem_fn,
update_mode,
],
&[list_layout, Layout::U32, layout_usize, Layout::U8],
&[
list_layout,
Layout::U32,
layout_usize,
Layout::BOOL,
layout_usize,
layout_usize,
Layout::U8,
],
ret_layout,
);
}
LowLevel::ListIncref => {
let list = args[0];
let list_layout = arg_layouts[0];
let list_argument = self.list_argument(list_layout);
let layout_isize = Layout::I64;
let amount = if args.len() == 2 {
// amount explicitly specified,
args[1]
} else {
// amount implicit 1.
let sym = self.debug_symbol("amount");
self.load_literal_i64(&sym, 1);
sym
};
// list: RocList,
// amount: isize,
// element_refcounted: bool,
self.build_fn_call(
sym,
bitcode::LIST_INCREF.to_string(),
&[list, amount, list_argument.element_refcounted],
&[list_layout, layout_isize, Layout::BOOL],
ret_layout,
);
}
LowLevel::ListDecref => {
let list = args[0];
let list_layout = arg_layouts[0];
let list_argument = self.list_argument(list_layout);
let dec_elem_fn = self.decrement_fn_pointer(list_argument.element_layout);
let layout_usize = Layout::U64;
// list: RocList,
// alignment: u32,
// element_width: usize,
// element_refcounted: bool,
// dec_elem_fn: Dec,
self.build_fn_call(
sym,
bitcode::LIST_DECREF.to_string(),
&[
list,
list_argument.alignment,
list_argument.element_width,
list_argument.element_refcounted,
dec_elem_fn,
],
&[
list_layout,
Layout::U32,
layout_usize,
Layout::BOOL,
layout_usize,
],
ret_layout,
);
}
@ -1986,12 +2125,15 @@ trait Backend<'a> {
self.load_literal_i8(&update_mode, UpdateMode::Immutable as i8);
let layout_usize = Layout::U64;
let element_increment = self.increment_fn_pointer(element_layout);
let element_decrement = self.decrement_fn_pointer(element_layout);
// list: RocList,
// alignment: u32,
// element_width: usize,
// element_refcounted: bool,
// drop_index: u64,
// inc: Inc,
// dec: Dec,
self.build_fn_call(
@ -2001,15 +2143,19 @@ trait Backend<'a> {
list,
list_argument.alignment,
list_argument.element_width,
list_argument.element_refcounted,
drop_index,
element_increment,
element_decrement,
],
&[
list_layout,
Layout::U32,
layout_usize,
Layout::BOOL,
Layout::U64,
layout_usize,
layout_usize,
],
ret_layout,
);
@ -2434,6 +2580,7 @@ trait Backend<'a> {
);
fn build_indirect_inc(&mut self, layout: InLayout<'a>) -> Symbol;
fn build_indirect_inc_n(&mut self, layout: InLayout<'a>) -> Symbol;
fn build_indirect_dec(&mut self, layout: InLayout<'a>) -> Symbol;
fn build_list_clone(

View file

@ -1113,6 +1113,57 @@ pub(crate) fn call_str_bitcode_fn<'ctx>(
}
}
pub(crate) fn call_void_list_bitcode_fn<'ctx>(
env: &Env<'_, 'ctx, '_>,
lists: &[StructValue<'ctx>],
other_arguments: &[BasicValueEnum<'ctx>],
fn_name: &str,
) {
use bumpalo::collections::Vec;
use roc_target::Architecture::*;
match env.target.architecture() {
Aarch32 | X86_32 => {
let mut arguments: Vec<BasicValueEnum> =
Vec::with_capacity_in(other_arguments.len() + 2 * lists.len(), env.arena);
for list in lists {
let (a, b) = pass_list_or_string_to_zig_32bit(env, *list);
arguments.push(a.into());
arguments.push(b.into());
}
arguments.extend(other_arguments);
call_void_bitcode_fn(env, &arguments, fn_name);
}
X86_64 | Aarch64 => {
let capacity = other_arguments.len() + lists.len();
let mut arguments: Vec<BasicValueEnum> = Vec::with_capacity_in(capacity, env.arena);
for list in lists {
arguments.push(pass_list_to_zig_64bit(env, (*list).into()).into());
}
arguments.extend(other_arguments);
call_void_bitcode_fn(env, &arguments, fn_name);
}
Wasm32 => {
let capacity = other_arguments.len() + lists.len();
let mut arguments: Vec<BasicValueEnum> = Vec::with_capacity_in(capacity, env.arena);
for list in lists {
arguments.push(pass_list_to_zig_wasm(env, (*list).into()).into());
}
arguments.extend(other_arguments);
call_void_bitcode_fn(env, &arguments, fn_name);
}
}
}
pub(crate) fn call_list_bitcode_fn<'ctx>(
env: &Env<'_, 'ctx, '_>,
lists: &[StructValue<'ctx>],

View file

@ -1,5 +1,7 @@
use crate::llvm::bitcode::call_bitcode_fn;
use crate::llvm::build_list::{self, allocate_list, empty_polymorphic_list};
use crate::llvm::bitcode::{build_dec_wrapper, call_bitcode_fn, call_void_list_bitcode_fn};
use crate::llvm::build_list::{
allocate_list, empty_polymorphic_list, layout_refcounted, layout_width,
};
use crate::llvm::convert::{
argument_type_from_layout, basic_type_from_builtin, basic_type_from_layout, zig_str_type,
};
@ -2860,19 +2862,6 @@ fn union_field_ptr_at_index<'a, 'ctx>(
.into_pointer_value()
}
pub fn reserve_with_refcount<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
layout: InLayout<'a>,
) -> PointerValue<'ctx> {
let stack_size = layout_interner.stack_size(layout);
let alignment_bytes = layout_interner.alignment_bytes(layout);
let basic_type = basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout));
reserve_with_refcount_help(env, basic_type, stack_size, alignment_bytes)
}
fn reserve_with_refcount_union_as_block_of_memory<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
@ -2887,7 +2876,7 @@ fn reserve_with_refcount_union_as_block_of_memory<'a, 'ctx>(
RocUnion::untagged_from_slices(layout_interner, env.context, fields)
};
reserve_with_refcount_help(
reserve_union_with_refcount_help(
env,
roc_union.struct_type(),
roc_union.tag_width(),
@ -2895,7 +2884,7 @@ fn reserve_with_refcount_union_as_block_of_memory<'a, 'ctx>(
)
}
fn reserve_with_refcount_help<'a, 'ctx, 'env>(
fn reserve_union_with_refcount_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
basic_type: impl BasicType<'ctx>,
stack_size: u32,
@ -2905,21 +2894,15 @@ fn reserve_with_refcount_help<'a, 'ctx, 'env>(
let value_bytes_intvalue = len_type.const_int(stack_size as u64, false);
allocate_with_refcount_help(env, basic_type, alignment_bytes, value_bytes_intvalue)
}
pub fn allocate_with_refcount<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
layout: InLayout<'a>,
value: BasicValueEnum<'ctx>,
) -> PointerValue<'ctx> {
let data_ptr = reserve_with_refcount(env, layout_interner, layout);
// store the value in the pointer
env.builder.new_build_store(data_ptr, value);
data_ptr
// elem_refcounted does not apply to unions, only lists.
let elem_refcounted = env.context.bool_type().const_zero().into();
allocate_with_refcount_help(
env,
basic_type,
alignment_bytes,
value_bytes_intvalue,
elem_refcounted,
)
}
pub fn allocate_with_refcount_help<'a, 'ctx, 'env>(
@ -2927,12 +2910,14 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>(
value_type: impl BasicType<'ctx>,
alignment_bytes: u32,
number_of_data_bytes: IntValue<'ctx>,
elem_refcounted: BasicValueEnum<'ctx>,
) -> PointerValue<'ctx> {
let ptr = call_bitcode_fn(
env,
&[
number_of_data_bytes.into(),
env.alignment_const(alignment_bytes).into(),
elem_refcounted,
],
roc_builtins::bitcode::UTILS_ALLOCATE_WITH_REFCOUNT,
)
@ -3473,10 +3458,19 @@ pub(crate) fn build_exp_stmt<'a, 'ctx>(
LayoutRepr::Builtin(Builtin::Str) => todo!(),
LayoutRepr::Builtin(Builtin::List(element_layout)) => {
debug_assert!(value.is_struct_value());
let element_layout = layout_interner.get_repr(element_layout);
let alignment = element_layout.alignment_bytes(layout_interner);
build_list::decref(env, value.into_struct_value(), alignment);
let dec_element_fn =
build_dec_wrapper(env, layout_interner, layout_ids, element_layout);
call_void_list_bitcode_fn(
env,
&[value.into_struct_value()],
&[
env.alignment_intvalue(layout_interner, element_layout),
layout_width(env, layout_interner, element_layout),
layout_refcounted(env, layout_interner, element_layout),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_DECREF,
)
}
other_layout if other_layout.is_refcounted(layout_interner) => {
@ -3546,7 +3540,8 @@ pub(crate) fn build_exp_stmt<'a, 'ctx>(
debug_assert!(value.is_pointer_value());
let value = value.into_pointer_value();
let clear_tag_id = match layout_interner.runtime_representation(layout) {
let runtime_layout = layout_interner.runtime_representation(layout);
let clear_tag_id = match runtime_layout {
LayoutRepr::Union(union) => union.stores_tag_id_in_pointer(env.target),
_ => false,
};
@ -3558,7 +3553,7 @@ pub(crate) fn build_exp_stmt<'a, 'ctx>(
};
let rc_ptr = PointerToRefcount::from_ptr_to_data(env, ptr);
rc_ptr.deallocate(env, alignment);
rc_ptr.deallocate(env, alignment, runtime_layout);
build_exp_stmt(
env,

View file

@ -12,7 +12,7 @@ use roc_mono::layout::{
Builtin, InLayout, Layout, LayoutIds, LayoutInterner, LayoutRepr, STLayoutInterner,
};
use super::bitcode::{call_list_bitcode_fn, BitcodeReturns};
use super::bitcode::{build_inc_wrapper, call_list_bitcode_fn, BitcodeReturns};
use super::build::{
create_entry_block_alloca, load_roc_value, store_roc_value, use_roc_value, BuilderExt,
};
@ -97,6 +97,20 @@ pub(crate) fn layout_width<'a, 'ctx>(
.into()
}
pub(crate) fn layout_refcounted<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
layout: InLayout<'a>,
) -> BasicValueEnum<'ctx> {
let is_refcounted = layout_interner
.get_repr(layout)
.contains_refcounted(layout_interner);
env.context
.bool_type()
.const_int(is_refcounted as u64, false)
.into()
}
pub(crate) fn pass_as_opaque<'ctx>(
env: &Env<'_, 'ctx, '_>,
ptr: PointerValue<'ctx>,
@ -113,9 +127,11 @@ pub(crate) fn pass_as_opaque<'ctx>(
pub(crate) fn list_with_capacity<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
capacity: IntValue<'ctx>,
element_layout: InLayout<'a>,
) -> BasicValueEnum<'ctx> {
let inc_element_fn = build_inc_wrapper(env, layout_interner, layout_ids, element_layout);
call_list_bitcode_fn(
env,
&[],
@ -123,6 +139,8 @@ pub(crate) fn list_with_capacity<'a, 'ctx>(
capacity.into(),
env.alignment_intvalue(layout_interner, element_layout),
layout_width(env, layout_interner, element_layout),
layout_refcounted(env, layout_interner, element_layout),
inc_element_fn.as_global_value().as_pointer_value().into(),
],
BitcodeReturns::List,
bitcode::LIST_WITH_CAPACITY,
@ -173,11 +191,13 @@ pub(crate) fn list_get_unsafe<'a, 'ctx>(
pub(crate) fn list_reserve<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
list: BasicValueEnum<'ctx>,
spare: BasicValueEnum<'ctx>,
element_layout: InLayout<'a>,
update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> {
let inc_element_fn = build_inc_wrapper(env, layout_interner, layout_ids, element_layout);
call_list_bitcode_fn_1(
env,
list.into_struct_value(),
@ -185,6 +205,8 @@ pub(crate) fn list_reserve<'a, 'ctx>(
env.alignment_intvalue(layout_interner, element_layout),
spare,
layout_width(env, layout_interner, element_layout),
layout_refcounted(env, layout_interner, element_layout),
inc_element_fn.as_global_value().as_pointer_value().into(),
pass_update_mode(env, update_mode),
],
bitcode::LIST_RESERVE,
@ -195,16 +217,22 @@ pub(crate) fn list_reserve<'a, 'ctx>(
pub(crate) fn list_release_excess_capacity<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
list: BasicValueEnum<'ctx>,
element_layout: InLayout<'a>,
update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> {
let inc_element_fn = build_inc_wrapper(env, layout_interner, layout_ids, element_layout);
let dec_element_fn = build_dec_wrapper(env, layout_interner, layout_ids, element_layout);
call_list_bitcode_fn_1(
env,
list.into_struct_value(),
&[
env.alignment_intvalue(layout_interner, element_layout),
layout_width(env, layout_interner, element_layout),
layout_refcounted(env, layout_interner, element_layout),
inc_element_fn.as_global_value().as_pointer_value().into(),
dec_element_fn.as_global_value().as_pointer_value().into(),
pass_update_mode(env, update_mode),
],
bitcode::LIST_RELEASE_EXCESS_CAPACITY,
@ -234,10 +262,12 @@ pub(crate) fn list_append_unsafe<'a, 'ctx>(
pub(crate) fn list_prepend<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
original_wrapper: StructValue<'ctx>,
element: BasicValueEnum<'ctx>,
element_layout: InLayout<'a>,
) -> BasicValueEnum<'ctx> {
let inc_element_fn = build_inc_wrapper(env, layout_interner, layout_ids, element_layout);
call_list_bitcode_fn_1(
env,
original_wrapper,
@ -245,21 +275,50 @@ pub(crate) fn list_prepend<'a, 'ctx>(
env.alignment_intvalue(layout_interner, element_layout),
pass_element_as_opaque(env, layout_interner, element, element_layout),
layout_width(env, layout_interner, element_layout),
layout_refcounted(env, layout_interner, element_layout),
inc_element_fn.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_PREPEND,
)
}
pub(crate) fn list_clone<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
list: StructValue<'ctx>,
element_layout: InLayout<'a>,
) -> BasicValueEnum<'ctx> {
let inc_element_fn = build_inc_wrapper(env, layout_interner, layout_ids, element_layout);
let dec_element_fn = build_dec_wrapper(env, layout_interner, layout_ids, element_layout);
call_list_bitcode_fn_1(
env,
list,
&[
env.alignment_intvalue(layout_interner, element_layout),
layout_width(env, layout_interner, element_layout),
layout_refcounted(env, layout_interner, element_layout),
inc_element_fn.as_global_value().as_pointer_value().into(),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_CLONE,
)
}
/// List.swap : List elem, U64, U64 -> List elem
pub(crate) fn list_swap<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
original_wrapper: StructValue<'ctx>,
index_1: IntValue<'ctx>,
index_2: IntValue<'ctx>,
element_layout: InLayout<'a>,
update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> {
let inc_element_fn = build_inc_wrapper(env, layout_interner, layout_ids, element_layout);
let dec_element_fn = build_dec_wrapper(env, layout_interner, layout_ids, element_layout);
call_list_bitcode_fn_1(
env,
original_wrapper,
@ -268,6 +327,9 @@ pub(crate) fn list_swap<'a, 'ctx>(
layout_width(env, layout_interner, element_layout),
index_1.into(),
index_2.into(),
layout_refcounted(env, layout_interner, element_layout),
inc_element_fn.as_global_value().as_pointer_value().into(),
dec_element_fn.as_global_value().as_pointer_value().into(),
pass_update_mode(env, update_mode),
],
bitcode::LIST_SWAP,
@ -291,6 +353,7 @@ pub(crate) fn list_sublist<'a, 'ctx>(
&[
env.alignment_intvalue(layout_interner, element_layout),
layout_width(env, layout_interner, element_layout),
layout_refcounted(env, layout_interner, element_layout),
start.into(),
len.into(),
dec_element_fn.as_global_value().as_pointer_value().into(),
@ -308,6 +371,7 @@ pub(crate) fn list_drop_at<'a, 'ctx>(
count: IntValue<'ctx>,
element_layout: InLayout<'a>,
) -> BasicValueEnum<'ctx> {
let inc_element_fn = build_inc_wrapper(env, layout_interner, layout_ids, element_layout);
let dec_element_fn = build_dec_wrapper(env, layout_interner, layout_ids, element_layout);
call_list_bitcode_fn_1(
env,
@ -315,7 +379,9 @@ pub(crate) fn list_drop_at<'a, 'ctx>(
&[
env.alignment_intvalue(layout_interner, element_layout),
layout_width(env, layout_interner, element_layout),
layout_refcounted(env, layout_interner, element_layout),
count.into(),
inc_element_fn.as_global_value().as_pointer_value().into(),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_DROP_AT,
@ -326,7 +392,7 @@ pub(crate) fn list_drop_at<'a, 'ctx>(
pub(crate) fn list_replace_unsafe<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
_layout_ids: &mut LayoutIds<'a>,
layout_ids: &mut LayoutIds<'a>,
list: BasicValueEnum<'ctx>,
index: IntValue<'ctx>,
element: BasicValueEnum<'ctx>,
@ -356,18 +422,27 @@ pub(crate) fn list_replace_unsafe<'a, 'ctx>(
],
bitcode::LIST_REPLACE_IN_PLACE,
),
UpdateMode::Immutable => call_list_bitcode_fn_1(
env,
list.into_struct_value(),
&[
env.alignment_intvalue(layout_interner, element_layout),
index.into(),
pass_element_as_opaque(env, layout_interner, element, element_layout),
layout_width(env, layout_interner, element_layout),
pass_as_opaque(env, element_ptr),
],
bitcode::LIST_REPLACE,
),
UpdateMode::Immutable => {
let inc_element_fn =
build_inc_wrapper(env, layout_interner, layout_ids, element_layout);
let dec_element_fn =
build_dec_wrapper(env, layout_interner, layout_ids, element_layout);
call_list_bitcode_fn_1(
env,
list.into_struct_value(),
&[
env.alignment_intvalue(layout_interner, element_layout),
index.into(),
pass_element_as_opaque(env, layout_interner, element, element_layout),
layout_width(env, layout_interner, element_layout),
layout_refcounted(env, layout_interner, element_layout),
inc_element_fn.as_global_value().as_pointer_value().into(),
dec_element_fn.as_global_value().as_pointer_value().into(),
pass_as_opaque(env, element_ptr),
],
bitcode::LIST_REPLACE,
)
}
};
// Load the element and returned list into a struct.
@ -436,36 +511,6 @@ pub(crate) fn list_len_usize<'ctx>(
.into_int_value()
}
pub(crate) fn list_capacity_or_ref_ptr<'ctx>(
builder: &Builder<'ctx>,
wrapper_struct: StructValue<'ctx>,
) -> IntValue<'ctx> {
builder
.build_extract_value(
wrapper_struct,
Builtin::WRAPPER_CAPACITY,
"list_capacity_or_ref_ptr",
)
.unwrap()
.into_int_value()
}
// Gets a pointer to just after the refcount for a list or seamless slice.
// The value is just after the refcount so that normal lists and seamless slices can share code paths easily.
pub(crate) fn list_allocation_ptr<'ctx>(
env: &Env<'_, 'ctx, '_>,
wrapper_struct: StructValue<'ctx>,
) -> PointerValue<'ctx> {
call_list_bitcode_fn(
env,
&[wrapper_struct],
&[],
BitcodeReturns::Basic,
bitcode::LIST_ALLOCATION_PTR,
)
.into_pointer_value()
}
pub(crate) fn destructure<'ctx>(
builder: &Builder<'ctx>,
wrapper_struct: StructValue<'ctx>,
@ -493,11 +538,14 @@ pub(crate) fn destructure<'ctx>(
pub(crate) fn list_sort_with<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
roc_function_call: RocFunctionCall<'ctx>,
compare_wrapper: PointerValue<'ctx>,
list: BasicValueEnum<'ctx>,
element_layout: InLayout<'a>,
) -> BasicValueEnum<'ctx> {
let inc_element_fn = build_inc_wrapper(env, layout_interner, layout_ids, element_layout);
let dec_element_fn = build_dec_wrapper(env, layout_interner, layout_ids, element_layout);
call_list_bitcode_fn_1(
env,
list.into_struct_value(),
@ -508,176 +556,34 @@ pub(crate) fn list_sort_with<'a, 'ctx>(
roc_function_call.data_is_owned.into(),
env.alignment_intvalue(layout_interner, element_layout),
layout_width(env, layout_interner, element_layout),
layout_refcounted(env, layout_interner, element_layout),
inc_element_fn.as_global_value().as_pointer_value().into(),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_SORT_WITH,
)
}
/// List.map : List before, (before -> after) -> List after
pub(crate) fn list_map<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
roc_function_call: RocFunctionCall<'ctx>,
list: BasicValueEnum<'ctx>,
element_layout: InLayout<'a>,
return_layout: InLayout<'a>,
) -> BasicValueEnum<'ctx> {
call_list_bitcode_fn_1(
env,
list.into_struct_value(),
&[
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
env.alignment_intvalue(layout_interner, return_layout),
layout_width(env, layout_interner, element_layout),
layout_width(env, layout_interner, return_layout),
],
bitcode::LIST_MAP,
)
}
pub(crate) fn list_map2<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
roc_function_call: RocFunctionCall<'ctx>,
list1: BasicValueEnum<'ctx>,
list2: BasicValueEnum<'ctx>,
element1_layout: InLayout<'a>,
element2_layout: InLayout<'a>,
return_layout: InLayout<'a>,
) -> BasicValueEnum<'ctx> {
let dec_a = build_dec_wrapper(env, layout_interner, layout_ids, element1_layout);
let dec_b = build_dec_wrapper(env, layout_interner, layout_ids, element2_layout);
call_list_bitcode_fn(
env,
&[list1.into_struct_value(), list2.into_struct_value()],
&[
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
env.alignment_intvalue(layout_interner, return_layout),
layout_width(env, layout_interner, element1_layout),
layout_width(env, layout_interner, element2_layout),
layout_width(env, layout_interner, return_layout),
dec_a.as_global_value().as_pointer_value().into(),
dec_b.as_global_value().as_pointer_value().into(),
],
BitcodeReturns::List,
bitcode::LIST_MAP2,
)
}
pub(crate) fn list_map3<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
roc_function_call: RocFunctionCall<'ctx>,
list1: BasicValueEnum<'ctx>,
list2: BasicValueEnum<'ctx>,
list3: BasicValueEnum<'ctx>,
element1_layout: InLayout<'a>,
element2_layout: InLayout<'a>,
element3_layout: InLayout<'a>,
result_layout: InLayout<'a>,
) -> BasicValueEnum<'ctx> {
let dec_a = build_dec_wrapper(env, layout_interner, layout_ids, element1_layout);
let dec_b = build_dec_wrapper(env, layout_interner, layout_ids, element2_layout);
let dec_c = build_dec_wrapper(env, layout_interner, layout_ids, element3_layout);
call_list_bitcode_fn(
env,
&[
list1.into_struct_value(),
list2.into_struct_value(),
list3.into_struct_value(),
],
&[
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
env.alignment_intvalue(layout_interner, result_layout),
layout_width(env, layout_interner, element1_layout),
layout_width(env, layout_interner, element2_layout),
layout_width(env, layout_interner, element3_layout),
layout_width(env, layout_interner, result_layout),
dec_a.as_global_value().as_pointer_value().into(),
dec_b.as_global_value().as_pointer_value().into(),
dec_c.as_global_value().as_pointer_value().into(),
],
BitcodeReturns::List,
bitcode::LIST_MAP3,
)
}
pub(crate) fn list_map4<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
roc_function_call: RocFunctionCall<'ctx>,
list1: BasicValueEnum<'ctx>,
list2: BasicValueEnum<'ctx>,
list3: BasicValueEnum<'ctx>,
list4: BasicValueEnum<'ctx>,
element1_layout: InLayout<'a>,
element2_layout: InLayout<'a>,
element3_layout: InLayout<'a>,
element4_layout: InLayout<'a>,
result_layout: InLayout<'a>,
) -> BasicValueEnum<'ctx> {
let dec_a = build_dec_wrapper(env, layout_interner, layout_ids, element1_layout);
let dec_b = build_dec_wrapper(env, layout_interner, layout_ids, element2_layout);
let dec_c = build_dec_wrapper(env, layout_interner, layout_ids, element3_layout);
let dec_d = build_dec_wrapper(env, layout_interner, layout_ids, element4_layout);
call_list_bitcode_fn(
env,
&[
list1.into_struct_value(),
list2.into_struct_value(),
list3.into_struct_value(),
list4.into_struct_value(),
],
&[
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
env.alignment_intvalue(layout_interner, result_layout),
layout_width(env, layout_interner, element1_layout),
layout_width(env, layout_interner, element2_layout),
layout_width(env, layout_interner, element3_layout),
layout_width(env, layout_interner, element4_layout),
layout_width(env, layout_interner, result_layout),
dec_a.as_global_value().as_pointer_value().into(),
dec_b.as_global_value().as_pointer_value().into(),
dec_c.as_global_value().as_pointer_value().into(),
dec_d.as_global_value().as_pointer_value().into(),
],
BitcodeReturns::List,
bitcode::LIST_MAP4,
)
}
/// List.concat : List elem, List elem -> List elem
pub(crate) fn list_concat<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
list1: BasicValueEnum<'ctx>,
list2: BasicValueEnum<'ctx>,
element_layout: InLayout<'a>,
) -> BasicValueEnum<'ctx> {
let inc_element_fn = build_inc_wrapper(env, layout_interner, layout_ids, element_layout);
let dec_element_fn = build_dec_wrapper(env, layout_interner, layout_ids, element_layout);
call_list_bitcode_fn(
env,
&[list1.into_struct_value(), list2.into_struct_value()],
&[
env.alignment_intvalue(layout_interner, element_layout),
layout_width(env, layout_interner, element_layout),
layout_refcounted(env, layout_interner, element_layout),
inc_element_fn.as_global_value().as_pointer_value().into(),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
BitcodeReturns::List,
bitcode::LIST_CONCAT,
@ -791,21 +697,6 @@ pub(crate) fn empty_polymorphic_list<'ctx>(env: &Env<'_, 'ctx, '_>) -> BasicValu
BasicValueEnum::StructValue(struct_type.const_zero())
}
pub(crate) fn load_list<'ctx>(
builder: &Builder<'ctx>,
wrapper_struct: StructValue<'ctx>,
ptr_type: PointerType<'ctx>,
) -> (IntValue<'ctx>, PointerValue<'ctx>) {
let ptr = load_list_ptr(builder, wrapper_struct, ptr_type);
let length = builder
.build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "list_len")
.unwrap()
.into_int_value();
(length, ptr)
}
pub(crate) fn load_list_ptr<'ctx>(
builder: &Builder<'ctx>,
wrapper_struct: StructValue<'ctx>,
@ -838,7 +729,14 @@ pub(crate) fn allocate_list<'a, 'ctx>(
let basic_type =
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(elem_layout));
let alignment_bytes = layout_interner.alignment_bytes(elem_layout);
allocate_with_refcount_help(env, basic_type, alignment_bytes, number_of_data_bytes)
let elem_refcounted = layout_refcounted(env, layout_interner, elem_layout);
allocate_with_refcount_help(
env,
basic_type,
alignment_bytes,
number_of_data_bytes,
elem_refcounted,
)
}
pub(crate) fn store_list<'ctx>(
@ -860,13 +758,3 @@ pub(crate) fn store_list<'ctx>(
.into_iter(),
)
}
pub(crate) fn decref<'ctx>(
env: &Env<'_, 'ctx, '_>,
wrapper_struct: StructValue<'ctx>,
alignment: u32,
) {
let refcount_ptr = list_allocation_ptr(env, wrapper_struct);
crate::llvm::refcounting::decref_pointer_check_null(env, refcount_ptr, alignment);
}

View file

@ -35,10 +35,10 @@ use crate::llvm::{
BuilderExt, FuncBorrowSpec, RocReturn,
},
build_list::{
layout_width, list_append_unsafe, list_concat, list_drop_at, list_get_unsafe,
list_len_usize, list_map, list_map2, list_map3, list_map4, list_prepend,
list_release_excess_capacity, list_replace_unsafe, list_reserve, list_sort_with,
list_sublist, list_swap, list_symbol_to_c_abi, list_with_capacity, pass_update_mode,
list_append_unsafe, list_clone, list_concat, list_drop_at, list_get_unsafe, list_len_usize,
list_prepend, list_release_excess_capacity, list_replace_unsafe, list_reserve,
list_sort_with, list_sublist, list_swap, list_symbol_to_c_abi, list_with_capacity,
pass_update_mode,
},
compare::{generic_eq, generic_neq},
convert::{
@ -645,6 +645,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
list_with_capacity(
env,
layout_interner,
layout_ids,
list_len.into_int_value(),
list_element_layout!(layout_interner, result_layout),
)
@ -661,6 +662,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
list_concat(
env,
layout_interner,
layout_ids,
first_list,
second_list,
element_layout,
@ -682,7 +684,14 @@ pub(crate) fn run_low_level<'a, 'ctx>(
let original_wrapper = scope.load_symbol(&args[0]).into_struct_value();
let (elem, elem_layout) = scope.load_symbol_and_layout(&args[1]);
list_prepend(env, layout_interner, original_wrapper, elem, elem_layout)
list_prepend(
env,
layout_interner,
layout_ids,
original_wrapper,
elem,
elem_layout,
)
}
ListReserve => {
// List.reserve : List elem, U64 -> List elem
@ -695,6 +704,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
list_reserve(
env,
layout_interner,
layout_ids,
list,
spare,
element_layout,
@ -708,7 +718,14 @@ pub(crate) fn run_low_level<'a, 'ctx>(
let (list, list_layout) = scope.load_symbol_and_layout(&args[0]);
let element_layout = list_element_layout!(layout_interner, list_layout);
list_release_excess_capacity(env, layout_interner, list, element_layout, update_mode)
list_release_excess_capacity(
env,
layout_interner,
layout_ids,
list,
element_layout,
update_mode,
)
}
ListSwap => {
// List.swap : List elem, U64, U64 -> List elem
@ -724,6 +741,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
list_swap(
env,
layout_interner,
layout_ids,
original_wrapper,
index_1.into_int_value(),
index_2.into_int_value(),
@ -826,19 +844,13 @@ pub(crate) fn run_low_level<'a, 'ctx>(
let element_layout = list_element_layout!(layout_interner, list_layout);
match update_mode {
UpdateMode::Immutable => {
//
call_list_bitcode_fn(
env,
&[list.into_struct_value()],
&[
env.alignment_intvalue(layout_interner, element_layout),
layout_width(env, layout_interner, element_layout),
],
BitcodeReturns::List,
bitcode::LIST_CLONE,
)
}
UpdateMode::Immutable => list_clone(
env,
layout_interner,
layout_ids,
list.into_struct_value(),
element_layout,
),
UpdateMode::InPlace => {
// we statically know the list is unique
list
@ -1294,7 +1306,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
unimplemented!()
}
ListMap | ListMap2 | ListMap3 | ListMap4 | ListSortWith => {
ListSortWith => {
unreachable!("these are higher order, and are handled elsewhere")
}
@ -1399,7 +1411,9 @@ pub(crate) fn run_low_level<'a, 'ctx>(
call_bitcode_fn(env, &[], bitcode::UTILS_DICT_PSEUDO_SEED)
}
SetJmp | LongJmp | SetLongJmpBuffer => unreachable!("only inserted in dev backend codegen"),
ListIncref | ListDecref | SetJmp | LongJmp | SetLongJmpBuffer => {
unreachable!("only inserted in dev backend codegen")
}
}
}
@ -2758,7 +2772,7 @@ pub(crate) fn run_higher_order_low_level<'a, 'ctx>(
layout_interner: &STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
scope: &Scope<'a, 'ctx>,
return_layout: InLayout<'a>,
_return_layout: InLayout<'a>,
func_spec: FuncSpec,
higher_order: &HigherOrderLowLevel<'a>,
) -> BasicValueEnum<'ctx> {
@ -2797,201 +2811,6 @@ pub(crate) fn run_higher_order_low_level<'a, 'ctx>(
}
match op {
ListMap { xs } => {
// List.map : List before, (before -> after) -> List after
let (list, list_layout) = scope.load_symbol_and_layout(xs);
let (function, closure, closure_layout) = function_details!();
match (
layout_interner.get_repr(list_layout),
layout_interner.get_repr(return_layout),
) {
(
LayoutRepr::Builtin(Builtin::List(element_layout)),
LayoutRepr::Builtin(Builtin::List(result_layout)),
) => {
let argument_layouts = &[element_layout];
let roc_function_call = roc_function_call(
env,
layout_interner,
layout_ids,
function,
closure,
closure_layout,
function_owns_closure_data,
argument_layouts,
result_layout,
);
list_map(
env,
layout_interner,
roc_function_call,
list,
element_layout,
result_layout,
)
}
_ => unreachable!("invalid list layout"),
}
}
ListMap2 { xs, ys } => {
let (list1, list1_layout) = scope.load_symbol_and_layout(xs);
let (list2, list2_layout) = scope.load_symbol_and_layout(ys);
let (function, closure, closure_layout) = function_details!();
match (
layout_interner.get_repr(list1_layout),
layout_interner.get_repr(list2_layout),
layout_interner.get_repr(return_layout),
) {
(
LayoutRepr::Builtin(Builtin::List(element1_layout)),
LayoutRepr::Builtin(Builtin::List(element2_layout)),
LayoutRepr::Builtin(Builtin::List(result_layout)),
) => {
let argument_layouts = &[element1_layout, element2_layout];
let roc_function_call = roc_function_call(
env,
layout_interner,
layout_ids,
function,
closure,
closure_layout,
function_owns_closure_data,
argument_layouts,
result_layout,
);
list_map2(
env,
layout_interner,
layout_ids,
roc_function_call,
list1,
list2,
element1_layout,
element2_layout,
result_layout,
)
}
_ => unreachable!("invalid list layout"),
}
}
ListMap3 { xs, ys, zs } => {
let (list1, list1_layout) = scope.load_symbol_and_layout(xs);
let (list2, list2_layout) = scope.load_symbol_and_layout(ys);
let (list3, list3_layout) = scope.load_symbol_and_layout(zs);
let (function, closure, closure_layout) = function_details!();
match (
layout_interner.get_repr(list1_layout),
layout_interner.get_repr(list2_layout),
layout_interner.get_repr(list3_layout),
layout_interner.get_repr(return_layout),
) {
(
LayoutRepr::Builtin(Builtin::List(element1_layout)),
LayoutRepr::Builtin(Builtin::List(element2_layout)),
LayoutRepr::Builtin(Builtin::List(element3_layout)),
LayoutRepr::Builtin(Builtin::List(result_layout)),
) => {
let argument_layouts = &[element1_layout, element2_layout, element3_layout];
let roc_function_call = roc_function_call(
env,
layout_interner,
layout_ids,
function,
closure,
closure_layout,
function_owns_closure_data,
argument_layouts,
result_layout,
);
list_map3(
env,
layout_interner,
layout_ids,
roc_function_call,
list1,
list2,
list3,
element1_layout,
element2_layout,
element3_layout,
result_layout,
)
}
_ => unreachable!("invalid list layout"),
}
}
ListMap4 { xs, ys, zs, ws } => {
let (list1, list1_layout) = scope.load_symbol_and_layout(xs);
let (list2, list2_layout) = scope.load_symbol_and_layout(ys);
let (list3, list3_layout) = scope.load_symbol_and_layout(zs);
let (list4, list4_layout) = scope.load_symbol_and_layout(ws);
let (function, closure, closure_layout) = function_details!();
match (
layout_interner.get_repr(list1_layout),
layout_interner.get_repr(list2_layout),
layout_interner.get_repr(list3_layout),
layout_interner.get_repr(list4_layout),
layout_interner.get_repr(return_layout),
) {
(
LayoutRepr::Builtin(Builtin::List(element1_layout)),
LayoutRepr::Builtin(Builtin::List(element2_layout)),
LayoutRepr::Builtin(Builtin::List(element3_layout)),
LayoutRepr::Builtin(Builtin::List(element4_layout)),
LayoutRepr::Builtin(Builtin::List(result_layout)),
) => {
let argument_layouts = &[
element1_layout,
element2_layout,
element3_layout,
element4_layout,
];
let roc_function_call = roc_function_call(
env,
layout_interner,
layout_ids,
function,
closure,
closure_layout,
function_owns_closure_data,
argument_layouts,
result_layout,
);
list_map4(
env,
layout_interner,
layout_ids,
roc_function_call,
list1,
list2,
list3,
list4,
element1_layout,
element2_layout,
element3_layout,
element4_layout,
result_layout,
)
}
_ => unreachable!("invalid list layout"),
}
}
ListSortWith { xs } => {
// List.sortWith : List a, (a, a -> Ordering) -> List a
let (list, list_layout) = scope.load_symbol_and_layout(xs);
@ -3030,6 +2849,7 @@ pub(crate) fn run_higher_order_low_level<'a, 'ctx>(
list_sort_with(
env,
layout_interner,
layout_ids,
roc_function_call,
compare_wrapper,
list,

View file

@ -1,12 +1,10 @@
use crate::debug_info_init;
use crate::llvm::bitcode::call_void_bitcode_fn;
use crate::llvm::bitcode::{build_dec_wrapper, call_void_bitcode_fn, call_void_list_bitcode_fn};
use crate::llvm::build::BuilderExt;
use crate::llvm::build::{
add_func, cast_basic_basic, get_tag_id, tag_pointer_clear_tag_id, Env, FAST_CALL_CONV,
};
use crate::llvm::build_list::{
incrementing_elem_loop, list_allocation_ptr, list_capacity_or_ref_ptr, load_list,
};
use crate::llvm::build_list::{layout_refcounted, layout_width};
use crate::llvm::build_str::str_allocation_ptr;
use crate::llvm::convert::{basic_type_from_layout, zig_str_type, RocUnion};
use crate::llvm::struct_::RocStruct;
@ -16,6 +14,7 @@ use inkwell::module::Linkage;
use inkwell::types::{AnyTypeEnum, BasicMetadataTypeEnum, BasicType, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, FunctionValue, InstructionValue, IntValue, PointerValue};
use inkwell::{AddressSpace, IntPredicate};
use roc_builtins::bitcode;
use roc_module::symbol::Interns;
use roc_module::symbol::Symbol;
use roc_mono::ir::ErasedField;
@ -111,13 +110,18 @@ impl<'ctx> PointerToRefcount<'ctx> {
layout_interner: &STLayoutInterner<'a>,
) {
match mode {
CallMode::Inc(inc_amount) => self.increment(inc_amount, env),
CallMode::Inc(inc_amount) => self.increment(inc_amount, env, layout),
CallMode::Dec => self.decrement(env, layout_interner, layout),
}
}
fn increment<'a, 'env>(&self, amount: IntValue<'ctx>, env: &Env<'a, 'ctx, 'env>) {
incref_pointer(env, self.value, amount);
fn increment<'a, 'env>(
&self,
amount: IntValue<'ctx>,
env: &Env<'a, 'ctx, 'env>,
layout: LayoutRepr<'a>,
) {
incref_pointer(env, self.value, amount, layout);
}
pub fn decrement<'a, 'env>(
@ -158,7 +162,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
debug_info_init!(env, function_value);
Self::build_decrement_function_body(env, function_value, alignment);
Self::build_decrement_function_body(env, function_value, alignment, layout);
function_value
}
@ -180,6 +184,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
alignment: u32,
layout: LayoutRepr<'a>,
) {
let builder = env.builder;
let ctx = env.context;
@ -193,6 +198,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
env,
parent.get_nth_param(0).unwrap().into_pointer_value(),
alignment,
layout,
);
builder.new_build_return(None);
@ -202,16 +208,23 @@ impl<'ctx> PointerToRefcount<'ctx> {
&self,
env: &Env<'a, 'ctx, 'env>,
alignment: u32,
layout: LayoutRepr<'a>,
) -> InstructionValue<'ctx> {
free_pointer(env, self.value, alignment)
free_pointer(env, self.value, alignment, layout)
}
}
fn debug_assert_not_list(layout: LayoutRepr<'_>) {
debug_assert!(!matches!(layout, LayoutRepr::Builtin(Builtin::List(_))), "List are no longer safe to refcount through pointer alone. They must go through the zig bitcode functions");
}
fn incref_pointer<'ctx>(
env: &Env<'_, 'ctx, '_>,
pointer: PointerValue<'ctx>,
amount: IntValue<'ctx>,
layout: LayoutRepr<'_>,
) {
debug_assert_not_list(layout);
call_void_bitcode_fn(
env,
&[
@ -232,7 +245,9 @@ fn free_pointer<'ctx>(
env: &Env<'_, 'ctx, '_>,
pointer: PointerValue<'ctx>,
alignment: u32,
layout: LayoutRepr<'_>,
) -> InstructionValue<'ctx> {
debug_assert_not_list(layout);
let alignment = env.context.i32_type().const_int(alignment as _, false);
call_void_bitcode_fn(
env,
@ -245,12 +260,19 @@ fn free_pointer<'ctx>(
)
.into(),
alignment.into(),
env.context.bool_type().const_int(0, false).into(),
],
roc_builtins::bitcode::UTILS_FREE_RC_PTR,
)
}
fn decref_pointer<'ctx>(env: &Env<'_, 'ctx, '_>, pointer: PointerValue<'ctx>, alignment: u32) {
fn decref_pointer<'ctx>(
env: &Env<'_, 'ctx, '_>,
pointer: PointerValue<'ctx>,
alignment: u32,
layout: LayoutRepr<'_>,
) {
debug_assert_not_list(layout);
let alignment = env.context.i32_type().const_int(alignment as _, false);
call_void_bitcode_fn(
env,
@ -263,6 +285,7 @@ fn decref_pointer<'ctx>(env: &Env<'_, 'ctx, '_>, pointer: PointerValue<'ctx>, al
)
.into(),
alignment.into(),
env.context.bool_type().const_int(0, false).into(),
],
roc_builtins::bitcode::UTILS_DECREF_RC_PTR,
);
@ -273,7 +296,9 @@ pub fn decref_pointer_check_null<'ctx>(
env: &Env<'_, 'ctx, '_>,
pointer: PointerValue<'ctx>,
alignment: u32,
layout: LayoutRepr<'_>,
) {
debug_assert_not_list(layout);
let alignment = env.context.i32_type().const_int(alignment as _, false);
call_void_bitcode_fn(
env,
@ -286,6 +311,7 @@ pub fn decref_pointer_check_null<'ctx>(
)
.into(),
alignment.into(),
env.context.bool_type().const_int(0, false).into(),
],
roc_builtins::bitcode::UTILS_DECREF_CHECK_NULL,
);
@ -764,7 +790,6 @@ fn modify_refcount_list<'a, 'ctx>(
layout_interner,
layout_ids,
mode,
list_layout,
element_layout,
function_value,
);
@ -791,7 +816,6 @@ fn modify_refcount_list_help<'a, 'ctx>(
layout_interner: &STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
layout: LayoutRepr<'a>,
element_layout: InLayout<'a>,
fn_val: FunctionValue<'ctx>,
) {
@ -807,73 +831,49 @@ fn modify_refcount_list_help<'a, 'ctx>(
// Add args to scope
let arg_symbol = Symbol::ARG_1;
let arg_val = fn_val.get_param_iter().next().unwrap();
let mut param_iter = fn_val.get_param_iter();
let arg_val = param_iter.next().unwrap();
arg_val.set_name(arg_symbol.as_str(&env.interns));
let parent = fn_val;
let original_wrapper = arg_val.into_struct_value();
// We use the raw capacity to ensure we always decrement the refcount of seamless slices.
let capacity = list_capacity_or_ref_ptr(builder, original_wrapper);
let is_non_empty = builder.new_build_int_compare(
IntPredicate::UGT,
capacity,
env.ptr_int().const_zero(),
"cap > 0",
);
// build blocks
let modification_list_block = ctx.append_basic_block(parent, "modification_list_block");
let cont_block = ctx.append_basic_block(parent, "modify_rc_list_cont");
builder.new_build_conditional_branch(is_non_empty, modification_list_block, cont_block);
builder.position_at_end(modification_list_block);
if layout_interner.contains_refcounted(element_layout) {
let ptr_type = basic_type_from_layout(
env,
layout_interner,
layout_interner.get_repr(element_layout),
)
.ptr_type(AddressSpace::default());
let (len, ptr) = load_list(env.builder, original_wrapper, ptr_type);
let loop_fn = |layout_interner, _index, element| {
modify_refcount_layout_help(
// List incrementing and decrementing is more complex now.
// Always go through zig.
match mode {
Mode::Dec => {
let dec_element_fn =
build_dec_wrapper(env, layout_interner, layout_ids, element_layout);
call_void_list_bitcode_fn(
env,
layout_interner,
layout_ids,
mode.to_call_mode(fn_val),
element,
element_layout,
);
};
&[original_wrapper],
&[
env.alignment_intvalue(layout_interner, element_layout),
layout_width(env, layout_interner, element_layout),
layout_refcounted(env, layout_interner, element_layout),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_DECREF,
)
}
Mode::Inc => {
let inc_amount_symbol = Symbol::ARG_2;
let inc_amount_val = param_iter.next().unwrap();
incrementing_elem_loop(
env,
layout_interner,
parent,
element_layout,
ptr,
len,
"modify_rc_index",
loop_fn,
);
inc_amount_val.set_name(inc_amount_symbol.as_str(&env.interns));
call_void_list_bitcode_fn(
env,
&[original_wrapper],
&[
inc_amount_val.into_int_value().into(),
layout_refcounted(env, layout_interner, element_layout),
],
bitcode::LIST_INCREF,
)
}
}
let refcount_ptr =
PointerToRefcount::from_ptr_to_data(env, list_allocation_ptr(env, original_wrapper));
let call_mode = mode_to_call_mode(fn_val, mode);
refcount_ptr.modify(call_mode, layout, env, layout_interner);
builder.new_build_unconditional_branch(cont_block);
builder.position_at_end(cont_block);
// this function returns void
builder.new_build_return(None);
}

View file

@ -39,7 +39,6 @@ pub enum ProcSource {
Roc,
Helper,
/// Wrapper function for higher-order calls from Zig to Roc
HigherOrderMapper(usize),
HigherOrderCompare(usize),
}
@ -491,126 +490,6 @@ impl<'a, 'r> WasmBackend<'a, 'r> {
self.module.names.append_function(wasm_fn_index, name);
}
/// Build a wrapper around a Roc procedure so that it can be called from Zig builtins List.map*
///
/// The generic Zig code passes *pointers* to all of the argument values (e.g. on the heap in a List).
/// Numbers up to 64 bits are passed by value, so we need to load them from the provided pointer.
/// Everything else is passed by reference, so we can just pass the pointer through.
///
/// NOTE: If the builtins expected the return pointer first and closure data last, we could eliminate the wrapper
/// when all args are pass-by-reference and non-zero size. But currently we need it to swap those around.
pub fn build_higher_order_mapper(
&mut self,
wrapper_lookup_idx: usize,
inner_lookup_idx: usize,
) {
use Align::*;
use ValueType::*;
let ProcLookupData {
name: wrapper_name,
layout: wrapper_proc_layout,
..
} = self.proc_lookup[wrapper_lookup_idx];
let wrapper_arg_layouts = wrapper_proc_layout.arguments;
// Our convention is that the last arg of the wrapper is the heap return pointer
let heap_return_ptr_id = LocalId(wrapper_arg_layouts.len() as u32 - 1);
let inner_ret_layout = match wrapper_arg_layouts
.last()
.map(|l| self.layout_interner.get_repr(*l))
{
Some(LayoutRepr::Ptr(inner)) => WasmLayout::new(self.layout_interner, inner),
x => internal_error!("Higher-order wrapper: invalid return layout {:?}", x),
};
let ret_type_and_size = match inner_ret_layout.return_method() {
ReturnMethod::NoReturnValue => None,
ReturnMethod::Primitive(ty, size) => {
// If the inner function returns a primitive, load the address to store it at
// After the call, it will be under the call result in the value stack
self.code_builder.get_local(heap_return_ptr_id);
Some((ty, size))
}
ReturnMethod::WriteToPointerArg => {
// If the inner function writes to a return pointer, load its address
self.code_builder.get_local(heap_return_ptr_id);
None
}
};
// Load all the arguments for the inner function
for (i, wrapper_arg) in wrapper_arg_layouts.iter().enumerate() {
let is_closure_data = i == 0; // Skip closure data (first for wrapper, last for inner). We'll handle it below.
let is_return_pointer = i == wrapper_arg_layouts.len() - 1; // Skip return pointer (may not be an arg for inner. And if it is, swaps from end to start)
if is_closure_data || is_return_pointer {
continue;
}
let inner_layout = match self.layout_interner.get_repr(*wrapper_arg) {
LayoutRepr::Ptr(inner) => inner,
x => internal_error!("Expected a Ptr layout, got {:?}", x),
};
if self.layout_interner.stack_size(inner_layout) == 0 {
continue;
}
// Load the argument pointer. If it's a primitive value, dereference it too.
self.code_builder.get_local(LocalId(i as u32));
self.dereference_boxed_value(inner_layout);
}
// If the inner function has closure data, it's the last arg of the inner fn
let closure_data_layout = wrapper_arg_layouts[0];
if self.layout_interner.stack_size(closure_data_layout) > 0 {
// The closure data exists, and will have been passed in to the wrapper as a
// one-element struct.
let inner_closure_data_layout = match self.layout_interner.get_repr(closure_data_layout)
{
LayoutRepr::Struct([inner]) => inner,
other => internal_error!(
"Expected a boxed layout for wrapped closure data, got {:?}",
other
),
};
self.code_builder.get_local(LocalId(0));
// Since the closure data is wrapped in a one-element struct, we've been passed in the
// pointer to that struct in the stack memory. To get the closure data we just need to
// dereference the pointer.
self.dereference_boxed_value(*inner_closure_data_layout);
}
// Call the wrapped inner function
let inner_wasm_fn_index = self.fn_index_offset + inner_lookup_idx as u32;
self.code_builder.call(inner_wasm_fn_index);
// If the inner function returns a primitive, store it to the address we loaded at the very beginning
if let Some((ty, size)) = ret_type_and_size {
match (ty, size) {
(I64, 8) => self.code_builder.i64_store(Bytes8, 0),
(I32, 4) => self.code_builder.i32_store(Bytes4, 0),
(I32, 2) => self.code_builder.i32_store16(Bytes2, 0),
(I32, 1) => self.code_builder.i32_store8(Bytes1, 0),
(F32, 4) => self.code_builder.f32_store(Bytes4, 0),
(F64, 8) => self.code_builder.f64_store(Bytes8, 0),
_ => {
internal_error!("Cannot store {:?} with alignment of {:?}", ty, size);
}
}
}
// Write empty function header (local variables array with zero length)
self.code_builder.build_fn_header_and_footer(&[], 0, None);
self.module.add_function_signature(Signature {
param_types: bumpalo::vec![in self.env.arena; I32; wrapper_arg_layouts.len()],
ret_type: None,
});
self.append_proc_debug_name(wrapper_name);
self.reset();
}
/// Build a wrapper around a Roc comparison proc so that it can be called from higher-order Zig builtins.
/// Comparison procedure signature is: closure_data, a, b -> Order (u8)
///
@ -986,6 +865,8 @@ impl<'a, 'r> WasmBackend<'a, 'r> {
fn stmt_refcounting_free(&mut self, value: Symbol, following: &'a Stmt<'a>) {
let layout = self.storage.symbol_layouts[&value];
debug_assert!(!matches!(self.layout_interner.get_repr(layout), LayoutRepr::Builtin(Builtin::List(_))), "List are no longer safe to refcount through pointer alone. They must go through the zig bitcode functions");
let alignment = self.layout_interner.allocation_alignment_bytes(layout);
// Get pointer and offset
@ -1010,6 +891,9 @@ impl<'a, 'r> WasmBackend<'a, 'r> {
// push the allocation's alignment
self.code_builder.i32_const(alignment as i32);
// elems_refcounted (always false except for list which are refcounted differently)
self.code_builder.i32_const(false as i32);
self.call_host_fn_after_loading_args(bitcode::UTILS_FREE_DATA_PTR);
self.stmt(following);
@ -1557,7 +1441,8 @@ impl<'a, 'r> WasmBackend<'a, 'r> {
// Allocate heap space and store its address in a local variable
let heap_local_id = self.storage.create_anonymous_local(PTR_TYPE);
let heap_alignment = self.layout_interner.alignment_bytes(elem_layout);
self.allocate_with_refcount(Some(size), heap_alignment, 1);
let elems_refcounted = self.layout_interner.contains_refcounted(elem_layout);
self.allocate_with_refcount(size, heap_alignment, elems_refcounted);
self.code_builder.set_local(heap_local_id);
let (stack_local_id, stack_offset) =
@ -1671,13 +1556,13 @@ impl<'a, 'r> WasmBackend<'a, 'r> {
}
self.code_builder.else_();
{
self.allocate_with_refcount(Some(data_size), data_alignment, 1);
self.allocate_with_refcount(data_size, data_alignment, false);
self.code_builder.set_local(*local_id);
}
self.code_builder.end();
} else {
// Call the allocator to get a memory address.
self.allocate_with_refcount(Some(data_size), data_alignment, 1);
self.allocate_with_refcount(data_size, data_alignment, false);
self.code_builder.set_local(*local_id);
}
(*local_id, 0)
@ -1960,53 +1845,31 @@ impl<'a, 'r> WasmBackend<'a, 'r> {
* Refcounting & Heap allocation
*******************************************************************/
/// Allocate heap space and write an initial refcount
/// If the data size is known at compile time, pass it in comptime_data_size.
/// If size is only known at runtime, push *data* size to the VM stack first.
/// Allocates heap space and write an initial refcount of 1.
/// Leaves the *data* address on the VM stack
///
/// elements_refcounted should only ever be set for lists.
fn allocate_with_refcount(
&mut self,
comptime_data_size: Option<u32>,
data_size: u32,
alignment_bytes: u32,
initial_refcount: u32,
elements_refcounted: bool,
) {
if !self.can_relocate_heap {
// This will probably only happen for test hosts.
panic!("The app tries to allocate heap memory but the host doesn't support that. It needs to export symbols __heap_base and __heap_end");
}
// Add extra bytes for the refcount
let extra_bytes = alignment_bytes.max(PTR_SIZE);
if let Some(data_size) = comptime_data_size {
// Data size known at compile time and passed as an argument
self.code_builder
.i32_const((data_size + extra_bytes) as i32);
} else {
// Data size known only at runtime and is on top of VM stack
self.code_builder.i32_const(extra_bytes as i32);
self.code_builder.i32_add();
}
// Zig arguments Wasm types
// data_bytes: usize i32
// element_alignment: u32 i32
// element_refcounted: bool i32
// Provide a constant for the alignment argument
self.code_builder.i32_const(data_size as i32);
self.code_builder.i32_const(alignment_bytes as i32);
self.code_builder.i32_const(elements_refcounted as i32);
// Call the foreign function. (Zig and C calling conventions are the same for this signature)
self.call_host_fn_after_loading_args("roc_alloc");
// Save the allocation address to a temporary local variable
let local_id = self.storage.create_anonymous_local(ValueType::I32);
self.code_builder.tee_local(local_id);
// Write the initial refcount
let refcount_offset = extra_bytes - PTR_SIZE;
let encoded_refcount = (initial_refcount as i32) - 1 + i32::MIN;
self.code_builder.i32_const(encoded_refcount);
self.code_builder.i32_store(Align::Bytes4, refcount_offset);
// Put the data address on the VM stack
self.code_builder.get_local(local_id);
self.code_builder.i32_const(extra_bytes as i32);
self.code_builder.i32_add();
self.call_host_fn_after_loading_args(bitcode::UTILS_ALLOCATE_WITH_REFCOUNT);
}
fn expr_reset(&mut self, argument: Symbol, ret_symbol: Symbol, ret_storage: &StoredValue) {
@ -2118,9 +1981,30 @@ impl<'a, 'r> WasmBackend<'a, 'r> {
self.register_helper_proc(spec_sym, spec_layout, ProcSource::Helper);
}
self.get_existing_refcount_fn_index(proc_symbol, layout, op)
}
/// return a pointer (table index) to a refcount helper procedure.
/// This allows it to be indirectly called from Zig code
pub fn get_existing_refcount_fn_index(
&mut self,
proc_symbol: Symbol,
layout: InLayout<'a>,
op: HelperOp,
) -> u32 {
let layout_repr = self.layout_interner.runtime_representation(layout);
let same_layout =
|layout| self.layout_interner.runtime_representation(layout) == layout_repr;
let same_layout = |layout| {
if op.is_indirect() {
if let LayoutRepr::Ptr(inner) = self.layout_interner.runtime_representation(layout)
{
self.layout_interner.runtime_representation(inner) == layout_repr
} else {
false
}
} else {
self.layout_interner.runtime_representation(layout) == layout_repr
}
};
let proc_index = self
.proc_lookup
.iter()

View file

@ -178,7 +178,6 @@ pub fn build_app_module<'a, 'r>(
match source {
Roc => { /* already generated */ }
Helper => backend.build_proc(helper_iter.next().unwrap()),
HigherOrderMapper(inner_idx) => backend.build_higher_order_mapper(idx, *inner_idx),
HigherOrderCompare(inner_idx) => backend.build_higher_order_compare(idx, *inner_idx),
}
}

View file

@ -275,27 +275,109 @@ impl<'a> LowLevelCall<'a> {
ListClone => {
let input_list: Symbol = self.arguments[0];
let elem_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let elem_layout = backend.layout_interner.get_repr(elem_layout);
let elem_in_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let elem_layout = backend.layout_interner.get_repr(elem_in_layout);
let (elem_width, elem_align) =
elem_layout.stack_size_and_alignment(backend.layout_interner);
let elem_refcounted = backend.layout_interner.contains_refcounted(elem_in_layout);
let inc_fn_ptr =
build_refcount_element_fn(backend, elem_in_layout, HelperOp::IndirectInc);
let dec_fn_ptr =
build_refcount_element_fn(backend, elem_in_layout, HelperOp::IndirectDec);
// Zig arguments Wasm types
// (return pointer) i32
// input_list: &RocList i32
// alignment: u32 i32
// element_width: usize i32
// element_refcounted: bool i32
// inc: Inc i32
// dec: Dec i32
backend
.storage
.load_symbols(&mut backend.code_builder, &[self.ret_symbol, input_list]);
backend.code_builder.i32_const(elem_align as i32);
backend.code_builder.i32_const(elem_width as i32);
backend.code_builder.i32_const(elem_refcounted as i32);
backend.code_builder.i32_const(inc_fn_ptr);
backend.code_builder.i32_const(dec_fn_ptr);
backend.call_host_fn_after_loading_args(bitcode::LIST_CLONE);
}
ListMap | ListMap2 | ListMap3 | ListMap4 | ListSortWith => {
ListIncref => {
let input_list: Symbol = self.arguments[0];
let list_layout = backend
.layout_interner
.get_repr(backend.storage.symbol_layouts[&input_list]);
let elem_in_layout = unwrap_list_elem_layout(list_layout);
let elem_refcounted = backend.layout_interner.contains_refcounted(elem_in_layout);
// Zig arguments Wasm types
// input_list: &RocList i32
// amount: isize i32
// element_refcounted: bool i32
backend
.storage
.load_symbols(&mut backend.code_builder, &[input_list]);
if self.arguments.len() == 2 {
// amount explicitly specified.
let amount = self.arguments[1];
backend
.storage
.load_symbols(&mut backend.code_builder, &[amount]);
} else {
// implicit 1 amount
backend.code_builder.i32_const(1);
}
backend.code_builder.i32_const(elem_refcounted as i32);
backend.call_host_fn_after_loading_args(bitcode::LIST_INCREF);
}
ListDecref => {
let input_list: Symbol = self.arguments[0];
let dec_fn_sym = self.arguments[1];
let list_layout = backend
.layout_interner
.get_repr(backend.storage.symbol_layouts[&input_list]);
let elem_in_layout = unwrap_list_elem_layout(list_layout);
let elem_layout = backend.layout_interner.get_repr(elem_in_layout);
let (elem_width, elem_align) =
elem_layout.stack_size_and_alignment(backend.layout_interner);
let elem_refcounted = backend.layout_interner.contains_refcounted(elem_in_layout);
let dec_fn = backend.get_existing_refcount_fn_index(
dec_fn_sym,
elem_in_layout,
HelperOp::IndirectDec,
);
let dec_fn_ptr = backend.get_fn_ptr(dec_fn);
// Zig arguments Wasm types
// input_list: &RocList i32
// alignment: u32 i32
// element_width: usize i32
// element_refcounted: bool i32
// dec: Dec i32
backend
.storage
.load_symbols(&mut backend.code_builder, &[input_list]);
backend.code_builder.i32_const(elem_align as i32);
backend.code_builder.i32_const(elem_width as i32);
backend.code_builder.i32_const(elem_refcounted as i32);
backend.code_builder.i32_const(dec_fn_ptr);
backend.call_host_fn_after_loading_args(bitcode::LIST_DECREF);
}
ListSortWith => {
internal_error!("HigherOrder lowlevels should not be handled here")
}
@ -334,14 +416,6 @@ impl<'a> LowLevelCall<'a> {
AddressValue::NotLoaded(elem_local),
0,
);
// Increment refcount
if self.ret_layout_raw.is_refcounted(backend.layout_interner) {
let inc_fn = backend.get_refcount_fn_index(self.ret_layout, HelperOp::Inc);
backend.code_builder.get_local(elem_local);
backend.code_builder.i32_const(1);
backend.code_builder.call(inc_fn);
}
}
ListReplaceUnsafe => {
// List.replace_unsafe : List elem, U64, elem -> { list: List elem, value: elem }
@ -359,7 +433,7 @@ impl<'a> LowLevelCall<'a> {
};
// Byte offsets of each field in the return struct
let (ret_list_offset, ret_elem_offset, elem_layout) = match self.ret_layout_raw {
let (ret_list_offset, ret_elem_offset, elem_in_layout) = match self.ret_layout_raw {
LayoutRepr::Struct(&[f1, f2]) => {
let l1 = backend.layout_interner.get_repr(f1);
let l2 = backend.layout_interner.get_repr(f2);
@ -387,20 +461,33 @@ impl<'a> LowLevelCall<'a> {
let (elem_width, elem_alignment) = backend
.layout_interner
.stack_size_and_alignment(elem_layout);
.stack_size_and_alignment(elem_in_layout);
// Ensure the new element is stored in memory so we can pass a pointer to Zig
let (new_elem_local, new_elem_offset, _) =
ensure_symbol_is_in_memory(backend, new_elem, elem_layout, backend.env.arena);
let (new_elem_local, new_elem_offset, _) = ensure_symbol_is_in_memory(
backend,
new_elem,
elem_in_layout,
backend.env.arena,
);
let elem_refcounted = backend.layout_interner.contains_refcounted(elem_in_layout);
let inc_fn_ptr =
build_refcount_element_fn(backend, elem_in_layout, HelperOp::IndirectInc);
let dec_fn_ptr =
build_refcount_element_fn(backend, elem_in_layout, HelperOp::IndirectDec);
// Load all the arguments for Zig
// (List return pointer) i32
// list: RocList, i32
// alignment: u32, i32
// index: usize, i32
// element: Opaque, i32
// element_width: usize, i32
// out_element: ?[*]u8, i32
// (List return pointer) i32
// list: RocList, i32
// alignment: u32, i32
// index: usize, i32
// element: Opaque, i32
// element_width: usize, i32
// element_refcounted: bool i32
// inc: Inc i32
// dec: Dec i32
// out_element: ?[*]u8, i32
let code_builder = &mut backend.code_builder;
@ -421,6 +508,9 @@ impl<'a> LowLevelCall<'a> {
}
code_builder.i32_const(elem_width as i32);
code_builder.i32_const(elem_refcounted as i32);
code_builder.i32_const(inc_fn_ptr);
code_builder.i32_const(dec_fn_ptr);
code_builder.get_local(ret_local);
if (ret_offset + ret_elem_offset) > 0 {
@ -435,33 +525,34 @@ impl<'a> LowLevelCall<'a> {
// List.withCapacity : U64 -> List elem
let capacity: Symbol = self.arguments[0];
let elem_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let elem_layout = backend.layout_interner.get_repr(elem_layout);
let elem_in_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let elem_layout = backend.layout_interner.get_repr(elem_in_layout);
let (elem_width, elem_align) =
elem_layout.stack_size_and_alignment(backend.layout_interner);
let elem_refcounted = backend.layout_interner.contains_refcounted(elem_in_layout);
let inc_fn_ptr =
build_refcount_element_fn(backend, elem_in_layout, HelperOp::IndirectInc);
// Zig arguments Wasm types
// (return pointer) i32
// capacity: u64 i64
// alignment: u32 i32
// element_width: usize i32
// element_refcounted: bool i32
// inc: Inc i32
backend
.storage
.load_symbols(&mut backend.code_builder, &[self.ret_symbol, capacity]);
backend.code_builder.i32_const(elem_align as i32);
backend.code_builder.i32_const(elem_width as i32);
backend.code_builder.i32_const(elem_refcounted as i32);
backend.code_builder.i32_const(inc_fn_ptr as i32);
backend.call_host_fn_after_loading_args(bitcode::LIST_WITH_CAPACITY);
}
ListConcat => {
// List.concat : List elem, List elem -> List elem
// Zig arguments Wasm types
// (return pointer) i32
// list_a: RocList i32
// list_b: RocList i32
// alignment: u32 i32
// element_width: usize i32
// Load the arguments that have symbols
backend.storage.load_symbols_for_call(
@ -472,12 +563,31 @@ impl<'a> LowLevelCall<'a> {
);
// Load monomorphization constants
let elem_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let elem_layout = backend.layout_interner.get_repr(elem_layout);
let elem_in_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let elem_layout = backend.layout_interner.get_repr(elem_in_layout);
let (elem_width, elem_align) =
elem_layout.stack_size_and_alignment(backend.layout_interner);
let elem_refcounted = backend.layout_interner.contains_refcounted(elem_in_layout);
let inc_fn_ptr =
build_refcount_element_fn(backend, elem_in_layout, HelperOp::IndirectInc);
let dec_fn_ptr =
build_refcount_element_fn(backend, elem_in_layout, HelperOp::IndirectDec);
// Zig arguments Wasm types
// (return pointer) i32
// list_a: RocList i32
// list_b: RocList i32
// alignment: u32 i32
// element_width: usize i32
// element_refcounted: bool i32
// inc: Inc i32
// dec: Dec i32
backend.code_builder.i32_const(elem_align as i32);
backend.code_builder.i32_const(elem_width as i32);
backend.code_builder.i32_const(elem_refcounted as i32);
backend.code_builder.i32_const(inc_fn_ptr);
backend.code_builder.i32_const(dec_fn_ptr);
backend.call_host_fn_after_loading_args(bitcode::LIST_CONCAT);
}
@ -489,10 +599,13 @@ impl<'a> LowLevelCall<'a> {
let list: Symbol = self.arguments[0];
let spare: Symbol = self.arguments[1];
let elem_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let elem_layout = backend.layout_interner.get_repr(elem_layout);
let elem_in_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let elem_layout = backend.layout_interner.get_repr(elem_in_layout);
let (elem_width, elem_align) =
elem_layout.stack_size_and_alignment(backend.layout_interner);
let elem_refcounted = backend.layout_interner.contains_refcounted(elem_in_layout);
let inc_fn_ptr =
build_refcount_element_fn(backend, elem_in_layout, HelperOp::IndirectInc);
// Zig arguments Wasm types
// (return pointer) i32
@ -500,6 +613,8 @@ impl<'a> LowLevelCall<'a> {
// alignment: u32 i32
// spare: u64 i64
// element_width: usize i32
// element_refcounted: bool i32
// inc: Inc i32
// update_mode: UpdateMode i32
// return pointer and list
@ -517,7 +632,8 @@ impl<'a> LowLevelCall<'a> {
.load_symbols(&mut backend.code_builder, &[spare]);
backend.code_builder.i32_const(elem_width as i32);
backend.code_builder.i32_const(elem_refcounted as i32);
backend.code_builder.i32_const(inc_fn_ptr);
backend.code_builder.i32_const(UPDATE_MODE_IMMUTABLE);
backend.call_host_fn_after_loading_args(bitcode::LIST_RESERVE);
@ -528,16 +644,25 @@ impl<'a> LowLevelCall<'a> {
let list: Symbol = self.arguments[0];
let elem_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let elem_layout = backend.layout_interner.get_repr(elem_layout);
let elem_in_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let elem_layout = backend.layout_interner.get_repr(elem_in_layout);
let (elem_width, elem_align) =
elem_layout.stack_size_and_alignment(backend.layout_interner);
let elem_refcounted = backend.layout_interner.contains_refcounted(elem_in_layout);
let inc_fn_ptr =
build_refcount_element_fn(backend, elem_in_layout, HelperOp::IndirectInc);
let dec_fn_ptr =
build_refcount_element_fn(backend, elem_in_layout, HelperOp::IndirectDec);
// Zig arguments Wasm types
// (return pointer) i32
// list: RocList i32
// alignment: u32 i32
// element_width: usize i32
// element_refcounted: bool i32
// inc: Inc i32
// dec: Dec i32
// update_mode: UpdateMode i32
// return pointer and list
@ -549,9 +674,10 @@ impl<'a> LowLevelCall<'a> {
);
backend.code_builder.i32_const(elem_align as i32);
backend.code_builder.i32_const(elem_width as i32);
backend.code_builder.i32_const(elem_refcounted as i32);
backend.code_builder.i32_const(inc_fn_ptr);
backend.code_builder.i32_const(dec_fn_ptr);
backend.code_builder.i32_const(UPDATE_MODE_IMMUTABLE);
backend.call_host_fn_after_loading_args(bitcode::LIST_RELEASE_EXCESS_CAPACITY);
@ -598,12 +724,16 @@ impl<'a> LowLevelCall<'a> {
let list: Symbol = self.arguments[0];
let elem: Symbol = self.arguments[1];
let elem_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let elem_in_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let (elem_width, elem_align) = backend
.layout_interner
.stack_size_and_alignment(elem_layout);
.stack_size_and_alignment(elem_in_layout);
let (elem_local, elem_offset, _) =
ensure_symbol_is_in_memory(backend, elem, elem_layout, backend.env.arena);
ensure_symbol_is_in_memory(backend, elem, elem_in_layout, backend.env.arena);
let elem_refcounted = backend.layout_interner.contains_refcounted(elem_in_layout);
let inc_fn_ptr =
build_refcount_element_fn(backend, elem_in_layout, HelperOp::IndirectInc);
// Zig arguments Wasm types
// (return pointer) i32
@ -611,6 +741,8 @@ impl<'a> LowLevelCall<'a> {
// alignment: u32 i32
// element: Opaque i32
// element_width: usize i32
// element_refcounted: bool i32
// inc: Inc i32
// return pointer and list
backend.storage.load_symbols_for_call(
@ -628,6 +760,8 @@ impl<'a> LowLevelCall<'a> {
backend.code_builder.i32_add();
}
backend.code_builder.i32_const(elem_width as i32);
backend.code_builder.i32_const(elem_refcounted as i32);
backend.code_builder.i32_const(inc_fn_ptr);
backend.call_host_fn_after_loading_args(bitcode::LIST_PREPEND);
}
@ -639,27 +773,21 @@ impl<'a> LowLevelCall<'a> {
let start: Symbol = self.arguments[1];
let len: Symbol = self.arguments[2];
let elem_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let elem_in_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let (elem_width, elem_align) = backend
.layout_interner
.stack_size_and_alignment(elem_layout);
.stack_size_and_alignment(elem_in_layout);
// The refcount function receives a pointer to an element in the list
// This is the same as a Struct containing the element
let in_memory_layout =
backend
.layout_interner
.insert_direct_no_semantic(LayoutRepr::Struct(
backend.env.arena.alloc([elem_layout]),
));
let dec_fn = backend.get_refcount_fn_index(in_memory_layout, HelperOp::Dec);
let dec_fn_ptr = backend.get_fn_ptr(dec_fn);
let elem_refcounted = backend.layout_interner.contains_refcounted(elem_in_layout);
let dec_fn_ptr =
build_refcount_element_fn(backend, elem_in_layout, HelperOp::IndirectDec);
// Zig arguments Wasm types
// (return pointer) i32
// list: RocList, i32
// alignment: u32, i32
// element_width: usize, i32
// element_recounted: bool, i32
// start: u64, i64
// len: u64, i64
// dec: Dec, i32
@ -673,6 +801,7 @@ impl<'a> LowLevelCall<'a> {
backend.code_builder.i32_const(elem_align as i32);
backend.code_builder.i32_const(elem_width as i32);
backend.code_builder.i32_const(elem_refcounted as i32);
backend
.storage
.load_symbols(&mut backend.code_builder, &[start, len]);
@ -685,28 +814,25 @@ impl<'a> LowLevelCall<'a> {
let list: Symbol = self.arguments[0];
let drop_index: Symbol = self.arguments[1];
let elem_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let elem_in_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let (elem_width, elem_align) = backend
.layout_interner
.stack_size_and_alignment(elem_layout);
.stack_size_and_alignment(elem_in_layout);
let elem_refcounted = backend.layout_interner.contains_refcounted(elem_in_layout);
// The refcount function receives a pointer to an element in the list
// This is the same as a Struct containing the element
let in_memory_layout =
backend
.layout_interner
.insert_direct_no_semantic(LayoutRepr::Struct(
backend.env.arena.alloc([elem_layout]),
));
let dec_fn = backend.get_refcount_fn_index(in_memory_layout, HelperOp::Dec);
let dec_fn_ptr = backend.get_fn_ptr(dec_fn);
let inc_fn_ptr =
build_refcount_element_fn(backend, elem_in_layout, HelperOp::IndirectInc);
let dec_fn_ptr =
build_refcount_element_fn(backend, elem_in_layout, HelperOp::IndirectDec);
// Zig arguments Wasm types
// (return pointer) i32
// list: RocList, i32
// element_width: usize, i32
// alignment: u32, i32
// element_width: usize, i32
// elements_refcounted: bool, i32
// drop_index: u64, i64
// inc: Inc, i32
// dec: Dec, i32
// Load the return pointer and the list
@ -717,11 +843,13 @@ impl<'a> LowLevelCall<'a> {
&WasmLayout::new(backend.layout_interner, self.ret_layout),
);
backend.code_builder.i32_const(elem_width as i32);
backend.code_builder.i32_const(elem_align as i32);
backend.code_builder.i32_const(elem_width as i32);
backend.code_builder.i32_const(elem_refcounted as i32);
backend
.storage
.load_symbols(&mut backend.code_builder, &[drop_index]);
backend.code_builder.i32_const(inc_fn_ptr);
backend.code_builder.i32_const(dec_fn_ptr);
backend.call_host_fn_after_loading_args(bitcode::LIST_DROP_AT);
@ -732,10 +860,16 @@ impl<'a> LowLevelCall<'a> {
let index_1: Symbol = self.arguments[1];
let index_2: Symbol = self.arguments[2];
let elem_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let elem_in_layout = unwrap_list_elem_layout(self.ret_layout_raw);
let (elem_width, elem_align) = backend
.layout_interner
.stack_size_and_alignment(elem_layout);
.stack_size_and_alignment(elem_in_layout);
let elem_refcounted = backend.layout_interner.contains_refcounted(elem_in_layout);
let inc_fn_ptr =
build_refcount_element_fn(backend, elem_in_layout, HelperOp::IndirectInc);
let dec_fn_ptr =
build_refcount_element_fn(backend, elem_in_layout, HelperOp::IndirectDec);
// Zig arguments Wasm types
// (return pointer) i32
@ -744,6 +878,9 @@ impl<'a> LowLevelCall<'a> {
// element_width: usize, i32
// index_1: u64, i64
// index_2: u64, i64
// element_refcounted: bool i32
// inc: Inc i32
// dec: Dec i32
// update_mode: UpdateMode, i32
// Load the return pointer and the list
@ -759,6 +896,9 @@ impl<'a> LowLevelCall<'a> {
backend
.storage
.load_symbols(&mut backend.code_builder, &[index_1, index_2]);
backend.code_builder.i32_const(elem_refcounted as i32);
backend.code_builder.i32_const(inc_fn_ptr);
backend.code_builder.i32_const(dec_fn_ptr);
backend.code_builder.i32_const(UPDATE_MODE_IMMUTABLE);
backend.call_host_fn_after_loading_args(bitcode::LIST_SWAP);
@ -2467,7 +2607,7 @@ fn num_is_finite(backend: &mut WasmBackend<'_, '_>, argument: Symbol) {
pub fn call_higher_order_lowlevel<'a>(
backend: &mut WasmBackend<'a, '_>,
return_sym: Symbol,
return_layout: &InLayout<'a>,
_return_layout: &InLayout<'a>,
higher_order: &'a HigherOrderLowLevel<'a>,
) {
use HigherOrder::*;
@ -2579,9 +2719,6 @@ pub fn call_higher_order_lowlevel<'a>(
.unwrap();
match op {
ListSortWith { .. } => ProcSource::HigherOrderCompare(passed_proc_index),
ListMap { .. } | ListMap2 { .. } | ListMap3 { .. } | ListMap4 { .. } => {
ProcSource::HigherOrderMapper(passed_proc_index)
}
}
};
let wrapper_sym = backend.create_symbol(&format!("#wrap#{fn_name:?}"));
@ -2606,19 +2743,6 @@ pub fn call_higher_order_lowlevel<'a>(
wrapper_arg_layouts.extend(boxed_closure_arg_layouts);
match helper_proc_source {
ProcSource::HigherOrderMapper(_) => {
// Our convention for mappers is that they write to the heap via the last argument
wrapper_arg_layouts.push(
backend
.layout_interner
.insert_direct_no_semantic(LayoutRepr::Ptr(*result_layout)),
);
ProcLayout {
arguments: wrapper_arg_layouts.into_bump_slice(),
result: Layout::UNIT,
niche: fn_name.niche(),
}
}
ProcSource::HigherOrderCompare(_) => ProcLayout {
arguments: wrapper_arg_layouts.into_bump_slice(),
result: *result_layout,
@ -2633,90 +2757,47 @@ pub fn call_higher_order_lowlevel<'a>(
let wrapper_fn_idx =
backend.register_helper_proc(wrapper_sym, wrapper_layout, helper_proc_source);
let wrapper_fn_ptr = backend.get_fn_ptr(wrapper_fn_idx);
let inc_fn_ptr = if !closure_data_exists {
let inc_n_fn_ptr = if !closure_data_exists {
// Our code gen would ignore the Unit arg, but the Zig builtin passes a pointer for it!
// That results in an exception (type signature mismatch in indirect call).
// The workaround is to use I32 layout, treating the (ignored) pointer as an integer.
let inc_fn = backend.get_refcount_fn_index(Layout::I32, HelperOp::Inc);
let inc_fn = backend.get_refcount_fn_index(Layout::I32, HelperOp::IncN);
backend.get_fn_ptr(inc_fn)
} else {
let inc_fn = backend.get_refcount_fn_index(wrapped_captures_layout, HelperOp::Inc);
let inc_fn = backend.get_refcount_fn_index(wrapped_captures_layout, HelperOp::IncN);
backend.get_fn_ptr(inc_fn)
};
match op {
ListMap { xs } => list_map_n(
bitcode::LIST_MAP,
backend,
&[*xs],
return_sym,
*return_layout,
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
wrapped_captured_environment,
*owns_captured_environment,
),
ListMap2 { xs, ys } => list_map_n(
bitcode::LIST_MAP2,
backend,
&[*xs, *ys],
return_sym,
*return_layout,
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
wrapped_captured_environment,
*owns_captured_environment,
),
ListMap3 { xs, ys, zs } => list_map_n(
bitcode::LIST_MAP3,
backend,
&[*xs, *ys, *zs],
return_sym,
*return_layout,
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
wrapped_captured_environment,
*owns_captured_environment,
),
ListMap4 { xs, ys, zs, ws } => list_map_n(
bitcode::LIST_MAP4,
backend,
&[*xs, *ys, *zs, *ws],
return_sym,
*return_layout,
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
wrapped_captured_environment,
*owns_captured_environment,
),
ListSortWith { xs } => {
let elem_layout = unwrap_list_elem_layout(
let elem_in_layout = unwrap_list_elem_layout(
backend
.layout_interner
.get_repr(backend.storage.symbol_layouts[xs]),
);
let elem_layout = backend.layout_interner.get_repr(elem_layout);
let elem_layout = backend.layout_interner.get_repr(elem_in_layout);
let (element_width, alignment) =
elem_layout.stack_size_and_alignment(backend.layout_interner);
let elem_refcounted = backend.layout_interner.contains_refcounted(elem_in_layout);
let inc_fn_ptr =
build_refcount_element_fn(backend, elem_in_layout, HelperOp::IndirectInc);
let dec_fn_ptr =
build_refcount_element_fn(backend, elem_in_layout, HelperOp::IndirectDec);
let cb = &mut backend.code_builder;
// (return pointer) i32
// input: RocList, i32
// caller: CompareFn, i32
// data: Opaque, i32
// inc_n_data: IncN, i32
// data_is_owned: bool, i32
// alignment: u32, i32
// element_width: usize, i32
// (return pointer) i32
// input: RocList, i32
// caller: CompareFn, i32
// data: Opaque, i32
// inc_n_data: IncN, i32
// data_is_owned: bool, i32
// alignment: u32, i32
// element_width: usize, i32
// element_refcounted: bool i32
// inc: Inc i32
// dec: Dec i32
backend.storage.load_symbols(cb, &[return_sym, *xs]);
cb.i32_const(wrapper_fn_ptr);
@ -2729,10 +2810,13 @@ pub fn call_higher_order_lowlevel<'a>(
// but that's a specialization that our Zig code doesn't have! Pass a null pointer.
cb.i32_const(0);
}
cb.i32_const(inc_fn_ptr);
cb.i32_const(inc_n_fn_ptr);
cb.i32_const(*owns_captured_environment as i32);
cb.i32_const(alignment as i32);
cb.i32_const(element_width as i32);
cb.i32_const(elem_refcounted as i32);
cb.i32_const(inc_fn_ptr);
cb.i32_const(dec_fn_ptr);
backend.call_host_fn_after_loading_args(bitcode::LIST_SORT_WITH);
}
@ -2746,75 +2830,6 @@ fn unwrap_list_elem_layout(list_layout: LayoutRepr) -> InLayout {
}
}
#[allow(clippy::too_many_arguments)]
fn list_map_n<'a>(
zig_fn_name: &'static str,
backend: &mut WasmBackend<'a, '_>,
arg_symbols: &[Symbol],
return_sym: Symbol,
return_layout: InLayout<'a>,
wrapper_fn_ptr: i32,
inc_fn_ptr: i32,
closure_data_exists: bool,
captured_environment: Symbol,
owns_captured_environment: bool,
) {
let arg_elem_layouts = Vec::from_iter_in(
arg_symbols.iter().map(|sym| {
unwrap_list_elem_layout(
backend
.layout_interner
.get_repr(backend.storage.symbol_layouts[sym]),
)
}),
backend.env.arena,
);
let elem_ret = unwrap_list_elem_layout(backend.layout_interner.get_repr(return_layout));
let elem_ret = backend.layout_interner.get_repr(elem_ret);
let (elem_ret_size, elem_ret_align) =
elem_ret.stack_size_and_alignment(backend.layout_interner);
let cb = &mut backend.code_builder;
let mut args_vec = Vec::with_capacity_in(arg_symbols.len() + 1, backend.env.arena);
args_vec.push(return_sym);
args_vec.extend_from_slice(arg_symbols);
backend.storage.load_symbols(cb, &args_vec);
cb.i32_const(wrapper_fn_ptr);
if closure_data_exists {
backend.storage.load_symbols(cb, &[captured_environment]);
} else {
// load_symbols assumes that a zero-size arg should be eliminated in code gen,
// but that's a specialization that our Zig code doesn't have! Pass a null pointer.
cb.i32_const(0);
}
cb.i32_const(inc_fn_ptr);
cb.i32_const(owns_captured_environment as i32);
cb.i32_const(elem_ret_align as i32);
for el in arg_elem_layouts.iter() {
cb.i32_const(backend.layout_interner.stack_size(*el) as i32);
}
cb.i32_const(elem_ret_size as i32);
// If we have lists of different lengths, we may need to decrement
if arg_elem_layouts.len() > 1 {
for el in arg_elem_layouts.iter() {
// The dec function will be passed a pointer to the element within the list, not the element itself!
// Here we wrap the layout in a Struct to ensure we get the right code gen
let el_ptr = backend
.layout_interner
.insert_direct_no_semantic(LayoutRepr::Struct(backend.env.arena.alloc([*el])));
let idx = backend.get_refcount_fn_index(el_ptr, HelperOp::Dec);
let ptr = backend.get_fn_ptr(idx);
backend.code_builder.i32_const(ptr);
}
};
backend.call_host_fn_after_loading_args(zig_fn_name);
}
fn ensure_symbol_is_in_memory<'a>(
backend: &mut WasmBackend<'a, '_>,
symbol: Symbol,
@ -2845,3 +2860,12 @@ fn ensure_symbol_is_in_memory<'a>(
}
}
}
fn build_refcount_element_fn<'a>(
backend: &mut WasmBackend<'a, '_>,
elem_layout: InLayout<'a>,
rc_op: HelperOp,
) -> i32 {
let rc_fn = backend.get_refcount_fn_index(elem_layout, rc_op);
backend.get_fn_ptr(rc_fn)
}

View file

@ -8,7 +8,7 @@ use bumpalo::{collections::Vec, Bump};
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_mono::layout::{Builtin, InLayout, LayoutInterner, LayoutRepr, UnionLayout};
use roc_std::{RocBox, RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128};
use roc_std::{RocBox, RocDec, RocList, RocOrder, RocRefcounted, RocResult, RocStr, I128, U128};
use roc_wasm_module::{
linking::SymInfo, linking::WasmObjectSymbol, Align, Export, ExportType, LocalId, Signature,
ValueType, WasmModule,
@ -197,13 +197,19 @@ impl Wasm32Result for RocStr {
}
}
impl<T: Wasm32Result> Wasm32Result for RocList<T> {
impl<T: Wasm32Result> Wasm32Result for RocList<T>
where
T: RocRefcounted,
{
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory(code_builder, main_function_index, 12)
}
}
impl<T: Wasm32Result> Wasm32Result for RocBox<T> {
impl<T: Wasm32Result> Wasm32Result for RocBox<T>
where
T: RocRefcounted,
{
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
// treat box as if it's just a isize value
<i32 as Wasm32Result>::build_wrapper_body(code_builder, main_function_index)

View file

@ -1,4 +1,4 @@
use roc_std::{RocBox, RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128};
use roc_std::{RocBox, RocDec, RocList, RocOrder, RocRefcounted, RocResult, RocStr, I128, U128};
pub trait Wasm32Sized: Sized {
const SIZE_OF_WASM: usize;
@ -53,12 +53,18 @@ impl Wasm32Sized for RocStr {
const ALIGN_OF_WASM: usize = 4;
}
impl<T: Wasm32Sized> Wasm32Sized for RocList<T> {
impl<T: Wasm32Sized> Wasm32Sized for RocList<T>
where
T: RocRefcounted,
{
const SIZE_OF_WASM: usize = 12;
const ALIGN_OF_WASM: usize = 4;
}
impl<T: Wasm32Sized> Wasm32Sized for RocBox<T> {
impl<T: Wasm32Sized> Wasm32Sized for RocBox<T>
where
T: RocRefcounted,
{
const SIZE_OF_WASM: usize = 4;
const ALIGN_OF_WASM: usize = 4;
}

View file

@ -4972,7 +4972,7 @@ mod test_reporting {
}
"
),@r###"
RECORD BUILDER IN MODULE PARAMS in ...ord_builder_in_module_params/Test.roc
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:
@ -4981,8 +4981,8 @@ mod test_reporting {
6 name: <- applyName
^^^^^^^^^^^^^^^^^^
This looks like a record builder field, but those are not allowed in
module params.
This looks like an old-style record builder field, but those are not
allowed in module params.
"###
);
@ -10700,7 +10700,7 @@ In roc, functions are always written as a lambda, like{}
// Record Builders
test_report!(
optional_field_in_record_builder,
optional_field_in_old_record_builder,
indoc!(
r#"
{
@ -10711,7 +10711,7 @@ In roc, functions are always written as a lambda, like{}
"#
),
@r#"
BAD RECORD BUILDER in tmp/optional_field_in_record_builder/Test.roc
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:
@ -10730,7 +10730,7 @@ In roc, functions are always written as a lambda, like{}
);
test_report!(
record_update_builder,
record_update_old_builder,
indoc!(
r#"
{ rec &
@ -10740,10 +10740,10 @@ In roc, functions are always written as a lambda, like{}
"#
),
@r#"
BAD RECORD UPDATE in tmp/record_update_builder/Test.roc
BAD RECORD UPDATE in tmp/record_update_old_builder/Test.roc
I am partway through parsing a record update, and I found a record
builder field:
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
@ -10752,12 +10752,12 @@ In roc, functions are always written as a lambda, like{}
5 a: <- apply "a",
^^^^^^^^^^^^^^^
Record builders cannot be updated like records.
Old-style record builders cannot be updated like records.
"#
);
test_report!(
multiple_record_builders,
multiple_old_record_builders,
indoc!(
r#"
succeed
@ -10766,32 +10766,31 @@ In roc, functions are always written as a lambda, like{}
"#
),
@r#"
MULTIPLE RECORD BUILDERS in /code/proj/Main.roc
MULTIPLE OLD-STYLE RECORD BUILDERS in /code/proj/Main.roc
This function is applied to multiple record builders:
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 record builder!
Note: Functions can only take at most one old-style record builder!
Tip: You can combine them or apply them separately.
"#
);
test_report!(
unapplied_record_builder,
unapplied_old_record_builder,
indoc!(
r#"
{ a: <- apply "a" }
"#
),
@r#"
UNAPPLIED RECORD BUILDER in /code/proj/Main.roc
UNAPPLIED OLD-STYLE RECORD BUILDER in /code/proj/Main.roc
This record builder was not applied to a function:
This old-style record builder was not applied to a function:
4 { a: <- apply "a" }
^^^^^^^^^^^^^^^^^^^
@ -10803,7 +10802,7 @@ In roc, functions are always written as a lambda, like{}
);
test_report!(
record_builder_apply_non_function,
old_record_builder_apply_non_function,
indoc!(
r#"
succeed = \_ -> crash ""
@ -10857,6 +10856,107 @@ In roc, functions are always written as a lambda, like{}
// "#
// );
test_report!(
empty_record_builder,
indoc!(
r#"
{ a <- }
"#
),
@r#"
EMPTY RECORD BUILDER in /code/proj/Main.roc
This record builder has no fields:
4 { a <- }
^^^^^^^^
I need at least two fields to combine their values into a record.
"#
);
test_report!(
single_field_record_builder,
indoc!(
r#"
{ a <-
b: 123
}
"#
),
@r#"
NOT ENOUGH FIELDS IN RECORD BUILDER in /code/proj/Main.roc
This record builder only has one field:
4> { a <-
5> b: 123
6> }
I need at least two fields to combine their values into a record.
"#
);
test_report!(
optional_field_in_record_builder,
indoc!(
r#"
{ a <-
b: 123,
c? 456
}
"#
),
@r#"
OPTIONAL FIELD IN RECORD BUILDER in /code/proj/Main.roc
Optional fields are not allowed to be used in record builders.
4 { a <-
5 b: 123,
6> c? 456
7 }
Record builders can only have required values for their fields.
"#
);
// CalledVia::RecordBuilder => {
// alloc.concat([
// alloc.note(""),
// alloc.reflow("Record builders need a mapper function before the "),
// alloc.keyword("<-"),
// alloc.reflow(" to combine fields together with.")
// ])
// }
// _ => {
// alloc.reflow("Are there any missing commas? Or missing parentheses?")
test_report!(
record_builder_with_non_function_mapper,
indoc!(
r#"
xyz = "abc"
{ xyz <-
b: 123,
c: 456
}
"#
),
@r#"
TOO MANY ARGS in /code/proj/Main.roc
The `xyz` value is not a function, but it was given 3 arguments:
6 { xyz <-
^^^
Note: Record builders need a mapper function before the `<-` to combine
fields together with.
"#
);
test_report!(
destructure_assignment_introduces_no_variables_nested,
indoc!(

View file

@ -2987,8 +2987,10 @@ fn update<'a>(
fn register_package_shorthands<'a>(
shorthands: &mut MutMap<&'a str, ShorthandPath>,
package_entries: &MutMap<&'a str, header::PackageName<'a>>,
#[allow(unused_variables)] // for wasm
module_path: &Path,
src_dir: &Path,
#[allow(unused_variables)] // for wasm
cache_dir: &Path,
) -> Result<(), LoadingProblem<'a>> {
for (shorthand, package_name) in package_entries.iter() {
@ -3651,6 +3653,8 @@ fn load_module<'a>(
#[derive(Debug)]
enum ShorthandPath {
/// e.g. "/home/rtfeldman/.cache/roc/0.1.0/oUkxSOI9zFGtSoIaMB40QPdrXphr1p1780eiui2iO9Mz"
#[allow(dead_code)]
// wasm warns FromHttpsUrl is unused, but errors if it is removed ¯\_(ツ)_/¯
FromHttpsUrl {
/// e.g. "/home/rtfeldman/.cache/roc/0.1.0/oUkxSOI9zFGtSoIaMB40QPdrXphr1p1780eiui2iO9Mz"
root_module_dir: PathBuf,
@ -4059,6 +4063,7 @@ fn load_packages<'a>(
app_module_id: Option<ModuleId>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: SharedIdentIdsByModule,
#[allow(unused_variables)] // for wasm
filename: PathBuf,
) {
// Load all the packages

View file

@ -1783,7 +1783,7 @@ fn roc_file_no_extension() {
indoc!(
r#"
app "helloWorld"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" }
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }
imports [pf.Stdout]
provides [main] to pf

View file

@ -89,8 +89,21 @@ pub enum CalledVia {
/// e.g. "$(first) $(last)" is transformed into Str.concat (Str.concat first " ") last.
StringInterpolation,
/// This call is the result of desugaring a Record Builder field.
/// 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.
/// ```roc
/// { Task.parallel <-
/// foo: get "a",
/// bar: get "b",
/// }
/// ```
/// is transformed into
/// ```roc
/// Task.parallel (get "a") (get "b") \foo, bar -> { foo, bar }
/// ```
RecordBuilder,
/// This call is the result of desugaring a Task.await from `!` syntax

View file

@ -36,10 +36,6 @@ pub enum LowLevel {
ListReplaceUnsafe,
ListConcat,
ListPrepend,
ListMap,
ListMap2,
ListMap3,
ListMap4,
ListSortWith,
ListSublist,
ListDropAt,
@ -48,6 +44,8 @@ pub enum LowLevel {
ListIsUnique,
ListClone,
ListConcatUtf8,
ListIncref,
ListDecref,
NumAdd,
NumAddWrap,
NumAddChecked,
@ -135,7 +133,7 @@ pub enum LowLevel {
macro_rules! higher_order {
() => {
ListMap | ListMap2 | ListMap3 | ListMap4 | ListSortWith
ListSortWith
};
}
@ -152,10 +150,6 @@ impl LowLevel {
use LowLevel::*;
match self {
ListMap => 1,
ListMap2 => 2,
ListMap3 => 3,
ListMap4 => 4,
ListSortWith => 1,
_ => unreachable!(),
}
@ -211,10 +205,6 @@ macro_rules! map_symbol_to_lowlevel {
// these are higher-order lowlevels. these need the surrounding
// function to provide enough type information for code generation
LowLevel::ListMap => unreachable!(),
LowLevel::ListMap2 => unreachable!(),
LowLevel::ListMap3 => unreachable!(),
LowLevel::ListMap4 => unreachable!(),
LowLevel::ListSortWith => unreachable!(),
// (un)boxing is handled in a custom way
@ -239,6 +229,8 @@ macro_rules! map_symbol_to_lowlevel {
LowLevel::RefCountIncDataPtr => unimplemented!(),
LowLevel::RefCountDecDataPtr=> unimplemented!(),
LowLevel::RefCountIsUnique => unimplemented!(),
LowLevel::ListIncref => unimplemented!(),
LowLevel::ListDecref => unimplemented!(),
LowLevel::SetJmp => unimplemented!(),
LowLevel::LongJmp => unimplemented!(),

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