Merge remote-tracking branch 'origin/trunk' into update_zig_09

This commit is contained in:
Folkert 2022-05-07 19:38:04 +02:00
commit 81caa96af2
No known key found for this signature in database
GPG Key ID: 1F17F6FFD112B97C
197 changed files with 7954 additions and 5825 deletions

View File

@ -8,3 +8,17 @@ test-gen-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --featur
# opt-level=s Optimizations should focus more on size than speed
# lto=fat Spend extra effort on link-time optimization across crates
rustflags = ["-Copt-level=s", "-Clto=fat"]
[env]
# Debug flags. Keep this up-to-date with compiler/debug_flags/src/lib.rs.
# Set = "1" to turn a debug flag on.
ROC_PRETTY_PRINT_ALIAS_CONTENTS = "0"
ROC_PRINT_UNIFICATIONS = "0"
ROC_PRINT_MISMATCHES = "0"
ROC_VERIFY_RIGID_LET_GENERALIZED = "0"
ROC_PRINT_IR_AFTER_SPECIALIZATION = "0"
ROC_PRINT_IR_AFTER_RESET_REUSE = "0"
ROC_PRINT_IR_AFTER_REFCOUNT = "0"
ROC_DEBUG_ALIAS_ANALYSIS = "0"
ROC_PRINT_LLVM_FN_VERIFICATION = "0"
ROC_PRINT_LOAD_LOG = "0"

View File

@ -78,3 +78,4 @@ Jared Cone <jared.cone@gmail.com>
Sean Hagstrom <sean@seanhagstrom.com>
Kas Buunk <kasbuunk@icloud.com>
Oskar Hahn <mail@oshahn.de>
Nuno Ferreira <nunogcferreira@gmail.com>

528
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -26,6 +26,7 @@ members = [
"compiler/arena_pool",
"compiler/test_gen",
"compiler/roc_target",
"compiler/debug_flags",
"vendor/ena",
"vendor/inkwell",
"vendor/pathfinding",
@ -45,6 +46,7 @@ members = [
"utils",
"docs",
"linker",
"wasi-libc-sys",
]
exclude = [
"ci/bench-runner",

View File

@ -1,4 +1,4 @@
FROM rust:1.58.0-slim-bullseye # make sure to update rust-toolchain.toml and nixpkgs-unstable in sources.json too so that it uses the same rust version > search for cargo on unstable here: https://search.nixos.org/packages
FROM rust:1.60.0-slim-bullseye # make sure to update rust-toolchain.toml too so that everything uses the same rust version
WORKDIR /earthbuild
prep-debian:
@ -10,6 +10,7 @@ install-other-libs:
RUN apt -y install libxcb-shape0-dev libxcb-xfixes0-dev # for editor clipboard
RUN apt -y install libasound2-dev # for editor sounds
RUN apt -y install libunwind-dev pkg-config libx11-dev zlib1g-dev
RUN apt -y install unzip # for www/build.sh
install-zig-llvm-valgrind-clippy-rustfmt:
FROM +install-other-libs
@ -18,7 +19,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
# zig
RUN wget -c https://ziglang.org/download/0.9.1/zig-linux-x86_64-0.9.1.tar.xz --no-check-certificate
RUN tar -xf zig-linux-x86_64-0.9.1.tar.xz
RUN ln -s /earthbuild/zig-linux-x86_64-0.9.1/zig /usr/bin/zig
RUN ln -s /earthbuild/zig-linux-x86_64-0.9.1/zig /bin/zig
# zig builtins wasm tests
RUN apt -y install build-essential
RUN cargo install wasmer-cli --features "singlepass"
@ -53,21 +54,26 @@ install-zig-llvm-valgrind-clippy-rustfmt:
copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros highlight utils test_utils reporting repl_cli repl_eval repl_test repl_wasm roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros highlight utils test_utils reporting repl_cli repl_eval repl_test repl_wasm repl_www roc_std vendor examples linker Cargo.toml Cargo.lock version.txt www wasi-libc-sys ./
test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir compiler/builtins/bitcode ./
RUN cd bitcode && ./run-tests.sh && ./run-wasm-tests.sh
check-clippy:
build-rust-test:
FROM +copy-dirs
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --locked --release --features with_sound --workspace --no-run && sccache --show-stats
check-clippy:
FROM +build-rust-test
RUN cargo clippy -V
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo clippy -- -D warnings
check-rustfmt:
FROM +copy-dirs
FROM +build-rust-test
RUN cargo fmt --version
RUN cargo fmt --all -- --check
@ -77,7 +83,7 @@ check-typos:
RUN typos
test-rust:
FROM +copy-dirs
FROM +build-rust-test
ENV RUST_BACKTRACE=1
# for race condition problem with cli test
ENV ROC_NUM_WORKERS=1
@ -100,6 +106,9 @@ test-rust:
# RUN echo "4" | cargo run --locked --release --features="target-x86" -- --target=x86_32 examples/benchmarks/NQueens.roc
# RUN --mount=type=cache,target=$SCCACHE_DIR \
# cargo test --locked --release --features with_sound --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
# make sure doc generation works (that is, make sure build.sh returns status code 0)
RUN bash www/build.sh
verify-no-git-changes:
FROM +test-rust

View File

@ -81,7 +81,7 @@ The core Roc language and standard library include no I/O operations, which give
## Project Goals
Roc is in relatively early stages of development. It's currently possible to build both platforms and applications (see the [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) folder for some examples that aren't particularly organized at the moment), although [documentation](https://github.com/rtfeldman/roc/tree/trunk/compiler/builtins/docs) is in even earlier stages than the compiler itself.
Roc is in relatively early stages of development. It's currently possible to build both platforms and applications (see the [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) folder for some examples that aren't particularly organized at the moment), although [documentation](https://github.com/rtfeldman/roc/tree/trunk/compiler/builtins/roc) is in even earlier stages than the compiler itself.
Besides the above language design, a separate goal is for Roc to ship with an ambitiously boundary-pushing graphical editor. Not like "an IDE," but rather something that makes people say "I have never seen anything remotely like this outside of Bret Victor demos."

View File

@ -27,7 +27,7 @@ use crate::{
},
env::Env,
},
mem_pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone},
mem_pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone},
};
/// A presence constraint is an additive constraint that defines the lower bound
@ -1846,7 +1846,7 @@ fn num_float(pool: &mut Pool, range: TypeId) -> Type2 {
Type2::Alias(
Symbol::NUM_FLOAT,
PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool),
PoolVec::new(vec![range].into_iter(), pool),
num_num_id,
)
}
@ -1859,7 +1859,7 @@ fn num_floatingpoint(pool: &mut Pool, range: TypeId) -> Type2 {
Type2::Opaque(
Symbol::NUM_FLOATINGPOINT,
PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool),
PoolVec::new(vec![range].into_iter(), pool),
pool.add(alias_content),
)
}
@ -1874,7 +1874,7 @@ fn num_int(pool: &mut Pool, range: TypeId) -> Type2 {
Type2::Alias(
Symbol::NUM_INT,
PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool),
PoolVec::new(vec![range].into_iter(), pool),
num_num_id,
)
}
@ -1907,7 +1907,7 @@ fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 {
Type2::Opaque(
Symbol::NUM_INTEGER,
PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool),
PoolVec::new(vec![range].into_iter(), pool),
pool.add(alias_content),
)
}
@ -1920,10 +1920,7 @@ fn num_num(pool: &mut Pool, type_id: TypeId) -> Type2 {
Type2::Opaque(
Symbol::NUM_NUM,
PoolVec::new(
vec![(PoolStr::new("range", pool), type_id)].into_iter(),
pool,
),
PoolVec::new(vec![type_id].into_iter(), pool),
pool.add(alias_content),
)
}

View File

@ -19,12 +19,14 @@ use crate::mem_pool::shallow_clone::ShallowClone;
pub type TypeId = NodeId<Type2>;
const TYPE2_SIZE: () = assert!(std::mem::size_of::<Type2>() == 3 * 8 + 4);
#[derive(Debug)]
pub enum Type2 {
Variable(Variable), // 4B
Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
Opaque(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
Alias(Symbol, PoolVec<TypeId>, TypeId), // 24B = 8B + 8B + 4B + pad
Opaque(Symbol, PoolVec<TypeId>, TypeId), // 24B = 8B + 8B + 4B + pad
AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
// 24B
@ -47,11 +49,6 @@ pub enum Type2 {
Erroneous(Problem2), // 24B
}
#[test]
fn type2_size() {
assert_eq!(std::mem::size_of::<Type2>(), 32); // 24B + pad
}
#[derive(Debug)]
pub enum Problem2 {
CanonicalizationProblem,
@ -736,7 +733,7 @@ fn can_tags<'a>(
enum TypeApply {
Apply(Symbol, PoolVec<Type2>),
Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId),
Alias(Symbol, PoolVec<TypeId>, TypeId),
Erroneous(roc_types::types::Problem),
}
@ -838,7 +835,17 @@ fn to_type_apply<'a>(
// instantiate variables
Type2::substitute(env.pool, &substitutions, actual);
TypeApply::Alias(symbol, arguments, actual)
let type_arguments = PoolVec::with_capacity(arguments.len() as u32, env.pool);
for (node_id, type_id) in arguments
.iter_node_ids()
.zip(type_arguments.iter_node_ids())
{
let typ = env.pool[node_id].1;
env.pool[type_id] = typ;
}
TypeApply::Alias(symbol, type_arguments, actual)
}
None => TypeApply::Apply(symbol, argument_type_ids),
}

View File

@ -48,7 +48,7 @@ fn to_type2(
SolvedType::Alias(symbol, solved_type_variables, _todo, solved_actual, _kind) => {
let type_variables = PoolVec::with_capacity(solved_type_variables.len() as u32, pool);
for (type_variable_node_id, (lowercase, solved_arg)) in type_variables
for (type_variable_node_id, solved_arg) in type_variables
.iter_node_ids()
.zip(solved_type_variables.iter())
{
@ -56,7 +56,7 @@ fn to_type2(
let node = pool.add(typ2);
pool[type_variable_node_id] = (PoolStr::new(lowercase.as_str(), pool), node);
pool[type_variable_node_id] = node;
}
let actual_typ2 = to_type2(pool, solved_actual, free_vars, var_store);
@ -246,7 +246,7 @@ impl Scope {
// use that existing IdentId. Otherwise, create a fresh one.
let ident_id = match exposed_ident_ids.get_id(&ident) {
Some(ident_id) => ident_id,
None => all_ident_ids.add_ident(&ident),
None => all_ident_ids.add_str(ident.as_str()),
};
let symbol = Symbol::new(self.home, ident_id);
@ -263,7 +263,7 @@ impl Scope {
///
/// Used for record guards like { x: Just _ }
pub fn ignore(&mut self, ident: Ident, all_ident_ids: &mut IdentIds) -> Symbol {
let ident_id = all_ident_ids.add_ident(&ident);
let ident_id = all_ident_ids.add_str(ident.as_str());
Symbol::new(self.home, ident_id)
}

View File

@ -1,10 +1,9 @@
use bumpalo::Bump;
use roc_load::{LoadedModule, Threading};
use roc_target::TargetInfo;
use std::path::Path;
use bumpalo::Bump;
use roc_load::LoadedModule;
use roc_target::TargetInfo;
pub fn load_module(src_file: &Path) -> LoadedModule {
pub fn load_module(src_file: &Path, threading: Threading) -> LoadedModule {
let subs_by_module = Default::default();
let arena = Bump::new();
@ -20,6 +19,7 @@ pub fn load_module(src_file: &Path) -> LoadedModule {
subs_by_module,
TargetInfo::default_x86_64(),
roc_reporting::report::RenderTarget::ColorTerminal,
threading,
);
match loaded {

View File

@ -899,7 +899,7 @@ fn type_to_variable<'a>(
let mut arg_vars = Vec::with_capacity(args.len());
for (_, arg_type_id) in args.iter(mempool) {
for arg_type_id in args.iter(mempool) {
let arg_type = mempool.get(*arg_type_id);
let arg_var = type_to_variable(arena, mempool, subs, rank, pools, cached, arg_type);

View File

@ -65,28 +65,26 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "3.0.0-beta.2"
version = "3.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142"
checksum = "85a35a599b11c089a7f49105658d089b8f2cf0882993c17daf6de15285c2c35d"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"indexmap",
"lazy_static",
"os_str_bytes",
"strsim",
"termcolor",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "clap_derive"
version = "3.0.0-beta.2"
version = "3.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1"
checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1"
dependencies = [
"heck",
"proc-macro-error",
@ -95,6 +93,15 @@ dependencies = [
"syn",
]
[[package]]
name = "clap_lex"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "data-encoding"
version = "2.3.2"
@ -109,12 +116,9 @@ checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "heck"
version = "0.3.3"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
@ -188,9 +192,9 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "os_str_bytes"
version = "2.4.0"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85"
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
[[package]]
name = "proc-macro-error"
@ -300,24 +304,9 @@ dependencies = [
[[package]]
name = "textwrap"
version = "0.12.1"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789"
dependencies = [
"unicode-width",
]
[[package]]
name = "unicode-segmentation"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]]
name = "unicode-xid"
@ -331,17 +320,11 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.3"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasm-bindgen"

View File

@ -6,7 +6,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = "3.0.0-beta.2"
clap = { version = "3.1.15", features = ["derive"] }
regex = "1.5.4"
is_executable = "1.0.1"
ring = "0.16.20"

View File

@ -1,4 +1,4 @@
use clap::{AppSettings, Clap};
use clap::Parser;
use data_encoding::HEXUPPER;
use is_executable::IsExecutable;
use regex::Regex;
@ -160,8 +160,7 @@ fn remove(file_or_folder: &str) {
.unwrap_or_else(|_| panic!("Something went wrong trying to remove {}", file_or_folder));
}
#[derive(Clap)]
#[clap(setting = AppSettings::ColoredHelp)]
#[derive(Parser)]
struct OptionalArgs {
/// How many times to repeat the benchmarks. A single benchmark has to fail every for a regression to be reported.
#[clap(long, default_value = "3")]

View File

@ -61,24 +61,24 @@ roc_error_macros = { path = "../error_macros" }
roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" }
roc_repl_cli = { path = "../repl_cli", optional = true }
clap = { version = "3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
clap = { version = "3.1.15", default-features = false, features = ["std", "color", "suggestions"] }
const_format = "0.2.22"
bumpalo = { version = "3.8.0", features = ["collections"] }
mimalloc = { version = "0.1.26", default-features = false }
target-lexicon = "0.12.3"
tempfile = "3.2.0"
wasmer-wasi = { version = "2.0.0", optional = true }
wasmer-wasi = { version = "2.2.1", optional = true }
# Wasmer singlepass compiler only works on x86_64.
[target.'cfg(target_arch = "x86_64")'.dependencies]
wasmer = { version = "2.0.0", optional = true, default-features = false, features = ["default-singlepass", "default-universal"] }
wasmer = { version = "2.2.1", optional = true, default-features = false, features = ["singlepass", "universal"] }
[target.'cfg(not(target_arch = "x86_64"))'.dependencies]
wasmer = { version = "2.0.0", optional = true, default-features = false, features = ["default-cranelift", "default-universal"] }
wasmer = { version = "2.2.1", optional = true, default-features = false, features = ["cranelift", "universal"] }
[dev-dependencies]
wasmer-wasi = "2.0.0"
wasmer-wasi = "2.2.1"
pretty_assertions = "1.0.0"
roc_test_utils = { path = "../test_utils" }
indoc = "1.0.3"
@ -88,10 +88,10 @@ cli_utils = { path = "../cli_utils" }
# Wasmer singlepass compiler only works on x86_64.
[target.'cfg(target_arch = "x86_64")'.dev-dependencies]
wasmer = { version = "2.0.0", default-features = false, features = ["default-singlepass", "default-universal"] }
wasmer = { version = "2.2.1", default-features = false, features = ["singlepass", "universal"] }
[target.'cfg(not(target_arch = "x86_64"))'.dev-dependencies]
wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] }
wasmer = { version = "2.2.1", default-features = false, features = ["cranelift", "universal"] }
[[bench]]
name = "time_bench"

View File

@ -4,7 +4,7 @@ use roc_build::{
program::{self, Problems},
};
use roc_builtins::bitcode;
use roc_load::LoadingProblem;
use roc_load::{LoadingProblem, Threading};
use roc_mono::ir::OptLevel;
use roc_reporting::report::RenderTarget;
use roc_target::TargetInfo;
@ -40,6 +40,7 @@ pub fn build_file<'a>(
surgically_link: bool,
precompiled: bool,
target_valgrind: bool,
threading: Threading,
) -> Result<BuiltFile, LoadingProblem<'a>> {
let compilation_start = SystemTime::now();
let target_info = TargetInfo::from(target);
@ -55,6 +56,7 @@ pub fn build_file<'a>(
target_info,
// TODO: expose this from CLI?
RenderTarget::ColorTerminal,
threading,
)?;
use target_lexicon::Architecture;
@ -366,6 +368,7 @@ pub fn check_file(
target_info,
// TODO: expose this from CLI?
RenderTarget::ColorTerminal,
Threading::Multi,
)?;
let buf = &mut String::with_capacity(1024);

View File

@ -2,29 +2,17 @@ use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use crate::FormatMode;
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_error_macros::{internal_error, user_error};
use roc_fmt::def::fmt_def;
use roc_fmt::module::fmt_module;
use roc_fmt::Buf;
use roc_module::called_via::{BinOp, UnaryOp};
use roc_parse::ast::{
AbilityMember, AssignedField, Collection, Expr, Has, HasClause, Pattern, Spaced, StrLiteral,
StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef, WhenBranch,
};
use roc_parse::header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
};
use roc_fmt::spaces::RemoveSpaces;
use roc_fmt::{Ast, Buf};
use roc_parse::{
ast::{Def, Module},
ident::UppercaseIdent,
module::{self, module_defs},
parser::{Parser, SyntaxError},
state::State,
};
use roc_region::all::{Loc, Region};
fn flatten_directories(files: std::vec::Vec<PathBuf>) -> std::vec::Vec<PathBuf> {
let mut to_flatten = files;
@ -166,12 +154,6 @@ pub fn format(files: std::vec::Vec<PathBuf>, mode: FormatMode) -> Result<(), Str
Ok(())
}
#[derive(Debug, PartialEq)]
struct Ast<'a> {
module: Module<'a>,
defs: Vec<'a, Loc<Def<'a>>>,
}
fn parse_all<'a>(arena: &'a Bump, src: &'a str) -> Result<Ast<'a>, SyntaxError<'a>> {
let (module, state) = module::parse_header(arena, State::new(src.as_bytes()))
.map_err(|e| SyntaxError::Header(e.problem))?;
@ -189,575 +171,3 @@ fn fmt_all<'a>(arena: &'a Bump, buf: &mut Buf<'a>, ast: &'a Ast) {
buf.fmt_end_of_file();
}
/// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting.
///
/// Currently this consists of:
/// * Removing newlines
/// * Removing comments
/// * Removing parens in Exprs
///
/// Long term, we actuall want this transform to preserve comments (so we can assert they're maintained by formatting)
/// - but there are currently several bugs where they're _not_ preserved.
/// TODO: ensure formatting retains comments
trait RemoveSpaces<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self;
}
impl<'a> RemoveSpaces<'a> for Ast<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
Ast {
module: self.module.remove_spaces(arena),
defs: {
let mut defs = Vec::with_capacity_in(self.defs.len(), arena);
for d in &self.defs {
defs.push(d.remove_spaces(arena))
}
defs
},
}
}
}
impl<'a> RemoveSpaces<'a> for Module<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match self {
Module::Interface { header } => Module::Interface {
header: InterfaceHeader {
name: header.name.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
before_header: &[],
after_interface_keyword: &[],
before_exposes: &[],
after_exposes: &[],
before_imports: &[],
after_imports: &[],
},
},
Module::App { header } => Module::App {
header: AppHeader {
name: header.name.remove_spaces(arena),
packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena),
provides_types: header.provides_types.map(|ts| ts.remove_spaces(arena)),
to: header.to.remove_spaces(arena),
before_header: &[],
after_app_keyword: &[],
before_packages: &[],
after_packages: &[],
before_imports: &[],
after_imports: &[],
before_provides: &[],
after_provides: &[],
before_to: &[],
after_to: &[],
},
},
Module::Platform { header } => Module::Platform {
header: PlatformHeader {
name: header.name.remove_spaces(arena),
requires: header.requires.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena),
before_header: &[],
after_platform_keyword: &[],
before_requires: &[],
after_requires: &[],
before_exposes: &[],
after_exposes: &[],
before_packages: &[],
after_packages: &[],
before_imports: &[],
after_imports: &[],
before_provides: &[],
after_provides: &[],
},
},
Module::Hosted { header } => Module::Hosted {
header: HostedHeader {
name: header.name.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
generates: header.generates.remove_spaces(arena),
generates_with: header.generates_with.remove_spaces(arena),
before_header: &[],
after_hosted_keyword: &[],
before_exposes: &[],
after_exposes: &[],
before_imports: &[],
after_imports: &[],
before_generates: &[],
after_generates: &[],
before_with: &[],
after_with: &[],
},
},
}
}
}
impl<'a> RemoveSpaces<'a> for &'a str {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
self
}
}
impl<'a, T: RemoveSpaces<'a> + Copy> RemoveSpaces<'a> for Spaced<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Spaced::Item(a) => Spaced::Item(a.remove_spaces(arena)),
Spaced::SpaceBefore(a, _) => a.remove_spaces(arena),
Spaced::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ExposedName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for ModuleName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for PackageName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for To<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
To::ExistingPackage(a) => To::ExistingPackage(a),
To::NewPackage(a) => To::NewPackage(a.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for TypedIdent<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
TypedIdent {
ident: self.ident.remove_spaces(arena),
spaces_before_colon: &[],
ann: self.ann.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
PlatformRequires {
rigids: self.rigids.remove_spaces(arena),
signature: self.signature.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for UppercaseIdent<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for PackageEntry<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
PackageEntry {
shorthand: self.shorthand,
spaces_after_shorthand: &[],
package_name: self.package_name.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ImportsEntry<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
ImportsEntry::Module(a, b) => ImportsEntry::Module(a, b.remove_spaces(arena)),
ImportsEntry::Package(a, b, c) => ImportsEntry::Package(a, b, c.remove_spaces(arena)),
}
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Option<T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
self.as_ref().map(|a| a.remove_spaces(arena))
}
}
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Loc<T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let res = self.value.remove_spaces(arena);
Loc::at(Region::zero(), res)
}
}
impl<'a, A: RemoveSpaces<'a>, B: RemoveSpaces<'a>> RemoveSpaces<'a> for (A, B) {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
(self.0.remove_spaces(arena), self.1.remove_spaces(arena))
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Collection<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let mut items = Vec::with_capacity_in(self.items.len(), arena);
for item in self.items {
items.push(item.remove_spaces(arena));
}
Collection::with_items(items.into_bump_slice())
}
}
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for &'a [T] {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let mut items = Vec::with_capacity_in(self.len(), arena);
for item in *self {
let res = item.remove_spaces(arena);
items.push(res);
}
items.into_bump_slice()
}
}
impl<'a> RemoveSpaces<'a> for UnaryOp {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for BinOp {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for &'a T {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
arena.alloc((*self).remove_spaces(arena))
}
}
impl<'a> RemoveSpaces<'a> for TypeDef<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
use TypeDef::*;
match *self {
Alias {
header: TypeHeader { name, vars },
ann,
} => Alias {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
ann: ann.remove_spaces(arena),
},
Opaque {
header: TypeHeader { name, vars },
typ,
} => Opaque {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
typ: typ.remove_spaces(arena),
},
Ability {
header: TypeHeader { name, vars },
loc_has,
members,
} => Ability {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
loc_has: loc_has.remove_spaces(arena),
members: members.remove_spaces(arena),
},
}
}
}
impl<'a> RemoveSpaces<'a> for ValueDef<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
use ValueDef::*;
match *self {
Annotation(a, b) => Annotation(a.remove_spaces(arena), b.remove_spaces(arena)),
Body(a, b) => Body(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
AnnotatedBody {
ann_pattern,
ann_type,
comment: _,
body_pattern,
body_expr,
} => AnnotatedBody {
ann_pattern: arena.alloc(ann_pattern.remove_spaces(arena)),
ann_type: arena.alloc(ann_type.remove_spaces(arena)),
comment: None,
body_pattern: arena.alloc(body_pattern.remove_spaces(arena)),
body_expr: arena.alloc(body_expr.remove_spaces(arena)),
},
Expect(a) => Expect(arena.alloc(a.remove_spaces(arena))),
}
}
}
impl<'a> RemoveSpaces<'a> for Def<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Def::Type(def) => Def::Type(def.remove_spaces(arena)),
Def::Value(def) => Def::Value(def.remove_spaces(arena)),
Def::NotYetImplemented(a) => Def::NotYetImplemented(a),
Def::SpaceBefore(a, _) | Def::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for Has<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
Has::Has
}
}
impl<'a> RemoveSpaces<'a> for AbilityMember<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
AbilityMember {
name: self.name.remove_spaces(arena),
typ: self.typ.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for WhenBranch<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
WhenBranch {
patterns: self.patterns.remove_spaces(arena),
value: self.value.remove_spaces(arena),
guard: self.guard.remove_spaces(arena),
}
}
}
impl<'a, T: RemoveSpaces<'a> + Copy + std::fmt::Debug> RemoveSpaces<'a> for AssignedField<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
AssignedField::RequiredValue(a, _, c) => AssignedField::RequiredValue(
a.remove_spaces(arena),
arena.alloc([]),
arena.alloc(c.remove_spaces(arena)),
),
AssignedField::OptionalValue(a, _, c) => AssignedField::OptionalValue(
a.remove_spaces(arena),
arena.alloc([]),
arena.alloc(c.remove_spaces(arena)),
),
AssignedField::LabelOnly(a) => AssignedField::LabelOnly(a.remove_spaces(arena)),
AssignedField::Malformed(a) => AssignedField::Malformed(a),
AssignedField::SpaceBefore(a, _) => a.remove_spaces(arena),
AssignedField::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for StrLiteral<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
StrLiteral::PlainLine(t) => StrLiteral::PlainLine(t),
StrLiteral::Line(t) => StrLiteral::Line(t.remove_spaces(arena)),
StrLiteral::Block(t) => StrLiteral::Block(t.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for StrSegment<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
StrSegment::Plaintext(t) => StrSegment::Plaintext(t),
StrSegment::Unicode(t) => StrSegment::Unicode(t.remove_spaces(arena)),
StrSegment::EscapedChar(c) => StrSegment::EscapedChar(c),
StrSegment::Interpolated(t) => StrSegment::Interpolated(t.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for Expr<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Expr::Float(a) => Expr::Float(a),
Expr::Num(a) => Expr::Num(a),
Expr::NonBase10Int {
string,
base,
is_negative,
} => Expr::NonBase10Int {
string,
base,
is_negative,
},
Expr::Str(a) => Expr::Str(a.remove_spaces(arena)),
Expr::Access(a, b) => Expr::Access(arena.alloc(a.remove_spaces(arena)), b),
Expr::AccessorFunction(a) => Expr::AccessorFunction(a),
Expr::List(a) => Expr::List(a.remove_spaces(arena)),
Expr::RecordUpdate { update, fields } => Expr::RecordUpdate {
update: arena.alloc(update.remove_spaces(arena)),
fields: fields.remove_spaces(arena),
},
Expr::Record(a) => Expr::Record(a.remove_spaces(arena)),
Expr::Var { module_name, ident } => Expr::Var { module_name, ident },
Expr::Underscore(a) => Expr::Underscore(a),
Expr::Tag(a) => Expr::Tag(a),
Expr::OpaqueRef(a) => Expr::OpaqueRef(a),
Expr::Closure(a, b) => Expr::Closure(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Expr::Defs(a, b) => {
Expr::Defs(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
}
Expr::Backpassing(a, b, c) => Expr::Backpassing(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
arena.alloc(c.remove_spaces(arena)),
),
Expr::Expect(a, b) => Expr::Expect(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Expr::Apply(a, b, c) => Expr::Apply(
arena.alloc(a.remove_spaces(arena)),
b.remove_spaces(arena),
c,
),
Expr::BinOps(a, b) => {
Expr::BinOps(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
}
Expr::UnaryOp(a, b) => {
Expr::UnaryOp(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
}
Expr::If(a, b) => Expr::If(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena))),
Expr::When(a, b) => {
Expr::When(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
}
Expr::ParensAround(a) => {
// The formatter can remove redundant parentheses, so also remove these when normalizing for comparison.
a.remove_spaces(arena)
}
Expr::MalformedIdent(a, b) => Expr::MalformedIdent(a, b),
Expr::MalformedClosure => Expr::MalformedClosure,
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
Expr::SingleQuote(a) => Expr::Num(a),
}
}
}
impl<'a> RemoveSpaces<'a> for Pattern<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Pattern::Identifier(a) => Pattern::Identifier(a),
Pattern::Tag(a) => Pattern::Tag(a),
Pattern::OpaqueRef(a) => Pattern::OpaqueRef(a),
Pattern::Apply(a, b) => Pattern::Apply(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Pattern::RecordDestructure(a) => Pattern::RecordDestructure(a.remove_spaces(arena)),
Pattern::RequiredField(a, b) => {
Pattern::RequiredField(a, arena.alloc(b.remove_spaces(arena)))
}
Pattern::OptionalField(a, b) => {
Pattern::OptionalField(a, arena.alloc(b.remove_spaces(arena)))
}
Pattern::NumLiteral(a) => Pattern::NumLiteral(a),
Pattern::NonBase10Literal {
string,
base,
is_negative,
} => Pattern::NonBase10Literal {
string,
base,
is_negative,
},
Pattern::FloatLiteral(a) => Pattern::FloatLiteral(a),
Pattern::StrLiteral(a) => Pattern::StrLiteral(a),
Pattern::Underscore(a) => Pattern::Underscore(a),
Pattern::Malformed(a) => Pattern::Malformed(a),
Pattern::MalformedIdent(a, b) => Pattern::MalformedIdent(a, b),
Pattern::QualifiedIdentifier { module_name, ident } => {
Pattern::QualifiedIdentifier { module_name, ident }
}
Pattern::SpaceBefore(a, _) => a.remove_spaces(arena),
Pattern::SpaceAfter(a, _) => a.remove_spaces(arena),
Pattern::SingleQuote(a) => Pattern::NumLiteral(a),
}
}
}
impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
TypeAnnotation::Function(a, b) => TypeAnnotation::Function(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
TypeAnnotation::Apply(a, b, c) => TypeAnnotation::Apply(a, b, c.remove_spaces(arena)),
TypeAnnotation::BoundVariable(a) => TypeAnnotation::BoundVariable(a),
TypeAnnotation::As(a, _, c) => {
TypeAnnotation::As(arena.alloc(a.remove_spaces(arena)), &[], c)
}
TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record {
fields: fields.remove_spaces(arena),
ext: ext.remove_spaces(arena),
},
TypeAnnotation::TagUnion { ext, tags } => TypeAnnotation::TagUnion {
ext: ext.remove_spaces(arena),
tags: tags.remove_spaces(arena),
},
TypeAnnotation::Inferred => TypeAnnotation::Inferred,
TypeAnnotation::Wildcard => TypeAnnotation::Wildcard,
TypeAnnotation::Where(annot, has_clauses) => TypeAnnotation::Where(
arena.alloc(annot.remove_spaces(arena)),
arena.alloc(has_clauses.remove_spaces(arena)),
),
TypeAnnotation::SpaceBefore(a, _) => a.remove_spaces(arena),
TypeAnnotation::SpaceAfter(a, _) => a.remove_spaces(arena),
TypeAnnotation::Malformed(a) => TypeAnnotation::Malformed(a),
}
}
}
impl<'a> RemoveSpaces<'a> for HasClause<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
HasClause {
var: self.var.remove_spaces(arena),
ability: self.ability.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for Tag<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Tag::Apply { name, args } => Tag::Apply {
name: name.remove_spaces(arena),
args: args.remove_spaces(arena),
},
Tag::Malformed(a) => Tag::Malformed(a),
Tag::SpaceBefore(a, _) => a.remove_spaces(arena),
Tag::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}

View File

@ -3,17 +3,17 @@ extern crate const_format;
use build::BuiltFile;
use bumpalo::Bump;
use clap::{App, AppSettings, Arg, ArgMatches};
use clap::Command;
use clap::{Arg, ArgMatches};
use roc_build::link::LinkType;
use roc_error_macros::user_error;
use roc_load::LoadingProblem;
use roc_load::{LoadingProblem, Threading};
use roc_mono::ir::OptLevel;
use std::env;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use std::process;
use std::process::Command;
use target_lexicon::BinaryFormat;
use target_lexicon::{
Architecture, Environment, OperatingSystem, Triple, Vendor, X86_32Architecture,
@ -40,7 +40,6 @@ pub const FLAG_LIB: &str = "lib";
pub const FLAG_NO_LINK: &str = "no-link";
pub const FLAG_TARGET: &str = "target";
pub const FLAG_TIME: &str = "time";
pub const FLAG_LINK: &str = "roc-linker";
pub const FLAG_LINKER: &str = "linker";
pub const FLAG_PRECOMPILED: &str = "precompiled-host";
pub const FLAG_VALGRIND: &str = "valgrind";
@ -52,39 +51,68 @@ pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP";
const VERSION: &str = include_str!("../../version.txt");
pub fn build_app<'a>() -> App<'a> {
let app = App::new("roc")
pub fn build_app<'a>() -> Command<'a> {
let flag_optimize = Arg::new(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
.requires(ROC_FILE)
.required(false);
let flag_opt_size = Arg::new(FLAG_OPT_SIZE)
.long(FLAG_OPT_SIZE)
.help("Optimize the compiled program to have a small binary size. (Optimization takes time to complete.)")
.required(false);
let flag_dev = Arg::new(FLAG_DEV)
.long(FLAG_DEV)
.help("Make compilation finish as soon as possible, at the expense of runtime performance.")
.required(false);
let flag_debug = Arg::new(FLAG_DEBUG)
.long(FLAG_DEBUG)
.help("Store LLVM debug information in the generated program.")
.requires(ROC_FILE)
.required(false);
let flag_valgrind = Arg::new(FLAG_VALGRIND)
.long(FLAG_VALGRIND)
.help("Some assembly instructions are not supported by valgrind, this flag prevents those from being output when building the host.")
.required(false);
let flag_time = Arg::new(FLAG_TIME)
.long(FLAG_TIME)
.help("Prints detailed compilation time information.")
.required(false);
let flag_linker = Arg::new(FLAG_LINKER)
.long(FLAG_LINKER)
.help("Sets which linker to use. The surgical linker is enabled by default only when building for wasm32 or x86_64 Linux, because those are the only targets it currently supports. Otherwise the legacy linker is used by default.")
.possible_values(["surgical", "legacy"])
.required(false);
let flag_precompiled = Arg::new(FLAG_PRECOMPILED)
.long(FLAG_PRECOMPILED)
.help("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using `roc build` with a --target other than `--target host`)")
.possible_values(["true", "false"])
.required(false);
let app = Command::new("roc")
.version(concatcp!(VERSION, "\n"))
.about("Runs the given .roc file, if there are no compilation errors.\nUse one of the SUBCOMMANDS below to do something else!")
.subcommand(App::new(CMD_BUILD)
.subcommand(Command::new(CMD_BUILD)
.about("Build a binary from the given .roc file, but don't run it")
.arg(
Arg::new(ROC_FILE)
.about("The .roc file to build")
.required(true),
)
.arg(
Arg::new(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
.about("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::new(FLAG_OPT_SIZE)
.long(FLAG_OPT_SIZE)
.about("Optimize your compiled Roc program to have a small binary size. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::new(FLAG_DEV)
.long(FLAG_DEV)
.about("Make compilation as fast as possible. (Runtime performance may suffer)")
.required(false),
)
.arg(flag_optimize.clone())
.arg(flag_opt_size.clone())
.arg(flag_dev.clone())
.arg(flag_debug.clone())
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_precompiled.clone())
.arg(flag_valgrind.clone())
.arg(
Arg::new(FLAG_TARGET)
.long(FLAG_TARGET)
.about("Choose a different target")
.help("Choose a different target")
.default_value(Target::default().as_str())
.possible_values(Target::OPTIONS)
.required(false),
@ -92,181 +120,108 @@ pub fn build_app<'a>() -> App<'a> {
.arg(
Arg::new(FLAG_LIB)
.long(FLAG_LIB)
.about("Build a C library instead of an executable.")
.help("Build a C library instead of an executable.")
.required(false),
)
.arg(
Arg::new(FLAG_NO_LINK)
.long(FLAG_NO_LINK)
.about("Does not link. Instead just outputs the `.o` file")
.help("Does not link. Instead just outputs the `.o` file")
.required(false),
)
.arg(
Arg::new(FLAG_DEBUG)
.long(FLAG_DEBUG)
.about("Store LLVM debug information in the generated program")
.required(false),
)
.arg(
Arg::new(FLAG_TIME)
.long(FLAG_TIME)
.about("Prints detailed compilation time information.")
.required(false),
)
.arg(
Arg::new(FLAG_LINK)
.long(FLAG_LINK)
.about("Deprecated in favor of --linker")
.required(false),
)
.arg(
Arg::new(FLAG_LINKER)
.long(FLAG_LINKER)
.about("Sets which linker to use. The surgical linker is enabeld by default only when building for wasm32 or x86_64 Linux, because those are the only targets it currently supports. Otherwise the legacy linker is used by default.")
.possible_values(["surgical", "legacy"])
.required(false),
)
.arg(
Arg::new(FLAG_PRECOMPILED)
.long(FLAG_PRECOMPILED)
.about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using a --target other than `--target host`)")
.possible_values(["true", "false"])
.required(false),
)
.arg(
Arg::new(FLAG_VALGRIND)
.long(FLAG_VALGRIND)
.about("Some assembly instructions are not supported by valgrind, this flag prevents those from being output when building the host.")
.required(false),
)
)
.subcommand(App::new(CMD_REPL)
.about("Launch the interactive Read Eval Print Loop (REPL)")
)
.subcommand(App::new(CMD_RUN)
.about("Run a .roc file even if it has build errors")
.arg(
Arg::new(ROC_FILE)
.about("The .roc file of an app to run")
.help("The .roc file to build")
.required(true),
)
)
.subcommand(App::new(CMD_FORMAT)
.subcommand(Command::new(CMD_REPL)
.about("Launch the interactive Read Eval Print Loop (REPL)")
)
.subcommand(Command::new(CMD_RUN)
.about("Run a .roc file even if it has build errors")
.arg(flag_optimize.clone())
.arg(flag_opt_size.clone())
.arg(flag_dev.clone())
.arg(flag_debug.clone())
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_precompiled.clone())
.arg(flag_valgrind.clone())
.arg(
Arg::new(ROC_FILE)
.help("The .roc file of an app to run")
.required(true),
)
)
.subcommand(Command::new(CMD_FORMAT)
.about("Format a .roc file using standard Roc formatting")
.arg(
Arg::new(DIRECTORY_OR_FILES)
.index(1)
.multiple_values(true)
.required(false))
.required(false)
.allow_invalid_utf8(true))
.arg(
Arg::new(FLAG_CHECK)
.long(FLAG_CHECK)
.about("Checks that specified files are formatted. If formatting is needed, it will return a non-zero exit code.")
.help("Checks that specified files are formatted. If formatting is needed, it will return a non-zero exit code.")
.required(false),
)
)
.subcommand(App::new(CMD_VERSION)
.subcommand(Command::new(CMD_VERSION)
.about(concatcp!("Print the Roc compilers version, which is currently ", VERSION)))
.subcommand(App::new(CMD_CHECK)
.subcommand(Command::new(CMD_CHECK)
.about("Check the code for problems, but doesnt build or run it")
.arg(
Arg::new(FLAG_TIME)
.long(FLAG_TIME)
.about("Prints detailed compilation time information.")
.required(false),
)
.arg(flag_time.clone())
.arg(
Arg::new(ROC_FILE)
.about("The .roc file of an app to check")
.help("The .roc file of an app to check")
.required(true),
)
)
.subcommand(
App::new(CMD_DOCS)
Command::new(CMD_DOCS)
.about("Generate documentation for Roc modules (Work In Progress)")
.arg(Arg::new(DIRECTORY_OR_FILES)
.index(1)
.multiple_values(true)
.required(false)
.about("The directory or files to build documentation for")
.help("The directory or files to build documentation for")
.allow_invalid_utf8(true)
)
)
.setting(AppSettings::TrailingVarArg)
.arg(
Arg::new(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
.about("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
.requires(ROC_FILE)
.required(false),
)
.arg(
Arg::new(FLAG_OPT_SIZE)
.long(FLAG_OPT_SIZE)
.about("Optimize the compiled program to have a small binary size. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::new(FLAG_DEV)
.long(FLAG_DEV)
.about("Make compilation finish as soon as possible, at the expense of runtime performance.")
.required(false),
)
.arg(
Arg::new(FLAG_DEBUG)
.long(FLAG_DEBUG)
.about("Store LLVM debug information in the generated program.")
.requires(ROC_FILE)
.required(false),
)
.arg(
Arg::new(FLAG_TIME)
.long(FLAG_TIME)
.about("Prints detailed compilation time information.")
.required(false),
)
.arg(
Arg::new(FLAG_LINK)
.long(FLAG_LINK)
.about("Deprecated in favor of --linker")
.required(false),
)
.arg(
Arg::new(FLAG_LINKER)
.long(FLAG_LINKER)
.about("Sets which linker to use. The surgical linker is enabeld by default only when building for wasm32 or x86_64 Linux, because those are the only targets it currently supports. Otherwise the legacy linker is used by default.")
.possible_values(["surgical", "legacy"])
.required(false),
)
.arg(
Arg::new(FLAG_PRECOMPILED)
.long(FLAG_PRECOMPILED)
.about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using `roc build` with a --target other than `--target host`)")
.possible_values(["true", "false"])
.required(false),
)
.trailing_var_arg(true)
.arg(flag_optimize)
.arg(flag_opt_size)
.arg(flag_dev)
.arg(flag_debug)
.arg(flag_time)
.arg(flag_linker)
.arg(flag_precompiled)
.arg(flag_valgrind)
.arg(
Arg::new(ROC_FILE)
.about("The .roc file of an app to build and run")
.help("The .roc file of an app to build and run")
.required(false),
)
.arg(
Arg::new(ARGS_FOR_APP)
.about("Arguments to pass into the app being run")
.help("Arguments to pass into the app being run")
.requires(ROC_FILE)
.multiple_values(true),
);
if cfg!(feature = "editor") {
app.subcommand(
App::new(CMD_EDIT)
Command::new(CMD_EDIT)
.about("Launch the Roc editor (Work In Progress)")
.arg(
Arg::new(DIRECTORY_OR_FILES)
.index(1)
.multiple_values(true)
.required(false)
.about("(optional) The directory or files to open on launch."),
.help("(optional) The directory or files to open on launch."),
),
)
} else {
@ -290,18 +245,15 @@ pub enum FormatMode {
CheckOnly,
}
pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
pub fn build(
matches: &ArgMatches,
config: BuildConfig,
triple: Triple,
link_type: LinkType,
) -> io::Result<i32> {
use build::build_file;
use std::str::FromStr;
use BuildConfig::*;
let target = match matches.value_of(FLAG_TARGET) {
Some(name) => Target::from_str(name).unwrap(),
None => Target::default(),
};
let triple = target.to_triple();
let arena = Bump::new();
let filename = matches.value_of(ROC_FILE).unwrap();
@ -320,22 +272,6 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
let emit_debug_info = matches.is_present(FLAG_DEBUG);
let emit_timings = matches.is_present(FLAG_TIME);
let link_type = match (
matches.is_present(FLAG_LIB),
matches.is_present(FLAG_NO_LINK),
) {
(true, false) => LinkType::Dylib,
(true, true) => user_error!("build can only be one of `--lib` or `--no-link`"),
(false, true) => LinkType::None,
(false, false) => LinkType::Executable,
};
// TODO remove FLAG_LINK from the code base anytime after the end of May 2022
if matches.is_present(FLAG_LINK) {
eprintln!("ERROR: The --roc-linker flag has been deprecated because the roc linker is now used automatically where it's supported. (Currently that's only x64 Linux.) No need to use --roc-linker anymore, but you can use the --linker flag to switch linkers.");
process::exit(1);
}
// Use surgical linking when supported, or when explicitly requested with --linker surgical
let surgically_link = if matches.is_present(FLAG_LINKER) {
matches.value_of(FLAG_LINKER) == Some("surgical")
@ -348,7 +284,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
} else {
// When compiling for a different target, default to assuming a precompiled host.
// Otherwise compilation would most likely fail!
target != Target::System
triple != Triple::host()
};
let path = Path::new(filename);
@ -385,6 +321,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
surgically_link,
precompiled,
target_valgrind,
Threading::Multi,
);
match res_binary_path {
@ -570,7 +507,7 @@ fn roc_run(
run_with_wasmer(generated_filename, &args);
return Ok(0);
}
_ => Command::new(&binary_path),
_ => std::process::Command::new(&binary_path),
};
if let Architecture::Wasm32 = triple.architecture {
@ -657,7 +594,7 @@ fn run_with_wasmer(_wasm_path: &std::path::Path, _args: &[String]) {
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum Target {
pub enum Target {
System,
Linux32,
Linux64,
@ -690,7 +627,7 @@ impl Target {
Target::Wasm32.as_str(),
];
fn to_triple(self) -> Triple {
pub fn to_triple(self) -> Triple {
use Target::*;
match self {
@ -733,15 +670,15 @@ impl std::fmt::Display for Target {
}
impl std::str::FromStr for Target {
type Err = ();
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
"system" => Ok(Target::System),
"linux32" => Ok(Target::Linux32),
"linux64" => Ok(Target::Linux64),
"wasm32" => Ok(Target::Wasm32),
_ => Err(()),
_ => Err(format!("Roc does not know how to compile to {}", string)),
}
}
}

View File

@ -1,13 +1,16 @@
use roc_build::link::LinkType;
use roc_cli::build::check_file;
use roc_cli::{
build_app, docs, format, BuildConfig, FormatMode, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT,
CMD_FORMAT, CMD_REPL, CMD_RUN, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_TIME,
ROC_FILE,
build_app, docs, format, BuildConfig, FormatMode, Target, CMD_BUILD, CMD_CHECK, CMD_DOCS,
CMD_EDIT, CMD_FORMAT, CMD_REPL, CMD_RUN, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_LIB,
FLAG_NO_LINK, FLAG_TARGET, FLAG_TIME, ROC_FILE,
};
use roc_error_macros::user_error;
use roc_load::LoadingProblem;
use std::fs::{self, FileType};
use std::io;
use std::path::{Path, PathBuf};
use target_lexicon::Triple;
#[macro_use]
extern crate const_format;
@ -31,6 +34,8 @@ fn main() -> io::Result<()> {
build(
&matches,
BuildConfig::BuildAndRunIfNoErrors { roc_file_arg_index },
Triple::host(),
LinkType::Executable,
)
}
@ -46,7 +51,12 @@ fn main() -> io::Result<()> {
Some(arg_index) => {
let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is!
build(matches, BuildConfig::BuildAndRun { roc_file_arg_index })
build(
matches,
BuildConfig::BuildAndRun { roc_file_arg_index },
Triple::host(),
LinkType::Executable,
)
}
None => {
@ -56,7 +66,26 @@ fn main() -> io::Result<()> {
}
}
}
Some((CMD_BUILD, matches)) => Ok(build(matches, BuildConfig::BuildOnly)?),
Some((CMD_BUILD, matches)) => {
let target: Target = matches.value_of_t(FLAG_TARGET).unwrap_or_default();
let link_type = match (
matches.is_present(FLAG_LIB),
matches.is_present(FLAG_NO_LINK),
) {
(true, false) => LinkType::Dylib,
(true, true) => user_error!("build can only be one of `--lib` or `--no-link`"),
(false, true) => LinkType::None,
(false, false) => LinkType::Executable,
};
Ok(build(
matches,
BuildConfig::BuildOnly,
target.to_triple(),
link_type,
)?)
}
Some((CMD_CHECK, matches)) => {
let arena = bumpalo::Bump::new();

View File

@ -165,23 +165,19 @@ The compiler is invoked from the CLI via `build_file` in cli/src/build.rs
For a more detailed understanding of the compilation phases, see the `Phase`, `BuildTask`, and `Msg` enums in `load/src/file.rs`.
## Debugging intermediate representations
## Debugging the compiler
### Debugging the typechecker
Please see the [debug flags](./debug_flags/src/lib.rs) for information on how to
ask the compiler to emit debug information during various stages of compilation.
Setting the following environment variables:
There are some goals for more sophisticated debugging tools:
- `ROC_PRINT_UNIFICATIONS` prints all type unifications that are done,
before and after the unification.
- `ROC_PRINT_MISMATCHES` prints all type mismatches hit during unification.
- `ROC_PRETTY_PRINT_ALIAS_CONTENTS` expands the contents of aliases during
pretty-printing of types.
- A nicer unification debugger, see https://github.com/rtfeldman/roc/issues/2486.
Any interest in helping out here is greatly appreciated.
Note that this is only relevant during debug builds. Eventually we should have
some better debugging tools here, see https://github.com/rtfeldman/roc/issues/2486
for one.
### General Tips
### The mono IR
#### Miscompilations
If you observe a miscomplication, you may first want to check the generated mono
IR for your code - maybe there was a problem during specialization or layout
@ -189,13 +185,16 @@ generation. One way to do this is to add a test to `test_mono/src/tests.rs`
and run the tests with `cargo test -p test_mono`; this will write the mono
IR to a file.
You may also want to set some or all of the following environment variables:
#### Typechecking errors
- `PRINT_IR_AFTER_SPECIALIZATION=1` prints the mono IR after function
specialization to stdout
- `PRINT_IR_AFTER_RESET_REUSE=1` prints the mono IR after insertion of
reset/reuse isntructions to stdout
- `PRINT_IR_AFTER_REFCOUNT=1` prints the mono IR after insertion of reference
counting instructions to stdout
- `PRETTY_PRINT_IR_SYMBOLS=1` instructs the pretty printer to dump all the
information it knows about the mono IR whenever it is printed
First, try to minimize your reproduction into a test that fits in
[`solve_expr`](./solve/tests/solve_expr.rs).
Once you've done this, check out the `ROC_PRINT_UNIFICATIONS` debug flag. It
will show you where type unification went right and wrong. This is usually
enough to figure out a fix for the bug.
If that doesn't work and you know your error has something to do with ranks,
you may want to instrument `deep_copy_var_help` in [solve](./solve/src/solve.rs).
If that doesn't work, chatting on Zulip is always a good strategy.

View File

@ -10,4 +10,4 @@ morphic_lib = {path = "../../vendor/morphic_lib"}
roc_collections = {path = "../collections"}
roc_module = {path = "../module"}
roc_mono = {path = "../mono"}
roc_debug_flags = {path = "../debug_flags"}

View File

@ -26,8 +26,16 @@ pub fn func_name_bytes(proc: &Proc) -> [u8; SIZE] {
func_name_bytes_help(proc.name, proc.args.iter().map(|x| x.0), &proc.ret_layout)
}
const DEBUG: bool = false;
const SIZE: usize = if DEBUG { 50 } else { 16 };
#[inline(always)]
fn debug() -> bool {
use roc_debug_flags::{dbg_do, ROC_DEBUG_ALIAS_ANALYSIS};
dbg_do!(ROC_DEBUG_ALIAS_ANALYSIS, {
return true;
});
false
}
const SIZE: usize = 16;
#[derive(Debug, Clone, Copy, Hash)]
struct TagUnionId(u64);
@ -87,7 +95,7 @@ where
*target = *source;
}
if DEBUG {
if debug() {
for (i, c) in (format!("{:?}", symbol)).chars().take(25).enumerate() {
name_bytes[25 + i] = c as u8;
}
@ -175,7 +183,7 @@ where
}
}
if DEBUG {
if debug() {
eprintln!(
"{:?}: {:?} with {:?} args",
proc.name,
@ -239,7 +247,7 @@ where
p.build()?
};
if DEBUG {
if debug() {
eprintln!("{}", program.to_source_string());
}

View File

@ -31,6 +31,7 @@ libloading = "0.7.1"
tempfile = "3.2.0"
inkwell = { path = "../../vendor/inkwell", optional = true }
target-lexicon = "0.12.3"
wasi_libc_sys = { path = "../../wasi-libc-sys" }
[target.'cfg(target_os = "macos")'.dependencies]
serde_json = "1.0.69"

View File

@ -72,16 +72,12 @@ fn find_zig_str_path() -> PathBuf {
}
fn find_wasi_libc_path() -> PathBuf {
let wasi_libc_path = PathBuf::from("compiler/builtins/bitcode/wasi-libc.a");
use wasi_libc_sys::WASI_LIBC_PATH;
if std::path::Path::exists(&wasi_libc_path) {
return wasi_libc_path;
}
// when running the tests, we start in the /cli directory
let wasi_libc_path = PathBuf::from("../compiler/builtins/bitcode/wasi-libc.a");
if std::path::Path::exists(&wasi_libc_path) {
return wasi_libc_path;
// Environment variable defined in wasi-libc-sys/build.rs
let wasi_libc_pathbuf = PathBuf::from(WASI_LIBC_PATH);
if std::path::Path::exists(&wasi_libc_pathbuf) {
return wasi_libc_pathbuf;
}
panic!("cannot find `wasi-libc.a`")

View File

@ -1354,3 +1354,9 @@ pub fn listFindUnsafe(
return .{ .value = null, .found = false };
}
}
pub fn listIsUnique(
list: RocList,
) callconv(.C) bool {
return list.isEmpty() or list.isUnique();
}

View File

@ -56,6 +56,7 @@ comptime {
exportListFn(list.listAny, "any");
exportListFn(list.listAll, "all");
exportListFn(list.listFindUnsafe, "find_unsafe");
exportListFn(list.listIsUnique, "is_unique");
}
// Dict Module
@ -105,6 +106,9 @@ comptime {
num.exportToIntCheckingMax(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max.");
num.exportToIntCheckingMaxAndMin(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max_and_min.");
}
num.exportRoundF32(FROM, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f32.");
num.exportRoundF64(FROM, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f64.");
}
inline for (FLOATS) |T| {
@ -113,7 +117,6 @@ comptime {
num.exportAtan(T, ROC_BUILTINS ++ "." ++ NUM ++ ".atan.");
num.exportIsFinite(T, ROC_BUILTINS ++ "." ++ NUM ++ ".is_finite.");
num.exportRound(T, ROC_BUILTINS ++ "." ++ NUM ++ ".round.");
}
}

View File

@ -90,10 +90,19 @@ pub fn exportAtan(comptime T: type, comptime name: []const u8) void {
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportRound(comptime T: type, comptime name: []const u8) void {
pub fn exportRoundF32(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: T) callconv(.C) i64 {
return @floatToInt(i64, (@round(input)));
fn func(input: f32) callconv(.C) T {
return @floatToInt(T, (@round(input)));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportRoundF64(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: f64) callconv(.C) T {
return @floatToInt(T, (@round(input)));
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });

View File

@ -1,89 +0,0 @@
interface Bool
exposes [ and, isEq, isNotEq, not, or, xor ]
imports []
## Returns `False` when given `True`, and vice versa.
not : [True, False] -> [True, False]
## Returns `True` when given `True` and `True`, and `False` when either argument is `False`.
##
## `a && b` is shorthand for `Bool.and a b`
##
## >>> True && True
##
## >>> True && False
##
## >>> False && True
##
## >>> False && False
##
## ## Performance Notes
##
## In some languages, `&&` and `||` are special-cased in the compiler to skip
## evaluating the expression after the operator under certain circumstances.
## For example, in some languages, `enablePets && likesDogs user` would compile
## to the equivalent of:
##
## if enablePets then
## likesDogs user
## else
## False
##
## In Roc, however, `&&` and `||` are not special. They work the same way as
## other functions. Conditionals like `if` and `when` have a performance cost,
## and sometimes calling a function like `likesDogs user` can be faster across
## the board than doing an `if` to decide whether to skip calling it.
##
## (Naturally, if you expect the `if` to improve performance, you can always add
## one explicitly!)
and : Bool, Bool -> Bool
## Returns `True` when given `True` for either argument, and `False` only when given `False` and `False`.
##
## `a || b` is shorthand for `Bool.or a b`.
##
## >>> True || True
##
## >>> True || False
##
## >>> False || True
##
## >>> False || False
##
## ## Performance Notes
##
## In some languages, `&&` and `||` are special-cased in the compiler to skip
## evaluating the expression after the operator under certain circumstances.
## In Roc, this is not the case. See the performance notes for [Bool.and] for details.
or : Bool, Bool -> Bool
## Exclusive or
xor : Bool, Bool -> Bool
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `isEq : 'val, 'val -> Bool`
## Returns `True` if the two values are *structurally equal*, and `False` otherwise.
##
## `a == b` is shorthand for `Bool.isEq a b`
##
## Structural equality works as follows:
##
## 1. Tags are equal if they have the same tag name, and also their contents (if any) are equal.
## 2. Records are equal if all their fields are equal.
## 3. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal.
## 4. [Num] values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `False`. See `Num.isNaN` for more about *NaN*.
##
## Note that `isEq` takes `'val` instead of `val`, which means `isEq` does not
## accept arguments whose types contain functions.
isEq : val, val -> Bool
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `isNotEq : 'val, 'val -> Bool`
## Calls [isEq] on the given values, then calls [not] on the result.
##
## `a != b` is shorthand for `Bool.isNotEq a b`
##
## Note that `isNotEq` takes `'val` instead of `val`, which means `isNotEq` does not
## accept arguments whose types contain functions.
isNotEq : val, val -> Bool

View File

@ -1,210 +0,0 @@
interface Dict
exposes
[
Dict,
contains,
difference,
empty,
get,
keys,
insert,
intersection,
len,
remove,
single,
union,
values,
walk
]
imports []
## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you can associate keys with values.
##
## ### Inserting
##
## The most basic way to use a dictionary is to start with an empty one and then:
## 1. Call [Dict.insert] passing a key and a value, to associate that key with that value in the dictionary.
## 2. Later, call [Dict.get] passing the same key as before, and it will return the value you stored.
##
## Here's an example of a dictionary which uses a city's name as the key, and its population as the associated value.
##
## populationByCity =
## Dict.empty
## |> Dict.insert "London" 8_961_989
## |> Dict.insert "Philadelphia" 1_603_797
## |> Dict.insert "Shanghai" 24_870_895
## |> Dict.insert "Delhi" 16_787_941
## |> Dict.insert "Amsterdam" 872_680
##
## ### Converting to a [List]
##
## We can call [Dict.toList] on `populationByCity` to turn it into a list of key-value pairs:
##
## Dict.toList populationByCity == [
## { k: "London", v: 8961989 },
## { k: "Philadelphia", v: 1603797 },
## { k: "Shanghai", v: 24870895 },
## { k: "Delhi", v: 16787941 },
## { k: "Amsterdam", v: 872680 },
## ]
##
## We can use the similar [Dict.keyList] and [Dict.values] functions to get only the keys or only the values,
## instead of getting these `{ k, v }` records that contain both.
##
## You may notice that these lists have the same order as the original insertion order. This will be true if
## all you ever do is [insert] and [get] operations on the dictionary, but [remove] operations can change this order.
## Let's see how that looks.
##
## ### Removing
##
## We can remove an element from the dictionary, like so:
##
## populationByCity
## |> Dict.remove "Philadelphia"
## |> Dict.toList
## ==
## [
## { k: "London", v: 8961989 },
## { k: "Amsterdam", v: 872680 },
## { k: "Shanghai", v: 24870895 },
## { k: "Delhi", v: 16787941 },
## ]
##
## Notice that the order changed! Philadelphia has been not only removed from the list, but Amsterdam - the last
## entry we inserted - has been moved into the spot where Philadelphia was previously. This is exactly what
## [Dict.remove] does: it removes an element and moves the most recent insertion into the vacated spot.
##
## This move is done as a performance optimization, and it lets [remove] have
## [constant time complexity](https://en.wikipedia.org/wiki/Time_complexity#Constant_time). If you need a removal
## operation which preserves ordering, [Dict.removeShift] will remove the element and then shift everything after it
## over one spot. Be aware that this shifting requires copying every single entry after the removed element, though,
## so it can be massively more costly than [remove]! This makes [remove] the recommended default choice;
## [removeShift] should only be used if maintaining original insertion order is absolutely necessary.
##
##
## ### Removing
##
## ### Equality
##
## When comparing two dictionaries for equality, they are `==` only if their both their contents and their
## orderings match. This preserves the property that if `dict1 == dict2`, you should be able to rely on
## `fn dict1 == fn dict2` also being `True`, even if `fn` relies on the dictionary's ordering (for example, if
## `fn` is `Dict.toList` or calls it internally.)
##
## The [Dict.hasSameContents] function gives an alternative to `==` which ignores ordering
## and returns `True` if both dictionaries have the same keys and associated values.
Dict k v := [ Dict k v ] # TODO k should require a hashing and equating constraint
## An empty dictionary.
empty : Dict * *
size : Dict * * -> Nat
isEmpty : Dict * * -> Bool
## Returns a [List] of the dictionary's key/value pairs.
##
## See [walk] to walk over the key/value pairs without creating an intermediate data structure.
toList : Dict k v -> List { k, v }
## Returns a [List] of the dictionary's keys.
##
## See [keySet] to get a [Set] of keys instead, or [walkKeys] to walk over the keys without creating
## an intermediate data structure.
keyList : Dict key * -> List key
## Returns a [Set] of the dictionary's keys.
##
## See [keyList] to get a [List] of keys instead, or [walkKeys] to walk over the keys without creating
## an intermediate data structure.
keySet : Dict key * -> Set key
## Returns a [List] of the dictionary's values.
##
## See [walkValues] to walk over the values without creating an intermediate data structure.
values : Dict * value -> List value
walk : Dict k v, state, (state, k, v -> state) -> state
walkKeys : Dict key *, state, (state, key -> state) -> state
walkValues : Dict * value, state, (state, value -> state) -> state
## Convert each key and value in the #Dict to something new, by calling a conversion
## function on each of them. Then return a new #Map of the converted keys and values.
##
## >>> Dict.map {{ 3.14 => "pi", 1.0 => "one" }} \{ key, value } -> { key:
##
## >>> Dict.map {[ "", "a", "bc" ]} Str.isEmpty
##
## `map` functions like this are common in Roc, and they all work similarly.
## See for example [List.map], [Result.map], and `Set.map`.
map :
Dict beforeKey beforeVal,
({ k: beforeKey, v: beforeVal } -> { k: afterKey, v: afterVal })
-> Dict afterKey afterVal
# DESIGN NOTES: The reason for panicking when given NaN is that:
# * If we allowed NaN in, Dict.insert would no longer be idempotent.
# * If we allowed NaN but overrode its semantics to make it feel like "NaN == NaN" we'd need isNaN checks in all hashing operations as well as all equality checks (during collision detection), not just insert. This would be much worse for performance than panicking on insert, which only requires one extra conditional on insert.
# * It's obviously invalid; the whole point of NaN is that an error occurred. Giving a runtime error notifies you when this problem happens. Giving it only on insert is the best for performance, because it means you aren't paying for isNaN checks on lookups as well.
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: insert : Dict 'key val, 'key, val -> Dict 'key val
## Make sure never to insert a key of *NaN* into a [Dict]! Because *NaN* is
## defined to be unequal to *NaN*, inserting a *NaN* key results in an entry
## that can never be retrieved or removed from the [Dict].
insert : Dict key val, key, val -> Dict key val
## Removes a key from the dictionary in [constant time](https://en.wikipedia.org/wiki/Time_complexity#Constant_time), without preserving insertion order.
##
## Since the internal [List] which determines the order of operations like [toList] and [walk] cannot have gaps in it,
## whenever an element is removed from the middle of that list, something must be done to eliminate the resulting gap.
##
## * [removeShift] eliminates the gap by shifting over every element after the removed one. This takes [linear time](https://en.wikipedia.org/wiki/Time_complexity#Linear_time),
## and preserves the original ordering.
## * [remove] eliminates the gap by replacing the removed element with the one at the end of the list - that is, the most recent insertion. This takes [constant time](https://en.wikipedia.org/wiki/Time_complexity#Constant_time), but does not preserve the original ordering.
##
## For example, suppose we have a `populationByCity` with these contents:
##
## Dict.toList populationByCity == [
## { k: "London", v: 8961989 },
## { k: "Philadelphia", v: 1603797 },
## { k: "Shanghai", v: 24870895 },
## { k: "Delhi", v: 16787941 },
## { k: "Amsterdam", v: 872680 },
## ]
##
## Using `Dict.remove "Philadelphia"` on this will replace the `"Philadelphia"` entry with the most recent insertion,
## which is `"Amsterdam"` in this case.
##
## populationByCity
## |> Dict.remove "Philadelphia"
## |> Dict.toList
## ==
## [
## { k: "London", v: 8961989 },
## { k: "Amsterdam", v: 872680 },
## { k: "Shanghai", v: 24870895 },
## { k: "Delhi", v: 16787941 },
## ]
##
## Both [remove] and [removeShift] leave the dictionary with the same contents; they only differ in ordering and in
## performance. Since ordering only affects operations like [toList] and [walk], [remove] is the better default
## choice because it has much better performance characteristics; [removeShift] should only be used when it's
## absolutely necessary for operations like [toList] and [walk] to preserve the exact original insertion order.
remove : Dict k v, k -> Dict k v
## Removes a key from the dictionary in [linear time](https://en.wikipedia.org/wiki/Time_complexity#Linear_time), while preserving insertion order.
##
## It's better to use [remove] than this by default, since [remove] has [constant time complexity](https://en.wikipedia.org/wiki/Time_complexity#Constant_time),
## which commonly leads [removeShift] to take many times as long to run as [remove] does. However, [remove] does not
## preserve insertion order, so the slower [removeShift] exists only for use cases where it's abolutely necessary for
## ordering-sensitive functions like [toList] and [walk] to preserve the exact original insertion order.
##
## See the [remove] documentation for more details about the differences between [remove] and [removeShift].
removeShift : Dict k v, k -> Dict k v
## Returns whether both dictionaries have the same keys, and the same values associated with those keys.
## This is different from `==` in that it disregards the ordering of the keys and values.
hasSameContents : Dict k v, Dict k v -> Bool

View File

@ -1,705 +0,0 @@
interface List
exposes
[
List,
append,
concat,
contains,
drop,
dropAt,
dropLast,
first,
get,
isEmpty,
join,
keepErrs,
keepIf,
keepOks,
last,
len,
map,
map2,
map3,
map4,
mapJoin,
mapOrDrop,
mapWithIndex,
prepend,
product,
range,
repeat,
reverse,
set,
single,
sortWith,
split,
sublist,
sum,
swap,
walk,
walkBackwards,
walkUntil
]
imports []
## Types
## A sequential list of values.
##
## >>> [ 1, 2, 3 ] # a list of numbers
## >>> [ "a", "b", "c" ] # a list of strings
## >>> [ [ 1.1 ], [], [ 2.2, 3.3 ] ] # a list of lists of numbers
##
## The list `[ 1, "a" ]` gives an error, because each element in a list must have
## the same type. If you want to put a mix of [I64] and [Str] values into a list, try this:
##
## ```
## mixedList : List [ IntElem I64, StrElem Str ]*
## mixedList = [ IntElem 1, IntElem 2, StrElem "a", StrElem "b" ]
## ```
##
## The maximum size of a [List] is limited by the amount of heap memory available
## to the current process. If there is not enough memory available, attempting to
## create the list could crash. (On Linux, where [overcommit](https://www.etalabs.net/overcommit.html)
## is normally enabled, not having enough memory could result in the list appearing
## to be created just fine, but then crashing later.)
##
## > The theoretical maximum length for a list created in Roc is half of
## > `Num.maxNat`. Attempting to create a list bigger than that
## > in Roc code will always fail, although in practice it is likely to fail
## > at much smaller lengths due to insufficient memory being available.
##
## ## Performance Details
##
## Under the hood, a list is a record containing a `len : Nat` field as well
## as a pointer to a reference count and a flat array of bytes. Unique lists
## store a capacity #Nat instead of a reference count.
##
## ## Shared Lists
##
## Shared lists are [reference counted](https://en.wikipedia.org/wiki/Reference_counting).
##
## Each time a given list gets referenced, its reference count ("refcount" for short)
## gets incremented. Each time a list goes out of scope, its refcount count gets
## decremented. Once a refcount, has been decremented more times than it has been
## incremented, we know nothing is referencing it anymore, and the list's memory
## will be immediately freed.
##
## Let's look at an example.
##
## ratings = [ 5, 4, 3 ]
##
## { foo: ratings, bar: ratings }
##
## The first line binds the name `ratings` to the list `[ 5, 4, 3 ]`. The list
## begins with a refcount of 1, because so far only `ratings` is referencing it.
##
## The second line alters this refcount. `{ foo: ratings` references
## the `ratings` list, which will result in its refcount getting incremented
## from 0 to 1. Similarly, `bar: ratings }` also references the `ratings` list,
## which will result in its refcount getting incremented from 1 to 2.
##
## Let's turn this example into a function.
##
## getRatings = \first ->
## ratings = [ first, 4, 3 ]
##
## { foo: ratings, bar: ratings }
##
## getRatings 5
##
## At the end of the `getRatings` function, when the record gets returned,
## the original `ratings =` binding has gone out of scope and is no longer
## accessible. (Trying to reference `ratings` outside the scope of the
## `getRatings` function would be an error!)
##
## Since `ratings` represented a way to reference the list, and that way is no
## longer accessible, the list's refcount gets decremented when `ratings` goes
## out of scope. It will decrease from 2 back down to 1.
##
## Putting these together, when we call `getRatings 5`, what we get back is
## a record with two fields, `foo`, and `bar`, each of which refers to the same
## list, and that list has a refcount of 1.
##
## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`:
##
## getRatings = \first ->
## ratings = [ first, 4, 3 ]
##
## { foo: ratings, bar: ratings }
##
## (getRatings 5).bar
##
## Now, when this expression returns, only the `bar` field of the record will
## be returned. This will mean that the `foo` field becomes inaccessible, causing
## the list's refcount to get decremented from 2 to 1. At this point, the list is back
## where it started: there is only 1 reference to it.
##
## Finally let's suppose the final line were changed to this:
##
## List.first (getRatings 5).bar
##
## This call to [List.first] means that even the list in the `bar` field has become
## inaccessible. As such, this line will cause the list's refcount to get
## decremented all the way to 0. At that point, nothing is referencing the list
## anymore, and its memory will get freed.
##
## Things are different if this is a list of lists instead of a list of numbers.
## Let's look at a simpler example using [List.first] - first with a list of numbers,
## and then with a list of lists, to see how they differ.
##
## Here's the example using a list of numbers.
##
## nums = [ 1, 2, 3, 4, 5, 6, 7 ]
##
## first = List.first nums
## last = List.last nums
##
## first
##
## It makes a list, calls [List.first] and [List.last] on it, and then returns `first`.
##
## Here's the equivalent code with a list of lists:
##
## lists = [ [ 1 ], [ 2, 3 ], [], [ 4, 5, 6, 7 ] ]
##
## first = List.first lists
## last = List.last lists
##
## first
##
## TODO explain how in the former example, when we go to free `nums` at the end,
## we can free it immediately because there are no other refcounts. However,
## in the case of `lists`, we have to iterate through the list and decrement
## the refcounts of each of its contained lists - because they, too, have
## refcounts! Importantly, because the first element had its refcount incremented
## because the function returned `first`, that element will actually end up
## *not* getting freed at the end - but all the others will be.
##
## In the `lists` example, `lists = [ ... ]` also creates a list with an initial
## refcount of 1. Separately, it also creates several other lists - each with
## their own refcounts - to go inside that list. (The empty list at the end
## does not use heap memory, and thus has no refcount.)
##
## At the end, we once again call [List.first] on the list, but this time
##
## * Copying small lists (64 elements or fewer) is typically slightly faster than copying small persistent data structures. This is because, at small sizes, persistent data structures tend to be thin wrappers around flat arrays anyway. They don't have any copying advantage until crossing a certain minimum size threshold.
## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, [List.map], [List.walk], and [List.keepIf] would all need to traverse every element in the list and build up the result from scratch. These operations are all
## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, [List.map], [List.keepIf], and [List.set] can all be optimized to perform in-place mutations.
## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way [List] worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using [List] under the hood!
List elem := [ List elem ]
## Initialize
## A list with a single element in it.
##
## This is useful in pipelines, like so:
##
## websites =
## Str.concat domain ".com"
## |> List.single
##
single : elem -> List elem
## An empty list.
empty : List *
## Returns a list with the given length, where every element is the given value.
##
##
repeat : elem, Nat -> List elem
## Returns a list of all the integers between one and another,
## including both of the given numbers.
##
## >>> List.range 2 8
range : Int a, Int a -> List (Int a)
## Transform
## Returns the list with its elements reversed.
##
## >>> List.reverse [ 1, 2, 3 ]
reverse : List elem -> List elem
## Sorts a list using a function which specifies how two elements are ordered.
##
## When sorting by numeric values, it's more efficient to use [sortAsc] or
## [sortDesc] instead.
sort : List elem, (elem, elem -> [ Lt, Eq, Gt ]) -> List elem
## Sorts a list in ascending order (lowest to highest), using a function which
## specifies a way to represent each element as a number.
##
## This is more efficient than [sort] because it skips
## calculating the `[ Lt, Eq, Gt ]` value and uses the number directly instead.
##
## To sort in descending order (highest to lowest), use [List.sortDesc] instead.
sortAsc : List elem, (elem -> Num *) -> List elem
## Sorts a list in descending order (highest to lowest), using a function which
## specifies a way to represent each element as a number.
##
## This is more efficient than [sort] because it skips
## calculating the `[ Lt, Eq, Gt ]` value and uses the number directly instead.
##
## To sort in ascending order (lowest to highest), use [List.sortAsc] instead.
sortDesc : List elem, (elem -> Num *) -> List elem
## Convert each element in the list to something new, by calling a conversion
## function on each of them. Then return a new list of the converted values.
##
## > List.map [ 1, 2, 3 ] (\num -> num + 1)
##
## > List.map [ "", "a", "bc" ] Str.isEmpty
##
## `map` functions like this are common in Roc, and they all work similarly.
## See for example `Set.map`, `Dict.map`, and [Result.map].
map : List before, (before -> after) -> List after
## 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.
##
## Some languages have a function named `zip`, which does something similar to
## calling [List.map2] passing two lists and `Pair`:
##
## >>> zipped = List.map2 [ "a", "b", "c" ] [ 1, 2, 3 ] Pair
map2 : List a, List b, (a, b -> c) -> List c
## 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
## 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
## This works like [List.map], except it also passes the index
## of the element to the conversion function.
mapWithIndex : List before, (before, Nat -> after) -> List after
## This works like [List.map], except at any time you can return `Err` to
## cancel the entire operation immediately, and return that #Err.
mapOrCancel : List before, (before -> Result after err) -> Result (List after) err
## Like [List.map], except the transformation function specifies whether to
## `Keep` or `Drop` each element from the final [List].
##
## You may know a similar function named `filterMap` in other languages.
mapOrDrop : List before, (before -> [ Keep after, Drop ]) -> List after
## Like [List.map], except the transformation function wraps the return value
## in a list. At the end, all the lists get joined together into one list.
##
## You may know a similar function named `concatMap` in other languages.
mapJoin : List before, (before -> List after) -> List after
## This works like [List.map], except only the transformed values that are
## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped.
##
## >>> List.mapOks [ [ "a", "b" ], [], [], [ "c", "d", "e" ] ] List.last
##
## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
## >>>
## >>> List.mapOks [ "", "a", "bc", "", "d", "ef", "" ]
mapOks : List before, (before -> Result after *) -> List after
## Returns a list with the element at the given index having been transformed by
## the given function.
##
## For a version of this which gives you more control over when to perform
## the transformation, see `List.updater`
##
## ## Performance notes
##
## In particular when updating nested collections, this is potentially much more
## efficient than using [List.get] to obtain the element, transforming it,
## and then putting it back in the same place.
update : List elem, Nat, (elem -> elem) -> List elem
## A more flexible version of `List.update`, which returns an "updater" function
## that lets you delay performing the update until later.
updater : List elem, Nat -> { elem, new : (elem -> List elem) }
## If all the elements in the list are #Ok, return a new list containing the
## contents of those #Ok tags. If any elements are #Err, return #Err.
allOks : List (Result ok err) -> Result (List ok) err
## Add a single element to the end of a list.
##
## >>> List.append [ 1, 2, 3 ] 4
##
## >>> [ 0, 1, 2 ]
## >>> |> List.append 3
append : List elem, elem -> List elem
## Add a single element to the beginning of a list.
##
## >>> List.prepend [ 1, 2, 3 ] 0
##
## >>> [ 2, 3, 4 ]
## >>> |> List.prepend 1
prepend : List elem, elem -> List elem
## Put two lists together.
##
## >>> List.concat [ 1, 2, 3 ] [ 4, 5 ]
##
## >>> [ 0, 1, 2 ]
## >>> |> List.concat [ 3, 4 ]
concat : List elem, List elem -> List elem
## Join the given lists together into one list.
##
## >>> List.join [ [ 1, 2, 3 ], [ 4, 5 ], [], [ 6, 7 ] ]
##
## >>> List.join [ [], [] ]
##
## >>> List.join []
join : List (List elem) -> List elem
## Like [List.join], but only keeps elements tagged with `Ok`. Elements
## tagged with `Err` are dropped.
##
## This can be useful after using an operation that returns a #Result
## on each element of a list, for example [List.first]:
##
## >>> [ [ 1, 2, 3 ], [], [], [ 4, 5 ] ]
## >>> |> List.map List.first
## >>> |> List.joinOks
##
## Eventually, `oks` type signature will be `List [Ok elem]* -> List elem`.
## The implementation for that is a lot tricker then `List (Result elem *)`
## so we're sticking with `Result` for now.
oks : List (Result elem *) -> List elem
## Filter
## Run the given function on each element of a list, and return all the
## elements for which the function returned `True`.
##
## >>> List.keepIf [ 1, 2, 3, 4 ] (\num -> num > 2)
##
## ## Performance Details
##
## [List.keepIf] always returns a list that takes up exactly the same amount
## of memory as the original, even if its length decreases. This is because it
## can't know in advance exactly how much space it will need, and if it guesses a
## length that's too low, it would have to re-allocate.
##
## (If you want to do an operation like this which reduces the memory footprint
## of the resulting list, you can do two passes over the lis with [List.walk] - one
## to calculate the precise new size, and another to populate the new list.)
##
## If given a unique list, [List.keepIf] will mutate it in place to assemble the appropriate list.
## If that happens, this function will not allocate any new memory on the heap.
## If all elements in the list end up being kept, Roc will return the original
## list unaltered.
##
keepIf : List elem, (elem -> Bool) -> List elem
## Run the given function on each element of a list, and return all the
## elements for which the function returned `False`.
##
## >>> List.dropIf [ 1, 2, 3, 4 ] (\num -> num > 2)
##
## ## Performance Details
##
## `List.dropIf` has the same performance characteristics as [List.keepIf].
## See its documentation for details on those characteristics!
dropIf : List elem, (elem -> Bool) -> List elem
## Access
## Returns the first element in the list, or `ListWasEmpty` if it was empty.
first : List elem -> Result elem [ ListWasEmpty ]*
## Returns the last element in the list, or `ListWasEmpty` if it was empty.
last : List elem -> Result elem [ ListWasEmpty ]*
get : List elem, Nat -> Result elem [ OutOfBounds ]*
max : List (Num a) -> Result (Num a) [ ListWasEmpty ]*
min : List (Num a) -> Result (Num a) [ ListWasEmpty ]*
## Modify
## Replaces the element at the given index with a replacement.
##
## >>> List.set [ "a", "b", "c" ] 1 "B"
##
## If the given index is outside the bounds of the list, returns the original
## list unmodified.
##
## To drop the element at a given index, instead of replacing it, see [List.dropAt].
set : List elem, Nat, elem -> List elem
## Drops n elements from the beginning of the list.
drop : List elem, Nat -> List elem
## Drops the element at the given index from the list.
##
## This has no effect if the given index is outside the bounds of the list.
##
## To replace the element at a given index, instead of dropping it, see [List.set].
dropAt : List elem, Nat -> List elem
## Adds a new element to the end of the list.
##
## >>> List.append [ "a", "b" ] "c"
##
## ## Performance Details
##
## When given a Unique list, this adds the new element in-place if possible.
## This is only possible if the list has enough capacity. Otherwise, it will
## have to *clone and grow*. See the section on [capacity](#capacity) in this
## module's documentation.
append : List elem, elem -> List elem
## Adds a new element to the beginning of the list.
##
## >>> List.prepend [ "b", "c" ] "a"
##
## ## Performance Details
##
## This always clones the entire list, even when given a Unique list. That means
## it runs about as fast as `List.addLast` when both are given a Shared list.
##
## If you have a Unique list instead, [List.append] will run much faster than
## [List.append] except in the specific case where the list has no excess capacity,
## and needs to *clone and grow*. In that uncommon case, both [List.append] and
## [List.append] will run at about the same speed—since [List.append] always
## has to clone and grow.
##
## | Unique list | Shared list |
##---------+--------------------------------+----------------+
## append | in-place given enough capacity | clone and grow |
## prepend | clone and grow | clone and grow |
prepend : List elem, elem -> List elem
## Remove the last element from the list.
##
## Returns both the removed element as well as the new list (with the removed
## element missing), or `Err ListWasEmpty` if the list was empty.
##
## Here's one way you can use this:
##
## when List.pop list is
## Ok { others, last } -> ...
## Err ListWasEmpty -> ...
##
## ## Performance Details
##
## Calling `List.pop` on a Unique list runs extremely fast. It's essentially
## the same as a [List.last] except it also returns the [List] it was given,
## with its length decreased by 1.
##
## In contrast, calling `List.pop` on a Shared list creates a new list, then
## copies over every element in the original list except the last one. This
## takes much longer.
dropLast : List elem -> Result { others : List elem, last : elem } [ ListWasEmpty ]*
##
## Here's one way you can use this:
##
## when List.pop list is
## Ok { others, last } -> ...
## Err ListWasEmpty -> ...
##
## ## Performance Details
##
## When calling either `List.dropFirst` or `List.dropLast` on a Unique list, `List.dropLast`
## runs *much* faster. This is because for `List.dropLast`, removing the last element
## in-place is as easy as reducing the length of the list by 1. In contrast,
## removing the first element from the list involves copying every other element
## in the list into the index before it - which is massively more costly.
##
## In the case of a Shared list,
##
## | Unique list | Shared list |
##-----------+----------------------------------+---------------------------------+
## dropFirst | [List.last] + length change | [List.last] + clone rest of list |
## dropLast | [List.last] + clone rest of list | [List.last] + clone rest of list |
dropFirst : List elem -> Result { first: elem, others : List elem } [ ListWasEmpty ]*
## Returns the given number of elements from the beginning of the list.
##
## >>> List.takeFirst 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ]
##
## If there are fewer elements in the list than the requested number,
## returns the entire list.
##
## >>> List.takeFirst 5 [ 1, 2 ]
##
## To *remove* elements from the beginning of the list, use `List.takeLast`.
##
## To remove elements from both the beginning and end of the list,
## use `List.sublist`.
##
## To split the list into two lists, use `List.split`.
##
## ## Performance Details
##
## When given a Unique list, this runs extremely fast. It sets the list's length
## to the given length value, and frees the leftover elements. This runs very
## slightly faster than `List.takeLast`.
##
## In fact, `List.takeFirst 1 list` runs faster than `List.first list` when given
## a Unique list, because [List.first] returns the first element as well -
## which introduces a conditional bounds check as well as a memory load.
takeFirst : List elem, Nat -> List elem
## Returns the given number of elements from the end of the list.
##
## >>> List.takeLast 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ]
##
## If there are fewer elements in the list than the requested number,
## returns the entire list.
##
## >>> List.takeLast 5 [ 1, 2 ]
##
## To *remove* elements from the end of the list, use `List.takeFirst`.
##
## To remove elements from both the beginning and end of the list,
## use `List.sublist`.
##
## To split the list into two lists, use `List.split`.
##
## ## Performance Details
##
## When given a Unique list, this runs extremely fast. It moves the list's
## pointer to the index at the given length value, updates its length,
## and frees the leftover elements. This runs very nearly as fast as
## `List.takeFirst` on a Unique list.
##
## In fact, `List.takeLast 1 list` runs faster than `List.first list` when given
## a Unique list, because [List.first] returns the first element as well -
## which introduces a conditional bounds check as well as a memory load.
takeLast : List elem, Nat -> List elem
## Deconstruct
## Splits the list into two lists, around the given index.
##
## The returned lists are labeled `before` and `others`. The `before` list will
## contain all the elements whose index in the original list was **less than**
## than the given index, # and the `others` list will be all the others. (This
## means if you give an index of 0, the `before` list will be empty and the
## `others` list will have the same elements as the original list.)
split : List elem, Nat -> { before: List elem, others: List elem }
## Returns a subsection of the given list, beginning at the `start` index and
## including a total of `len` elements.
##
## If `start` is outside the bounds of the given list, returns the empty list.
##
## >>> List.sublist { start: 4, len: 0 } [ 1, 2, 3 ]
##
## If more elements are requested than exist in the list, returns as many as it can.
##
## >>> List.sublist { start: 2, len: 10 } [ 1, 2, 3, 4, 5 ]
##
## > If you want a sublist which goes all the way to the end of the list, no
## > matter how long the list is, `List.takeLast` can do that more efficiently.
##
## Some languages have a function called **`slice`** which works similarly to this.
sublist : List elem, { start : Nat, len : Nat } -> List elem
## Build a value using each element in the list.
##
## Starting with a given `state` value, this walks through each element in the
## list from first to last, running a given `step` function on that element
## which updates the `state`. It returns the final `state` at the end.
##
## You can use it in a pipeline:
##
## [ 2, 4, 8 ]
## |> List.walk { start: 0, step: Num.add }
##
## This returns 14 because:
## * `state` starts at 0 (because of `start: 0`)
## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`.
##
## Here is a table of how `state` changes as [List.walk] walks over the elements
## `[ 2, 4, 8 ]` using #Num.add as its `step` function to determine the next `state`.
##
## `state` | `elem` | `step state elem` (`Num.add state elem`)
## --------+--------+-----------------------------------------
## 0 | |
## 0 | 2 | 2
## 2 | 4 | 6
## 6 | 8 | 14
##
## So `state` goes through these changes:
## 1. `0` (because of `start: 0`)
## 2. `1` (because of `Num.add state elem` with `state` = 0 and `elem` = 1
##
## [ 1, 2, 3 ]
## |> List.walk { start: 0, step: Num.sub }
##
## This returns -6 because
##
## Note that in other languages, `walk` is sometimes called `reduce`,
## `fold`, `foldLeft`, or `foldl`.
walk : List elem, state, (state, elem -> state) -> state
## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`,
## `fold`, `foldRight`, or `foldr`.
walkBackwards : List elem, state, (state, elem -> state) -> state
## Same as [List.walk], except you can stop walking early.
##
## ## Performance Details
##
## Compared to [List.walk], this can potentially visit fewer elements (which can
## improve performance) at the cost of making each step take longer.
## However, the added cost to each step is extremely small, and can easily
## be outweighed if it results in skipping even a small number of elements.
##
## As such, it is typically better for performance to use this over [List.walk]
## if returning `Done` earlier than the last element is expected to be common.
walkUntil : List elem, state, (state, elem -> [ Continue state, Done state ]) -> state
# Same as [List.walk]Backwards, except you can stop walking early.
walkBackwardsUntil : List elem, state, (state, elem -> [ Continue state, Done state ]) -> state
## Check
## Returns the length of the list - the number of elements it contains.
##
## One [List] can store up to 2,147,483,648 elements (just over 2 billion), which
## is exactly equal to the highest valid #I32 value. This means the #U32 this function
## returns can always be safely converted to an #I32 without losing any data.
len : List * -> Nat
isEmpty : List * -> Bool
contains : List elem, elem -> Bool
startsWith : List elem, List elem -> Bool
endsWith : List elem, List elem -> Bool
## Run the given predicate on each element of the list, returning `True` if
## any of the elements satisfy it.
any : List elem, (elem -> Bool) -> Bool
## Run the given predicate on each element of the list, returning `True` if
## all of the elements satisfy it.
all : List elem, (elem -> Bool) -> Bool
## Returns the first element of the list satisfying a predicate function.
## If no satisfying element is found, an `Err NotFound` is returned.
find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*
## Apply a function that returns a Result on a list, only successful
## Results are kept and returned unwrapped.
keepOks : List before, (before -> Result after *) -> List after
## Apply a function that returns a Result on a list, only unsuccessful
## Results are kept and returned unwrapped.
keepErrs : List before, (before -> Result * after) -> List after

File diff suppressed because it is too large Load Diff

View File

@ -1,67 +0,0 @@
interface Result
exposes
[
Result,
after,
isOk,
isErr,
map,
mapErr,
withDefault
]
imports []
## The result of an operation that could fail: either the operation went
## okay, or else there was an error of some sort.
Result ok err : [ Ok ok, Err err ]
## Return True if the result indicates a success, else return False
##
## >>> Result.isOk (Ok 5)
isOk : Result * * -> bool
## Return True if the result indicates a failure, else return False
##
## >>> Result.isErr (Err "uh oh")
isErr : Result * * -> bool
## If the result is `Ok`, return the value it holds. Otherwise, return
## the given default value.
##
## >>> Result.withDefault (Ok 7) 42
##
## >>> Result.withDefault (Err "uh oh") 42
withDefault : Result ok err, ok -> ok
## If the result is `Ok`, transform the entire result by running a conversion
## function on the value the `Ok` holds. Then return that new result.
##
## (If the result is `Err`, this has no effect. Use `afterErr` to transform an `Err`.)
##
## >>> Result.after (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num
##
## >>> Result.after (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num
after : Result before err, (before -> Result after err) -> Result after err
## If the result is `Ok`, transform the value it holds by running a conversion
## function on it. Then return a new `Ok` holding the transformed value.
##
## (If the result is `Err`, this has no effect. Use [mapErr] to transform an `Err`.)
##
## >>> Result.map (Ok 12) Num.negate
##
## >>> Result.map (Err "yipes!") Num.negate
##
## `map` functions like this are common in Roc, and they all work similarly.
## See for example [List.map], `Set.map`, and `Dict.map`.
map : Result before err, (before -> after) -> Result after err
## If the result is `Err`, transform the value it holds by running a conversion
## function on it. Then return a new `Err` holding the transformed value.
##
## (If the result is `Ok`, this has no effect. Use [map] to transform an `Ok`.)
##
## >>> Result.mapErr (Err "yipes!") Str.isEmpty
##
## >>> Result.mapErr (Ok 12) Str.isEmpty
mapErr : Result ok before, (before -> after) -> Result ok after

View File

@ -1,59 +0,0 @@
interface Set
exposes
[
Set,
contains,
difference,
empty,
fromList,
insert,
intersection,
len,
remove,
single,
toList,
union,
walk
]
imports []
## A Set is an unordered collection of unique elements.
Set elem := [ Set elem ]
## An empty set.
empty : Set *
## Check
isEmpty : Set * -> Bool
len : Set * -> Nat
## Modify
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `add : Set 'elem, 'elem -> Set 'elem`
## Make sure never to add a *NaN* to a [Set]! Because *NaN* is defined to be
## unequal to *NaN*, adding a *NaN* results in an entry that can never be
## retrieved or removed from the [Set].
add : Set elem, elem -> Set elem
## Drops the given element from the set.
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `drop : Set 'elem, 'elem -> Set 'elem`
drop : Set elem, elem -> Set elem
## Transform
## Convert each element in the set to something new, by calling a conversion
## function on each of them. Then return a new set of the converted values.
##
## >>> Set.map {: -1, 1, 3 :} Num.negate
##
## >>> Set.map {: "", "a", "bc" :} Str.isEmpty
##
## `map` functions like this are common in Roc, and they all work similarly.
## See for example [List.map], `Dict.map`, and [Result.map].
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `map : Set 'elem, ('before -> 'after) -> Set 'after`
map : Set elem, (before -> after) -> Set after

View File

@ -1,470 +0,0 @@
interface Str
exposes
[
Str,
append,
concat,
countGraphemes,
endsWith,
fromUtf8,
isEmpty,
joinWith,
split,
startsWith,
startsWithCodePt,
toUtf8,
Utf8Problem,
Utf8ByteProblem
]
imports []
## # Types
##
## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks
## to the basics.
##
## _For more advanced use cases like working with raw [code points](https://unicode.org/glossary/#code_point),
## see the [roc/unicode](roc/unicode) package. For locale-specific text
## functions (including uppercasing strings, as capitalization rules vary by locale;
## in English, `"i"` capitalizes to `"I"`, but [in Turkish](https://en.wikipedia.org/wiki/Dotted_and_dotless_I#In_computing),
## the same `"i"` capitalizes to `"İ"` - as well as sorting strings, which also varies
## by locale; `"ö"` is sorted differently in German and Swedish) see the [roc/locale](roc/locale) package._
##
## ### Unicode
##
## Unicode can represent text values which span multiple languages, symbols, and emoji.
## Here are some valid Roc strings:
##
## "Roc!"
## "鹏"
## "🕊"
##
## Every Unicode string is a sequence of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster).
## An extended grapheme cluster represents what a person reading a string might
## call a "character" - like "A" or "ö" or "👩‍👩‍👦‍👦".
## Because the term "character" means different things in different areas of
## programming, and "extended grapheme cluster" is a mouthful, in Roc we use the
## term "grapheme" as a shorthand for the more precise "extended grapheme cluster."
##
## You can get the number of graphemes in a string by calling [Str.countGraphemes] on it:
##
## Str.countGraphemes "Roc!"
## Str.countGraphemes "折り紙"
## Str.countGraphemes "🕊"
##
## > The `countGraphemes` function walks through the entire string to get its answer,
## > so if you want to check whether a string is empty, you'll get much better performance
## > by calling `Str.isEmpty myStr` instead of `Str.countGraphemes myStr == 0`.
##
## ### Escape sequences
##
## If you put a `\` in a Roc string literal, it begins an *escape sequence*.
## An escape sequence is a convenient way to insert certain strings into other strings.
## For example, suppose you write this Roc string:
##
## "I took the one less traveled by,\nAnd that has made all the difference."
##
## The `"\n"` in the middle will insert a line break into this string. There are
## other ways of getting a line break in there, but `"\n"` is the most common.
##
## Another way you could insert a newlines is by writing `\u{0x0A}` instead of `\n`.
## That would result in the same string, because the `\u` escape sequence inserts
## [Unicode code points](https://unicode.org/glossary/#code_point) directly into
## the string. The Unicode code point 10 is a newline, and 10 is `0A` in hexadecimal.
## `0x0A` is a Roc hexadecimal literal, and `\u` escape sequences are always
## followed by a hexadecimal literal inside `{` and `}` like this.
##
## As another example, `"R\u{0x6F}c"` is the same string as `"Roc"`, because
## `"\u{0x6F}"` corresponds to the Unicode code point for lowercase `o`. If you
## want to [spice things up a bit](https://en.wikipedia.org/wiki/Metal_umlaut),
## you can write `"R\u{0xF6}c"` as an alternative way to get the string `"Röc"\.
##
## Roc strings also support these escape sequences:
##
## * `\\` - an actual backslash (writing a single `\` always begins an escape sequence!)
## * `\"` - an actual quotation mark (writing a `"` without a `\` ends the string)
## * `\r` - [carriage return](https://en.wikipedia.org/wiki/Carriage_Return)
## * `\t` - [horizontal tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
## * `\v` - [vertical tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
##
## You can also use escape sequences to insert named strings into other strings, like so:
##
## name = "Lee"
## city = "Roctown"
##
## greeting = "Hello there, \(name)! Welcome to \(city)."
##
## Here, `greeting` will become the string `"Hello there, Lee! Welcome to Roctown."`.
## This is known as [string interpolation](https://en.wikipedia.org/wiki/String_interpolation),
## and you can use it as many times as you like inside a string. The name
## between the parentheses must refer to a `Str` value that is currently in
## scope, and it must be a name - it can't be an arbitrary expression like a function call.
##
## ### Encoding
##
## Roc strings are not coupled to any particular
## [encoding](https://en.wikipedia.org/wiki/Character_encoding). As it happens,
## they are currently encoded in UTF-8, but this module is intentionally designed
## not to rely on that implementation detail so that a future release of Roc can
## potentially change it without breaking existing Roc applications. (UTF-8
## seems pretty great today, but so did UTF-16 at an earlier point in history.)
##
## This module has functions to can convert a [Str] to a [List] of raw [code unit](https://unicode.org/glossary/#code_unit)
## integers (not to be confused with the [code points](https://unicode.org/glossary/#code_point)
## mentioned earlier) in a particular encoding. If you need encoding-specific functions,
## you should take a look at the [roc/unicode](roc/unicode) package.
## It has many more tools than this module does!
## A [Unicode](https://unicode.org) text value.
Str := [ Str ]
## Convert
## Convert a [Float] to a decimal string, rounding off to the given number of decimal places.
##
## If you want to keep all the digits, use [Str.num] instead.
decimal : Float *, Nat -> Str
## Convert a [Num] to a string.
num : Float *, Nat -> Str
## Split a string around a separator.
##
## >>> Str.split "1,2,3" ","
##
## Passing `""` for the separator is not useful; it returns the original string
## wrapped in a list.
##
## >>> Str.split "1,2,3" ""
##
## To split a string into its individual graphemes, use `Str.graphemes`
split : Str, Str -> List Str
## Split a string around newlines.
##
## On strings that use `"\n"` for their line endings, this gives the same answer
## as passing `"\n"` to [Str.split]. However, on strings that use `"\n\r"` (such
## as [in Windows files](https://en.wikipedia.org/wiki/Newline#History)), this
## will consume the entire `"\n\r"` instead of just the `"\n"`.
##
## >>> Str.lines "Hello, World!\nNice to meet you!"
##
## >>> Str.lines "Hello, World!\n\rNice to meet you!"
##
## To split a string using a custom separator, use [Str.split]. For more advanced
## string splitting, use a #Parser.
lines : Str, Str -> List Str
## Check
## Returns `True` if the string is empty, and `False` otherwise.
##
## >>> Str.isEmpty "hi!"
##
## >>> Str.isEmpty ""
isEmpty : Str -> Bool
startsWith : Str, Str -> Bool
## If the string begins with a [Unicode code point](http://www.unicode.org/glossary/#code_point)
## equal to the given [U32], return `True`. Otherwise return `False`.
##
## If the given [Str] is empty, or if the given [U32] is not a valid
## code point, this will return `False`.
##
## **Performance Note:** This runs slightly faster than [Str.startsWith], so
## if you want to check whether a string begins with something that's representable
## in a single code point, you can use (for example) `Str.startsWithCodePt '鹏'`
## instead of `Str.startsWithCodePt "鹏"`. ('鹏' evaluates to the [U32]
## value `40527`.) This will not work for graphemes which take up multiple code
## points, however; `Str.startsWithCodePt '👩‍👩‍👦‍👦'` would be a compiler error
## because 👩‍👩‍👦‍👦 takes up multiple code points and cannot be represented as a
## single [U32]. You'd need to use `Str.startsWithCodePt "🕊"` instead.
startsWithCodePt : Str, U32 -> Bool
endsWith : Str, Str -> Bool
contains : Str, Str -> Bool
anyGraphemes : Str, (Str -> Bool) -> Bool
allGraphemes : Str, (Str -> Bool) -> Bool
## Combine
## Combine a list of strings into a single string.
##
## >>> Str.join [ "a", "bc", "def" ]
join : List Str -> Str
## Combine a list of strings into a single string, with a separator
## string in between each.
##
## >>> Str.joinWith [ "one", "two", "three" ] ", "
joinWith : List Str, Str -> Str
## Add to the start of a string until it has at least the given number of
## graphemes.
##
## >>> Str.padGraphemesStart "0" 5 "36"
##
## >>> Str.padGraphemesStart "0" 1 "36"
##
## >>> Str.padGraphemesStart "0" 5 "12345"
##
## >>> Str.padGraphemesStart "✈️"" 5 "👩‍👩‍👦‍👦👩‍👩‍👦‍👦👩‍👩‍👦‍👦"
padGraphemesStart : Str, Nat, Str -> Str
## Add to the end of a string until it has at least the given number of
## graphemes.
##
## >>> Str.padGraphemesStart "0" 5 "36"
##
## >>> Str.padGraphemesStart "0" 1 "36"
##
## >>> Str.padGraphemesStart "0" 5 "12345"
##
## >>> Str.padGraphemesStart "✈️"" 5 "👩‍👩‍👦‍👦👩‍👩‍👦‍👦👩‍👩‍👦‍👦"
padGraphemesEnd : Str, Nat, Str -> Str
## Graphemes
## Split a string into its individual graphemes.
##
## >>> Str.graphemes "1,2,3"
##
## >>> Str.graphemes "👍👍👍"
##
graphemes : Str -> List Str
## Count the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
## in the string.
##
## Str.countGraphemes "Roc!" # 4
## Str.countGraphemes "七巧板" # 3
## Str.countGraphemes "🕊" # 1
countGraphemes : Str -> Nat
## Reverse the order of the string's individual graphemes.
##
## >>> Str.reverseGraphemes "1-2-3"
##
## >>> Str.reverseGraphemes "🐦✈️"👩‍👩‍👦‍👦"
##
## >>> Str.reversegraphemes "Crème Brûlée"
reverseGraphemes : Str -> Str
## Returns `True` if the two strings are equal when ignoring case.
##
## >>> Str.caseInsensitiveEq "hi" "Hi"
isCaseInsensitiveEq : Str, Str -> Bool
isCaseInsensitiveNeq : Str, Str -> Bool
walkGraphemes : Str, { start: state, step: (state, Str -> state) } -> state
walkGraphemesUntil : Str, { start: state, step: (state, Str -> [ Continue state, Done state ]) } -> state
walkGraphemesBackwards : Str, { start: state, step: (state, Str -> state) } -> state
walkGraphemesBackwardsUntil : Str, { start: state, step: (state, Str -> [ Continue state, Done state ]) } -> state
## Returns `True` if the string begins with an uppercase letter.
##
## >>> Str.isCapitalized "Hi"
##
## >>> Str.isCapitalized " Hi"
##
## >>> Str.isCapitalized "hi"
##
## >>> Str.isCapitalized "Česká"
##
## >>> Str.isCapitalized "Э"
##
## >>> Str.isCapitalized "東京"
##
## >>> Str.isCapitalized "🐦"
##
## >>> Str.isCapitalized ""
##
## Since the rules for how to capitalize a string vary by locale,
## (for example, in English, `"i"` capitalizes to `"I"`, but
## [in Turkish](https://en.wikipedia.org/wiki/Dotted_and_dotless_I#In_computing),
## the same `"i"` capitalizes to `"İ"`) see the [roc/locale](roc/locale) package
## package for functions which capitalize strings.
isCapitalized : Str -> Bool
## Returns `True` if the string consists entirely of uppercase letters.
##
## >>> Str.isAllUppercase "hi"
##
## >>> Str.isAllUppercase "Hi"
##
## >>> Str.isAllUppercase "HI"
##
## >>> Str.isAllUppercase " Hi"
##
## >>> Str.isAllUppercase "Česká"
##
## >>> Str.isAllUppercase "Э"
##
## >>> Str.isAllUppercase "東京"
##
## >>> Str.isAllUppercase "🐦"
##
## >>> Str.isAllUppercase ""
isAllUppercase : Str -> Bool
## Returns `True` if the string consists entirely of lowercase letters.
##
## >>> Str.isAllLowercase "hi"
##
## >>> Str.isAllLowercase "Hi"
##
## >>> Str.isAllLowercase "HI"
##
## >>> Str.isAllLowercase " Hi"
##
## >>> Str.isAllLowercase "Česká"
##
## >>> Str.isAllLowercase "Э"
##
## >>> Str.isAllLowercase "東京"
##
## >>> Str.isAllLowercase "🐦"
##
## >>> Str.isAllLowercase ""
isAllLowercase : Str -> Bool
## Return the string with any blank spaces removed from both the beginning
## as well as the end.
trim : Str -> Str
## If the given [U32] is a valid [Unicode Scalar Value](http://www.unicode.org/glossary/#unicode_scalar_value),
## return a [Str] containing only that scalar.
fromScalar : U32 -> Result Str [ BadScalar ]*
fromCodePts : List U32 -> Result Str [ BadCodePt U32 ]*
fromUtf8 : List U8 -> Result Str [ BadUtf8 ]*
## Create a [Str] from bytes encoded as [UTF-16LE](https://en.wikipedia.org/wiki/UTF-16#Byte-order_encoding_schemes).
# fromUtf16Le : List U8 -> Result Str [ BadUtf16Le Endi ]*
# ## Create a [Str] from bytes encoded as [UTF-16BE](https://en.wikipedia.org/wiki/UTF-16#Byte-order_encoding_schemes).
# fromUtf16Be : List U8 -> Result Str [ BadUtf16Be Endi ]*
# ## Create a [Str] from bytes encoded as UTF-16 with a [Byte Order Mark](https://en.wikipedia.org/wiki/Byte_order_mark).
# fromUtf16Bom : List U8 -> Result Str [ BadUtf16 Endi, NoBom ]*
# ## Create a [Str] from bytes encoded as [UTF-32LE](https://web.archive.org/web/20120322145307/http://mail.apps.ietf.org/ietf/charsets/msg01095.html)
# fromUtf32Le : List U8 -> Result Str [ BadUtf32Le Endi ]*
# ## Create a [Str] from bytes encoded as [UTF-32BE](https://web.archive.org/web/20120322145307/http://mail.apps.ietf.org/ietf/charsets/msg01095.html)
# fromUtf32Be : List U8 -> Result Str [ BadUtf32Be Endi ]*
# ## Create a [Str] from bytes encoded as UTF-32 with a [Byte Order Mark](https://en.wikipedia.org/wiki/Byte_order_mark).
# fromUtf32Bom : List U8 -> Result Str [ BadUtf32 Endi, NoBom ]*
# ## Convert from UTF-8, substituting the replacement character ("<22>") for any
# ## invalid sequences encountered.
# fromUtf8Sub : List U8 -> Str
# fromUtf16Sub : List U8, Endi -> Str
# fromUtf16BomSub : List U8 -> Result Str [ NoBom ]*
## Return a [List] of the string's [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit).
## (To split the string into a [List] of smaller [Str] values instead of [U8] values,
## see [Str.split] and `Str.graphemes`.)
##
## >>> Str.toUtf8 "👩‍👩‍👦‍👦"
##
## >>> Str.toUtf8 "Roc"
##
## >>> Str.toUtf8 "鹏"
##
## >>> Str.toUtf8 "🐦"
##
## For a more flexible function that walks through each of these [U8] code units
## without creating a [List], see `Str.walkUtf8` and `Str.walkRevUtf8`.
toUtf8 : Str -> List U8
toUtf16Be : Str -> List U8
toUtf16Le : Str -> List U8
# toUtf16Bom : Str, Endi -> List U8
toUtf32Be : Str -> List U8
toUtf32Le : Str -> List U8
# toUtf32Bom : Str, Endi -> List U8
# Parsing
## If the bytes begin with a valid [extended grapheme cluster](http://www.unicode.org/glossary/#extended_grapheme_cluster)
## encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8), return it along with the number of bytes it took up.
##
## If the bytes do not begin with a valid grapheme, for example because the list was
## empty or began with an invalid grapheme, return `Err`.
parseUtf8Grapheme : List U8 -> Result { grapheme : Str, bytesParsed: Nat } [ InvalidGrapheme ]*
## If the bytes begin with a valid [Unicode code point](http://www.unicode.org/glossary/#code_point)
## encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8), return it along with the number of bytes it took up.
##
## If the string does not begin with a valid code point, for example because the list was
## empty or began with an invalid code point, return an `Err`.
parseUtf8CodePt : List U8 -> Result { codePt : U32, bytesParsed: Nat } [ InvalidCodePt ]*
## If the string represents a valid [U8] number, return that number.
##
## For more advanced options, see [parseU8].
toU8 : Str -> Result U8 [ InvalidU8 ]*
toI8 : Str -> Result I8 [ InvalidI8 ]*
toU16 : Str -> Result U16 [ InvalidU16 ]*
toI16 : Str -> Result I16 [ InvalidI16 ]*
toU32 : Str -> Result U32 [ InvalidU32 ]*
toI32 : Str -> Result I32 [ InvalidI32 ]*
toU64 : Str -> Result U64 [ InvalidU64 ]*
toI64 : Str -> Result I64 [ InvalidI64 ]*
toU128 : Str -> Result U128 [ InvalidU128 ]*
toI128 : Str -> Result I128 [ InvalidI128 ]*
toF64 : Str -> Result U128 [ InvalidF64 ]*
toF32 : Str -> Result I128 [ InvalidF32 ]*
toDec : Str -> Result Dec [ InvalidDec ]*
## If the string represents a valid number, return that number.
##
## The exact number type to look for will be inferred from usage.
## In the example below, the usage of I64 in the type signature will require that type instead of (Num *).
##
## >>> strToI64 : Str -> Result I64 [ InvalidNumStr ]*
## >>> strToI64 = \inputStr ->
## >>> Str.toNum inputStr
##
## If the string is exactly `"NaN"`, `"∞"`, or `"-∞"`, they will be accepted
## only when converting to [F64] or [F32] numbers, and will be translated accordingly.
##
## This never accepts numbers with underscores or commas in them. For more
## advanced options, see [parseNum].
toNum : Str -> Result (Num *) [ InvalidNumStr ]*
## If the string begins with an [Int] or a [finite](Num.isFinite) [Frac], return
## that number along with the rest of the string after it.
##
## The exact number type to look for will be inferred from usage.
## In the example below, the usage of Float64 in the type signature will require that type instead of (Num *).
##
## >>> parseFloat64 : Str -> Result { val: Float64, rest: Str } [ InvalidNumStr ]*
## >>> Str.parseNum input {}
##
## If the string begins with `"NaN"`, `"∞"`, and `"-∞"` (which do not represent
## [finite](Num.isFinite) numbers), they will be accepted only when parsing
## [F64] or [F32] numbers, and translated accordingly.
# parseNum : Str, NumParseConfig -> Result { val : Num *, rest : Str } [ InvalidNumStr ]*
## Notes:
## * You can allow a decimal mark for integers; they'll only parse if the numbers after it are all 0.
## * For `wholeSep`, `Required` has a payload for how many digits (e.g. "required every 3 digits")
## * For `wholeSep`, `Allowed` allows the separator to appear anywhere.
# NumParseConfig :
# {
# base ? [ Decimal, Hexadecimal, Octal, Binary ],
# notation ? [ Standard, Scientific, Any ],
# decimalMark ? [ Allowed Str, Required Str, Disallowed ],
# decimalDigits ? [ Any, AtLeast U16, Exactly U16 ],
# wholeDigits ? [ Any, AtLeast U16, Exactly U16 ],
# leadingZeroes ? [ Allowed, Disallowed ],
# trailingZeroes ? [ Allowed, Disallowed ],
# wholeSep ? { mark : Str, policy : [ Allowed, Required U64 ] }
# }

View File

@ -4,11 +4,81 @@ interface Bool
Bool : [ True, False ]
## Returns `True` when given `True` and `True`, and `False` when either argument is `False`.
##
## `a && b` is shorthand for `Bool.and a b`
##
## >>> True && True
##
## >>> True && False
##
## >>> False && True
##
## >>> False && False
##
## ## Performance Notes
##
## In some languages, `&&` and `||` are special-cased in the compiler to skip
## evaluating the expression after the operator under certain circumstances.
## For example, in some languages, `enablePets && likesDogs user` would compile
## to the equivalent of:
##
## if enablePets then
## likesDogs user
## else
## False
##
## In Roc, however, `&&` and `||` are not special. They work the same way as
## other functions. Conditionals like `if` and `when` have a performance cost,
## and sometimes calling a function like `likesDogs user` can be faster across
## the board than doing an `if` to decide whether to skip calling it.
##
## (Naturally, if you expect the `if` to improve performance, you can always add
## one explicitly!)
and : Bool, Bool -> Bool
## Returns `True` when given `True` for either argument, and `False` only when given `False` and `False`.
##
## `a || b` is shorthand for `Bool.or a b`.
##
## >>> True || True
##
## >>> True || False
##
## >>> False || True
##
## >>> False || False
##
## ## Performance Notes
##
## In some languages, `&&` and `||` are special-cased in the compiler to skip
## evaluating the expression after the operator under certain circumstances.
## In Roc, this is not the case. See the performance notes for [Bool.and] for details.
or : Bool, Bool -> Bool
# xor : Bool, Bool -> Bool # currently unimplemented
## Returns `False` when given `True`, and vice versa.
not : Bool -> Bool
## Returns `True` if the two values are *structurally equal*, and `False` otherwise.
##
## `a == b` is shorthand for `Bool.isEq a b`
##
## Structural equality works as follows:
##
## 1. Tags are equal if they have the same tag name, and also their contents (if any) are equal.
## 2. Records are equal if all their fields are equal.
## 3. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal.
## 4. [Num](Num#Num) values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `False`. See `Num.isNaN` for more about *NaN*.
##
## Note that `isEq` takes `'val` instead of `val`, which means `isEq` does not
## accept arguments whose types contain functions.
isEq : a, a -> Bool
## Calls [isEq] on the given values, then calls [not] on the result.
##
## `a != b` is shorthand for `Bool.isNotEq a b`
##
## Note that `isNotEq` takes `'val` instead of `val`, which means `isNotEq` does not
## accept arguments whose types contain functions.
isNotEq : a, a -> Bool

View File

@ -20,6 +20,57 @@ interface Dict
Bool.{ Bool }
]
## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you can associate keys with values.
##
## ### Inserting
##
## The most basic way to use a dictionary is to start with an empty one and then:
## 1. Call [Dict.insert] passing a key and a value, to associate that key with that value in the dictionary.
## 2. Later, call [Dict.get] passing the same key as before, and it will return the value you stored.
##
## Here's an example of a dictionary which uses a city's name as the key, and its population as the associated value.
##
## populationByCity =
## Dict.empty
## |> Dict.insert "London" 8_961_989
## |> Dict.insert "Philadelphia" 1_603_797
## |> Dict.insert "Shanghai" 24_870_895
## |> Dict.insert "Delhi" 16_787_941
## |> Dict.insert "Amsterdam" 872_680
##
## ### Accessing keys or values
##
## We can use [Dict.keys] and [Dict.values] functions to get only the keys or only the values.
##
## You may notice that these lists have the same order as the original insertion order. This will be true if
## all you ever do is [insert] and [get] operations on the dictionary, but [remove] operations can change this order.
## Let's see how that looks.
##
## ### Removing
##
## We can remove an element from the dictionary, like so:
##
## populationByCity
## |> Dict.remove "Philadelphia"
## |> Dict.keys
## ==
## [ "London", "Amsterdam", "Shanghai", "Delhi" ]
##
## Notice that the order changed! Philadelphia has been not only removed from the list, but Amsterdam - the last
## entry we inserted - has been moved into the spot where Philadelphia was previously. This is exactly what
## [Dict.remove] does: it removes an element and moves the most recent insertion into the vacated spot.
##
## This move is done as a performance optimization, and it lets [remove] have
## [constant time complexity](https://en.wikipedia.org/wiki/Time_complexity#Constant_time). ##
##
## ### Equality
##
## When comparing two dictionaries for equality, they are `==` only if their both their contents and their
## orderings match. This preserves the property that if `dict1 == dict2`, you should be able to rely on
## `fn dict1 == fn dict2` also being `True`, even if `fn` relies on the dictionary's ordering.
## An empty dictionary.
empty : Dict k v
single : k, v -> Dict k v
get : Dict k v, k -> Result v [ KeyNotFound ]*
@ -28,7 +79,11 @@ insert : Dict k v, k, v -> Dict k v
len : Dict k v -> Nat
remove : Dict k v, k -> Dict k v
contains : Dict k v, k -> Bool
## Returns a [List] of the dictionary's keys.
keys : Dict k v -> List k
## Returns a [List] of the dictionary's values.
values : Dict k v -> List v
union : Dict k v, Dict k v -> Dict k v
intersection : Dict k v, Dict k v -> Dict k v

View File

@ -56,6 +56,149 @@ interface List
Bool.{ Bool }
]
## Types
## A sequential list of values.
##
## >>> [ 1, 2, 3 ] # a list of numbers
## >>> [ "a", "b", "c" ] # a list of strings
## >>> [ [ 1.1 ], [], [ 2.2, 3.3 ] ] # a list of lists of numbers
##
## The maximum size of a [List] is limited by the amount of heap memory available
## to the current process. If there is not enough memory available, attempting to
## create the list could crash. (On Linux, where [overcommit](https://www.etalabs.net/overcommit.html)
## is normally enabled, not having enough memory could result in the list appearing
## to be created just fine, but then crashing later.)
##
## > The theoretical maximum length for a list created in Roc is half of
## > `Num.maxNat`. Attempting to create a list bigger than that
## > in Roc code will always fail, although in practice it is likely to fail
## > at much smaller lengths due to insufficient memory being available.
##
## ## Performance Details
##
## Under the hood, a list is a record containing a `len : Nat` field as well
## as a pointer to a reference count and a flat array of bytes. Unique lists
## store a capacity #Nat instead of a reference count.
##
## ## Shared Lists
##
## Shared lists are [reference counted](https://en.wikipedia.org/wiki/Reference_counting).
##
## Each time a given list gets referenced, its reference count ("refcount" for short)
## gets incremented. Each time a list goes out of scope, its refcount count gets
## decremented. Once a refcount, has been decremented more times than it has been
## incremented, we know nothing is referencing it anymore, and the list's memory
## will be immediately freed.
##
## Let's look at an example.
##
## ratings = [ 5, 4, 3 ]
##
## { foo: ratings, bar: ratings }
##
## The first line binds the name `ratings` to the list `[ 5, 4, 3 ]`. The list
## begins with a refcount of 1, because so far only `ratings` is referencing it.
##
## The second line alters this refcount. `{ foo: ratings` references
## the `ratings` list, which will result in its refcount getting incremented
## from 0 to 1. Similarly, `bar: ratings }` also references the `ratings` list,
## which will result in its refcount getting incremented from 1 to 2.
##
## Let's turn this example into a function.
##
## getRatings = \first ->
## ratings = [ first, 4, 3 ]
##
## { foo: ratings, bar: ratings }
##
## getRatings 5
##
## At the end of the `getRatings` function, when the record gets returned,
## the original `ratings =` binding has gone out of scope and is no longer
## accessible. (Trying to reference `ratings` outside the scope of the
## `getRatings` function would be an error!)
##
## Since `ratings` represented a way to reference the list, and that way is no
## longer accessible, the list's refcount gets decremented when `ratings` goes
## out of scope. It will decrease from 2 back down to 1.
##
## Putting these together, when we call `getRatings 5`, what we get back is
## a record with two fields, `foo`, and `bar`, each of which refers to the same
## list, and that list has a refcount of 1.
##
## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`:
##
## getRatings = \first ->
## ratings = [ first, 4, 3 ]
##
## { foo: ratings, bar: ratings }
##
## (getRatings 5).bar
##
## Now, when this expression returns, only the `bar` field of the record will
## be returned. This will mean that the `foo` field becomes inaccessible, causing
## the list's refcount to get decremented from 2 to 1. At this point, the list is back
## where it started: there is only 1 reference to it.
##
## Finally let's suppose the final line were changed to this:
##
## List.first (getRatings 5).bar
##
## This call to [List.first] means that even the list in the `bar` field has become
## inaccessible. As such, this line will cause the list's refcount to get
## decremented all the way to 0. At that point, nothing is referencing the list
## anymore, and its memory will get freed.
##
## Things are different if this is a list of lists instead of a list of numbers.
## Let's look at a simpler example using [List.first] - first with a list of numbers,
## and then with a list of lists, to see how they differ.
##
## Here's the example using a list of numbers.
##
## nums = [ 1, 2, 3, 4, 5, 6, 7 ]
##
## first = List.first nums
## last = List.last nums
##
## first
##
## It makes a list, calls [List.first] and [List.last] on it, and then returns `first`.
##
## Here's the equivalent code with a list of lists:
##
## lists = [ [ 1 ], [ 2, 3 ], [], [ 4, 5, 6, 7 ] ]
##
## first = List.first lists
## last = List.last lists
##
## first
##
## TODO explain how in the former example, when we go to free `nums` at the end,
## we can free it immediately because there are no other refcounts. However,
## in the case of `lists`, we have to iterate through the list and decrement
## the refcounts of each of its contained lists - because they, too, have
## refcounts! Importantly, because the first element had its refcount incremented
## because the function returned `first`, that element will actually end up
## *not* getting freed at the end - but all the others will be.
##
## In the `lists` example, `lists = [ ... ]` also creates a list with an initial
## refcount of 1. Separately, it also creates several other lists - each with
## their own refcounts - to go inside that list. (The empty list at the end
## does not use heap memory, and thus has no refcount.)
##
## At the end, we once again call [List.first] on the list, but this time
##
## * Copying small lists (64 elements or fewer) is typically slightly faster than copying small persistent data structures. This is because, at small sizes, persistent data structures tend to be thin wrappers around flat arrays anyway. They don't have any copying advantage until crossing a certain minimum size threshold.
## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, [List.map], [List.walk], and [List.keepIf] would all need to traverse every element in the list and build up the result from scratch. These operations are all
## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, [List.map], [List.keepIf], and [List.set] can all be optimized to perform in-place mutations.
## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way [List] worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using [List] under the hood!
## Check if the list is empty.
##
## >>> List.isEmpty [ 1, 2, 3 ]
##
## >>> List.isEmpty []
isEmpty : List a -> Bool
isEmpty = \list ->
List.len list == 0
@ -63,22 +206,134 @@ isEmpty = \list ->
get : List a, Nat -> Result a [ OutOfBounds ]*
replace : List a, Nat, a -> { list : List a, value : a }
## Replaces the element at the given index with a replacement.
##
## >>> List.set [ "a", "b", "c" ] 1 "B"
##
## If the given index is outside the bounds of the list, returns the original
## list unmodified.
##
## To drop the element at a given index, instead of replacing it, see [List.dropAt].
set : List a, Nat, a -> List a
set = \list, index, value ->
(List.replace list index value).list
## Add a single element to the end of a list.
##
## >>> List.append [ 1, 2, 3 ] 4
##
## >>> [ 0, 1, 2 ]
## >>> |> List.append 3
append : List a, a -> List a
## Add a single element to the beginning of a list.
##
## >>> List.prepend [ 1, 2, 3 ] 0
##
## >>> [ 2, 3, 4 ]
## >>> |> List.prepend 1
prepend : List a, a -> List a
## Returns the length of the list - the number of elements it contains.
##
## One [List] can store up to 2,147,483,648 elements (just over 2 billion), which
## is exactly equal to the highest valid #I32 value. This means the #U32 this function
## returns can always be safely converted to an #I32 without losing any data.
len : List a -> Nat
## Put two lists together.
##
## >>> List.concat [ 1, 2, 3 ] [ 4, 5 ]
##
## >>> [ 0, 1, 2 ]
## >>> |> List.concat [ 3, 4 ]
concat : List a, List a -> List a
## Returns the last element in the list, or `ListWasEmpty` if it was empty.
last : List a -> Result a [ ListWasEmpty ]*
## A list with a single element in it.
##
## This is useful in pipelines, like so:
##
## websites =
## Str.concat domain ".com"
## |> List.single
##
single : a -> List a
## Returns a list with the given length, where every element is the given value.
##
##
repeat : a, Nat -> List a
## Returns the list with its elements reversed.
##
## >>> List.reverse [ 1, 2, 3 ]
reverse : List a -> List a
## Join the given lists together into one list.
##
## >>> List.join [ [ 1, 2, 3 ], [ 4, 5 ], [], [ 6, 7 ] ]
##
## >>> List.join [ [], [] ]
##
## >>> List.join []
join : List (List a) -> List a
contains : List a, a -> Bool
## Build a value using each element in the list.
##
## Starting with a given `state` value, this walks through each element in the
## list from first to last, running a given `step` function on that element
## which updates the `state`. It returns the final `state` at the end.
##
## You can use it in a pipeline:
##
## [ 2, 4, 8 ]
## |> List.walk { start: 0, step: Num.add }
##
## This returns 14 because:
## * `state` starts at 0 (because of `start: 0`)
## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`.
##
## Here is a table of how `state` changes as [List.walk] walks over the elements
## `[ 2, 4, 8 ]` using #Num.add as its `step` function to determine the next `state`.
##
## `state` | `elem` | `step state elem` (`Num.add state elem`)
## --------+--------+-----------------------------------------
## 0 | |
## 0 | 2 | 2
## 2 | 4 | 6
## 6 | 8 | 14
##
## So `state` goes through these changes:
## 1. `0` (because of `start: 0`)
## 2. `1` (because of `Num.add state elem` with `state` = 0 and `elem` = 1
##
## [ 1, 2, 3 ]
## |> List.walk { start: 0, step: Num.sub }
##
## This returns -6 because
##
## Note that in other languages, `walk` is sometimes called `reduce`,
## `fold`, `foldLeft`, or `foldl`.
walk : List elem, state, (state, elem -> state) -> state
## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`,
## `fold`, `foldRight`, or `foldr`.
walkBackwards : List elem, state, (state, elem -> state) -> state
## Same as [List.walk], except you can stop walking early.
##
## ## Performance Details
##
## Compared to [List.walk], this can potentially visit fewer elements (which can
## improve performance) at the cost of making each step take longer.
## However, the added cost to each step is extremely small, and can easily
## be outweighed if it results in skipping even a small number of elements.
##
## As such, it is typically better for performance to use this over [List.walk]
## if returning `Done` earlier than the last element is expected to be common.
walkUntil : List elem, state, (state, elem -> [ Continue state, Stop state ]) -> state
sum : List (Num a) -> Num a
@ -89,40 +344,201 @@ product : List (Num a) -> Num a
product = \list ->
List.walk list 1 Num.mul
## Run the given predicate on each element of the list, returning `True` if
## any of the elements satisfy it.
any : List a, (a -> Bool) -> Bool
## Run the given predicate on each element of the list, returning `True` if
## all of the elements satisfy it.
all : List a, (a -> Bool) -> Bool
## Run the given function on each element of a list, and return all the
## elements for which the function returned `True`.
##
## >>> List.keepIf [ 1, 2, 3, 4 ] (\num -> num > 2)
##
## ## Performance Details
##
## [List.keepIf] always returns a list that takes up exactly the same amount
## of memory as the original, even if its length decreases. This is because it
## can't know in advance exactly how much space it will need, and if it guesses a
## length that's too low, it would have to re-allocate.
##
## (If you want to do an operation like this which reduces the memory footprint
## of the resulting list, you can do two passes over the lis with [List.walk] - one
## to calculate the precise new size, and another to populate the new list.)
##
## If given a unique list, [List.keepIf] will mutate it in place to assemble the appropriate list.
## If that happens, this function will not allocate any new memory on the heap.
## If all elements in the list end up being kept, Roc will return the original
## list unaltered.
##
keepIf : List a, (a -> Bool) -> List a
## Run the given function on each element of a list, and return all the
## elements for which the function returned `False`.
##
## >>> List.dropIf [ 1, 2, 3, 4 ] (\num -> num > 2)
##
## ## Performance Details
##
## `List.dropIf` has the same performance characteristics as [List.keepIf].
## See its documentation for details on those characteristics!
dropIf : List a, (a -> Bool) -> List a
dropIf = \list, predicate ->
List.keepIf list (\e -> Bool.not (predicate e))
## This works like [List.map], except only the transformed values that are
## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped.
##
## >>> List.keepOks [ [ "a", "b" ], [], [], [ "c", "d", "e" ] ] List.last
##
## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
## >>>
## >>> List.keepOks [ "", "a", "bc", "", "d", "ef", "" ]
keepOks : List before, (before -> Result after *) -> List after
## This works like [List.map], except only the transformed values that are
## wrapped in `Err` are kept. Any that are wrapped in `Ok` are dropped.
##
## >>> List.keepErrs [ [ "a", "b" ], [], [], [ "c", "d", "e" ] ] List.last
##
## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
## >>>
## >>> List.keepErrs [ "", "a", "bc", "", "d", "ef", "" ]
keepErrs: List before, (before -> Result * after) -> List after
## Convert each element in the list to something new, by calling a conversion
## function on each of them. Then return a new list of the converted values.
##
## > List.map [ 1, 2, 3 ] (\num -> num + 1)
##
## > List.map [ "", "a", "bc" ] Str.isEmpty
map : List a, (a -> b) -> List b
## 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.
##
## Some languages have a function named `zip`, which does something similar to
## calling [List.map2] passing two lists and `Pair`:
##
## >>> zipped = List.map2 [ "a", "b", "c" ] [ 1, 2, 3 ] Pair
map2 : List a, List b, (a, b -> c) -> List c
## 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
## 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
## This works like [List.map], except it also passes the index
## of the element to the conversion function.
mapWithIndex : List a, (a, Nat -> b) -> List b
## Returns a list of all the integers between one and another,
## including both of the given numbers.
##
## >>> List.range 2 8
range : Int a, Int a -> List (Int a)
sortWith : List a, (a, a -> [ LT, EQ, GT ] ) -> List a
## Sorts a list in ascending order (lowest to highest), using a function which
## specifies a way to represent each element as a number.
##
## To sort in descending order (highest to lowest), use [List.sortDesc] instead.
sortAsc : List (Num a) -> List (Num a)
sortAsc = \list -> List.sortWith list Num.compare
## Sorts a list in descending order (highest to lowest), using a function which
## specifies a way to represent each element as a number.
##
## To sort in ascending order (lowest to highest), use [List.sortAsc] instead.
sortDesc : List (Num a) -> List (Num a)
sortDesc = \list -> List.sortWith list (\a, b -> Num.compare b a)
swap : List a, Nat, Nat -> List a
## Returns the first element in the list, or `ListWasEmpty` if it was empty.
first : List a -> Result a [ ListWasEmpty ]*
## Remove the first element from the list.
##
## Returns the new list (with the removed element missing).
dropFirst : List elem -> List elem
## Remove the last element from the list.
##
## Returns the new list (with the removed element missing).
dropLast : List elem -> List elem
## Returns the given number of elements from the beginning of the list.
##
## >>> List.takeFirst 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ]
##
## If there are fewer elements in the list than the requested number,
## returns the entire list.
##
## >>> List.takeFirst 5 [ 1, 2 ]
##
## To *remove* elements from the beginning of the list, use `List.takeLast`.
##
## To remove elements from both the beginning and end of the list,
## use `List.sublist`.
##
## To split the list into two lists, use `List.split`.
##
## ## Performance Details
##
## When given a Unique list, this runs extremely fast. It sets the list's length
## to the given length value, and frees the leftover elements. This runs very
## slightly faster than `List.takeLast`.
##
## In fact, `List.takeFirst 1 list` runs faster than `List.first list` when given
## a Unique list, because [List.first] returns the first element as well -
## which introduces a conditional bounds check as well as a memory load.
takeFirst : List elem, Nat -> List elem
## Returns the given number of elements from the end of the list.
##
## >>> List.takeLast 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ]
##
## If there are fewer elements in the list than the requested number,
## returns the entire list.
##
## >>> List.takeLast 5 [ 1, 2 ]
##
## To *remove* elements from the end of the list, use `List.takeFirst`.
##
## To remove elements from both the beginning and end of the list,
## use `List.sublist`.
##
## To split the list into two lists, use `List.split`.
##
## ## Performance Details
##
## When given a Unique list, this runs extremely fast. It moves the list's
## pointer to the index at the given length value, updates its length,
## and frees the leftover elements. This runs very nearly as fast as
## `List.takeFirst` on a Unique list.
##
## In fact, `List.takeLast 1 list` runs faster than `List.first list` when given
## a Unique list, because [List.first] returns the first element as well -
## which introduces a conditional bounds check as well as a memory load.
takeLast : List elem, Nat -> List elem
## Drops n elements from the beginning of the list.
drop : List elem, Nat -> List elem
## Drops the element at the given index from the list.
##
## This has no effect if the given index is outside the bounds of the list.
##
## To replace the element at a given index, instead of dropping it, see [List.set].
dropAt : List elem, Nat -> List elem
min : List (Num a) -> Result (Num a) [ ListWasEmpty ]*
@ -163,11 +579,41 @@ maxHelp = \list, initial ->
else
bestSoFar
## Like [List.map], except the transformation function wraps the return value
## in a list. At the end, all the lists get joined together into one list.
##
## You may know a similar function named `concatMap` in other languages.
joinMap : List a, (a -> List b) -> List b
joinMap = \list, mapper ->
List.walk list [] (\state, elem -> List.concat state (mapper elem))
## Returns the first element of the list satisfying a predicate function.
## If no satisfying element is found, an `Err NotFound` is returned.
find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*
## Returns a subsection of the given list, beginning at the `start` index and
## including a total of `len` elements.
##
## If `start` is outside the bounds of the given list, returns the empty list.
##
## >>> List.sublist { start: 4, len: 0 } [ 1, 2, 3 ]
##
## If more elements are requested than exist in the list, returns as many as it can.
##
## >>> List.sublist { start: 2, len: 10 } [ 1, 2, 3, 4, 5 ]
##
## > If you want a sublist which goes all the way to the end of the list, no
## > matter how long the list is, `List.takeLast` can do that more efficiently.
##
## Some languages have a function called **`slice`** which works similarly to this.
sublist : List elem, { start : Nat, len : Nat } -> List elem
intersperse : List elem, elem -> List elem
## Splits the list into two lists, around the given index.
##
## The returned lists are labeled `before` and `others`. The `before` list will
## contain all the elements whose index in the original list was **less than**
## than the given index, # and the `others` list will be all the others. (This
## means if you give an index of 0, the `before` list will be empty and the
## `others` list will have the same elements as the original list.)
split : List elem, Nat -> { before: List elem, others: List elem }

File diff suppressed because it is too large Load Diff

View File

@ -2,38 +2,79 @@ interface Result
exposes [ Result, isOk, isErr, map, mapErr, after, withDefault ]
imports [ Bool.{ Bool } ]
## The result of an operation that could fail: either the operation went
## okay, or else there was an error of some sort.
Result ok err : [ Ok ok, Err err ]
## Return True if the result indicates a success, else return False
##
## >>> Result.isOk (Ok 5)
isOk : Result ok err -> Bool
isOk = \result ->
when result is
Ok _ -> True
Err _ -> False
## Return True if the result indicates a failure, else return False
##
## >>> Result.isErr (Err "uh oh")
isErr : Result ok err -> Bool
isErr = \result ->
when result is
Ok _ -> False
Err _ -> True
## If the result is `Ok`, return the value it holds. Otherwise, return
## the given default value.
##
## >>> Result.withDefault (Ok 7) 42
##
## >>> Result.withDefault (Err "uh oh") 42
withDefault : Result ok err, ok -> ok
withDefault = \result, default ->
when result is
Ok value -> value
Err _ -> default
## If the result is `Ok`, transform the value it holds by running a conversion
## function on it. Then return a new `Ok` holding the transformed value.
##
## (If the result is `Err`, this has no effect. Use [mapErr] to transform an `Err`.)
##
## >>> Result.map (Ok 12) Num.negate
##
## >>> Result.map (Err "yipes!") Num.negate
##
## `map` functions like this are common in Roc, and they all work similarly.
## See for example [List.map], `Set.map`, and `Dict.map`.
map : Result a err, (a -> b) -> Result b err
map = \result, transform ->
when result is
Ok v -> Ok (transform v)
Err e -> Err e
## If the result is `Err`, transform the value it holds by running a conversion
## function on it. Then return a new `Err` holding the transformed value.
##
## (If the result is `Ok`, this has no effect. Use [map] to transform an `Ok`.)
##
## >>> Result.mapErr (Err "yipes!") Str.isEmpty
##
## >>> Result.mapErr (Ok 12) Str.isEmpty
mapErr : Result ok a, (a -> b) -> Result ok b
mapErr = \result, transform ->
when result is
Ok v -> Ok v
Err e -> Err (transform e)
## If the result is `Ok`, transform the entire result by running a conversion
## function on the value the `Ok` holds. Then return that new result.
##
## (If the result is `Err`, this has no effect. Use `afterErr` to transform an `Err`.)
##
## >>> Result.after (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num
##
## >>> Result.after (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num
after : Result a err, (a -> Result b err) -> Result b err
after = \result, transform ->
when result is

View File

@ -16,10 +16,17 @@ interface Set
]
imports [ List, Bool.{ Bool }, Dict.{ values } ]
## An empty set.
empty : Set k
single : k -> Set k
## Make sure never to insert a *NaN* to a [Set]! Because *NaN* is defined to be
## unequal to *NaN*, adding a *NaN* results in an entry that can never be
## retrieved or removed from the [Set].
insert : Set k, k -> Set k
len : Set k -> Nat
## Drops the given element from the set.
remove : Set k, k -> Set k
contains : Set k, k -> Bool

View File

@ -36,6 +36,81 @@ interface Str
]
imports [ Bool.{ Bool }, Result.{ Result } ]
## # Types
##
## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks
## to the basics.
##
## ### Unicode
##
## Unicode can represent text values which span multiple languages, symbols, and emoji.
## Here are some valid Roc strings:
##
## "Roc!"
## "鹏"
## "🕊"
##
## Every Unicode string is a sequence of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster).
## An extended grapheme cluster represents what a person reading a string might
## call a "character" - like "A" or "ö" or "👩‍👩‍👦‍👦".
## Because the term "character" means different things in different areas of
## programming, and "extended grapheme cluster" is a mouthful, in Roc we use the
## term "grapheme" as a shorthand for the more precise "extended grapheme cluster."
##
## You can get the number of graphemes in a string by calling [Str.countGraphemes] on it:
##
## Str.countGraphemes "Roc!"
## Str.countGraphemes "折り紙"
## Str.countGraphemes "🕊"
##
## > The `countGraphemes` function walks through the entire string to get its answer,
## > so if you want to check whether a string is empty, you'll get much better performance
## > by calling `Str.isEmpty myStr` instead of `Str.countGraphemes myStr == 0`.
##
## ### Escape sequences
##
## If you put a `\` in a Roc string literal, it begins an *escape sequence*.
## An escape sequence is a convenient way to insert certain strings into other strings.
## For example, suppose you write this Roc string:
##
## "I took the one less traveled by,\nAnd that has made all the difference."
##
## The `"\n"` in the middle will insert a line break into this string. There are
## other ways of getting a line break in there, but `"\n"` is the most common.
##
## Another way you could insert a newlines is by writing `\u{0x0A}` instead of `\n`.
## That would result in the same string, because the `\u` escape sequence inserts
## [Unicode code points](https://unicode.org/glossary/#code_point) directly into
## the string. The Unicode code point 10 is a newline, and 10 is `0A` in hexadecimal.
## `0x0A` is a Roc hexadecimal literal, and `\u` escape sequences are always
## followed by a hexadecimal literal inside `{` and `}` like this.
##
## As another example, `"R\u{0x6F}c"` is the same string as `"Roc"`, because
## `"\u{0x6F}"` corresponds to the Unicode code point for lowercase `o`. If you
## want to [spice things up a bit](https://en.wikipedia.org/wiki/Metal_umlaut),
## you can write `"R\u{0xF6}c"` as an alternative way to get the string `"Röc"\.
##
## Roc strings also support these escape sequences:
##
## * `\\` - an actual backslash (writing a single `\` always begins an escape sequence!)
## * `\"` - an actual quotation mark (writing a `"` without a `\` ends the string)
## * `\r` - [carriage return](https://en.wikipedia.org/wiki/Carriage_Return)
## * `\t` - [horizontal tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
## * `\v` - [vertical tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
##
## You can also use escape sequences to insert named strings into other strings, like so:
##
## name = "Lee"
## city = "Roctown"
##
## greeting = "Hello there, \(name)! Welcome to \(city)."
##
## Here, `greeting` will become the string `"Hello there, Lee! Welcome to Roctown."`.
## This is known as [string interpolation](https://en.wikipedia.org/wiki/String_interpolation),
## and you can use it as many times as you like inside a string. The name
## between the parentheses must refer to a `Str` value that is currently in
## scope, and it must be a name - it can't be an arbitrary expression like a function call.
Utf8ByteProblem :
@ -50,15 +125,68 @@ Utf8ByteProblem :
Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem }
## Returns `True` if the string is empty, and `False` otherwise.
##
## >>> Str.isEmpty "hi!"
##
## >>> Str.isEmpty ""
isEmpty : Str -> Bool
concat : Str, Str -> Str
## Combine a list of strings into a single string, with a separator
## string in between each.
##
## >>> Str.joinWith [ "one", "two", "three" ] ", "
joinWith : List Str, Str -> Str
## Split a string around a separator.
##
## >>> Str.split "1,2,3" ","
##
## Passing `""` for the separator is not useful; it returns the original string
## wrapped in a list.
##
## >>> Str.split "1,2,3" ""
##
## To split a string into its individual graphemes, use `Str.graphemes`
split : Str, Str -> List Str
repeat : Str, Nat -> Str
## Count the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
## in the string.
##
## Str.countGraphemes "Roc!" # 4
## Str.countGraphemes "七巧板" # 3
## Str.countGraphemes "üïä" # 1
countGraphemes : Str -> Nat
## If the string begins with a [Unicode code point](http://www.unicode.org/glossary/#code_point)
## equal to the given [U32], return `True`. Otherwise return `False`.
##
## If the given [Str] is empty, or if the given [U32] is not a valid
## code point, this will return `False`.
##
## **Performance Note:** This runs slightly faster than [Str.startsWith], so
## if you want to check whether a string begins with something that's representable
## in a single code point, you can use (for example) `Str.startsWithCodePt '鹏'`
## instead of `Str.startsWithCodePt "鹏"`. ('鹏' evaluates to the [U32]
## value `40527`.) This will not work for graphemes which take up multiple code
## points, however; `Str.startsWithCodePt '👩‍👩‍👦‍👦'` would be a compiler error
## because 👩‍👩‍👦‍👦 takes up multiple code points and cannot be represented as a
## single [U32]. You'd need to use `Str.startsWithCodePt "🕊"` instead.
startsWithCodePt : Str, U32 -> Bool
## Return a [List] of the string's [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit).
## (To split the string into a [List] of smaller [Str] values instead of [U8] values,
## see [Str.split].)
##
## >>> Str.toUtf8 "👩‍👩‍👦‍👦"
##
## >>> Str.toUtf8 "Roc"
##
## >>> Str.toUtf8 "鹏"
##
## >>> Str.toUtf8 "🐦"
toUtf8 : Str -> List U8
# fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]*
@ -70,6 +198,8 @@ fromUtf8Range : List U8, { start : Nat, count : Nat } -> Result Str [ BadUtf8 Ut
startsWith : Str, Str -> Bool
endsWith : Str, Str -> Bool
## Return the string with any blank spaces removed from both the beginning
## as well as the end.
trim : Str -> Str
trimLeft : Str -> Str
trimRight : Str -> Str

View File

@ -269,7 +269,9 @@ pub const NUM_ATAN: IntrinsicName = float_intrinsic!("roc_builtins.num.atan");
pub const NUM_IS_FINITE: IntrinsicName = float_intrinsic!("roc_builtins.num.is_finite");
pub const NUM_POW_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.pow_int");
pub const NUM_DIV_CEIL: IntrinsicName = int_intrinsic!("roc_builtins.num.div_ceil");
pub const NUM_ROUND: IntrinsicName = float_intrinsic!("roc_builtins.num.round");
pub const NUM_ROUND_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f32");
pub const NUM_ROUND_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f64");
pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16";
pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32";
@ -345,6 +347,7 @@ pub const LIST_REPLACE_IN_PLACE: &str = "roc_builtins.list.replace_in_place";
pub const LIST_ANY: &str = "roc_builtins.list.any";
pub const LIST_ALL: &str = "roc_builtins.list.all";
pub const LIST_FIND_UNSAFE: &str = "roc_builtins.list.find_unsafe";
pub const LIST_IS_UNIQUE: &str = "roc_builtins.list.is_unique";
pub const DEC_FROM_STR: &str = "roc_builtins.dec.from_str";
pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64";

View File

@ -14,8 +14,6 @@ roc_module = { path = "../module" }
roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
ven_graph = { path = "../../vendor/pathfinding" }
bumpalo = { version = "3.8.0", features = ["collections"] }
static_assertions = "1.1.0"
bitvec = "1"

View File

@ -1,8 +1,9 @@
use crate::env::Env;
use crate::procedure::References;
use crate::scope::Scope;
use roc_collections::{ImMap, MutSet, SendMap, VecMap, VecSet};
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_module::symbol::Symbol;
use roc_parse::ast::{AssignedField, ExtractSpaces, Pattern, Tag, TypeAnnotation, TypeHeader};
use roc_problem::can::ShadowKind;
use roc_region::all::{Loc, Region};
@ -20,6 +21,27 @@ pub struct Annotation {
pub aliases: SendMap<Symbol, Alias>,
}
impl Annotation {
pub fn add_to(
&self,
aliases: &mut VecMap<Symbol, Alias>,
references: &mut References,
introduced_variables: &mut IntroducedVariables,
) {
for symbol in self.references.iter() {
references.insert_type_lookup(*symbol);
}
introduced_variables.union(&self.introduced_variables);
for (name, alias) in self.aliases.iter() {
if !aliases.contains_key(name) {
aliases.insert(*name, alias.clone());
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum NamedOrAbleVariable<'a> {
Named(&'a NamedVariable),
@ -298,8 +320,7 @@ fn make_apply_symbol(
/// For example, in `[ A Age U8, B Str {} ]`, there are three type definition references - `Age`,
/// `U8`, and `Str`.
pub fn find_type_def_symbols(
module_id: ModuleId,
ident_ids: &mut IdentIds,
scope: &mut Scope,
initial_annotation: &roc_parse::ast::TypeAnnotation,
) -> Vec<Symbol> {
use roc_parse::ast::TypeAnnotation::*;
@ -312,9 +333,8 @@ pub fn find_type_def_symbols(
match annotation {
Apply(_module_name, ident, arguments) => {
let ident: Ident = (*ident).into();
let ident_id = ident_ids.get_or_insert(&ident);
let symbol = scope.scopeless_symbol(&ident, Region::zero());
let symbol = Symbol::new(module_id, ident_id);
result.push(symbol);
for t in arguments.iter() {
@ -508,51 +528,28 @@ fn can_annotation_help(
return error;
}
let is_structural = alias.kind == AliasKind::Structural;
if is_structural {
let mut type_var_to_arg = Vec::new();
let mut type_var_to_arg = Vec::new();
for (loc_var, arg_ann) in alias.type_variables.iter().zip(args) {
let name = loc_var.value.0.clone();
type_var_to_arg.push((name, arg_ann));
}
let mut lambda_set_variables =
Vec::with_capacity(alias.lambda_set_variables.len());
for _ in 0..alias.lambda_set_variables.len() {
let lvar = var_store.fresh();
introduced_variables.insert_lambda_set(lvar);
lambda_set_variables.push(LambdaSet(Type::Variable(lvar)));
}
Type::DelayedAlias(AliasCommon {
symbol,
type_arguments: type_var_to_arg,
lambda_set_variables,
})
} else {
let (type_arguments, lambda_set_variables, actual) =
instantiate_and_freshen_alias_type(
var_store,
introduced_variables,
&alias.type_variables,
args,
&alias.lambda_set_variables,
alias.typ.clone(),
);
Type::Alias {
symbol,
type_arguments,
lambda_set_variables,
actual: Box::new(actual),
kind: alias.kind,
}
for (_, arg_ann) in alias.type_variables.iter().zip(args) {
type_var_to_arg.push(arg_ann);
}
let mut lambda_set_variables =
Vec::with_capacity(alias.lambda_set_variables.len());
for _ in 0..alias.lambda_set_variables.len() {
let lvar = var_store.fresh();
introduced_variables.insert_lambda_set(lvar);
lambda_set_variables.push(LambdaSet(Type::Variable(lvar)));
}
Type::DelayedAlias(AliasCommon {
symbol,
type_arguments: type_var_to_arg,
lambda_set_variables,
})
}
None => Type::Apply(symbol, args, region),
}
@ -620,20 +617,20 @@ fn can_annotation_help(
let var_name = Lowercase::from(var);
if let Some(var) = introduced_variables.var_by_name(&var_name) {
vars.push((var_name.clone(), Type::Variable(var)));
vars.push(Type::Variable(var));
lowercase_vars.push(Loc::at(loc_var.region, (var_name, var)));
} else {
let var = var_store.fresh();
introduced_variables
.insert_named(var_name.clone(), Loc::at(loc_var.region, var));
vars.push((var_name.clone(), Type::Variable(var)));
vars.push(Type::Variable(var));
lowercase_vars.push(Loc::at(loc_var.region, (var_name, var)));
}
}
let alias_args = vars.iter().map(|(_, v)| v.clone()).collect::<Vec<_>>();
let alias_args = vars.clone();
let alias_actual = if let Type::TagUnion(tags, ext) = inner_type {
let rec_var = var_store.fresh();
@ -692,8 +689,6 @@ fn can_annotation_help(
let alias = scope.lookup_alias(symbol).unwrap();
local_aliases.insert(symbol, alias.clone());
// Type::Alias(symbol, vars, Box::new(alias.typ.clone()))
if vars.is_empty() && env.home == symbol.module_id() {
let actual_var = var_store.fresh();
introduced_variables.insert_host_exposed_alias(symbol, actual_var);
@ -1045,26 +1040,35 @@ pub fn instantiate_and_freshen_alias_type(
pub fn freshen_opaque_def(
var_store: &mut VarStore,
opaque: &Alias,
) -> (Vec<(Lowercase, Type)>, Vec<LambdaSet>, Type) {
) -> (Vec<Variable>, Vec<LambdaSet>, Type) {
debug_assert!(opaque.kind == AliasKind::Opaque);
let fresh_arguments = opaque
let fresh_variables: Vec<Variable> = opaque
.type_variables
.iter()
.map(|_| Type::Variable(var_store.fresh()))
.map(|_| var_store.fresh())
.collect();
// TODO this gets ignored; is that a problem
let fresh_type_arguments = fresh_variables
.iter()
.copied()
.map(Type::Variable)
.collect();
// NB: We don't introduce the fresh variables here, we introduce them during constraint gen.
// NB: If there are bugs, check whether this is a problem!
let mut introduced_variables = IntroducedVariables::default();
instantiate_and_freshen_alias_type(
let (_fresh_type_arguments, fresh_lambda_set, fresh_type) = instantiate_and_freshen_alias_type(
var_store,
&mut introduced_variables,
&opaque.type_variables,
fresh_arguments,
fresh_type_arguments,
&opaque.lambda_set_variables,
opaque.typ.clone(),
)
);
(fresh_variables, fresh_lambda_set, fresh_type)
}
fn insertion_sort_by<T, F>(arr: &mut [T], mut compare: F)

View File

@ -150,6 +150,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_ANY => list_any,
LIST_ALL => list_all,
LIST_FIND => list_find,
LIST_IS_UNIQUE => list_is_unique,
DICT_LEN => dict_len,
DICT_EMPTY => dict_empty,
DICT_SINGLE => dict_single,
@ -2506,7 +2507,10 @@ fn list_take_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
let get_sub = RunLowLevel {
op: LowLevel::NumSubWrap,
args: vec![(len_var, get_list_len), (len_var, Var(Symbol::ARG_2))],
args: vec![
(len_var, get_list_len.clone()),
(len_var, Var(Symbol::ARG_2)),
],
ret_var: len_var,
};
@ -2516,7 +2520,7 @@ fn list_take_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
branches: vec![(
no_region(RunLowLevel {
op: LowLevel::NumGt,
args: vec![(len_var, get_sub.clone()), (len_var, zero.clone())],
args: vec![(len_var, get_list_len), (len_var, Var(Symbol::ARG_2))],
ret_var: bool_var,
}),
no_region(get_sub),
@ -3770,6 +3774,11 @@ fn list_find(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// List.isUnique : List * -> Bool
fn list_is_unique(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::ListIsUnique, var_store)
}
/// Dict.len : Dict * * -> Nat
fn dict_len(symbol: Symbol, var_store: &mut VarStore) -> Def {
let arg1_var = var_store.fresh();

View File

@ -1,5 +1,6 @@
use crate::abilities::MemberVariables;
use crate::annotation::canonicalize_annotation;
use crate::annotation::find_type_def_symbols;
use crate::annotation::IntroducedVariables;
use crate::env::Env;
use crate::expr::AnnotatedMark;
@ -53,7 +54,7 @@ pub struct Annotation {
pub(crate) struct CanDefs {
defs: Vec<Option<Def>>,
def_ordering: DefOrdering,
pub(crate) abilities_in_scope: Vec<Symbol>,
aliases: VecMap<Symbol, Alias>,
}
@ -191,17 +192,12 @@ fn sort_type_defs_before_introduction(
}
// find the strongly connected components and their relations
let nodes: Vec<_> = (0..capacity as u32).collect();
let mut output = Vec::with_capacity(capacity);
for group in matrix.strongly_connected_components(&nodes).groups() {
for index in group.iter_ones() {
output.push(symbols[index])
}
}
output
matrix
.strongly_connected_components_all()
.groups()
.flat_map(|group| group.iter_ones())
.map(|index| symbols[index])
.collect()
}
#[inline(always)]
@ -277,12 +273,7 @@ pub(crate) fn canonicalize_defs<'a>(
ann,
kind,
} => {
let referenced_symbols = crate::annotation::find_type_def_symbols(
env.home,
// TODO IDENT_IDS
&mut scope.ident_ids,
&ann.value,
);
let referenced_symbols = find_type_def_symbols(scope, &ann.value);
referenced_type_symbols.insert(name.value, referenced_symbols);
@ -295,12 +286,7 @@ pub(crate) fn canonicalize_defs<'a>(
// Add the referenced type symbols of each member function. We need to make
// sure those are processed first before we resolve the whole ability
// definition.
referenced_symbols.extend(crate::annotation::find_type_def_symbols(
env.home,
// TODO IDENT_IDS
&mut scope.ident_ids,
&member.typ.value,
));
referenced_symbols.extend(find_type_def_symbols(scope, &member.typ.value));
}
referenced_type_symbols.insert(name.value, referenced_symbols);
@ -532,6 +518,7 @@ pub(crate) fn canonicalize_defs<'a>(
CanDefs {
defs,
def_ordering,
abilities_in_scope,
// The result needs a thread-safe `SendMap`
aliases,
},
@ -775,8 +762,11 @@ pub(crate) fn sort_can_defs(
mut defs,
def_ordering,
aliases,
abilities_in_scope,
} = defs;
output.abilities_in_scope = abilities_in_scope;
for (symbol, alias) in aliases.into_iter() {
output.aliases.insert(symbol, alias);
}
@ -795,14 +785,10 @@ pub(crate) fn sort_can_defs(
};
}
let nodes: Vec<_> = (0..defs.len() as u32).collect();
// We first perform SCC based on any reference, both variable usage and calls
// considering both value definitions and function bodies. This will spot any
// recursive relations between any 2 definitions.
let sccs = def_ordering
.references
.strongly_connected_components(&nodes);
let sccs = def_ordering.references.strongly_connected_components_all();
let mut declarations = Vec::new();
@ -843,10 +829,9 @@ pub(crate) fn sort_can_defs(
// boom = \{} -> boom {}
//
// In general we cannot spot faulty recursion (halting problem) so this is our best attempt
let nodes: Vec<_> = group.iter_ones().map(|v| v as u32).collect();
let direct_sccs = def_ordering
.direct_references
.strongly_connected_components(&nodes);
.strongly_connected_components_subset(group);
let declaration = if direct_sccs.groups().count() == 1 {
// all defs are part of the same direct cycle, that is invalid!
@ -975,17 +960,6 @@ fn single_can_def(
}
}
fn add_annotation_aliases(
type_annotation: &crate::annotation::Annotation,
aliases: &mut VecMap<Symbol, Alias>,
) {
for (name, alias) in type_annotation.aliases.iter() {
if !aliases.contains_key(name) {
aliases.insert(*name, alias.clone());
}
}
}
// Functions' references don't count in defs.
// See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its
// parent commit for the bug this fixed!
@ -1038,16 +1012,11 @@ fn canonicalize_pending_value_def<'a>(
);
// Record all the annotation's references in output.references.lookups
for symbol in type_annotation.references.iter() {
output.references.insert_type_lookup(*symbol);
}
add_annotation_aliases(&type_annotation, aliases);
output
.introduced_variables
.union(&type_annotation.introduced_variables);
type_annotation.add_to(
aliases,
&mut output.references,
&mut output.introduced_variables,
);
pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var);
@ -1140,15 +1109,11 @@ fn canonicalize_pending_value_def<'a>(
);
// Record all the annotation's references in output.references.lookups
for symbol in type_annotation.references.iter() {
output.references.insert_type_lookup(*symbol);
}
add_annotation_aliases(&type_annotation, aliases);
output
.introduced_variables
.union(&type_annotation.introduced_variables);
type_annotation.add_to(
aliases,
&mut output.references,
&mut output.introduced_variables,
);
canonicalize_pending_body(
env,
@ -1596,8 +1561,7 @@ fn correct_mutual_recursive_type_alias<'a>(
let mut solved_aliases = bitvec::vec::BitVec::<usize>::repeat(false, capacity);
let group: Vec<_> = (0u32..capacity as u32).collect();
let sccs = matrix.strongly_connected_components(&group);
let sccs = matrix.strongly_connected_components_all();
// scratchpad to store aliases that are modified in the current iteration.
// Only used when there is are more than one alias in a group. See below why
@ -1788,7 +1752,10 @@ fn make_tag_union_of_alias_recursive<'a>(
let made_recursive = make_tag_union_recursive_help(
env,
Loc::at(alias.header_region(), (alias_name, &alias_args)),
Loc::at(
alias.header_region(),
(alias_name, alias_args.iter().map(|ta| &ta.1)),
),
alias.region,
others,
&mut alias.typ,
@ -1835,12 +1802,12 @@ enum MakeTagUnionRecursive {
/// ```
///
/// When `Err` is returned, a problem will be added to `env`.
fn make_tag_union_recursive_help<'a>(
fn make_tag_union_recursive_help<'a, 'b>(
env: &mut Env<'a>,
recursive_alias: Loc<(Symbol, &[(Lowercase, Type)])>,
recursive_alias: Loc<(Symbol, impl Iterator<Item = &'b Type>)>,
region: Region,
others: Vec<Symbol>,
typ: &mut Type,
typ: &'b mut Type,
var_store: &mut VarStore,
can_report_cyclic_error: &mut bool,
) -> MakeTagUnionRecursive {
@ -1852,7 +1819,7 @@ fn make_tag_union_recursive_help<'a>(
match typ {
Type::TagUnion(tags, ext) => {
let recursion_variable = var_store.fresh();
let type_arguments = args.iter().map(|(_, t)| t.clone()).collect::<Vec<_>>();
let type_arguments: Vec<_> = args.into_iter().cloned().collect();
let mut pending_typ =
Type::RecursiveTagUnion(recursion_variable, tags.to_vec(), ext.clone());
@ -1890,7 +1857,7 @@ fn make_tag_union_recursive_help<'a>(
// try to make `actual` recursive
make_tag_union_recursive_help(
env,
Loc::at_zero((symbol, type_arguments)),
Loc::at_zero((symbol, type_arguments.iter())),
region,
others,
actual,

View File

@ -5,7 +5,7 @@ use crate::pattern::Pattern;
use crate::scope::Scope;
use roc_collections::{SendMap, VecSet};
use roc_module::called_via::CalledVia;
use roc_module::ident::{Lowercase, TagName};
use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_types::subs::{ExhaustiveMark, RedundantMark, VarStore, Variable};
@ -184,7 +184,6 @@ fn build_effect_always(
let effect_a = build_effect_opaque(
effect_symbol,
"a",
var_a,
Type::Variable(var_a),
var_store,
@ -362,7 +361,6 @@ fn build_effect_map(
let effect_a = build_effect_opaque(
effect_symbol,
"a",
var_a,
Type::Variable(var_a),
var_store,
@ -371,7 +369,6 @@ fn build_effect_map(
let effect_b = build_effect_opaque(
effect_symbol,
"b",
var_b,
Type::Variable(var_b),
var_store,
@ -515,7 +512,6 @@ fn build_effect_after(
let effect_a = build_effect_opaque(
effect_symbol,
"a",
var_a,
Type::Variable(var_a),
var_store,
@ -524,7 +520,6 @@ fn build_effect_after(
let effect_b = build_effect_opaque(
effect_symbol,
"b",
var_b,
Type::Variable(var_b),
var_store,
@ -758,7 +753,6 @@ fn build_effect_forever(
let effect_a = build_effect_opaque(
effect_symbol,
"a",
var_a,
Type::Variable(var_a),
var_store,
@ -767,7 +761,6 @@ fn build_effect_forever(
let effect_b = build_effect_opaque(
effect_symbol,
"b",
var_b,
Type::Variable(var_b),
var_store,
@ -985,7 +978,6 @@ fn build_effect_loop(
let effect_b = build_effect_opaque(
effect_symbol,
"b",
var_b,
Type::Variable(var_b),
var_store,
@ -1017,7 +1009,7 @@ fn build_effect_loop(
Type::Alias {
symbol: effect_symbol,
type_arguments: vec![("a".into(), state_type)],
type_arguments: vec![state_type],
lambda_set_variables: vec![roc_types::types::LambdaSet(Type::Variable(
closure_var,
))],
@ -1440,7 +1432,6 @@ pub fn build_effect_actual(a_type: Type, var_store: &mut VarStore) -> Type {
/// Effect a := {} -> a
fn build_effect_opaque(
effect_symbol: Symbol,
a_name: &str,
a_var: Variable,
a_type: Type,
var_store: &mut VarStore,
@ -1457,7 +1448,7 @@ fn build_effect_opaque(
Type::Alias {
symbol: effect_symbol,
type_arguments: vec![(a_name.into(), Type::Variable(a_var))],
type_arguments: vec![Type::Variable(a_var)],
lambda_set_variables: vec![roc_types::types::LambdaSet(Type::Variable(closure_var))],
actual: Box::new(actual),
kind: AliasKind::Opaque,
@ -1466,7 +1457,7 @@ fn build_effect_opaque(
fn build_fresh_opaque_variables(
var_store: &mut VarStore,
) -> (Box<Type>, Vec<(Lowercase, Type)>, Vec<LambdaSet>) {
) -> (Box<Type>, Vec<Variable>, Vec<LambdaSet>) {
let closure_var = var_store.fresh();
// NB: if there are bugs, check whether not introducing variables is a problem!
@ -1478,7 +1469,7 @@ fn build_fresh_opaque_variables(
Box::new(Type::Variable(closure_var)),
Box::new(Type::Variable(a_var)),
);
let type_arguments = vec![("a".into(), Type::Variable(a_var))];
let type_arguments = vec![a_var];
let lambda_set_variables = vec![roc_types::types::LambdaSet(Type::Variable(closure_var))];
(Box::new(actual), type_arguments, lambda_set_variables)

View File

@ -77,7 +77,7 @@ impl<'a> Env<'a> {
// You can do qualified lookups on your own module, e.g.
// if I'm in the Foo module, I can do a `Foo.bar` lookup.
if module_id == self.home {
match scope.ident_ids.get_id(&ident) {
match scope.locals.ident_ids.get_id(&ident) {
Some(ident_id) => {
let symbol = Symbol::new(module_id, ident_id);
@ -96,6 +96,7 @@ impl<'a> Env<'a> {
region,
},
scope
.locals
.ident_ids
.ident_strs()
.map(|(_, string)| string.into())

View File

@ -30,6 +30,7 @@ pub struct Output {
pub introduced_variables: IntroducedVariables,
pub aliases: VecMap<Symbol, Alias>,
pub non_closures: VecSet<Symbol>,
pub abilities_in_scope: Vec<Symbol>,
}
impl Output {
@ -190,7 +191,7 @@ pub enum Expr {
// for the expression from the opaque definition. `type_arguments` is something like
// [(n, fresh1)], and `specialized_def_type` becomes "[ Id U64 fresh1 ]".
specialized_def_type: Box<Type>,
type_arguments: Vec<(Lowercase, Type)>,
type_arguments: Vec<Variable>,
lambda_set_variables: Vec<LambdaSet>,
},
@ -356,7 +357,7 @@ pub struct Field {
pub loc_expr: Box<Loc<Expr>>,
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Recursive {
NotRecursive = 0,
Recursive = 1,

View File

@ -1,4 +1,5 @@
use crate::abilities::AbilitiesStore;
use crate::annotation::canonicalize_annotation;
use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
use crate::effect_module::HostedGeneratedFunctions;
use crate::env::Env;
@ -11,7 +12,7 @@ use roc_collections::{MutMap, SendMap, VecSet};
use roc_module::ident::Ident;
use roc_module::ident::Lowercase;
use roc_module::symbol::{IdentIds, IdentIdsByModule, ModuleId, ModuleIds, Symbol};
use roc_parse::ast;
use roc_parse::ast::{self, TypeAnnotation};
use roc_parse::header::HeaderFor;
use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError};
@ -49,6 +50,7 @@ pub struct ModuleOutput {
pub problems: Vec<Problem>,
pub referenced_values: VecSet<Symbol>,
pub referenced_types: VecSet<Symbol>,
pub symbols_from_requires: Vec<(Loc<Symbol>, Loc<Type>)>,
pub scope: Scope,
}
@ -156,16 +158,17 @@ fn has_no_implementation(expr: &Expr) -> bool {
// TODO trim these down
#[allow(clippy::too_many_arguments)]
pub fn canonicalize_module_defs<'a>(
arena: &Bump,
arena: &'a Bump,
loc_defs: &'a [Loc<ast::Def<'a>>],
header_for: &roc_parse::header::HeaderFor,
home: ModuleId,
module_ids: &ModuleIds,
module_ids: &'a ModuleIds,
exposed_ident_ids: IdentIds,
dep_idents: &'a IdentIdsByModule,
aliases: MutMap<Symbol, Alias>,
exposed_imports: MutMap<Ident, (Symbol, Region)>,
exposed_symbols: &VecSet<Symbol>,
symbols_from_requires: &[(Loc<Symbol>, Loc<TypeAnnotation<'a>>)],
var_store: &mut VarStore,
) -> Result<ModuleOutput, RuntimeError> {
let mut can_exposed_imports = MutMap::default();
@ -349,9 +352,37 @@ pub fn canonicalize_module_defs<'a>(
};
match sort_can_defs(&mut env, defs, new_output) {
(Ok(mut declarations), output) => {
(Ok(mut declarations), mut output) => {
use crate::def::Declaration::*;
let symbols_from_requires = symbols_from_requires
.iter()
.map(|(symbol, loc_ann)| {
let ann = canonicalize_annotation(
&mut env,
&mut scope,
&loc_ann.value,
loc_ann.region,
var_store,
&output.abilities_in_scope,
);
ann.add_to(
&mut output.aliases,
&mut output.references,
&mut output.introduced_variables,
);
(
*symbol,
Loc {
value: ann.typ,
region: loc_ann.region,
},
)
})
.collect();
if let GeneratedInfo::Hosted {
effect_symbol,
generated_functions,
@ -404,8 +435,12 @@ pub fn canonicalize_module_defs<'a>(
GeneratedInfo::Hosted { effect_symbol, .. } => {
let symbol = def.pattern_vars.iter().next().unwrap().0;
let ident_id = symbol.ident_id();
let ident =
scope.ident_ids.get_name(ident_id).unwrap().to_string();
let ident = scope
.locals
.ident_ids
.get_name(ident_id)
.unwrap()
.to_string();
let def_annotation = def.annotation.clone().unwrap();
let annotation = crate::annotation::Annotation {
typ: def_annotation.signature,
@ -541,6 +576,7 @@ pub fn canonicalize_module_defs<'a>(
referenced_types,
exposed_imports: can_exposed_imports,
problems: env.problems,
symbols_from_requires,
lookups,
};
@ -561,7 +597,7 @@ fn fix_values_captured_in_closure_def(
}
fn fix_values_captured_in_closure_defs(
defs: &mut Vec<crate::def::Def>,
defs: &mut [crate::def::Def],
no_capture_symbols: &mut VecSet<Symbol>,
) {
// recursive defs cannot capture each other

View File

@ -47,7 +47,7 @@ pub enum Pattern {
// for the expression from the opaque definition. `type_arguments` is something like
// [(n, fresh1)], and `specialized_def_type` becomes "[ Id U64 fresh1 ]".
specialized_def_type: Box<Type>,
type_arguments: Vec<(Lowercase, Type)>,
type_arguments: Vec<Variable>,
lambda_set_variables: Vec<LambdaSet>,
},
RecordDestructure {
@ -288,7 +288,7 @@ pub fn canonicalize_pattern<'a>(
use PatternType::*;
let can_pattern = match pattern {
Identifier(name) => match scope.introduce((*name).into(), region) {
Identifier(name) => match scope.introduce_str(name, region) {
Ok(symbol) => {
output.references.insert_bound(symbol);
@ -541,7 +541,8 @@ pub fn canonicalize_pattern<'a>(
RequiredField(label, loc_guard) => {
// a guard does not introduce the label into scope!
let symbol = scope.ignore(&Ident::from(label));
let symbol =
scope.scopeless_symbol(&Ident::from(label), loc_pattern.region);
let can_guard = canonicalize_pattern(
env,
var_store,

View File

@ -129,8 +129,14 @@ impl ReferenceMatrix {
TopologicalSort::Groups { groups }
}
/// Get the strongly-connected components of the set of input nodes.
pub fn strongly_connected_components(&self, nodes: &[u32]) -> Sccs {
/// Get the strongly-connected components all nodes in the matrix
pub fn strongly_connected_components_all(&self) -> Sccs {
let bitvec = BitVec::repeat(true, self.length);
self.strongly_connected_components_subset(&bitvec)
}
/// Get the strongly-connected components of a set of input nodes.
pub fn strongly_connected_components_subset(&self, nodes: &BitSlice) -> Sccs {
let mut params = Params::new(self.length, nodes);
'outer: loop {
@ -176,15 +182,15 @@ struct Params {
p: Vec<u32>,
s: Vec<u32>,
scc: Sccs,
scca: Vec<u32>,
scca: BitVec,
}
impl Params {
fn new(length: usize, group: &[u32]) -> Self {
fn new(length: usize, group: &BitSlice) -> Self {
let mut preorders = vec![Preorder::Removed; length];
for value in group {
preorders[*value as usize] = Preorder::Empty;
for index in group.iter_ones() {
preorders[index] = Preorder::Empty;
}
Self {
@ -196,7 +202,7 @@ impl Params {
matrix: ReferenceMatrix::new(length),
components: 0,
},
scca: Vec::new(),
scca: BitVec::repeat(false, length),
}
}
}
@ -210,7 +216,7 @@ fn recurse_onto(length: usize, bitvec: &BitVec, v: usize, params: &mut Params) {
params.p.push(v as u32);
for w in bitvec[v * length..][..length].iter_ones() {
if !params.scca.contains(&(w as u32)) {
if !params.scca[w] {
match params.preorders[w] {
Preorder::Filled(pw) => loop {
let index = *params.p.last().unwrap();
@ -241,7 +247,7 @@ fn recurse_onto(length: usize, bitvec: &BitVec, v: usize, params: &mut Params) {
.scc
.matrix
.set_row_col(params.scc.components, node as usize, true);
params.scca.push(node);
params.scca.set(node as usize, true);
params.preorders[node as usize] = Preorder::Removed;
if node as usize == v {
break;

View File

@ -1,17 +1,17 @@
use roc_collections::{MutSet, SmallStringInterner, VecMap};
use roc_collections::VecMap;
use roc_module::ident::{Ident, Lowercase};
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_module::symbol::{IdentId, IdentIds, ModuleId, Symbol};
use roc_problem::can::RuntimeError;
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::subs::Variable;
use roc_types::types::{Alias, AliasKind, Type};
use crate::abilities::AbilitiesStore;
use bitvec::vec::BitVec;
#[derive(Clone, Debug)]
pub struct Scope {
idents: IdentStore,
/// The type aliases currently in scope
pub aliases: VecMap<Symbol, Alias>,
@ -22,94 +22,46 @@ pub struct Scope {
/// unqualified idents into Symbols.
home: ModuleId,
pub ident_ids: IdentIds,
/// The first `exposed_ident_count` identifiers are exposed
exposed_ident_count: usize,
}
fn add_aliases(var_store: &mut VarStore) -> VecMap<Symbol, Alias> {
use roc_types::solved_types::{BuiltinAlias, FreeVars};
/// Identifiers that are imported (and introduced in the header)
imports: Vec<(Ident, Symbol, Region)>,
let solved_aliases = roc_types::builtin_aliases::aliases();
let mut aliases = VecMap::default();
for (symbol, builtin_alias) in solved_aliases {
let BuiltinAlias {
region,
vars,
typ,
kind,
} = builtin_alias;
let mut free_vars = FreeVars::default();
let typ = roc_types::solved_types::to_type(&typ, &mut free_vars, var_store);
let mut variables = Vec::new();
// make sure to sort these variables to make them line up with the type arguments
let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect();
type_variables.sort();
for (loc_name, (_, var)) in vars.iter().zip(type_variables) {
variables.push(Loc::at(loc_name.region, (loc_name.value.clone(), var)));
}
let alias = Alias {
region,
typ,
lambda_set_variables: Vec::new(),
recursion_variables: MutSet::default(),
type_variables: variables,
kind,
};
aliases.insert(symbol, alias);
}
aliases
/// Identifiers that are in scope, and defined in the current module
pub locals: ScopedIdentIds,
}
impl Scope {
pub fn new(home: ModuleId, initial_ident_ids: IdentIds) -> Scope {
let imports = Symbol::default_in_scope()
.into_iter()
.map(|(a, (b, c))| (a, b, c))
.collect();
Scope {
home,
exposed_ident_count: initial_ident_ids.len(),
ident_ids: initial_ident_ids,
idents: IdentStore::new(),
locals: ScopedIdentIds::from_ident_ids(home, initial_ident_ids),
aliases: VecMap::default(),
// TODO(abilities): default abilities in scope
abilities_store: AbilitiesStore::default(),
}
}
pub fn new_with_aliases(
home: ModuleId,
var_store: &mut VarStore,
initial_ident_ids: IdentIds,
) -> Scope {
Scope {
home,
exposed_ident_count: initial_ident_ids.len(),
ident_ids: initial_ident_ids,
idents: IdentStore::new(),
aliases: add_aliases(var_store),
// TODO(abilities): default abilities in scope
abilities_store: AbilitiesStore::default(),
imports,
}
}
pub fn lookup(&self, ident: &Ident, region: Region) -> Result<Symbol, RuntimeError> {
match self.idents.get_symbol(ident) {
Some(symbol) => Ok(symbol),
None => {
use ContainsIdent::*;
match self.scope_contains_ident(ident.as_str()) {
InScope(symbol, _) => Ok(symbol),
NotInScope(_) | NotPresent => {
let error = RuntimeError::LookupNotInScope(
Loc {
region,
value: ident.clone(),
},
self.idents
.iter_idents()
.map(|v| v.as_ref().into())
.collect(),
self.idents_in_scope().map(|v| v.as_ref().into()).collect(),
);
Err(error)
@ -117,13 +69,11 @@ impl Scope {
}
}
#[cfg(test)]
fn idents_in_scope(&self) -> impl Iterator<Item = Ident> + '_ {
self.idents.iter_idents()
}
let it1 = self.locals.idents_in_scope();
let it2 = self.imports.iter().map(|t| t.0.clone());
pub fn lookup_alias(&self, symbol: Symbol) -> Option<&Alias> {
self.aliases.get(&symbol)
it2.chain(it1)
}
/// Check if there is an opaque type alias referenced by `opaque_ref` referenced in the
@ -135,38 +85,56 @@ impl Scope {
lookup_region: Region,
) -> Result<(Symbol, &Alias), RuntimeError> {
debug_assert!(opaque_ref.starts_with('@'));
let opaque = opaque_ref[1..].into();
let opaque_str = &opaque_ref[1..];
let opaque = opaque_str.into();
match self.idents.get_symbol_and_region(&opaque) {
// TODO: is it worth caching any of these results?
Some((symbol, decl_region)) => {
if symbol.module_id() != self.home {
// The reference is to an opaque type declared in another module - this is
// illegal, as opaque types can only be wrapped/unwrapped in the scope they're
// declared.
return Err(RuntimeError::OpaqueOutsideScope {
match self.locals.has_in_scope(&opaque) {
Some((symbol, _)) => match self.lookup_opaque_alias(symbol) {
Ok(alias) => Ok((symbol, alias)),
Err(opt_alias_def_region) => {
Err(self.opaque_not_defined_error(opaque, lookup_region, opt_alias_def_region))
}
},
None => {
// opaque types can only be wrapped/unwrapped in the scope they are defined in (and below)
let error = if let Some((_, decl_region)) = self.has_imported(opaque_str) {
// specific error for when the opaque is imported, which definitely does not work
RuntimeError::OpaqueOutsideScope {
opaque,
referenced_region: lookup_region,
imported_region: decl_region,
});
}
}
} else {
self.opaque_not_defined_error(opaque, lookup_region, None)
};
match self.aliases.get(&symbol) {
None => Err(self.opaque_not_defined_error(opaque, lookup_region, None)),
Some(alias) => match alias.kind {
// The reference is to a proper alias like `Age : U32`, not an opaque type!
AliasKind::Structural => Err(self.opaque_not_defined_error(
opaque,
lookup_region,
Some(alias.header_region()),
)),
// All is good
AliasKind::Opaque => Ok((symbol, alias)),
},
}
Err(error)
}
None => Err(self.opaque_not_defined_error(opaque, lookup_region, None)),
}
}
fn lookup_opaque_alias(&self, symbol: Symbol) -> Result<&Alias, Option<Region>> {
match self.aliases.get(&symbol) {
None => Err(None),
Some(alias) => match alias.kind {
AliasKind::Opaque => Ok(alias),
AliasKind::Structural => Err(Some(alias.header_region())),
},
}
}
fn is_opaque(&self, ident_id: IdentId, string: &str) -> Option<Box<str>> {
if string.is_empty() {
return None;
}
let symbol = Symbol::new(self.home, ident_id);
if let Some(AliasKind::Opaque) = self.aliases.get(&symbol).map(|alias| alias.kind) {
Some(string.into())
} else {
None
}
}
@ -176,17 +144,13 @@ impl Scope {
lookup_region: Region,
opt_defined_alias: Option<Region>,
) -> RuntimeError {
// for opaques, we only look at the locals because opaques can only be matched
// on in the module that defines them.
let opaques_in_scope = self
.idents
.iter_idents_symbols()
.filter(|(_, sym)| {
self.aliases
.get(sym)
.map(|alias| alias.kind)
.unwrap_or(AliasKind::Structural)
== AliasKind::Opaque
})
.map(|(v, _)| v.as_ref().into())
.locals
.ident_ids
.ident_strs()
.filter_map(|(ident_id, string)| self.is_opaque(ident_id, string))
.collect();
RuntimeError::OpaqueNotDefined {
@ -196,6 +160,58 @@ impl Scope {
}
}
fn has_imported(&self, ident: &str) -> Option<(Symbol, Region)> {
for (import, shadow, original_region) in self.imports.iter() {
if ident == import.as_str() {
return Some((*shadow, *original_region));
}
}
None
}
/// Is an identifier in scope, either in the locals or imports
fn scope_contains_ident(&self, ident: &str) -> ContainsIdent {
// exposed imports are likely to be small
match self.has_imported(ident) {
Some((symbol, region)) => ContainsIdent::InScope(symbol, region),
None => self.locals.contains_ident(ident),
}
}
fn introduce_help(&mut self, ident: &str, region: Region) -> Result<Symbol, (Symbol, Region)> {
match self.scope_contains_ident(ident) {
ContainsIdent::InScope(original_symbol, original_region) => {
// the ident is already in scope; up to the caller how to handle that
// (usually it's shadowing, but it is valid to shadow ability members)
Err((original_symbol, original_region))
}
ContainsIdent::NotPresent => {
// We know nothing about this ident yet; introduce it to the scope
let ident_id = self.locals.introduce_into_scope(ident, region);
Ok(Symbol::new(self.home, ident_id))
}
ContainsIdent::NotInScope(existing) => {
// The ident is not in scope, but its name is already in the string interner
if existing.index() < self.exposed_ident_count {
// if the identifier is exposed, use the IdentId we already have for it
// other modules depend on the symbol having that IdentId
let symbol = Symbol::new(self.home, existing);
self.locals.in_scope.set(existing.index(), true);
self.locals.regions[existing.index()] = region;
Ok(symbol)
} else {
// create a new IdentId that under the hood uses the same string bytes as an existing one
let ident_id = self.locals.introduce_into_scope_duplicate(existing, region);
Ok(Symbol::new(self.home, ident_id))
}
}
}
}
/// Introduce a new ident to scope.
///
/// Returns Err if this would shadow an existing ident, including the
@ -210,11 +226,22 @@ impl Scope {
ident: Ident,
region: Region,
) -> Result<Symbol, (Region, Loc<Ident>, Symbol)> {
match self.introduce_without_shadow_symbol(&ident, region) {
self.introduce_str(ident.as_str(), region)
}
pub fn introduce_str(
&mut self,
ident: &str,
region: Region,
) -> Result<Symbol, (Region, Loc<Ident>, Symbol)> {
match self.introduce_help(ident, region) {
Ok(symbol) => Ok(symbol),
Err((original_region, shadow)) => {
let ident_id = self.ident_ids.add_ident(&ident);
let symbol = Symbol::new(self.home, ident_id);
Err((_, original_region)) => {
let shadow = Loc {
value: Ident::from(ident),
region,
};
let symbol = self.locals.scopeless_symbol(ident, region);
Err((original_region, shadow, symbol))
}
@ -227,15 +254,15 @@ impl Scope {
ident: &Ident,
region: Region,
) -> Result<Symbol, (Region, Loc<Ident>)> {
match self.idents.get_symbol_and_region(ident) {
Some((_, original_region)) => {
match self.introduce_help(ident.as_str(), region) {
Err((_, original_region)) => {
let shadow = Loc {
value: ident.clone(),
region,
};
Err((original_region, shadow))
}
None => Ok(self.commit_introduction(ident, region)),
Ok(symbol) => Ok(symbol),
}
}
@ -251,22 +278,16 @@ impl Scope {
ident: Ident,
region: Region,
) -> Result<(Symbol, Option<Symbol>), (Region, Loc<Ident>, Symbol)> {
match self.idents.get_index(&ident) {
Some(index) => {
let original_symbol = self.idents.symbols[index];
let original_region = self.idents.regions[index];
let ident = &ident;
let shadow_ident_id = self.ident_ids.add_ident(&ident);
let shadow_symbol = Symbol::new(self.home, shadow_ident_id);
match self.introduce_help(ident.as_str(), region) {
Err((original_symbol, original_region)) => {
let shadow_symbol = self.scopeless_symbol(ident, region);
if self.abilities_store.is_ability_member_name(original_symbol) {
self.abilities_store
.register_specializing_symbol(shadow_symbol, original_symbol);
// Add a symbol for the shadow, but don't re-associate the member name.
let dummy = Ident::default();
self.idents.insert_unchecked(&dummy, shadow_symbol, region);
Ok((shadow_symbol, Some(original_symbol)))
} else {
// This is an illegal shadow.
@ -278,33 +299,17 @@ impl Scope {
Err((original_region, shadow, shadow_symbol))
}
}
None => {
let new_symbol = self.commit_introduction(&ident, region);
Ok((new_symbol, None))
}
Ok(symbol) => Ok((symbol, None)),
}
}
fn commit_introduction(&mut self, ident: &Ident, region: Region) -> Symbol {
// if the identifier is exposed, use the IdentId we already have for it
let ident_id = match self.ident_ids.get_id(ident) {
Some(ident_id) if ident_id.index() < self.exposed_ident_count => ident_id,
_ => self.ident_ids.add_ident(ident),
};
let symbol = Symbol::new(self.home, ident_id);
self.idents.insert_unchecked(ident, symbol, region);
symbol
}
/// Ignore an identifier.
/// Create a new symbol, but don't add it to the scope (yet)
///
/// Used for record guards like { x: Just _ }
pub fn ignore(&mut self, ident: &Ident) -> Symbol {
let ident_id = self.ident_ids.add_ident(ident);
Symbol::new(self.home, ident_id)
/// Used for record guards like { x: Just _ } where the `x` is not added to the scope,
/// but also in other places where we need to create a symbol and we don't have the right
/// scope information yet. An identifier can be introduced later, and will use the same IdentId
pub fn scopeless_symbol(&mut self, ident: &Ident, region: Region) -> Symbol {
self.locals.scopeless_symbol(ident.as_str(), region)
}
/// Import a Symbol from another module into this module's top-level scope.
@ -317,14 +322,13 @@ impl Scope {
symbol: Symbol,
region: Region,
) -> Result<(), (Symbol, Region)> {
match self.idents.get_symbol_and_region(&ident) {
Some(shadowed) => Err(shadowed),
None => {
self.idents.insert_unchecked(&ident, symbol, region);
Ok(())
}
if let Some((s, r)) = self.has_imported(ident.as_str()) {
return Err((s, r));
}
self.imports.push((ident, symbol, region));
Ok(())
}
pub fn add_alias(
@ -339,6 +343,10 @@ impl Scope {
self.aliases.insert(name, alias);
}
pub fn lookup_alias(&self, symbol: Symbol) -> Option<&Alias> {
self.aliases.get(&symbol)
}
pub fn contains_alias(&mut self, name: Symbol) -> bool {
self.aliases.contains_key(&name)
}
@ -350,23 +358,27 @@ impl Scope {
// store enough information to roll back to the original outer scope
//
// - abilities_store: ability definitions not allowed in inner scopes
// - ident_ids: identifiers in inner scopes should still be available in the ident_ids
// - idents: we have to clone for now
// - locals: everything introduced in the inner scope is marked as not in scope in the rollback
// - aliases: stored in a VecMap, we just discard anything added in an inner scope
// - exposed_ident_count: unchanged
let idents = self.idents.clone();
// - home: unchanged
let aliases_count = self.aliases.len();
let locals_snapshot = self.locals.in_scope.len();
let result = f(self);
self.idents = idents;
self.aliases.truncate(aliases_count);
// anything added in the inner scope is no longer in scope now
for i in locals_snapshot..self.locals.in_scope.len() {
self.locals.in_scope.set(i, false);
}
result
}
pub fn register_debug_idents(&self) {
self.home.register_debug_idents(&self.ident_ids)
self.home.register_debug_idents(&self.locals.ident_ids)
}
/// Generates a unique, new symbol like "$1" or "$5",
@ -375,9 +387,7 @@ impl Scope {
/// This is used, for example, during canonicalization of an Expr::Closure
/// to generate a unique symbol to refer to that closure.
pub fn gen_unique_symbol(&mut self) -> Symbol {
let ident_id = self.ident_ids.gen_unique();
Symbol::new(self.home, ident_id)
Symbol::new(self.home, self.locals.gen_unique())
}
}
@ -426,87 +436,117 @@ pub fn create_alias(
}
}
#[derive(Clone, Debug)]
struct IdentStore {
interner: SmallStringInterner,
/// A Symbol for each Ident
symbols: Vec<Symbol>,
/// A Region for each Ident
regions: Vec<Region>,
#[derive(Debug)]
enum ContainsIdent {
InScope(Symbol, Region),
NotInScope(IdentId),
NotPresent,
}
impl IdentStore {
fn new() -> Self {
let defaults = Symbol::default_in_scope();
let capacity = defaults.len();
#[derive(Clone, Debug)]
pub struct ScopedIdentIds {
pub ident_ids: IdentIds,
in_scope: BitVec,
regions: Vec<Region>,
home: ModuleId,
}
let mut this = Self {
interner: SmallStringInterner::with_capacity(capacity),
symbols: Vec::with_capacity(capacity),
regions: Vec::with_capacity(capacity),
};
impl ScopedIdentIds {
fn from_ident_ids(home: ModuleId, ident_ids: IdentIds) -> Self {
let capacity = ident_ids.len();
for (ident, (symbol, region)) in defaults {
this.insert_unchecked(&ident, symbol, region);
Self {
in_scope: BitVec::repeat(false, capacity),
ident_ids,
regions: vec![Region::zero(); capacity],
home,
}
}
fn has_in_scope(&self, ident: &Ident) -> Option<(Symbol, Region)> {
match self.contains_ident(ident.as_str()) {
ContainsIdent::InScope(symbol, region) => Some((symbol, region)),
ContainsIdent::NotInScope(_) | ContainsIdent::NotPresent => None,
}
}
fn contains_ident(&self, ident: &str) -> ContainsIdent {
use ContainsIdent::*;
let mut result = NotPresent;
for ident_id in self.ident_ids.get_id_many(ident) {
let index = ident_id.index();
if self.in_scope[index] {
return InScope(Symbol::new(self.home, ident_id), self.regions[index]);
} else {
result = NotInScope(ident_id)
}
}
this
result
}
fn iter_idents(&self) -> impl Iterator<Item = Ident> + '_ {
self.interner.iter().filter_map(move |string| {
// empty string is used when ability members are shadowed
if string.is_empty() {
None
} else {
Some(Ident::from(string))
}
})
}
fn iter_idents_symbols(&self) -> impl Iterator<Item = (Ident, Symbol)> + '_ {
self.interner
.iter()
.zip(self.symbols.iter())
.filter_map(move |(string, symbol)| {
// empty slice is used when ability members are shadowed
if string.is_empty() {
None
fn idents_in_scope(&self) -> impl Iterator<Item = Ident> + '_ {
self.ident_ids
.ident_strs()
.zip(self.in_scope.iter())
.filter_map(|((_, string), keep)| {
if *keep {
Some(Ident::from(string))
} else {
Some((Ident::from(string), *symbol))
None
}
})
}
fn get_index(&self, ident: &Ident) -> Option<usize> {
let ident_str = ident.as_inline_str().as_str();
fn introduce_into_scope(&mut self, ident_name: &str, region: Region) -> IdentId {
let id = self.ident_ids.add_str(ident_name);
self.interner.find_index(ident_str)
}
debug_assert_eq!(id.index(), self.in_scope.len());
debug_assert_eq!(id.index(), self.regions.len());
fn get_symbol(&self, ident: &Ident) -> Option<Symbol> {
Some(self.symbols[self.get_index(ident)?])
}
fn get_symbol_and_region(&self, ident: &Ident) -> Option<(Symbol, Region)> {
let index = self.get_index(ident)?;
Some((self.symbols[index], self.regions[index]))
}
/// Does not check that the ident is unique
fn insert_unchecked(&mut self, ident: &Ident, symbol: Symbol, region: Region) {
let ident_str = ident.as_inline_str().as_str();
let index = self.interner.insert(ident_str);
debug_assert_eq!(index, self.symbols.len());
debug_assert_eq!(index, self.regions.len());
self.symbols.push(symbol);
self.in_scope.push(true);
self.regions.push(region);
id
}
fn introduce_into_scope_duplicate(&mut self, existing: IdentId, region: Region) -> IdentId {
let id = self.ident_ids.duplicate_ident(existing);
debug_assert_eq!(id.index(), self.in_scope.len());
debug_assert_eq!(id.index(), self.regions.len());
self.in_scope.push(true);
self.regions.push(region);
id
}
/// Adds an IdentId, but does not introduce it to the scope
fn scopeless_symbol(&mut self, ident_name: &str, region: Region) -> Symbol {
let id = self.ident_ids.add_str(ident_name);
debug_assert_eq!(id.index(), self.in_scope.len());
debug_assert_eq!(id.index(), self.regions.len());
self.in_scope.push(false);
self.regions.push(region);
Symbol::new(self.home, id)
}
fn gen_unique(&mut self) -> IdentId {
let id = self.ident_ids.gen_unique();
debug_assert_eq!(id.index(), self.in_scope.len());
debug_assert_eq!(id.index(), self.regions.len());
self.in_scope.push(false);
self.regions.push(Region::zero());
id
}
}
@ -575,6 +615,29 @@ mod test {
assert!(scope.lookup(&ident, region).is_err());
}
#[test]
fn default_idents_in_scope() {
let _register_module_debug_names = ModuleIds::default();
let scope = Scope::new(ModuleId::ATTR, IdentIds::default());
let idents: Vec<_> = scope.idents_in_scope().collect();
assert_eq!(
&idents,
&[
Ident::from("Box"),
Ident::from("Set"),
Ident::from("Dict"),
Ident::from("Str"),
Ident::from("Ok"),
Ident::from("False"),
Ident::from("List"),
Ident::from("True"),
Ident::from("Err"),
]
);
}
#[test]
fn idents_with_inner_scope() {
let _register_module_debug_names = ModuleIds::default();
@ -641,4 +704,48 @@ mod test {
assert_eq!(&idents[builtin_count..], &[ident1, ident2, ident3,]);
}
#[test]
fn import_is_in_scope() {
let _register_module_debug_names = ModuleIds::default();
let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default());
let ident = Ident::from("product");
let symbol = Symbol::LIST_PRODUCT;
let region = Region::zero();
assert!(scope.lookup(&ident, region).is_err());
assert!(scope.import(ident.clone(), symbol, region).is_ok());
assert!(scope.lookup(&ident, region).is_ok());
assert!(scope.idents_in_scope().any(|x| x == ident));
}
#[test]
fn shadow_of_import() {
let _register_module_debug_names = ModuleIds::default();
let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default());
let ident = Ident::from("product");
let symbol = Symbol::LIST_PRODUCT;
let region1 = Region::from_pos(Position { offset: 10 });
let region2 = Region::from_pos(Position { offset: 20 });
scope.import(ident.clone(), symbol, region1).unwrap();
let (original_region, _ident, shadow_symbol) =
scope.introduce(ident.clone(), region2).unwrap_err();
scope.register_debug_idents();
assert_ne!(symbol, shadow_symbol);
assert_eq!(original_region, region1);
let lookup = scope.lookup(&ident, Region::zero()).unwrap();
assert_eq!(symbol, lookup);
}
}

View File

@ -75,7 +75,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
);
let mut all_ident_ids = IdentIds::exposed_builtins(1);
all_ident_ids.insert(home, scope.ident_ids);
all_ident_ids.insert(home, scope.locals.ident_ids);
let interns = Interns {
module_ids: env.module_ids.clone(),

View File

@ -1,3 +1,5 @@
use std::{fmt::Debug, mem::ManuallyDrop};
/// Collection of small (length < u16::MAX) strings, stored compactly.
#[derive(Clone, Default, PartialEq, Eq)]
pub struct SmallStringInterner {
@ -5,10 +7,54 @@ pub struct SmallStringInterner {
// lengths could be Vec<u8>, but the mono refcount generation
// stringifies Layout's and that creates > 256 character strings
lengths: Vec<u16>,
lengths: Vec<Length>,
offsets: Vec<u32>,
}
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(transparent)]
struct Length(i16);
impl std::fmt::Debug for Length {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.kind().fmt(f)
}
}
impl Length {
#[inline(always)]
const fn kind(self) -> Kind {
if self.0 == 0 {
Kind::Empty
} else if self.0 > 0 {
Kind::Interned(self.0 as usize)
} else {
Kind::Generated(self.0.abs() as usize)
}
}
#[inline(always)]
const fn from_usize(len: usize) -> Self {
Self(len as i16)
}
}
enum Kind {
Generated(usize),
Empty,
Interned(usize),
}
impl Debug for Kind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Generated(arg0) => write!(f, "Generated({})", arg0),
Self::Empty => write!(f, "Empty"),
Self::Interned(arg0) => write!(f, "Interned({})", arg0),
}
}
}
impl std::fmt::Debug for SmallStringInterner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let strings: Vec<_> = self.iter().collect();
@ -33,7 +79,19 @@ impl SmallStringInterner {
}
}
pub const fn from_parts(buffer: Vec<u8>, lengths: Vec<u16>, offsets: Vec<u32>) -> Self {
/// # Safety
///
/// lengths must be non-negative integers less than 2^15
pub unsafe fn from_parts(buffer: Vec<u8>, lengths: Vec<u16>, offsets: Vec<u32>) -> Self {
// the recommended way of transmuting a vector
let mut lengths = ManuallyDrop::new(lengths);
let lengths = Vec::from_raw_parts(
lengths.as_mut_ptr().cast(),
lengths.len(),
lengths.capacity(),
);
Self {
buffer,
lengths,
@ -45,13 +103,14 @@ impl SmallStringInterner {
(0..self.offsets.len()).map(move |index| self.get(index))
}
/// Insert without deduplicating
pub fn insert(&mut self, string: &str) -> usize {
let bytes = string.as_bytes();
assert!(bytes.len() < u16::MAX as usize);
assert!(bytes.len() < (1 << 15));
let offset = self.buffer.len() as u32;
let length = bytes.len() as u16;
let length = Length::from_usize(bytes.len());
let index = self.lengths.len();
@ -63,6 +122,19 @@ impl SmallStringInterner {
index
}
/// Create a new entry that uses the same string bytes as an existing entry
pub fn duplicate(&mut self, existing: usize) -> usize {
let offset = self.offsets[existing];
let length = self.lengths[existing];
let index = self.lengths.len();
self.lengths.push(length);
self.offsets.push(offset);
index
}
/// Insert a string equal to the current length into the interner.
///
/// Assuming that normally you don't insert strings consisting of just digits,
@ -76,9 +148,11 @@ impl SmallStringInterner {
let offset = self.buffer.len();
write!(self.buffer, "{}", index).unwrap();
let length = self.buffer.len() - offset;
self.lengths.push(length as u16);
// this is a generated name, so store it as a negative length
let length = Length(-((self.buffer.len() - offset) as i16));
self.lengths.push(length);
self.offsets.push(offset as u32);
index
@ -86,31 +160,53 @@ impl SmallStringInterner {
#[inline(always)]
pub fn find_index(&self, string: &str) -> Option<usize> {
let target_length = string.len() as u16;
self.find_indices(string).next()
}
#[inline(always)]
pub fn find_indices<'a>(&'a self, string: &'a str) -> impl Iterator<Item = usize> + 'a {
let target_length = string.len();
// there can be gaps in the parts of the string that we use (because of updates)
// hence we can't just sum the lengths we've seen so far to get the next offset
for (index, length) in self.lengths.iter().enumerate() {
if *length == target_length {
let offset = self.offsets[index];
let slice = &self.buffer[offset as usize..][..*length as usize];
if string.as_bytes() == slice {
return Some(index);
self.lengths
.iter()
.enumerate()
.filter_map(move |(index, length)| match length.kind() {
Kind::Generated(_) => None,
Kind::Empty => {
if target_length == 0 {
Some(index)
} else {
None
}
}
}
}
Kind::Interned(length) => {
if target_length == length {
let offset = self.offsets[index];
let slice = &self.buffer[offset as usize..][..length];
None
if string.as_bytes() == slice {
return Some(index);
}
}
None
}
})
}
fn get(&self, index: usize) -> &str {
let length = self.lengths[index] as usize;
let offset = self.offsets[index] as usize;
match self.lengths[index].kind() {
Kind::Empty => "",
Kind::Generated(length) | Kind::Interned(length) => {
let offset = self.offsets[index] as usize;
let bytes = &self.buffer[offset..][..length];
let bytes = &self.buffer[offset..][..length];
unsafe { std::str::from_utf8_unchecked(bytes) }
unsafe { std::str::from_utf8_unchecked(bytes) }
}
}
}
pub fn try_get(&self, index: usize) -> Option<&str> {
@ -129,7 +225,7 @@ impl SmallStringInterner {
// `buffer`, we can update them in-place
self.buffer.extend(new_string.bytes());
self.lengths[index] = length as u16;
self.lengths[index] = Length::from_usize(length);
self.offsets[index] = offset as u32;
}

View File

@ -13,6 +13,13 @@ impl<K, V> Default for VecMap<K, V> {
}
}
impl<K, V> VecMap<K, V> {
pub fn len(&self) -> usize {
debug_assert_eq!(self.keys.len(), self.values.len());
self.keys.len()
}
}
impl<K: PartialEq, V> VecMap<K, V> {
pub fn with_capacity(capacity: usize) -> Self {
Self {
@ -21,11 +28,6 @@ impl<K: PartialEq, V> VecMap<K, V> {
}
}
pub fn len(&self) -> usize {
debug_assert_eq!(self.keys.len(), self.values.len());
self.keys.len()
}
pub fn is_empty(&self) -> bool {
debug_assert_eq!(self.keys.len(), self.values.len());
self.keys.is_empty()
@ -58,15 +60,9 @@ impl<K: PartialEq, V> VecMap<K, V> {
self.keys.contains(key)
}
pub fn remove(&mut self, key: &K) {
match self.keys.iter().position(|x| x == key) {
None => {
// just do nothing
}
Some(index) => {
self.swap_remove(index);
}
}
pub fn remove(&mut self, key: &K) -> Option<(K, V)> {
let index = self.keys.iter().position(|x| x == key)?;
Some(self.swap_remove(index))
}
pub fn get(&self, key: &K) -> Option<&V> {
@ -83,7 +79,7 @@ impl<K: PartialEq, V> VecMap<K, V> {
}
}
pub fn get_or_insert(&mut self, key: K, default_value: impl Fn() -> V) -> &mut V {
pub fn get_or_insert(&mut self, key: K, default_value: impl FnOnce() -> V) -> &mut V {
match self.keys.iter().position(|x| x == &key) {
Some(index) => &mut self.values[index],
None => {
@ -97,15 +93,15 @@ impl<K: PartialEq, V> VecMap<K, V> {
}
}
pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
pub fn iter(&self) -> impl ExactSizeIterator<Item = (&K, &V)> {
self.keys.iter().zip(self.values.iter())
}
pub fn keys(&self) -> impl Iterator<Item = &K> {
pub fn keys(&self) -> impl ExactSizeIterator<Item = &K> {
self.keys.iter()
}
pub fn values(&self) -> impl Iterator<Item = &V> {
pub fn values(&self) -> impl ExactSizeIterator<Item = &V> {
self.values.iter()
}
@ -159,6 +155,7 @@ impl<K, V> IntoIterator for VecMap<K, V> {
fn into_iter(self) -> Self::IntoIter {
IntoIter {
len: self.len(),
keys: self.keys.into_iter(),
values: self.values.into_iter(),
}
@ -166,6 +163,7 @@ impl<K, V> IntoIterator for VecMap<K, V> {
}
pub struct IntoIter<K, V> {
len: usize,
keys: std::vec::IntoIter<K>,
values: std::vec::IntoIter<V>,
}
@ -180,3 +178,9 @@ impl<K, V> Iterator for IntoIter<K, V> {
}
}
}
impl<K, V> ExactSizeIterator for IntoIter<K, V> {
fn len(&self) -> usize {
self.len
}
}

View File

@ -40,6 +40,17 @@ impl<T: PartialEq> VecSet<T> {
}
}
/// Returns true iff any of the given elements previoously existed in the set.
pub fn insert_all<I: Iterator<Item = T>>(&mut self, values: I) -> bool {
let mut any_existed = false;
for value in values {
any_existed = any_existed || self.insert(value);
}
any_existed
}
pub fn contains(&self, value: &T) -> bool {
self.elements.contains(value)
}
@ -58,6 +69,10 @@ impl<T: PartialEq> VecSet<T> {
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.elements.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
self.elements.iter_mut()
}
}
impl<A: Ord> Extend<A> for VecSet<A> {

View File

@ -2,7 +2,6 @@ use arrayvec::ArrayVec;
use roc_can::constraint::{Constraint, Constraints};
use roc_can::expected::Expected::{self, *};
use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumericBound, SignDemand};
use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_region::all::Region;
use roc_types::subs::Variable;
@ -161,7 +160,7 @@ pub fn str_type() -> Type {
#[inline(always)]
fn builtin_alias(
symbol: Symbol,
type_arguments: Vec<(Lowercase, Type)>,
type_arguments: Vec<Type>,
actual: Box<Type>,
kind: AliasKind,
) -> Type {
@ -178,7 +177,7 @@ fn builtin_alias(
pub fn num_float(range: Type) -> Type {
builtin_alias(
Symbol::NUM_FLOAT,
vec![("range".into(), range.clone())],
vec![range.clone()],
Box::new(num_num(num_floatingpoint(range))),
AliasKind::Structural,
)
@ -188,7 +187,7 @@ pub fn num_float(range: Type) -> Type {
pub fn num_floatingpoint(range: Type) -> Type {
builtin_alias(
Symbol::NUM_FLOATINGPOINT,
vec![("range".into(), range.clone())],
vec![range.clone()],
Box::new(range),
AliasKind::Opaque,
)
@ -228,7 +227,7 @@ pub fn num_binary64() -> Type {
pub fn num_int(range: Type) -> Type {
builtin_alias(
Symbol::NUM_INT,
vec![("range".into(), range.clone())],
vec![range.clone()],
Box::new(num_num(num_integer(range))),
AliasKind::Structural,
)
@ -248,7 +247,7 @@ pub fn num_signed64() -> Type {
pub fn num_integer(range: Type) -> Type {
builtin_alias(
Symbol::NUM_INTEGER,
vec![("range".into(), range.clone())],
vec![range.clone()],
Box::new(range),
AliasKind::Opaque,
)
@ -258,7 +257,7 @@ pub fn num_integer(range: Type) -> Type {
pub fn num_num(typ: Type) -> Type {
builtin_alias(
Symbol::NUM_NUM,
vec![("range".into(), typ.clone())],
vec![typ.clone()],
Box::new(typ),
AliasKind::Opaque,
)

View File

@ -1022,7 +1022,7 @@ pub fn constrain_expr(
let opaque_type = Type::Alias {
symbol: *name,
type_arguments: type_arguments.clone(),
type_arguments: type_arguments.iter().copied().map(Type::Variable).collect(),
lambda_set_variables: lambda_set_variables.clone(),
actual: Box::new(arg_type.clone()),
kind: AliasKind::Opaque,
@ -1059,9 +1059,7 @@ pub fn constrain_expr(
let mut vars = vec![*arg_var, *opaque_var];
// Also add the fresh variables we created for the type argument and lambda sets
vars.extend(type_arguments.iter().map(|(_, t)| {
t.expect_variable("all type arguments should be fresh variables here")
}));
vars.extend(type_arguments);
vars.extend(lambda_set_variables.iter().map(|v| {
v.0.expect_variable("all lambda sets should be fresh variables here")
}));
@ -1302,7 +1300,7 @@ pub fn constrain_decls(
constraint
}
fn constrain_def_pattern(
pub fn constrain_def_pattern(
constraints: &mut Constraints,
env: &Env,
loc_pattern: &Loc<Pattern>,
@ -1708,7 +1706,7 @@ fn constrain_def(
}
}
fn constrain_def_make_constraint(
pub fn constrain_def_make_constraint(
constraints: &mut Constraints,
new_rigid_variables: Vec<Variable>,
new_infer_variables: Vec<Variable>,

View File

@ -3,13 +3,16 @@ use roc_can::abilities::AbilitiesStore;
use roc_can::constraint::{Constraint, Constraints};
use roc_can::def::Declaration;
use roc_can::expected::Expected;
use roc_can::pattern::Pattern;
use roc_collections::all::MutMap;
use roc_error_macros::internal_error;
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Loc, Region};
use roc_types::solved_types::{FreeVars, SolvedType};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Category, Type};
use roc_types::types::{AnnotationSource, Category, Type};
use crate::expr::{constrain_def_make_constraint, constrain_def_pattern, Env};
/// The types of all exposed values/functions of a collection of modules
#[derive(Clone, Debug, Default)]
@ -94,12 +97,14 @@ pub enum ExposedModuleTypes {
pub fn constrain_module(
constraints: &mut Constraints,
symbols_from_requires: Vec<(Loc<Symbol>, Loc<Type>)>,
abilities_store: &AbilitiesStore,
declarations: &[Declaration],
home: ModuleId,
) -> Constraint {
let constraint = crate::expr::constrain_decls(constraints, home, declarations);
let constraint =
constrain_symbols_from_requires(constraints, symbols_from_requires, home, constraint);
let constraint = frontload_ability_constraints(constraints, abilities_store, constraint);
// The module constraint should always save the environment at the end.
@ -108,6 +113,60 @@ pub fn constrain_module(
constraint
}
fn constrain_symbols_from_requires(
constraints: &mut Constraints,
symbols_from_requires: Vec<(Loc<Symbol>, Loc<Type>)>,
home: ModuleId,
constraint: Constraint,
) -> Constraint {
symbols_from_requires
.into_iter()
.fold(constraint, |constraint, (loc_symbol, loc_type)| {
if loc_symbol.value.module_id() == home {
// 1. Required symbols can only be specified in package modules
// 2. Required symbols come from app modules
// But, if we are running e.g. `roc check` on a package module, there is no app
// module, and we will have instead put the required symbols in the package module
// namespace. If this is the case, we want to introduce the symbols as if they had
// the types they are annotated with.
let rigids = Default::default();
let env = Env { home, rigids };
let pattern = Loc::at_zero(roc_can::pattern::Pattern::Identifier(loc_symbol.value));
let def_pattern_state =
constrain_def_pattern(constraints, &env, &pattern, loc_type.value);
constrain_def_make_constraint(
constraints,
// No new rigids or flex vars because they are represented in the type
// annotation.
vec![],
vec![],
Constraint::True,
constraint,
def_pattern_state,
)
} else {
// Otherwise, this symbol comes from an app module - we want to check that the type
// provided by the app is in fact what the package module requires.
let arity = loc_type.value.arity();
let provided_eq_requires_constr = constraints.lookup(
loc_symbol.value,
Expected::FromAnnotation(
loc_symbol.map(|&s| Pattern::Identifier(s)),
arity,
AnnotationSource::RequiredSymbol {
region: loc_type.region,
},
loc_type.value,
),
loc_type.region,
);
constraints.and_constraint([provided_eq_requires_constr, constraint])
}
})
}
pub fn frontload_ability_constraints(
constraints: &mut Constraints,
abilities_store: &AbilitiesStore,

View File

@ -514,7 +514,7 @@ pub fn constrain_pattern(
let opaque_type = Type::Alias {
symbol: *opaque,
type_arguments: type_arguments.clone(),
type_arguments: type_arguments.iter().copied().map(Type::Variable).collect(),
lambda_set_variables: lambda_set_variables.clone(),
actual: Box::new(arg_pattern_type.clone()),
kind: AliasKind::Opaque,
@ -571,9 +571,7 @@ pub fn constrain_pattern(
.vars
.extend_from_slice(&[*arg_pattern_var, *whole_var]);
// Also add the fresh variables we created for the type argument and lambda sets
state.vars.extend(type_arguments.iter().map(|(_, t)| {
t.expect_variable("all type arguments should be fresh variables here")
}));
state.vars.extend(type_arguments);
state.vars.extend(lambda_set_variables.iter().map(|v| {
v.0.expect_variable("all lambda sets should be fresh variables here")
}));

View File

@ -0,0 +1,6 @@
[package]
name = "roc_debug_flags"
version = "0.1.0"
edition = "2021"
[dependencies]

View File

@ -0,0 +1,111 @@
//! Flags for debugging the Roc compiler.
//!
//! Lists environment variable flags that can be enabled for verbose debugging features in debug
//! builds of the compiler.
//!
//! For example, I might define the following alias to run cargo with all unifications and
//! expanded type aliases printed:
//!
//! ```bash
//! alias cargo="\
//! ROC_PRINT_UNIFICATIONS=1 \
//! ROC_PRETTY_PRINT_ALIAS_CONTENTS=1 \
//! cargo"
//! ```
//!
//! More generally, I have the following:
//!
//! ```bash
//! alias cargo="\
//! ROC_PRETTY_PRINT_ALIAS_CONTENTS=0 \
//! ROC_PRINT_UNIFICATIONS=0 \
//! ROC_PRINT_MISMATCHES=0 \
//! ROC_PRINT_IR_AFTER_SPECIALIZATION=0 \
//! ROC_PRINT_IR_AFTER_RESET_REUSE=0 \
//! ROC_PRINT_IR_AFTER_REFCOUNT=0 \
//! ROC_PRETTY_PRINT_IR_SYMBOLS=0 \
//! # ...other flags
//! cargo"
//! ```
//!
//! Now you can turn debug flags on and off as you like.
//!
//! These flags are also set in .cargo/config found at the repository root. You can modify them
//! there to avoid maintaining a separate script.
#[macro_export]
macro_rules! dbg_do {
($flag:path, $expr:expr) => {
#[cfg(debug_assertions)]
{
let flag = std::env::var($flag);
if !flag.is_err() && flag.as_deref() != Ok("0") {
$expr
}
}
};
}
macro_rules! flags {
($($(#[doc = $doc:expr])+ $flag:ident)*) => {$(
$(#[doc = $doc])+
pub static $flag: &str = stringify!($flag);
)*};
}
flags! {
// ===Types===
/// Expands the contents of aliases during pretty-printing of types.
ROC_PRETTY_PRINT_ALIAS_CONTENTS
// ===Solve===
/// Prints type unifications, before and after they happen.
ROC_PRINT_UNIFICATIONS
/// Prints all type mismatches hit during type unification.
ROC_PRINT_MISMATCHES
/// Verifies that after let-generalization of a def, any rigid variables in the type annotation
/// of the def are indeed generalized.
///
/// Note that rigids need not always be generalized in a def. For example, they may be
/// constrained by a type from a lower rank, as `b` is in the following def:
///
/// F a : { foo : a }
/// foo = \arg ->
/// x : F b
/// x = arg
/// x.foo
///
/// Instead, this flag is useful for checking that in general, introduction is correct, when
/// chainging how defs are constrained.
ROC_VERIFY_RIGID_LET_GENERALIZED
// ===Mono===
/// Writes a pretty-printed mono IR to stderr after function specialization.
ROC_PRINT_IR_AFTER_SPECIALIZATION
/// Writes a pretty-printed mono IR to stderr after insertion of reset/reuse
/// instructions.
ROC_PRINT_IR_AFTER_RESET_REUSE
/// Writes a pretty-printed mono IR to stderr after insertion of refcount
/// instructions.
ROC_PRINT_IR_AFTER_REFCOUNT
/// Prints debug information during the alias analysis pass.
ROC_DEBUG_ALIAS_ANALYSIS
// ===LLVM Gen===
/// Prints LLVM function verification output.
ROC_PRINT_LLVM_FN_VERIFICATION
// ===Load===
/// Print load phases as they complete.
ROC_PRINT_LOAD_LOG
}

View File

@ -2,7 +2,7 @@ use roc_parse::ast::{Collection, ExtractSpaces};
use crate::{
annotation::{Formattable, Newlines},
spaces::{fmt_comments_only, NewlineAt, INDENT},
spaces::{count_leading_newlines, fmt_comments_only, NewlineAt, INDENT},
Buf,
};
@ -25,12 +25,27 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
buf.indent(braces_indent);
buf.push(start);
for item in items.iter() {
for (index, item) in items.iter().enumerate() {
let item = item.extract_spaces();
let is_first_item = index == 0;
buf.newline();
if !item.before.is_empty() {
let is_only_newlines = item.before.iter().all(|s| s.is_newline());
if !is_first_item
&& !is_only_newlines
&& count_leading_newlines(item.before.iter()) > 1
{
buf.newline();
}
fmt_comments_only(buf, item.before.iter(), NewlineAt::Bottom, item_indent);
if !is_only_newlines && count_leading_newlines(item.before.iter().rev()) > 0 {
buf.newline();
}
}
item.item.format(buf, item_indent);
@ -41,6 +56,11 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
fmt_comments_only(buf, item.after.iter(), NewlineAt::Top, item_indent);
}
}
if count_leading_newlines(items.final_comments().iter()) > 1 {
buf.newline();
}
fmt_comments_only(
buf,
items.final_comments().iter(),

View File

@ -2,7 +2,7 @@ use crate::annotation::{Formattable, Newlines, Parens};
use crate::collection::fmt_collection;
use crate::def::fmt_def;
use crate::pattern::fmt_pattern;
use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT};
use crate::spaces::{count_leading_newlines, fmt_comments_only, fmt_spaces, NewlineAt, INDENT};
use crate::Buf;
use roc_module::called_via::{self, BinOp};
use roc_parse::ast::{
@ -1105,16 +1105,38 @@ fn fmt_record<'a, 'buf>(
if is_multiline {
let field_indent = indent + INDENT;
for field in loc_fields.iter() {
for (index, field) in loc_fields.iter().enumerate() {
// comma addition is handled by the `format_field_multiline` function
// since we can have stuff like:
// { x # comment
// , y
// }
// In this case, we have to move the comma before the comment.
let is_first_item = index == 0;
if let AssignedField::SpaceBefore(_sub_field, spaces) = &field.value {
let is_only_newlines = spaces.iter().all(|s| s.is_newline());
if !is_first_item
&& !is_only_newlines
&& count_leading_newlines(spaces.iter()) > 1
{
buf.newline();
}
fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, field_indent);
if !is_only_newlines && count_leading_newlines(spaces.iter().rev()) > 0 {
buf.newline();
}
}
format_field_multiline(buf, &field.value, field_indent, "");
}
if count_leading_newlines(final_comments.iter()) > 1 {
buf.newline();
}
fmt_comments_only(buf, final_comments.iter(), NewlineAt::Top, field_indent);
buf.newline();
@ -1189,7 +1211,7 @@ fn format_field_multiline<'a, 'buf, T>(
buf.push_str(name.value);
buf.push(',');
}
AssignedField::SpaceBefore(sub_field, spaces) => {
AssignedField::SpaceBefore(sub_field, _spaces) => {
// We have something like that:
// ```
// # comment
@ -1197,7 +1219,6 @@ fn format_field_multiline<'a, 'buf, T>(
// ```
// we'd like to preserve this
fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, indent);
format_field_multiline(buf, sub_field, indent, separator_prefix);
}
AssignedField::SpaceAfter(sub_field, spaces) => {

View File

@ -10,6 +10,14 @@ pub mod pattern;
pub mod spaces;
use bumpalo::{collections::String, Bump};
use roc_parse::ast::{Def, Module};
use roc_region::all::Loc;
#[derive(Debug, PartialEq)]
pub struct Ast<'a> {
pub module: Module<'a>,
pub defs: bumpalo::collections::vec::Vec<'a, Loc<Def<'a>>>,
}
#[derive(Debug)]
pub struct Buf<'a> {

View File

@ -11,7 +11,7 @@ use roc_parse::header::{
use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc;
pub fn fmt_module<'a, 'buf>(buf: &mut Buf<'buf>, module: &'a Module<'a>) {
pub fn fmt_module<'a>(buf: &mut Buf<'_>, module: &'a Module<'a>) {
match module {
Module::Interface { header } => {
fmt_interface_header(buf, header);

View File

@ -1,6 +1,21 @@
use roc_parse::ast::CommentOrNewline;
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use roc_module::called_via::{BinOp, UnaryOp};
use roc_parse::{
ast::{
AbilityMember, AssignedField, Collection, CommentOrNewline, Def, Expr, Has, HasClause,
Module, Pattern, Spaced, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader,
ValueDef, WhenBranch,
},
header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName,
PackageEntry, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
},
ident::UppercaseIdent,
};
use roc_region::all::{Loc, Region};
use crate::Buf;
use crate::{Ast, Buf};
/// The number of spaces to indent.
pub const INDENT: u16 = 4;
@ -117,6 +132,31 @@ fn fmt_comment<'buf>(buf: &mut Buf<'buf>, comment: &str) {
buf.push_str(comment.trim_end());
}
pub fn count_leading_newlines<'a, I>(data: I) -> u16
where
I: Iterator<Item = &'a CommentOrNewline<'a>>,
{
let mut count = 0;
let mut allow_counting = false;
for (index, val) in data.enumerate() {
let is_first = index == 0;
let is_newline = matches!(val, CommentOrNewline::Newline);
if is_first && is_newline {
allow_counting = true
}
if is_newline && allow_counting {
count += 1;
} else {
break;
}
}
count
}
fn fmt_docs<'buf>(buf: &mut Buf<'buf>, docs: &str) {
buf.push_str("##");
if !docs.starts_with(' ') {
@ -124,3 +164,575 @@ fn fmt_docs<'buf>(buf: &mut Buf<'buf>, docs: &str) {
}
buf.push_str(docs);
}
/// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting.
///
/// Currently this consists of:
/// * Removing newlines
/// * Removing comments
/// * Removing parens in Exprs
///
/// Long term, we actuall want this transform to preserve comments (so we can assert they're maintained by formatting)
/// - but there are currently several bugs where they're _not_ preserved.
/// TODO: ensure formatting retains comments
pub trait RemoveSpaces<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self;
}
impl<'a> RemoveSpaces<'a> for Ast<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
Ast {
module: self.module.remove_spaces(arena),
defs: {
let mut defs = Vec::with_capacity_in(self.defs.len(), arena);
for d in &self.defs {
defs.push(d.remove_spaces(arena))
}
defs
},
}
}
}
impl<'a> RemoveSpaces<'a> for Module<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match self {
Module::Interface { header } => Module::Interface {
header: InterfaceHeader {
name: header.name.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
before_header: &[],
after_interface_keyword: &[],
before_exposes: &[],
after_exposes: &[],
before_imports: &[],
after_imports: &[],
},
},
Module::App { header } => Module::App {
header: AppHeader {
name: header.name.remove_spaces(arena),
packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena),
provides_types: header.provides_types.map(|ts| ts.remove_spaces(arena)),
to: header.to.remove_spaces(arena),
before_header: &[],
after_app_keyword: &[],
before_packages: &[],
after_packages: &[],
before_imports: &[],
after_imports: &[],
before_provides: &[],
after_provides: &[],
before_to: &[],
after_to: &[],
},
},
Module::Platform { header } => Module::Platform {
header: PlatformHeader {
name: header.name.remove_spaces(arena),
requires: header.requires.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena),
before_header: &[],
after_platform_keyword: &[],
before_requires: &[],
after_requires: &[],
before_exposes: &[],
after_exposes: &[],
before_packages: &[],
after_packages: &[],
before_imports: &[],
after_imports: &[],
before_provides: &[],
after_provides: &[],
},
},
Module::Hosted { header } => Module::Hosted {
header: HostedHeader {
name: header.name.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
generates: header.generates.remove_spaces(arena),
generates_with: header.generates_with.remove_spaces(arena),
before_header: &[],
after_hosted_keyword: &[],
before_exposes: &[],
after_exposes: &[],
before_imports: &[],
after_imports: &[],
before_generates: &[],
after_generates: &[],
before_with: &[],
after_with: &[],
},
},
}
}
}
impl<'a> RemoveSpaces<'a> for &'a str {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
self
}
}
impl<'a, T: RemoveSpaces<'a> + Copy> RemoveSpaces<'a> for Spaced<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Spaced::Item(a) => Spaced::Item(a.remove_spaces(arena)),
Spaced::SpaceBefore(a, _) => a.remove_spaces(arena),
Spaced::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ExposedName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for ModuleName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for PackageName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for To<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
To::ExistingPackage(a) => To::ExistingPackage(a),
To::NewPackage(a) => To::NewPackage(a.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for TypedIdent<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
TypedIdent {
ident: self.ident.remove_spaces(arena),
spaces_before_colon: &[],
ann: self.ann.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
PlatformRequires {
rigids: self.rigids.remove_spaces(arena),
signature: self.signature.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for UppercaseIdent<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for PackageEntry<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
PackageEntry {
shorthand: self.shorthand,
spaces_after_shorthand: &[],
package_name: self.package_name.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ImportsEntry<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
ImportsEntry::Module(a, b) => ImportsEntry::Module(a, b.remove_spaces(arena)),
ImportsEntry::Package(a, b, c) => ImportsEntry::Package(a, b, c.remove_spaces(arena)),
}
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Option<T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
self.as_ref().map(|a| a.remove_spaces(arena))
}
}
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Loc<T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let res = self.value.remove_spaces(arena);
Loc::at(Region::zero(), res)
}
}
impl<'a, A: RemoveSpaces<'a>, B: RemoveSpaces<'a>> RemoveSpaces<'a> for (A, B) {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
(self.0.remove_spaces(arena), self.1.remove_spaces(arena))
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Collection<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let mut items = Vec::with_capacity_in(self.items.len(), arena);
for item in self.items {
items.push(item.remove_spaces(arena));
}
Collection::with_items(items.into_bump_slice())
}
}
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for &'a [T] {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let mut items = Vec::with_capacity_in(self.len(), arena);
for item in *self {
let res = item.remove_spaces(arena);
items.push(res);
}
items.into_bump_slice()
}
}
impl<'a> RemoveSpaces<'a> for UnaryOp {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for BinOp {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for &'a T {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
arena.alloc((*self).remove_spaces(arena))
}
}
impl<'a> RemoveSpaces<'a> for TypeDef<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
use TypeDef::*;
match *self {
Alias {
header: TypeHeader { name, vars },
ann,
} => Alias {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
ann: ann.remove_spaces(arena),
},
Opaque {
header: TypeHeader { name, vars },
typ,
} => Opaque {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
typ: typ.remove_spaces(arena),
},
Ability {
header: TypeHeader { name, vars },
loc_has,
members,
} => Ability {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
loc_has: loc_has.remove_spaces(arena),
members: members.remove_spaces(arena),
},
}
}
}
impl<'a> RemoveSpaces<'a> for ValueDef<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
use ValueDef::*;
match *self {
Annotation(a, b) => Annotation(a.remove_spaces(arena), b.remove_spaces(arena)),
Body(a, b) => Body(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
AnnotatedBody {
ann_pattern,
ann_type,
comment: _,
body_pattern,
body_expr,
} => AnnotatedBody {
ann_pattern: arena.alloc(ann_pattern.remove_spaces(arena)),
ann_type: arena.alloc(ann_type.remove_spaces(arena)),
comment: None,
body_pattern: arena.alloc(body_pattern.remove_spaces(arena)),
body_expr: arena.alloc(body_expr.remove_spaces(arena)),
},
Expect(a) => Expect(arena.alloc(a.remove_spaces(arena))),
}
}
}
impl<'a> RemoveSpaces<'a> for Def<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Def::Type(def) => Def::Type(def.remove_spaces(arena)),
Def::Value(def) => Def::Value(def.remove_spaces(arena)),
Def::NotYetImplemented(a) => Def::NotYetImplemented(a),
Def::SpaceBefore(a, _) | Def::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for Has<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
Has::Has
}
}
impl<'a> RemoveSpaces<'a> for AbilityMember<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
AbilityMember {
name: self.name.remove_spaces(arena),
typ: self.typ.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for WhenBranch<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
WhenBranch {
patterns: self.patterns.remove_spaces(arena),
value: self.value.remove_spaces(arena),
guard: self.guard.remove_spaces(arena),
}
}
}
impl<'a, T: RemoveSpaces<'a> + Copy + std::fmt::Debug> RemoveSpaces<'a> for AssignedField<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
AssignedField::RequiredValue(a, _, c) => AssignedField::RequiredValue(
a.remove_spaces(arena),
arena.alloc([]),
arena.alloc(c.remove_spaces(arena)),
),
AssignedField::OptionalValue(a, _, c) => AssignedField::OptionalValue(
a.remove_spaces(arena),
arena.alloc([]),
arena.alloc(c.remove_spaces(arena)),
),
AssignedField::LabelOnly(a) => AssignedField::LabelOnly(a.remove_spaces(arena)),
AssignedField::Malformed(a) => AssignedField::Malformed(a),
AssignedField::SpaceBefore(a, _) => a.remove_spaces(arena),
AssignedField::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for StrLiteral<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
StrLiteral::PlainLine(t) => StrLiteral::PlainLine(t),
StrLiteral::Line(t) => StrLiteral::Line(t.remove_spaces(arena)),
StrLiteral::Block(t) => StrLiteral::Block(t.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for StrSegment<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
StrSegment::Plaintext(t) => StrSegment::Plaintext(t),
StrSegment::Unicode(t) => StrSegment::Unicode(t.remove_spaces(arena)),
StrSegment::EscapedChar(c) => StrSegment::EscapedChar(c),
StrSegment::Interpolated(t) => StrSegment::Interpolated(t.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for Expr<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Expr::Float(a) => Expr::Float(a),
Expr::Num(a) => Expr::Num(a),
Expr::NonBase10Int {
string,
base,
is_negative,
} => Expr::NonBase10Int {
string,
base,
is_negative,
},
Expr::Str(a) => Expr::Str(a.remove_spaces(arena)),
Expr::Access(a, b) => Expr::Access(arena.alloc(a.remove_spaces(arena)), b),
Expr::AccessorFunction(a) => Expr::AccessorFunction(a),
Expr::List(a) => Expr::List(a.remove_spaces(arena)),
Expr::RecordUpdate { update, fields } => Expr::RecordUpdate {
update: arena.alloc(update.remove_spaces(arena)),
fields: fields.remove_spaces(arena),
},
Expr::Record(a) => Expr::Record(a.remove_spaces(arena)),
Expr::Var { module_name, ident } => Expr::Var { module_name, ident },
Expr::Underscore(a) => Expr::Underscore(a),
Expr::Tag(a) => Expr::Tag(a),
Expr::OpaqueRef(a) => Expr::OpaqueRef(a),
Expr::Closure(a, b) => Expr::Closure(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Expr::Defs(a, b) => {
Expr::Defs(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
}
Expr::Backpassing(a, b, c) => Expr::Backpassing(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
arena.alloc(c.remove_spaces(arena)),
),
Expr::Expect(a, b) => Expr::Expect(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Expr::Apply(a, b, c) => Expr::Apply(
arena.alloc(a.remove_spaces(arena)),
b.remove_spaces(arena),
c,
),
Expr::BinOps(a, b) => {
Expr::BinOps(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
}
Expr::UnaryOp(a, b) => {
Expr::UnaryOp(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
}
Expr::If(a, b) => Expr::If(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena))),
Expr::When(a, b) => {
Expr::When(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
}
Expr::ParensAround(a) => {
// The formatter can remove redundant parentheses, so also remove these when normalizing for comparison.
a.remove_spaces(arena)
}
Expr::MalformedIdent(a, b) => Expr::MalformedIdent(a, b),
Expr::MalformedClosure => Expr::MalformedClosure,
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
Expr::SingleQuote(a) => Expr::Num(a),
}
}
}
impl<'a> RemoveSpaces<'a> for Pattern<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Pattern::Identifier(a) => Pattern::Identifier(a),
Pattern::Tag(a) => Pattern::Tag(a),
Pattern::OpaqueRef(a) => Pattern::OpaqueRef(a),
Pattern::Apply(a, b) => Pattern::Apply(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Pattern::RecordDestructure(a) => Pattern::RecordDestructure(a.remove_spaces(arena)),
Pattern::RequiredField(a, b) => {
Pattern::RequiredField(a, arena.alloc(b.remove_spaces(arena)))
}
Pattern::OptionalField(a, b) => {
Pattern::OptionalField(a, arena.alloc(b.remove_spaces(arena)))
}
Pattern::NumLiteral(a) => Pattern::NumLiteral(a),
Pattern::NonBase10Literal {
string,
base,
is_negative,
} => Pattern::NonBase10Literal {
string,
base,
is_negative,
},
Pattern::FloatLiteral(a) => Pattern::FloatLiteral(a),
Pattern::StrLiteral(a) => Pattern::StrLiteral(a),
Pattern::Underscore(a) => Pattern::Underscore(a),
Pattern::Malformed(a) => Pattern::Malformed(a),
Pattern::MalformedIdent(a, b) => Pattern::MalformedIdent(a, b),
Pattern::QualifiedIdentifier { module_name, ident } => {
Pattern::QualifiedIdentifier { module_name, ident }
}
Pattern::SpaceBefore(a, _) => a.remove_spaces(arena),
Pattern::SpaceAfter(a, _) => a.remove_spaces(arena),
Pattern::SingleQuote(a) => Pattern::NumLiteral(a),
}
}
}
impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
TypeAnnotation::Function(a, b) => TypeAnnotation::Function(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
TypeAnnotation::Apply(a, b, c) => TypeAnnotation::Apply(a, b, c.remove_spaces(arena)),
TypeAnnotation::BoundVariable(a) => TypeAnnotation::BoundVariable(a),
TypeAnnotation::As(a, _, c) => {
TypeAnnotation::As(arena.alloc(a.remove_spaces(arena)), &[], c)
}
TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record {
fields: fields.remove_spaces(arena),
ext: ext.remove_spaces(arena),
},
TypeAnnotation::TagUnion { ext, tags } => TypeAnnotation::TagUnion {
ext: ext.remove_spaces(arena),
tags: tags.remove_spaces(arena),
},
TypeAnnotation::Inferred => TypeAnnotation::Inferred,
TypeAnnotation::Wildcard => TypeAnnotation::Wildcard,
TypeAnnotation::Where(annot, has_clauses) => TypeAnnotation::Where(
arena.alloc(annot.remove_spaces(arena)),
arena.alloc(has_clauses.remove_spaces(arena)),
),
TypeAnnotation::SpaceBefore(a, _) => a.remove_spaces(arena),
TypeAnnotation::SpaceAfter(a, _) => a.remove_spaces(arena),
TypeAnnotation::Malformed(a) => TypeAnnotation::Malformed(a),
}
}
}
impl<'a> RemoveSpaces<'a> for HasClause<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
HasClause {
var: self.var.remove_spaces(arena),
ability: self.ability.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for Tag<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Tag::Apply { name, args } => Tag::Apply {
name: name.remove_spaces(arena),
args: args.remove_spaces(arena),
},
Tag::Malformed(a) => Tag::Malformed(a),
Tag::SpaceBefore(a, _) => a.remove_spaces(arena),
Tag::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}

View File

@ -10,60 +10,153 @@ mod test_fmt {
use roc_fmt::def::fmt_def;
use roc_fmt::module::fmt_module;
use roc_fmt::Buf;
use roc_parse::ast::Module;
use roc_parse::module::{self, module_defs};
use roc_parse::parser::Parser;
use roc_parse::state::State;
use roc_test_utils::assert_multiline_str_eq;
// Not intended to be used directly in tests; please use expr_formats_to or expr_formats_same
fn expect_format_expr_helper(input: &str, expected: &str) {
fn expr_formats_to(input: &str, expected: &str) {
let arena = Bump::new();
match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) {
let input = input.trim();
let expected = expected.trim();
match roc_parse::test_helpers::parse_expr_with(&arena, input) {
Ok(actual) => {
use roc_fmt::spaces::RemoveSpaces;
let mut buf = Buf::new_in(&arena);
actual.format_with_options(&mut buf, Parens::NotNeeded, Newlines::Yes, 0);
assert_multiline_str_eq!(expected, buf.as_str());
let output = buf.as_str();
assert_multiline_str_eq!(expected, output);
let reparsed_ast = roc_parse::test_helpers::parse_expr_with(&arena, output).unwrap_or_else(|err| {
panic!(
"After formatting, the source code no longer parsed!\n\nParse error was: {:?}\n\nThe code that failed to parse:\n\n{}\n\n",
err, output
);
});
let ast_normalized = actual.remove_spaces(&arena);
let reparsed_ast_normalized = reparsed_ast.remove_spaces(&arena);
// HACK!
// We compare the debug format strings of the ASTs, because I'm finding in practice that _somewhere_ deep inside the ast,
// the PartialEq implementation is returning `false` even when the Debug-formatted impl is exactly the same.
// I don't have the patience to debug this right now, so let's leave it for another day...
// TODO: fix PartialEq impl on ast types
if format!("{:?}", ast_normalized) != format!("{:?}", reparsed_ast_normalized) {
panic!(
"Formatting bug; formatting didn't reparse to the same AST (after removing spaces)\n\n\
* * * Source code before formatting:\n{}\n\n\
* * * Source code after formatting:\n{}\n\n",
input,
output
);
}
// Now verify that the resultant formatting is _stable_ - i.e. that it doesn't change again if re-formatted
let mut reformatted_buf = Buf::new_in(&arena);
reparsed_ast.format_with_options(&mut reformatted_buf, Parens::NotNeeded, Newlines::Yes, 0);
if output != reformatted_buf.as_str() {
eprintln!("Formatting bug; formatting is not stable. Reformatting the formatted code changed it again, as follows:\n\n");
assert_multiline_str_eq!(output, reformatted_buf.as_str());
}
}
Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{}\n\nParse error was:\n\n{:?}\n\n", input, error)
};
}
fn expr_formats_to(input: &str, expected: &str) {
let input = input.trim_end();
let expected = expected.trim_end();
// First check that input formats to the expected version
expect_format_expr_helper(input, expected);
// Parse the expected result format it, asserting that it doesn't change
// It's important that formatting be stable / idempotent
expect_format_expr_helper(expected, expected);
}
fn expr_formats_same(input: &str) {
expr_formats_to(input, input);
}
fn fmt_module_and_defs<'a>(
arena: &Bump,
src: &str,
module: &Module<'a>,
state: State<'a>,
buf: &mut Buf<'_>,
) {
fmt_module(buf, module);
match module_defs().parse(&arena, state) {
Ok((_, loc_defs, _)) => {
for loc_def in loc_defs {
fmt_def(buf, arena.alloc(loc_def.value), 0);
}
}
Err(error) => panic!("Unexpected parse failure when parsing this for defs formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
}
}
// Not intended to be used directly in tests; please use module_formats_to or module_formats_same
fn expect_format_module_helper(src: &str, expected: &str) {
let arena = Bump::new();
let src = src.trim();
let expected = expected.trim();
match module::parse_header(&arena, State::new(src.as_bytes())) {
Ok((actual, state)) => {
use roc_fmt::spaces::RemoveSpaces;
let mut buf = Buf::new_in(&arena);
fmt_module(&mut buf, &actual);
fmt_module_and_defs(&arena, src, &actual, state, &mut buf);
match module_defs().parse(&arena, state) {
Ok((_, loc_defs, _)) => {
for loc_def in loc_defs {
fmt_def(&mut buf, arena.alloc(loc_def.value), 0);
}
}
Err(error) => panic!("Unexpected parse failure when parsing this for defs formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
let output = buf.as_str().trim();
let (reparsed_ast, state) = module::parse_header(&arena, State::new(output.as_bytes())).unwrap_or_else(|err| {
panic!(
"After formatting, the source code no longer parsed!\n\nParse error was: {:?}\n\nThe code that failed to parse:\n\n{}\n\n",
err, output
);
});
let ast_normalized = actual.remove_spaces(&arena);
let reparsed_ast_normalized = reparsed_ast.remove_spaces(&arena);
// HACK!
// We compare the debug format strings of the ASTs, because I'm finding in practice that _somewhere_ deep inside the ast,
// the PartialEq implementation is returning `false` even when the Debug-formatted impl is exactly the same.
// I don't have the patience to debug this right now, so let's leave it for another day...
// TODO: fix PartialEq impl on ast types
if format!("{:?}", ast_normalized) != format!("{:?}", reparsed_ast_normalized) {
panic!(
"Formatting bug; formatting didn't reparse to the same AST (after removing spaces)\n\n\
* * * Source code before formatting:\n{}\n\n\
* * * Source code after formatting:\n{}\n\n",
src,
output
);
}
assert_multiline_str_eq!(expected, buf.as_str())
// Now verify that the resultant formatting is _stable_ - i.e. that it doesn't change again if re-formatted
let mut reformatted_buf = Buf::new_in(&arena);
fmt_module_and_defs(&arena, output, &reparsed_ast, state, &mut reformatted_buf);
let reformatted = reformatted_buf.as_str().trim();
if output != reformatted {
eprintln!("Formatting bug; formatting is not stable. Reformatting the formatted code changed it again, as follows:\n\n");
assert_multiline_str_eq!(output, reformatted);
}
// If everything was idempotent re-parsing worked, finally assert
// that the formatted code was what we expected it to be.
//
// Do this last because if there were any serious problems with the
// formatter (e.g. it wasn't idempotent), we want to know about
// those more than we want to know that the expectation failed!
assert_multiline_str_eq!(expected, output);
}
Err(error) => panic!("Unexpected parse failure when parsing this for module header formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
};
@ -192,6 +285,714 @@ mod test_fmt {
);
}
#[test]
fn type_annotation_allow_blank_line_before_and_after_comment() {
expr_formats_same(indoc!(
r#"
person :
{
firstName : Str,
# comment
lastName : Str,
}
person
"#
));
expr_formats_same(indoc!(
r#"
person :
{
firstName : Str,
# comment
lastName : Str,
}
person
"#
));
expr_formats_same(indoc!(
r#"
person :
{
firstName : Str,
# comment
lastName : Str,
}
person
"#
));
expr_formats_same(indoc!(
r#"
person :
{
firstName : Str,
# comment
lastName : Str,
}
person
"#
));
expr_formats_same(indoc!(
r#"
person :
{
firstName : Str,
# comment 1
lastName : Str,
# comment 2
# comment 3
}
person
"#
));
expr_formats_same(indoc!(
r#"
person :
{
firstName : Str,
# comment 1
lastName : Str,
# comment 2
# comment 3
}
person
"#
));
expr_formats_to(
indoc!(
r#"
person :
{
# comment
firstName : Str,
lastName : Str,
}
person
"#
),
indoc!(
r#"
person :
{
# comment
firstName : Str,
lastName : Str,
}
person
"#
),
);
expr_formats_to(
indoc!(
r#"
person :
{
firstName : Str,
lastName : Str,
# comment
}
person
"#
),
indoc!(
r#"
person :
{
firstName : Str,
lastName : Str,
# comment
}
person
"#
),
);
expr_formats_to(
indoc!(
r#"
person :
{
firstName : Str,
# comment
lastName : Str,
}
person
"#
),
indoc!(
r#"
person :
{
firstName : Str,
# comment
lastName : Str,
}
person
"#
),
);
expr_formats_to(
indoc!(
r#"
person :
{
firstName : Str,
# comment
lastName : Str,
}
person
"#
),
indoc!(
r#"
person :
{
firstName : Str,
# comment
lastName : Str,
}
person
"#
),
);
expr_formats_to(
indoc!(
r#"
person :
{
firstName : Str,
# comment
lastName : Str,
}
person
"#
),
indoc!(
r#"
person :
{
firstName : Str,
# comment
lastName : Str,
}
person
"#
),
);
}
#[test]
fn record_allow_blank_line_before_and_after_comment() {
expr_formats_same(indoc!(
r#"
person = {
firstName: "first",
# comment 1
lastName: "last",
}
person
"#
));
expr_formats_same(indoc!(
r#"
person = {
firstName: "first",
# comment 1
lastName: "last",
}
person
"#
));
expr_formats_same(indoc!(
r#"
person = {
firstName: "first",
# comment 1
lastName: "last",
}
person
"#
));
expr_formats_same(indoc!(
r#"
person = {
firstName: "first",
# comment 1
lastName: "last",
}
person
"#
));
expr_formats_same(indoc!(
r#"
person = {
firstName: "first",
# comment 1
lastName: "last",
# comment 2
# comment 3
}
person
"#
));
expr_formats_same(indoc!(
r#"
person = {
firstName: "first",
# comment 1
lastName: "last",
# comment 2
# comment 3
}
person
"#
));
expr_formats_to(
indoc!(
r#"
person = {
# comment
firstName: "first",
lastName: "last",
}
person
"#
),
indoc!(
r#"
person = {
# comment
firstName: "first",
lastName: "last",
}
person
"#
),
);
expr_formats_to(
indoc!(
r#"
person = {
firstName: "first",
lastName: "last",
# comment
}
person
"#
),
indoc!(
r#"
person = {
firstName: "first",
lastName: "last",
# comment
}
person
"#
),
);
expr_formats_to(
indoc!(
r#"
person = {
firstName: "first",
# comment 1
lastName: "last",
}
person
"#
),
indoc!(
r#"
person = {
firstName: "first",
# comment 1
lastName: "last",
}
person
"#
),
);
expr_formats_to(
indoc!(
r#"
person = {
firstName: "first",
# comment 1
lastName: "last",
}
person
"#
),
indoc!(
r#"
person = {
firstName: "first",
# comment 1
lastName: "last",
}
person
"#
),
);
expr_formats_to(
indoc!(
r#"
person = {
firstName: "first",
# comment 1
lastName: "last",
}
person
"#
),
indoc!(
r#"
person = {
firstName: "first",
# comment 1
lastName: "last",
}
person
"#
),
);
}
#[test]
fn list_allow_blank_line_before_and_after_comment() {
expr_formats_same(indoc!(
r#"
list = [
0,
# comment
1,
]
list
"#
));
expr_formats_same(indoc!(
r#"
list = [
0,
# comment
1,
]
list
"#
));
expr_formats_same(indoc!(
r#"
list = [
0,
# comment
1,
]
list
"#
));
expr_formats_same(indoc!(
r#"
list = [
0,
# comment
1,
]
list
"#
));
expr_formats_same(indoc!(
r#"
list = [
0,
# comment 1
1,
# comment 2
# comment 3
]
list
"#
));
expr_formats_same(indoc!(
r#"
list = [
0,
# comment 1
1,
# comment 2
# comment 3
]
list
"#
));
expr_formats_to(
indoc!(
r#"
list = [
# comment
0,
1,
]
list
"#
),
indoc!(
r#"
list = [
# comment
0,
1,
]
list
"#
),
);
expr_formats_to(
indoc!(
r#"
list = [
0,
1,
# comment
]
list
"#
),
indoc!(
r#"
list = [
0,
1,
# comment
]
list
"#
),
);
expr_formats_to(
indoc!(
r#"
list = [
0,
# comment
1,
]
list
"#
),
indoc!(
r#"
list = [
0,
# comment
1,
]
list
"#
),
);
expr_formats_to(
indoc!(
r#"
list = [
0,
# comment
1,
]
list
"#
),
indoc!(
r#"
list = [
0,
# comment
1,
]
list
"#
),
);
expr_formats_to(
indoc!(
r#"
list = [
0,
# comment
1,
]
list
"#
),
indoc!(
r#"
list = [
0,
# comment
1,
]
list
"#
),
);
}
#[test]
fn force_space_at_beginning_of_comment() {
expr_formats_to(
@ -736,7 +1537,7 @@ mod test_fmt {
3,
]
toList
toList
"#
));
@ -1648,6 +2449,7 @@ mod test_fmt {
r#"
[
# Thirty Seven
37,
# Thirty Eight
38,
@ -3230,6 +4032,39 @@ mod test_fmt {
);
}
#[test]
fn format_tui_package_config() {
// At one point this failed to reformat.
module_formats_to(
indoc!(
r#"
platform "tui"
requires { Model } { main : { init : ({} -> Model), update : (Model, Str -> Model), view : (Model -> Str) } }
exposes []
packages {}
imports []
provides [ mainForHost ]
mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View }
mainForHost = main
"#
),
indoc!(
r#"
platform "tui"
requires { Model } { main : { init : {} -> Model, update : Model, Str -> Model, view : Model -> Str } }
exposes []
packages {}
imports []
provides [ mainForHost ]
mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View }
mainForHost = main
"#
),
);
}
#[test]
fn single_line_hosted() {
module_formats_same(indoc!(

View File

@ -554,7 +554,7 @@ trait Backend<'a> {
}
LowLevel::NumRound => self.build_fn_call(
sym,
bitcode::NUM_ROUND[FloatWidth::F64].to_string(),
bitcode::NUM_ROUND_F64[IntWidth::I64].to_string(),
args,
arg_layouts,
ret_layout,

View File

@ -15,6 +15,7 @@ roc_error_macros = { path = "../../error_macros" }
roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" }
roc_std = { path = "../../roc_std", default-features = false }
roc_debug_flags = { path = "../debug_flags" }
morphic_lib = { path = "../../vendor/morphic_lib" }
bumpalo = { version = "3.8.0", features = ["collections"] }
inkwell = { path = "../../vendor/inkwell" }

View File

@ -56,6 +56,7 @@ use morphic_lib::{
use roc_builtins::bitcode::{self, FloatWidth, IntWidth, IntrinsicName};
use roc_builtins::{float_intrinsic, llvm_int_intrinsic};
use roc_collections::all::{ImMap, MutMap, MutSet};
use roc_debug_flags::{dbg_do, ROC_PRINT_LLVM_FN_VERIFICATION};
use roc_error_macros::internal_error;
use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, ModuleId, Symbol};
@ -69,13 +70,13 @@ use target_lexicon::{Architecture, OperatingSystem, Triple};
use super::convert::zig_with_overflow_roc_dec;
/// This is for Inkwell's FunctionValue::verify - we want to know the verification
/// output in debug builds, but we don't want it to print to stdout in release builds!
#[cfg(debug_assertions)]
const PRINT_FN_VERIFICATION_OUTPUT: bool = true;
#[cfg(not(debug_assertions))]
const PRINT_FN_VERIFICATION_OUTPUT: bool = false;
#[inline(always)]
fn print_fn_verification_output() -> bool {
dbg_do!(ROC_PRINT_LLVM_FN_VERIFICATION, {
return true;
});
false
}
#[macro_export]
macro_rules! debug_info_init {
@ -603,6 +604,7 @@ static LLVM_SIN: IntrinsicName = float_intrinsic!("llvm.sin");
static LLVM_COS: IntrinsicName = float_intrinsic!("llvm.cos");
static LLVM_CEILING: IntrinsicName = float_intrinsic!("llvm.ceil");
static LLVM_FLOOR: IntrinsicName = float_intrinsic!("llvm.floor");
static LLVM_ROUND: IntrinsicName = float_intrinsic!("llvm.round");
static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64";
static LLVM_MEMSET_I32: &str = "llvm.memset.p0i8.i32";
@ -807,20 +809,24 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(),
Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(),
Str(str_literal) => {
let global = if str_literal.len() < env.small_str_bytes() as usize {
if str_literal.len() < env.small_str_bytes() as usize {
match env.small_str_bytes() {
24 => small_str_ptr_width_8(env, parent, str_literal),
12 => small_str_ptr_width_4(env, parent, str_literal),
24 => small_str_ptr_width_8(env, parent, str_literal).into(),
12 => small_str_ptr_width_4(env, parent, str_literal).into(),
_ => unreachable!("incorrect small_str_bytes"),
}
} else {
let ptr = define_global_str_literal_ptr(env, *str_literal);
let number_of_elements = env.ptr_int().const_int(str_literal.len() as u64, false);
const_str_alloca_ptr(env, parent, ptr, number_of_elements, number_of_elements)
};
let alloca =
const_str_alloca_ptr(env, parent, ptr, number_of_elements, number_of_elements);
global.into()
match env.target_info.ptr_width() {
PtrWidth::Bytes4 => env.builder.build_load(alloca, "load_const_str"),
PtrWidth::Bytes8 => alloca.into(),
}
}
}
}
}
@ -4515,7 +4521,7 @@ pub fn build_proc<'a, 'ctx, 'env>(
}
pub fn verify_fn(fn_val: FunctionValue<'_>) {
if !fn_val.verify(PRINT_FN_VERIFICATION_OUTPUT) {
if !fn_val.verify(print_fn_verification_output()) {
unsafe {
fn_val.delete();
}
@ -5675,6 +5681,15 @@ fn run_low_level<'a, 'ctx, 'env>(
update_mode,
)
}
ListIsUnique => {
// List.isUnique : List a -> Bool
debug_assert_eq!(args.len(), 1);
let list = load_symbol(scope, &args[0]);
let list = list_to_c_abi(env, list).into();
call_bitcode_fn(env, &[list], bitcode::LIST_IS_UNIQUE)
}
NumToStr => {
// Num.toStr : Num a -> Str
debug_assert_eq!(args.len(), 1);
@ -6679,10 +6694,34 @@ fn build_int_binop<'a, 'ctx, 'env>(
&LLVM_MUL_WITH_OVERFLOW[int_width],
&[lhs.into(), rhs.into()],
),
NumGt => bd.build_int_compare(SGT, lhs, rhs, "int_gt").into(),
NumGte => bd.build_int_compare(SGE, lhs, rhs, "int_gte").into(),
NumLt => bd.build_int_compare(SLT, lhs, rhs, "int_lt").into(),
NumLte => bd.build_int_compare(SLE, lhs, rhs, "int_lte").into(),
NumGt => {
if int_width.is_signed() {
bd.build_int_compare(SGT, lhs, rhs, "gt_int").into()
} else {
bd.build_int_compare(UGT, lhs, rhs, "gt_uint").into()
}
}
NumGte => {
if int_width.is_signed() {
bd.build_int_compare(SGE, lhs, rhs, "gte_int").into()
} else {
bd.build_int_compare(UGE, lhs, rhs, "gte_uint").into()
}
}
NumLt => {
if int_width.is_signed() {
bd.build_int_compare(SLT, lhs, rhs, "lt_int").into()
} else {
bd.build_int_compare(ULT, lhs, rhs, "lt_uint").into()
}
}
NumLte => {
if int_width.is_signed() {
bd.build_int_compare(SLE, lhs, rhs, "lte_int").into()
} else {
bd.build_int_compare(ULE, lhs, rhs, "lte_uint").into()
}
}
NumRemUnchecked => {
if int_width.is_signed() {
bd.build_int_signed_rem(lhs, rhs, "rem_int").into()
@ -7448,20 +7487,67 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
}
}
}
NumCeiling => env.builder.build_cast(
InstructionOpcode::FPToSI,
env.call_intrinsic(&LLVM_CEILING[float_width], &[arg.into()]),
env.context.i64_type(),
"num_ceiling",
),
NumFloor => env.builder.build_cast(
InstructionOpcode::FPToSI,
env.call_intrinsic(&LLVM_FLOOR[float_width], &[arg.into()]),
env.context.i64_type(),
"num_floor",
),
NumCeiling => {
let (return_signed, return_type) = match layout {
Layout::Builtin(Builtin::Int(int_width)) => (
int_width.is_signed(),
convert::int_type_from_int_width(env, *int_width),
),
_ => internal_error!("Ceiling return layout is not int: {:?}", layout),
};
let opcode = if return_signed {
InstructionOpcode::FPToSI
} else {
InstructionOpcode::FPToUI
};
env.builder.build_cast(
opcode,
env.call_intrinsic(&LLVM_CEILING[float_width], &[arg.into()]),
return_type,
"num_ceiling",
)
}
NumFloor => {
let (return_signed, return_type) = match layout {
Layout::Builtin(Builtin::Int(int_width)) => (
int_width.is_signed(),
convert::int_type_from_int_width(env, *int_width),
),
_ => internal_error!("Ceiling return layout is not int: {:?}", layout),
};
let opcode = if return_signed {
InstructionOpcode::FPToSI
} else {
InstructionOpcode::FPToUI
};
env.builder.build_cast(
opcode,
env.call_intrinsic(&LLVM_FLOOR[float_width], &[arg.into()]),
return_type,
"num_floor",
)
}
NumRound => {
let (return_signed, return_type) = match layout {
Layout::Builtin(Builtin::Int(int_width)) => (
int_width.is_signed(),
convert::int_type_from_int_width(env, *int_width),
),
_ => internal_error!("Ceiling return layout is not int: {:?}", layout),
};
let opcode = if return_signed {
InstructionOpcode::FPToSI
} else {
InstructionOpcode::FPToUI
};
env.builder.build_cast(
opcode,
env.call_intrinsic(&LLVM_ROUND[float_width], &[arg.into()]),
return_type,
"num_round",
)
}
NumIsFinite => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_FINITE[float_width]),
NumRound => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ROUND[float_width]),
// trigonometry
NumSin => env.call_intrinsic(&LLVM_SIN[float_width], &[arg.into()]),

View File

@ -4,7 +4,6 @@ use std::fmt::Write;
use code_builder::Align;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::MutMap;
use roc_module::ident::Ident;
use roc_module::low_level::{LowLevel, LowLevelWrapperType};
use roc_module::symbol::{Interns, Symbol};
use roc_mono::code_gen_help::{CodeGenHelp, HelperOp, REFCOUNT_MAX};
@ -192,7 +191,7 @@ impl<'a> WasmBackend<'a> {
.get_mut(&self.env.module_id)
.unwrap();
let ident_id = ident_ids.add_ident(&Ident::from(debug_name));
let ident_id = ident_ids.add_str(debug_name);
Symbol::new(self.env.module_id, ident_id)
}

View File

@ -116,6 +116,13 @@ impl From<&StoredValue> for CodeGenNumType {
}
}
fn integer_symbol_is_signed(backend: &WasmBackend<'_>, symbol: Symbol) -> bool {
return match backend.storage.symbol_layouts[&symbol] {
Layout::Builtin(Builtin::Int(int_width)) => int_width.is_signed(),
x => internal_error!("Expected integer, found {:?}", x),
};
}
pub struct LowLevelCall<'a> {
pub lowlevel: LowLevel,
pub arguments: &'a [Symbol],
@ -291,6 +298,8 @@ impl<'a> LowLevelCall<'a> {
_ => internal_error!("invalid storage for List"),
},
ListIsUnique => self.load_args_and_call_zig(backend, bitcode::LIST_IS_UNIQUE),
ListMap | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk
| ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith
| ListAny | ListAll | ListFindUnsafe | DictWalk => {
@ -398,8 +407,20 @@ impl<'a> LowLevelCall<'a> {
NumGt => {
self.load_args(backend);
match CodeGenNumType::for_symbol(backend, self.arguments[0]) {
I32 => backend.code_builder.i32_gt_s(),
I64 => backend.code_builder.i64_gt_s(),
I32 => {
if integer_symbol_is_signed(backend, self.arguments[0]) {
backend.code_builder.i32_gt_s()
} else {
backend.code_builder.i32_gt_u()
}
}
I64 => {
if integer_symbol_is_signed(backend, self.arguments[0]) {
backend.code_builder.i64_gt_s()
} else {
backend.code_builder.i64_gt_u()
}
}
F32 => backend.code_builder.f32_gt(),
F64 => backend.code_builder.f64_gt(),
x => todo!("{:?} for {:?}", self.lowlevel, x),
@ -408,8 +429,20 @@ impl<'a> LowLevelCall<'a> {
NumGte => {
self.load_args(backend);
match CodeGenNumType::for_symbol(backend, self.arguments[0]) {
I32 => backend.code_builder.i32_ge_s(),
I64 => backend.code_builder.i64_ge_s(),
I32 => {
if integer_symbol_is_signed(backend, self.arguments[0]) {
backend.code_builder.i32_ge_s()
} else {
backend.code_builder.i32_ge_u()
}
}
I64 => {
if integer_symbol_is_signed(backend, self.arguments[0]) {
backend.code_builder.i64_ge_s()
} else {
backend.code_builder.i64_ge_u()
}
}
F32 => backend.code_builder.f32_ge(),
F64 => backend.code_builder.f64_ge(),
x => todo!("{:?} for {:?}", self.lowlevel, x),
@ -418,8 +451,20 @@ impl<'a> LowLevelCall<'a> {
NumLt => {
self.load_args(backend);
match CodeGenNumType::for_symbol(backend, self.arguments[0]) {
I32 => backend.code_builder.i32_lt_s(),
I64 => backend.code_builder.i64_lt_s(),
I32 => {
if integer_symbol_is_signed(backend, self.arguments[0]) {
backend.code_builder.i32_lt_s()
} else {
backend.code_builder.i32_lt_u()
}
}
I64 => {
if integer_symbol_is_signed(backend, self.arguments[0]) {
backend.code_builder.i64_lt_s()
} else {
backend.code_builder.i64_lt_u()
}
}
F32 => backend.code_builder.f32_lt(),
F64 => backend.code_builder.f64_lt(),
x => todo!("{:?} for {:?}", self.lowlevel, x),
@ -428,8 +473,20 @@ impl<'a> LowLevelCall<'a> {
NumLte => {
self.load_args(backend);
match CodeGenNumType::for_symbol(backend, self.arguments[0]) {
I32 => backend.code_builder.i32_le_s(),
I64 => backend.code_builder.i64_le_s(),
I32 => {
if integer_symbol_is_signed(backend, self.arguments[0]) {
backend.code_builder.i32_le_s()
} else {
backend.code_builder.i32_le_u()
}
}
I64 => {
if integer_symbol_is_signed(backend, self.arguments[0]) {
backend.code_builder.i64_le_s()
} else {
backend.code_builder.i64_le_u()
}
}
F32 => backend.code_builder.f32_le(),
F64 => backend.code_builder.f64_le(),
x => todo!("{:?} for {:?}", self.lowlevel, x),
@ -525,18 +582,6 @@ impl<'a> LowLevelCall<'a> {
NumCos => todo!("{:?}", self.lowlevel),
NumSqrtUnchecked => todo!("{:?}", self.lowlevel),
NumLogUnchecked => todo!("{:?}", self.lowlevel),
NumRound => {
self.load_args(backend);
match CodeGenNumType::for_symbol(backend, self.arguments[0]) {
F32 => {
self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND[FloatWidth::F32])
}
F64 => {
self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND[FloatWidth::F64])
}
_ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout),
}
}
NumToFloat => {
self.load_args(backend);
let ret_type = CodeGenNumType::from(self.ret_layout);
@ -556,35 +601,54 @@ impl<'a> LowLevelCall<'a> {
}
}
NumPow => todo!("{:?}", self.lowlevel),
NumCeiling => {
NumRound => {
self.load_args(backend);
match CodeGenNumType::from(self.ret_layout) {
I32 => {
let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]);
let ret_type = CodeGenNumType::from(self.ret_layout);
let width = match ret_type {
CodeGenNumType::I32 => IntWidth::I32,
CodeGenNumType::I64 => IntWidth::I64,
CodeGenNumType::I128 => todo!("{:?} for I128", self.lowlevel),
_ => internal_error!("Invalid return type for round: {:?}", ret_type),
};
match arg_type {
F32 => self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND_F32[width]),
F64 => self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND_F64[width]),
_ => internal_error!("Invalid argument type for round: {:?}", arg_type),
}
}
NumCeiling | NumFloor => {
self.load_args(backend);
let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]);
let ret_type = CodeGenNumType::from(self.ret_layout);
match (arg_type, self.lowlevel) {
(F32, NumCeiling) => {
backend.code_builder.f32_ceil();
backend.code_builder.i32_trunc_s_f32()
}
I64 => {
(F64, NumCeiling) => {
backend.code_builder.f64_ceil();
backend.code_builder.i64_trunc_s_f64()
}
(F32, NumFloor) => {
backend.code_builder.f32_floor();
}
(F64, NumFloor) => {
backend.code_builder.f64_floor();
}
_ => internal_error!("Invalid argument type for ceiling: {:?}", arg_type),
}
match (ret_type, arg_type) {
// TODO: unsigned truncation
(I32, F32) => backend.code_builder.i32_trunc_s_f32(),
(I32, F64) => backend.code_builder.i32_trunc_s_f64(),
(I64, F32) => backend.code_builder.i64_trunc_s_f32(),
(I64, F64) => backend.code_builder.i64_trunc_s_f64(),
(I128, _) => todo!("{:?} for I128", self.lowlevel),
_ => panic_ret_type(),
}
}
NumPowInt => todo!("{:?}", self.lowlevel),
NumFloor => {
self.load_args(backend);
match CodeGenNumType::from(self.ret_layout) {
I32 => {
backend.code_builder.f32_floor();
backend.code_builder.i32_trunc_s_f32()
}
I64 => {
backend.code_builder.f64_floor();
backend.code_builder.i64_trunc_s_f64()
}
_ => panic_ret_type(),
}
}
NumIsFinite => num_is_finite(backend, self.arguments[0]),
NumAtan => match self.ret_layout {

View File

@ -4,6 +4,3 @@ version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
[dependencies]
arrayvec = "0.7.2"

View File

@ -1,6 +1,7 @@
use std::path::PathBuf;
use bumpalo::Bump;
use roc_load_internal::file::Threading;
use roc_module::symbol::ModuleId;
const MODULES: &[(ModuleId, &str)] = &[
@ -37,6 +38,7 @@ fn write_subs_for_module(module_id: ModuleId, filename: &str) {
Default::default(),
target_info,
roc_reporting::report::RenderTarget::ColorTerminal,
Threading::Multi,
);
let module = res_module.unwrap();

View File

@ -1,3 +1,5 @@
pub use roc_load_internal::file::Threading;
use bumpalo::Bump;
use roc_collections::all::MutMap;
use roc_constrain::module::ExposedByModule;
@ -12,6 +14,7 @@ pub use roc_load_internal::file::{
LoadResult, LoadStart, LoadedModule, LoadingProblem, MonomorphizedModule, Phase,
};
#[allow(clippy::too_many_arguments)]
fn load<'a>(
arena: &'a Bump,
load_start: LoadStart<'a>,
@ -20,6 +23,7 @@ fn load<'a>(
goal_phase: Phase,
target_info: TargetInfo,
render: RenderTarget,
threading: Threading,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
let cached_subs = read_cached_subs();
@ -32,6 +36,7 @@ fn load<'a>(
target_info,
cached_subs,
render,
threading,
)
}
@ -59,6 +64,7 @@ pub fn load_single_threaded<'a>(
)
}
#[allow(clippy::too_many_arguments)]
pub fn load_and_monomorphize_from_str<'a>(
arena: &'a Bump,
filename: PathBuf,
@ -67,6 +73,7 @@ pub fn load_and_monomorphize_from_str<'a>(
exposed_types: ExposedByModule,
target_info: TargetInfo,
render: RenderTarget,
threading: Threading,
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
use LoadResult::*;
@ -80,6 +87,7 @@ pub fn load_and_monomorphize_from_str<'a>(
Phase::MakeSpecializations,
target_info,
render,
threading,
)? {
Monomorphized(module) => Ok(module),
TypeChecked(_) => unreachable!(""),
@ -93,6 +101,7 @@ pub fn load_and_monomorphize<'a>(
exposed_types: ExposedByModule,
target_info: TargetInfo,
render: RenderTarget,
threading: Threading,
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
use LoadResult::*;
@ -106,6 +115,7 @@ pub fn load_and_monomorphize<'a>(
Phase::MakeSpecializations,
target_info,
render,
threading,
)? {
Monomorphized(module) => Ok(module),
TypeChecked(_) => unreachable!(""),
@ -119,6 +129,7 @@ pub fn load_and_typecheck<'a>(
exposed_types: ExposedByModule,
target_info: TargetInfo,
render: RenderTarget,
threading: Threading,
) -> Result<LoadedModule, LoadingProblem<'a>> {
use LoadResult::*;
@ -132,6 +143,7 @@ pub fn load_and_typecheck<'a>(
Phase::SolveTypes,
target_info,
render,
threading,
)? {
Monomorphized(_) => unreachable!(""),
TypeChecked(module) => Ok(module),

View File

@ -21,7 +21,7 @@ roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" }
roc_reporting = { path = "../../reporting" }
morphic_lib = { path = "../../vendor/morphic_lib" }
roc_debug_flags = { path = "../debug_flags" }
ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.8.0", features = ["collections"] }
parking_lot = "0.12"
@ -29,8 +29,7 @@ crossbeam = "0.8.1"
num_cpus = "1.13.0"
[dev-dependencies]
tempfile = "3.2.0"
pretty_assertions = "1.0.0"
maplit = "1.0.2"
indoc = "1.0.3"
roc_test_utils = { path = "../../test_utils" }
roc_test_utils = { path = "../../test_utils" }

View File

@ -95,7 +95,12 @@ pub fn generate_module_docs<'a>(
parsed_defs
.iter()
.fold((vec![], None), |(acc, maybe_comments_after), def| {
generate_entry_doc(&scope.ident_ids, acc, maybe_comments_after, &def.value)
generate_entry_doc(
&scope.locals.ident_ids,
acc,
maybe_comments_after,
&def.value,
)
});
ModuleDocumentation {

View File

@ -15,8 +15,12 @@ use roc_constrain::module::{
constrain_builtin_imports, constrain_module, ExposedByModule, ExposedForModule,
ExposedModuleTypes,
};
use roc_debug_flags::{
dbg_do, ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE,
ROC_PRINT_IR_AFTER_SPECIALIZATION, ROC_PRINT_LOAD_LOG,
};
use roc_error_macros::internal_error;
use roc_module::ident::{Ident, ModuleName, QualifiedModuleName, TagName};
use roc_module::ident::{Ident, ModuleName, QualifiedModuleName};
use roc_module::symbol::{
IdentIds, IdentIdsByModule, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds,
PackageQualified, Symbol,
@ -26,7 +30,7 @@ use roc_mono::ir::{
UpdateModeIds,
};
use roc_mono::layout::{Layout, LayoutCache, LayoutProblem};
use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral};
use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation};
use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent};
use roc_parse::header::{HeaderFor, ModuleNameEnum, PackageName};
use roc_parse::ident::UppercaseIdent;
@ -39,7 +43,7 @@ use roc_solve::solve;
use roc_target::TargetInfo;
use roc_types::solved_types::Solved;
use roc_types::subs::{Subs, VarStore, Variable};
use roc_types::types::{Alias, AliasCommon, AliasKind, TypeExtension};
use roc_types::types::{Alias, AliasKind};
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::collections::HashMap;
use std::io;
@ -70,13 +74,46 @@ const PKG_CONFIG_FILE_NAME: &str = "Package-Config";
/// The . in between module names like Foo.Bar.Baz
const MODULE_SEPARATOR: char = '.';
const SHOW_MESSAGE_LOG: bool = false;
const EXPANDED_STACK_SIZE: usize = 8 * 1024 * 1024;
const PRELUDE_TYPES: [(&str, Symbol); 33] = [
("Num", Symbol::NUM_NUM),
("Int", Symbol::NUM_INT),
("Float", Symbol::NUM_FLOAT),
("Integer", Symbol::NUM_INTEGER),
("FloatingPoint", Symbol::NUM_FLOATINGPOINT),
("Binary32", Symbol::NUM_BINARY32),
("Binary64", Symbol::NUM_BINARY64),
("Signed128", Symbol::NUM_SIGNED128),
("Signed64", Symbol::NUM_SIGNED64),
("Signed32", Symbol::NUM_SIGNED32),
("Signed16", Symbol::NUM_SIGNED16),
("Signed8", Symbol::NUM_SIGNED8),
("Unsigned128", Symbol::NUM_UNSIGNED128),
("Unsigned64", Symbol::NUM_UNSIGNED64),
("Unsigned32", Symbol::NUM_UNSIGNED32),
("Unsigned16", Symbol::NUM_UNSIGNED16),
("Unsigned8", Symbol::NUM_UNSIGNED8),
("Natural", Symbol::NUM_NATURAL),
("Decimal", Symbol::NUM_DECIMAL),
("Nat", Symbol::NUM_NAT),
("I8", Symbol::NUM_I8),
("I16", Symbol::NUM_I16),
("I32", Symbol::NUM_I32),
("I64", Symbol::NUM_I64),
("I128", Symbol::NUM_I128),
("U8", Symbol::NUM_U8),
("U16", Symbol::NUM_U16),
("U32", Symbol::NUM_U32),
("U64", Symbol::NUM_U64),
("U128", Symbol::NUM_U128),
("F32", Symbol::NUM_F32),
("F64", Symbol::NUM_F64),
("Dec", Symbol::NUM_DEC),
];
macro_rules! log {
() => (if SHOW_MESSAGE_LOG { println!()} else {});
($($arg:tt)*) => (if SHOW_MESSAGE_LOG { println!($($arg)*); } else {})
($($arg:tt)*) => (dbg_do!(ROC_PRINT_LOAD_LOG, println!($($arg)*)))
}
/// Struct storing various intermediate stages by their ModuleId
@ -292,7 +329,11 @@ fn start_phase<'a>(
}
}
let skip_constraint_gen = state.cached_subs.lock().contains_key(&module_id);
let skip_constraint_gen = {
// Give this its own scope to make sure that the Guard from the lock() is dropped
// immediately after contains_key returns
state.cached_subs.lock().contains_key(&module_id)
};
BuildTask::CanonicalizeAndConstrain {
parsed,
@ -471,8 +512,9 @@ struct ModuleHeader<'a> {
exposes: Vec<Symbol>,
exposed_imports: MutMap<Ident, (Symbol, Region)>,
parse_state: roc_parse::state::State<'a>,
module_timing: ModuleTiming,
header_for: HeaderFor<'a>,
symbols_from_requires: Vec<(Loc<Symbol>, Loc<TypeAnnotation<'a>>)>,
module_timing: ModuleTiming,
}
#[derive(Debug)]
@ -562,6 +604,7 @@ struct ParsedModule<'a> {
exposed_imports: MutMap<Ident, (Symbol, Region)>,
parsed_defs: &'a [Loc<roc_parse::ast::Def<'a>>],
module_name: ModuleNameEnum<'a>,
symbols_from_requires: Vec<(Loc<Symbol>, Loc<TypeAnnotation<'a>>)>,
header_for: HeaderFor<'a>,
}
@ -907,6 +950,7 @@ fn enqueue_task<'a>(
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn load_and_typecheck_str<'a>(
arena: &'a Bump,
filename: PathBuf,
@ -915,6 +959,7 @@ pub fn load_and_typecheck_str<'a>(
exposed_types: ExposedByModule,
target_info: TargetInfo,
render: RenderTarget,
threading: Threading,
) -> Result<LoadedModule, LoadingProblem<'a>> {
use LoadResult::*;
@ -933,6 +978,7 @@ pub fn load_and_typecheck_str<'a>(
target_info,
cached_subs,
render,
threading,
)? {
Monomorphized(_) => unreachable!(""),
TypeChecked(module) => Ok(module),
@ -1050,6 +1096,12 @@ pub enum LoadResult<'a> {
Monomorphized(MonomorphizedModule<'a>),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Threading {
Single,
Multi,
}
/// The loading process works like this, starting from the given filename (e.g. "main.roc"):
///
/// 1. Open the file.
@ -1103,10 +1155,11 @@ pub fn load<'a>(
target_info: TargetInfo,
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
render: RenderTarget,
threading: Threading,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
// When compiling to wasm, we cannot spawn extra threads
// so we have a single-threaded implementation
if cfg!(target_family = "wasm") {
if threading == Threading::Single || cfg!(target_family = "wasm") {
load_single_threaded(
arena,
load_start,
@ -1627,21 +1680,20 @@ fn start_tasks<'a>(
Ok(())
}
#[cfg(debug_assertions)]
fn debug_print_ir(state: &State, flag: &str) {
if env::var(flag) != Ok("1".into()) {
return;
}
macro_rules! debug_print_ir {
($state:expr, $flag:path) => {
dbg_do!($flag, {
let procs_string = $state
.procedures
.values()
.map(|proc| proc.to_pretty(200))
.collect::<Vec<_>>();
let procs_string = state
.procedures
.values()
.map(|proc| proc.to_pretty(200))
.collect::<Vec<_>>();
let result = procs_string.join("\n");
let result = procs_string.join("\n");
println!("{}", result);
eprintln!("{}", result);
})
};
}
/// Report modules that are imported, but from which nothing is used
@ -1796,46 +1848,10 @@ fn update<'a>(
.imported_modules
.insert(ModuleId::NUM, Region::zero());
let prelude_types = [
(Ident::from("Num"), Symbol::NUM_NUM),
(Ident::from("Int"), Symbol::NUM_INT),
(Ident::from("Float"), Symbol::NUM_FLOAT),
(Ident::from("Integer"), Symbol::NUM_INTEGER),
(Ident::from("FloatingPoint"), Symbol::NUM_FLOATINGPOINT),
(Ident::from("Binary32"), Symbol::NUM_BINARY32),
(Ident::from("Binary64"), Symbol::NUM_BINARY64),
(Ident::from("Signed128"), Symbol::NUM_SIGNED128),
(Ident::from("Signed64"), Symbol::NUM_SIGNED64),
(Ident::from("Signed32"), Symbol::NUM_SIGNED32),
(Ident::from("Signed16"), Symbol::NUM_SIGNED16),
(Ident::from("Signed8"), Symbol::NUM_SIGNED8),
(Ident::from("Unsigned128"), Symbol::NUM_UNSIGNED128),
(Ident::from("Unsigned64"), Symbol::NUM_UNSIGNED64),
(Ident::from("Unsigned32"), Symbol::NUM_UNSIGNED32),
(Ident::from("Unsigned16"), Symbol::NUM_UNSIGNED16),
(Ident::from("Unsigned8"), Symbol::NUM_UNSIGNED8),
(Ident::from("Natural"), Symbol::NUM_NATURAL),
(Ident::from("Decimal"), Symbol::NUM_DECIMAL),
(Ident::from("Nat"), Symbol::NUM_NAT),
(Ident::from("I8"), Symbol::NUM_I8),
(Ident::from("I16"), Symbol::NUM_I16),
(Ident::from("I32"), Symbol::NUM_I32),
(Ident::from("I64"), Symbol::NUM_I64),
(Ident::from("I128"), Symbol::NUM_I128),
(Ident::from("U8"), Symbol::NUM_U8),
(Ident::from("U16"), Symbol::NUM_U16),
(Ident::from("U32"), Symbol::NUM_U32),
(Ident::from("U64"), Symbol::NUM_U64),
(Ident::from("U128"), Symbol::NUM_U128),
(Ident::from("F32"), Symbol::NUM_F32),
(Ident::from("F64"), Symbol::NUM_F64),
(Ident::from("Dec"), Symbol::NUM_DEC),
];
for (ident, symbol) in prelude_types {
for (type_name, symbol) in PRELUDE_TYPES {
header
.exposed_imports
.insert(ident, (symbol, Region::zero()));
.insert(Ident::from(type_name), (symbol, Region::zero()));
}
}
@ -2181,8 +2197,7 @@ fn update<'a>(
&& state.dependencies.solved_all()
&& state.goal_phase == Phase::MakeSpecializations
{
#[cfg(debug_assertions)]
debug_print_ir(&state, "PRINT_IR_AFTER_SPECIALIZATION");
debug_print_ir!(state, ROC_PRINT_IR_AFTER_SPECIALIZATION);
Proc::insert_reset_reuse_operations(
arena,
@ -2192,13 +2207,11 @@ fn update<'a>(
&mut state.procedures,
);
#[cfg(debug_assertions)]
debug_print_ir(&state, "PRINT_IR_AFTER_RESET_REUSE");
debug_print_ir!(state, ROC_PRINT_IR_AFTER_RESET_REUSE);
Proc::insert_refcount_operations(arena, &mut state.procedures);
#[cfg(debug_assertions)]
debug_print_ir(&state, "PRINT_IR_AFTER_REFCOUNT");
debug_print_ir!(state, ROC_PRINT_IR_AFTER_REFCOUNT);
// This is not safe with the new non-recursive RC updates that we do for tag unions
//
@ -2493,7 +2506,7 @@ fn load_pkg_config<'a>(
let pkg_config_module_msg = fabricate_pkg_config_module(
arena,
shorthand,
app_module_id,
Some(app_module_id),
filename,
parser_state,
module_ids.clone(),
@ -2941,11 +2954,18 @@ fn parse_header<'a>(
To::NewPackage(_package_name) => Ok((module_id, app_module_header_msg)),
}
}
Ok((ast::Module::Platform { header }, _parse_state)) => {
Err(LoadingProblem::UnexpectedHeader(format!(
"got an unexpected platform header\n{:?}",
header
)))
Ok((ast::Module::Platform { header }, parse_state)) => {
Ok(fabricate_pkg_config_module(
arena,
"", // Use a shorthand of "" - it will be fine for `roc check` and bindgen
None,
filename,
parse_state,
module_ids.clone(),
ident_ids_by_module,
&header,
module_timing,
))
}
Err(fail) => Err(LoadingProblem::ParsingFailed(
@ -3220,8 +3240,9 @@ fn send_header<'a>(
exposes: exposed,
parse_state,
exposed_imports: scope,
module_timing,
symbols_from_requires: Vec::new(),
header_for: extra,
module_timing,
}),
)
}
@ -3231,7 +3252,7 @@ struct PlatformHeaderInfo<'a> {
filename: PathBuf,
is_root_module: bool,
shorthand: &'a str,
app_module_id: ModuleId,
opt_app_module_id: Option<ModuleId>,
packages: &'a [Loc<PackageEntry<'a>>],
provides: &'a [Loc<ExposedName<'a>>],
requires: &'a [Loc<TypedIdent<'a>>],
@ -3252,7 +3273,7 @@ fn send_header_two<'a>(
filename,
shorthand,
is_root_module,
app_module_id,
opt_app_module_id,
packages,
provides,
requires,
@ -3261,6 +3282,7 @@ fn send_header_two<'a>(
} = info;
let declared_name: ModuleName = "".into();
let mut symbols_from_requires = Vec::with_capacity(requires.len());
let mut imported: Vec<(QualifiedModuleName, Vec<Ident>, Region)> =
Vec::with_capacity(imports.len());
@ -3270,12 +3292,16 @@ fn send_header_two<'a>(
let mut deps_by_name: MutMap<PQModuleName, ModuleId> =
HashMap::with_capacity_and_hasher(num_exposes, default_hasher());
// add standard imports
imported_modules.insert(app_module_id, Region::zero());
deps_by_name.insert(
PQModuleName::Unqualified(ModuleName::APP.into()),
app_module_id,
);
// Add standard imports, if there is an app module.
// (There might not be, e.g. when running `roc check Package-Config.roc` or
// when generating bindings.)
if let Some(app_module_id) = opt_app_module_id {
imported_modules.insert(app_module_id, Region::zero());
deps_by_name.insert(
PQModuleName::Unqualified(ModuleName::APP.into()),
app_module_id,
);
}
let mut scope_size = 0;
@ -3340,30 +3366,37 @@ fn send_header_two<'a>(
}
{
let ident_ids = ident_ids_by_module.get_or_insert(app_module_id);
// If we don't have an app module id (e.g. because we're doing
// `roc check Package-Config.roc` or because we're doing bindgen),
// insert the `requires` symbols into the platform module's IdentIds.
//
// Otherwise, get them from the app module's IdentIds, because it
// should already have a symbol for each `requires` entry, and we
// want to make sure we're referencing the same symbols!
let module_id = opt_app_module_id.unwrap_or(home);
let ident_ids = ident_ids_by_module.get_or_insert(module_id);
for entry in requires {
let entry = entry.value;
let ident: Ident = entry.ident.value.into();
let ident_id = ident_ids.get_or_insert(&ident);
let symbol = Symbol::new(app_module_id, ident_id);
let symbol = Symbol::new(module_id, ident_id);
// Since this value is exposed, add it to our module's default scope.
debug_assert!(!scope.contains_key(&ident.clone()));
scope.insert(ident, (symbol, entry.ident.region));
symbols_from_requires.push((Loc::at(entry.ident.region, symbol), entry.ann));
}
for entry in requires_types {
let string: &str = entry.value.into();
let ident: Ident = string.into();
let ident_id = ident_ids.get_or_insert(&ident);
let symbol = Symbol::new(app_module_id, ident_id);
let symbol = Symbol::new(module_id, ident_id);
// Since this value is exposed, add it to our module's default scope.
debug_assert!(!scope.contains_key(&ident.clone()));
scope.insert(ident, (symbol, entry.region));
}
}
@ -3453,6 +3486,7 @@ fn send_header_two<'a>(
parse_state,
exposed_imports: scope,
module_timing,
symbols_from_requires,
header_for: extra,
}),
)
@ -3738,7 +3772,7 @@ fn unspace<'a, T: Copy>(arena: &'a Bump, items: &[Loc<Spaced<'a, T>>]) -> &'a [L
fn fabricate_pkg_config_module<'a>(
arena: &'a Bump,
shorthand: &'a str,
app_module_id: ModuleId,
opt_app_module_id: Option<ModuleId>,
filename: PathBuf,
parse_state: roc_parse::state::State<'a>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
@ -3746,11 +3780,15 @@ fn fabricate_pkg_config_module<'a>(
header: &PlatformHeader<'a>,
module_timing: ModuleTiming,
) -> (ModuleId, Msg<'a>) {
// If we have an app module, then it's the root module;
// otherwise, we must be the root.
let is_root_module = opt_app_module_id.is_none();
let info = PlatformHeaderInfo {
filename,
is_root_module: false,
is_root_module,
shorthand,
app_module_id,
opt_app_module_id,
packages: &[],
provides: unspace(arena, header.provides.items),
requires: &*arena.alloc([Loc::at(
@ -3792,6 +3830,7 @@ fn canonicalize_and_constrain<'a>(
exposed_imports,
imported_modules,
mut module_timing,
symbols_from_requires,
..
} = parsed;
@ -3809,6 +3848,7 @@ fn canonicalize_and_constrain<'a>(
aliases,
exposed_imports,
&exposed_symbols,
&symbols_from_requires,
&mut var_store,
);
@ -3853,6 +3893,7 @@ fn canonicalize_and_constrain<'a>(
} else {
constrain_module(
&mut constraints,
module_output.symbols_from_requires,
&module_output.scope.abilities_store,
&module_output.declarations,
module_id,
@ -3907,7 +3948,7 @@ fn canonicalize_and_constrain<'a>(
var_store,
constraints,
constraint,
ident_ids: module_output.scope.ident_ids,
ident_ids: module_output.scope.locals.ident_ids,
dep_idents,
module_timing,
};
@ -3964,6 +4005,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
exposed_imports,
module_path,
header_for,
symbols_from_requires,
..
} = header;
@ -3978,6 +4020,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
exposed_ident_ids,
exposed_imports,
parsed_defs,
symbols_from_requires,
header_for,
};
@ -4689,157 +4732,14 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin
/// Builtin aliases that are not covered by type checker optimizations
///
/// Types like `F64` and `I32` are hardcoded into Subs and therefore we don't define them here.
/// All that remains are the generic number types (Num, Int, Float) and Result
/// Generic number types (Num, Int, Float, etc.) are treated as `DelayedAlias`es resolved during
/// type solving.
/// All that remains are Signed8, Signed16, etc.
fn default_aliases() -> roc_solve::solve::Aliases {
use roc_types::types::Type;
let mut solve_aliases = roc_solve::solve::Aliases::default();
let mut var_store = VarStore::default();
// Num range := range
{
let symbol = Symbol::NUM_NUM;
let tvar = var_store.fresh();
let alias = Alias {
region: Region::zero(),
type_variables: vec![Loc::at_zero(("range".into(), tvar))],
lambda_set_variables: Default::default(),
recursion_variables: Default::default(),
typ: Type::Variable(tvar),
kind: roc_types::types::AliasKind::Structural,
};
solve_aliases.insert(symbol, alias);
}
// FloatingPoint range := []
{
let symbol = Symbol::NUM_FLOATINGPOINT;
let tvar = var_store.fresh();
let alias = Alias {
region: Region::zero(),
type_variables: vec![Loc::at_zero(("range".into(), tvar))],
lambda_set_variables: Default::default(),
recursion_variables: Default::default(),
typ: Type::Variable(tvar),
kind: roc_types::types::AliasKind::Opaque,
};
solve_aliases.insert(symbol, alias);
}
// Int range : Num (Integer range)
{
let symbol = Symbol::NUM_INT;
let tvar = var_store.fresh();
let typ = Type::DelayedAlias(AliasCommon {
symbol: Symbol::NUM_NUM,
type_arguments: vec![(
"range".into(),
Type::Alias {
symbol: Symbol::NUM_INTEGER,
type_arguments: vec![("range".into(), Type::Variable(tvar))],
lambda_set_variables: vec![],
actual: Box::new(Type::Variable(tvar)),
kind: AliasKind::Opaque,
},
)],
lambda_set_variables: vec![],
});
let alias = Alias {
region: Region::zero(),
type_variables: vec![Loc::at_zero(("range".into(), tvar))],
lambda_set_variables: Default::default(),
recursion_variables: Default::default(),
typ,
kind: roc_types::types::AliasKind::Structural,
};
solve_aliases.insert(symbol, alias);
}
// Float range : Num (FloatingPoint range)
{
let symbol = Symbol::NUM_FLOAT;
let tvar = var_store.fresh();
let typ = Type::DelayedAlias(AliasCommon {
symbol: Symbol::NUM_NUM,
type_arguments: vec![(
"range".into(),
Type::Alias {
symbol: Symbol::NUM_FLOATINGPOINT,
type_arguments: vec![("range".into(), Type::Variable(tvar))],
lambda_set_variables: vec![],
actual: Box::new(Type::Variable(tvar)),
kind: AliasKind::Opaque,
},
)],
lambda_set_variables: vec![],
});
let alias = Alias {
region: Region::zero(),
type_variables: vec![Loc::at_zero(("range".into(), tvar))],
lambda_set_variables: Default::default(),
recursion_variables: Default::default(),
typ,
kind: roc_types::types::AliasKind::Structural,
};
solve_aliases.insert(symbol, alias);
}
// Integer range := range
{
let symbol = Symbol::NUM_INTEGER;
let tvar = var_store.fresh();
let alias = Alias {
region: Region::zero(),
type_variables: vec![Loc::at_zero(("range".into(), tvar))],
lambda_set_variables: Default::default(),
recursion_variables: Default::default(),
typ: Type::Variable(tvar),
kind: roc_types::types::AliasKind::Structural,
};
solve_aliases.insert(symbol, alias);
}
{
let symbol = Symbol::RESULT_RESULT;
let tvar1 = var_store.fresh();
let tvar2 = var_store.fresh();
let typ = Type::TagUnion(
vec![
(TagName::Tag("Ok".into()), vec![Type::Variable(tvar1)]),
(TagName::Tag("Err".into()), vec![Type::Variable(tvar2)]),
],
TypeExtension::Closed,
);
let alias = Alias {
region: Region::zero(),
type_variables: vec![
Loc::at_zero(("ok".into(), tvar1)),
Loc::at_zero(("err".into(), tvar2)),
],
lambda_set_variables: Default::default(),
recursion_variables: Default::default(),
typ,
kind: roc_types::types::AliasKind::Structural,
};
solve_aliases.insert(symbol, alias);
}
let mut zero_opaque = |alias_name: Symbol| {
let alias = Alias {
region: Region::zero(),

View File

@ -19,6 +19,7 @@ mod test_load {
use roc_can::def::Declaration::*;
use roc_can::def::Def;
use roc_constrain::module::ExposedByModule;
use roc_load_internal::file::Threading;
use roc_load_internal::file::{LoadResult, LoadStart, LoadedModule, LoadingProblem, Phase};
use roc_module::ident::ModuleName;
use roc_module::symbol::{Interns, ModuleId};
@ -53,6 +54,7 @@ mod test_load {
target_info,
Default::default(), // these tests will re-compile the builtins
RenderTarget::Generic,
Threading::Single,
)? {
Monomorphized(_) => unreachable!(""),
TypeChecked(module) => Ok(module),

View File

@ -14,4 +14,3 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
lazy_static = "1.4.0"
static_assertions = "1.1.0"
snafu = { version = "0.6.10", features = ["backtraces"] }
arrayvec = "0.7.2"

View File

@ -63,7 +63,7 @@ pub enum TagName {
}
roc_error_macros::assert_sizeof_aarch64!(TagName, 24);
roc_error_macros::assert_sizeof_wasm!(TagName, 16);
roc_error_macros::assert_sizeof_wasm!(TagName, 12);
roc_error_macros::assert_sizeof_default!(TagName, 24);
impl TagName {

View File

@ -53,6 +53,7 @@ pub enum LowLevel {
ListAny,
ListAll,
ListFindUnsafe,
ListIsUnique,
DictSize,
DictEmpty,
DictInsert,

View File

@ -5,11 +5,40 @@ use roc_ident::IdentStr;
use roc_region::all::Region;
use snafu::OptionExt;
use std::collections::HashMap;
use std::num::NonZeroU32;
use std::{fmt, u32};
// TODO: benchmark this as { ident_id: u32, module_id: u32 } and see if perf stays the same
// the packed(4) is needed for faster equality comparisons. With it, the structure is
// treated as a single u64, and comparison is one instruction
//
// example::eq_sym64:
// cmp rdi, rsi
// sete al
// ret
//
// while without it we get 2 extra instructions
//
// example::eq_sym64:
// xor edi, edx
// xor esi, ecx
// or esi, edi
// sete al
// ret
//
// #[repr(packed)] gives you #[repr(packed(1))], and then all your reads are unaligned
// so we set the alignment to (the natural) 4
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Symbol(u64);
#[repr(packed(4))]
pub struct Symbol {
ident_id: u32,
module_id: NonZeroU32,
}
/// An Option<Symbol> will use the 0 that is not used by the NonZeroU32 module_id field to encode
/// the Nothing case. An Option<Symbol> hence takes no more space than a Symbol.
#[allow(dead_code)]
const SYMBOL_HAS_NICHE: () =
assert!(std::mem::size_of::<Symbol>() == std::mem::size_of::<Option<Symbol>>());
// When this is `true` (which it normally should be), Symbol's Debug::fmt implementation
// attempts to pretty print debug symbols using interns recorded using
@ -28,7 +57,7 @@ impl Symbol {
// e.g. pub const NUM_NUM: Symbol = …
pub const fn new(module_id: ModuleId, ident_id: IdentId) -> Symbol {
// The bit layout of the u64 inside a Symbol is:
// The bit layout of the inside of a Symbol is:
//
// |------ 32 bits -----|------ 32 bits -----|
// | ident_id | module_id |
@ -37,20 +66,22 @@ impl Symbol {
// module_id comes second because we need to query it more often,
// and this way we can get it by truncating the u64 to u32,
// whereas accessing the first slot requires a bit shift first.
let bits = ((ident_id.0 as u64) << 32) | (module_id.0 as u64);
Symbol(bits)
Self {
module_id: module_id.0,
ident_id: ident_id.0,
}
}
pub fn module_id(self) -> ModuleId {
ModuleId(self.0 as u32)
pub const fn module_id(self) -> ModuleId {
ModuleId(self.module_id)
}
pub fn ident_id(self) -> IdentId {
IdentId((self.0 >> 32) as u32)
pub const fn ident_id(self) -> IdentId {
IdentId(self.ident_id)
}
pub fn is_builtin(self) -> bool {
pub const fn is_builtin(self) -> bool {
self.module_id().is_builtin()
}
@ -88,8 +119,8 @@ impl Symbol {
})
}
pub fn as_u64(self) -> u64 {
self.0
pub const fn as_u64(self) -> u64 {
u64::from_ne_bytes(self.to_ne_bytes())
}
pub fn fully_qualified(self, interns: &Interns, home: ModuleId) -> ModuleName {
@ -109,7 +140,12 @@ impl Symbol {
}
pub const fn to_ne_bytes(self) -> [u8; 8] {
self.0.to_ne_bytes()
unsafe { std::mem::transmute(self) }
}
#[cfg(debug_assertions)]
pub fn contains(self, needle: &str) -> bool {
format!("{:?}", self).contains(needle)
}
}
@ -128,7 +164,7 @@ impl fmt::Debug for Symbol {
let ident_id = self.ident_id();
match DEBUG_IDENT_IDS_BY_MODULE_ID.lock() {
Ok(names) => match &names.get(&module_id.0) {
Ok(names) => match &names.get(&(module_id.to_zero_indexed() as u32)) {
Some(ident_ids) => match ident_ids.get_name(ident_id) {
Some(ident_str) => write!(f, "`{:?}.{}`", module_id, ident_str),
None => fallback_debug_fmt(*self, f),
@ -169,7 +205,7 @@ impl fmt::Display for Symbol {
impl From<Symbol> for u64 {
fn from(symbol: Symbol) -> Self {
symbol.0
symbol.as_u64()
}
}
@ -287,18 +323,31 @@ lazy_static! {
/// A globally unique ID that gets assigned to each module as it is loaded.
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct ModuleId(u32);
pub struct ModuleId(NonZeroU32);
impl ModuleId {
// NOTE: the define_builtins! macro adds a bunch of constants to this impl,
//
// e.g. pub const NUM: ModuleId = …
const fn from_zero_indexed(mut id: usize) -> Self {
id += 1;
// only happens on overflow
debug_assert!(id != 0);
ModuleId(unsafe { NonZeroU32::new_unchecked(id as u32) })
}
const fn to_zero_indexed(self) -> usize {
(self.0.get() - 1) as usize
}
#[cfg(debug_assertions)]
pub fn register_debug_idents(self, ident_ids: &IdentIds) {
let mut all = DEBUG_IDENT_IDS_BY_MODULE_ID.lock().expect("Failed to acquire lock for Debug interning into DEBUG_MODULE_ID_NAMES, presumably because a thread panicked.");
all.insert(self.0, ident_ids.clone());
all.insert(self.to_zero_indexed() as u32, ident_ids.clone());
}
#[cfg(not(debug_assertions))]
@ -331,7 +380,7 @@ impl fmt::Debug for ModuleId {
.expect("Failed to acquire lock for Debug reading from DEBUG_MODULE_ID_NAMES, presumably because a thread panicked.");
if PRETTY_PRINT_DEBUG_SYMBOLS {
match names.get(&self.0) {
match names.get(&(self.to_zero_indexed() as u32)) {
Some(str_ref) => write!(f, "{}", str_ref.clone()),
None => {
panic!(
@ -389,7 +438,7 @@ impl<'a> PackageModuleIds<'a> {
Some(id) => *id,
None => {
let by_id = &mut self.by_id;
let module_id = ModuleId(by_id.len() as u32);
let module_id = ModuleId::from_zero_indexed(by_id.len());
by_id.push(module_name.clone());
@ -425,7 +474,7 @@ impl<'a> PackageModuleIds<'a> {
let mut names = DEBUG_MODULE_ID_NAMES.lock().expect("Failed to acquire lock for Debug interning into DEBUG_MODULE_ID_NAMES, presumably because a thread panicked.");
names
.entry(module_id.0)
.entry(module_id.to_zero_indexed() as u32)
.or_insert_with(|| match module_name {
PQModuleName::Unqualified(module) => module.as_str().into(),
PQModuleName::Qualified(package, module) => {
@ -445,7 +494,7 @@ impl<'a> PackageModuleIds<'a> {
}
pub fn get_name(&self, id: ModuleId) -> Option<&PQModuleName> {
self.by_id.get(id.0 as usize)
self.by_id.get(id.to_zero_indexed())
}
pub fn available_modules(&self) -> impl Iterator<Item = &PQModuleName> {
@ -470,7 +519,7 @@ impl ModuleIds {
Some(id) => *id,
None => {
let by_id = &mut self.by_id;
let module_id = ModuleId(by_id.len() as u32);
let module_id = ModuleId::from_zero_indexed(by_id.len());
by_id.push(module_name.clone());
@ -491,7 +540,7 @@ impl ModuleIds {
// TODO make sure modules are never added more than once!
names
.entry(module_id.0)
.entry(module_id.to_zero_indexed() as u32)
.or_insert_with(|| module_name.as_str().to_string().into());
}
@ -505,7 +554,7 @@ impl ModuleIds {
}
pub fn get_name(&self, id: ModuleId) -> Option<&ModuleName> {
self.by_id.get(id.0 as usize)
self.by_id.get(id.to_zero_indexed())
}
pub fn available_modules(&self) -> impl Iterator<Item = &ModuleName> {
@ -531,7 +580,7 @@ impl IdentId {
/// Stores a mapping between Ident and IdentId.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct IdentIds {
interner: SmallStringInterner,
pub interner: SmallStringInterner,
}
impl IdentIds {
@ -543,13 +592,21 @@ impl IdentIds {
}
pub fn add_ident(&mut self, ident_name: &Ident) -> IdentId {
IdentId(self.interner.insert(ident_name.as_str()) as u32)
self.add_str(ident_name.as_str())
}
pub fn add_str(&mut self, ident_name: &str) -> IdentId {
IdentId(self.interner.insert(ident_name) as u32)
}
pub fn duplicate_ident(&mut self, ident_id: IdentId) -> IdentId {
IdentId(self.interner.duplicate(ident_id.0 as usize) as u32)
}
pub fn get_or_insert(&mut self, name: &Ident) -> IdentId {
match self.get_id(name) {
Some(id) => id,
None => self.add_ident(name),
None => self.add_str(name.as_str()),
}
}
@ -580,6 +637,13 @@ impl IdentIds {
.map(|i| IdentId(i as u32))
}
#[inline(always)]
pub fn get_id_many<'a>(&'a self, ident_name: &'a str) -> impl Iterator<Item = IdentId> + 'a {
self.interner
.find_indices(ident_name)
.map(|i| IdentId(i as u32))
}
pub fn get_name(&self, id: IdentId) -> Option<&str> {
self.interner.try_get(id.0 as usize)
}
@ -716,7 +780,8 @@ macro_rules! define_builtins {
let mut exposed_idents_by_module = VecMap::with_capacity(extra_capacity + $total);
$(
debug_assert!(!exposed_idents_by_module.contains_key(&ModuleId($module_id)), r"Error setting up Builtins: when setting up module {} {:?} - the module ID {} is already present in the map. Check the map for duplicate module IDs!", $module_id, $module_name, $module_id);
let module_id = ModuleId::$module_const;
debug_assert!(!exposed_idents_by_module.contains_key(&module_id), r"Error setting up Builtins: when setting up module {} {:?} - the module ID {} is already present in the map. Check the map for duplicate module IDs!", $module_id, $module_name, $module_id);
let ident_ids = {
const TOTAL : usize = [ $($ident_name),+ ].len();
@ -748,18 +813,18 @@ macro_rules! define_builtins {
}
};
let interner = SmallStringInterner::from_parts (
// Safety: all lengths are non-negative and smaller than 2^15
let interner = unsafe {
SmallStringInterner::from_parts (
BUFFER.as_bytes().to_vec(),
LENGTHS.to_vec(),
OFFSETS.to_vec(),
);
)};
IdentIds{ interner }
};
if cfg!(debug_assertions) {
let module_id = ModuleId($module_id);
let name = PQModuleName::Unqualified($module_name.into());
PackageModuleIds::insert_debug_name(module_id, &name);
module_id.register_debug_idents(&ident_ids);
@ -767,7 +832,7 @@ macro_rules! define_builtins {
exposed_idents_by_module.insert(
ModuleId($module_id),
module_id,
ident_ids
);
)+
@ -779,15 +844,15 @@ macro_rules! define_builtins {
}
impl ModuleId {
pub fn is_builtin(&self) -> bool {
pub const fn is_builtin(self) -> bool {
// This is a builtin ModuleId iff it's below the
// total number of builtin modules, since they
// take up the first $total ModuleId numbers.
self.0 < $total
self.to_zero_indexed() < $total
}
$(
pub const $module_const: ModuleId = ModuleId($module_id);
pub const $module_const: ModuleId = ModuleId::from_zero_indexed($module_id);
)+
}
@ -811,7 +876,7 @@ macro_rules! define_builtins {
};
$(
insert_both(ModuleId($module_id), $module_name);
insert_both(ModuleId::$module_const, $module_name);
)+
ModuleIds { by_name, by_id }
@ -839,7 +904,7 @@ macro_rules! define_builtins {
};
$(
insert_both(ModuleId($module_id), $module_name);
insert_both(ModuleId::$module_const, $module_name);
)+
PackageModuleIds { by_name, by_id }
@ -849,7 +914,7 @@ macro_rules! define_builtins {
impl Symbol {
$(
$(
pub const $ident_const: Symbol = Symbol::new(ModuleId($module_id), IdentId($ident_id));
pub const $ident_const: Symbol = Symbol::new(ModuleId::$module_const, IdentId($ident_id));
)+
)+
@ -870,7 +935,7 @@ macro_rules! define_builtins {
let $imported = true;
if $imported {
scope.insert($ident_name.into(), (Symbol::new(ModuleId($module_id), IdentId($ident_id)), Region::zero()));
scope.insert($ident_name.into(), (Symbol::new(ModuleId::$module_const, IdentId($ident_id)), Region::zero()));
}
)?
)+
@ -1187,6 +1252,7 @@ define_builtins! {
55 LIST_SORT_DESC: "sortDesc"
56 LIST_SORT_DESC_COMPARE: "#sortDescCompare"
57 LIST_REPLACE: "replace"
58 LIST_IS_UNIQUE: "#isUnique"
}
5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" // the Result.Result type alias

View File

@ -19,8 +19,8 @@ roc_problem = { path = "../problem" }
roc_builtins = { path = "../builtins" }
roc_target = { path = "../roc_target" }
roc_error_macros = {path="../../error_macros"}
roc_debug_flags = {path="../debug_flags"}
ven_pretty = { path = "../../vendor/pretty" }
morphic_lib = { path = "../../vendor/morphic_lib" }
bumpalo = { version = "3.8.0", features = ["collections"] }
hashbrown = { version = "0.11.2", features = [ "bumpalo" ] }
ven_graph = { path = "../../vendor/pathfinding" }

View File

@ -1031,6 +1031,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ExpectTrue => arena.alloc_slice_copy(&[irrelevant]),
ListIsUnique => arena.alloc_slice_copy(&[borrowed]),
BoxExpr | UnboxExpr => {
unreachable!("These lowlevel operations are turned into mono Expr's")
}

View File

@ -1,6 +1,5 @@
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use roc_module::ident::Ident;
use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_target::TargetInfo;
@ -84,7 +83,7 @@ pub struct CodeGenHelp<'a> {
impl<'a> CodeGenHelp<'a> {
pub fn new(arena: &'a Bump, target_info: TargetInfo, home: ModuleId) -> Self {
let layout_isize = Layout::usize(target_info);
let layout_isize = Layout::isize(target_info);
// Refcount is a boxed isize. TODO: use the new Box layout when dev backends support it
let union_refcount = UnionLayout::NonNullableUnwrapped(arena.alloc([layout_isize]));
@ -396,7 +395,7 @@ impl<'a> CodeGenHelp<'a> {
}
fn create_symbol(&self, ident_ids: &mut IdentIds, debug_name: &str) -> Symbol {
let ident_id = ident_ids.add_ident(&Ident::from(debug_name));
let ident_id = ident_ids.add_str(debug_name);
Symbol::new(self.home, ident_id)
}

738
compiler/mono/src/copy.rs Normal file
View File

@ -0,0 +1,738 @@
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_can::{
def::Def,
expr::{AccessorData, ClosureData, Expr, Field, WhenBranch},
};
use roc_types::subs::{
AliasVariables, Descriptor, OptVariable, RecordFields, Subs, SubsSlice, UnionTags, Variable,
VariableSubsSlice,
};
/// Deep copies the type variables in the type hosted by [`var`] into [`expr`].
/// Returns [`None`] if the expression does not need to be copied.
pub fn deep_copy_type_vars_into_expr<'a>(
arena: &'a Bump,
subs: &mut Subs,
var: Variable,
expr: &Expr,
) -> Option<(Variable, Expr)> {
// Always deal with the root, so that aliases propagate correctly.
let var = subs.get_root_key_without_compacting(var);
let substitutions = deep_copy_type_vars(arena, subs, var);
if substitutions.is_empty() {
return None;
}
let new_var = substitutions
.iter()
.find_map(|&(original, new)| if original == var { Some(new) } else { None })
.expect("Variable marked as cloned, but it isn't");
return Some((new_var, help(subs, expr, &substitutions)));
fn help(subs: &Subs, expr: &Expr, substitutions: &[(Variable, Variable)]) -> Expr {
use Expr::*;
macro_rules! sub {
($var:expr) => {{
// Always deal with the root, so that aliases propagate correctly.
let root = subs.get_root_key_without_compacting($var);
substitutions
.iter()
.find_map(|&(original, new)| if original == root { Some(new) } else { None })
.unwrap_or($var)
}};
}
let go_help = |e: &Expr| help(subs, e, substitutions);
match expr {
Num(var, str, val, bound) => Num(sub!(*var), str.clone(), val.clone(), *bound),
Int(v1, v2, str, val, bound) => {
Int(sub!(*v1), sub!(*v2), str.clone(), val.clone(), *bound)
}
Float(v1, v2, str, val, bound) => {
Float(sub!(*v1), sub!(*v2), str.clone(), *val, *bound)
}
Str(str) => Str(str.clone()),
SingleQuote(char) => SingleQuote(*char),
List {
elem_var,
loc_elems,
} => List {
elem_var: sub!(*elem_var),
loc_elems: loc_elems.iter().map(|le| le.map(go_help)).collect(),
},
Var(sym) => Var(*sym),
When {
loc_cond,
cond_var,
expr_var,
region,
branches,
branches_cond_var,
exhaustive,
} => When {
loc_cond: Box::new(loc_cond.map(go_help)),
cond_var: sub!(*cond_var),
expr_var: sub!(*expr_var),
region: *region,
branches: branches
.iter()
.map(
|WhenBranch {
patterns,
value,
guard,
redundant,
}| WhenBranch {
patterns: patterns.clone(),
value: value.map(go_help),
guard: guard.as_ref().map(|le| le.map(go_help)),
redundant: *redundant,
},
)
.collect(),
branches_cond_var: sub!(*branches_cond_var),
exhaustive: *exhaustive,
},
If {
cond_var,
branch_var,
branches,
final_else,
} => If {
cond_var: sub!(*cond_var),
branch_var: sub!(*branch_var),
branches: branches
.iter()
.map(|(c, e)| (c.map(go_help), e.map(go_help)))
.collect(),
final_else: Box::new(final_else.map(go_help)),
},
LetRec(defs, body, var) => LetRec(
defs.iter()
.map(
|Def {
loc_pattern,
loc_expr,
expr_var,
pattern_vars,
annotation,
}| Def {
loc_pattern: loc_pattern.clone(),
loc_expr: loc_expr.map(go_help),
expr_var: sub!(*expr_var),
pattern_vars: pattern_vars
.iter()
.map(|(s, v)| (*s, sub!(*v)))
.collect(),
annotation: annotation.clone(),
},
)
.collect(),
Box::new(body.map(go_help)),
sub!(*var),
),
LetNonRec(def, body, var) => {
let Def {
loc_pattern,
loc_expr,
expr_var,
pattern_vars,
annotation,
} = &**def;
let def = Def {
loc_pattern: loc_pattern.clone(),
loc_expr: loc_expr.map(go_help),
expr_var: sub!(*expr_var),
pattern_vars: pattern_vars.iter().map(|(s, v)| (*s, sub!(*v))).collect(),
annotation: annotation.clone(),
};
LetNonRec(Box::new(def), Box::new(body.map(go_help)), sub!(*var))
}
Call(f, args, called_via) => {
let (fn_var, fn_expr, clos_var, ret_var) = &**f;
Call(
Box::new((
sub!(*fn_var),
fn_expr.map(go_help),
sub!(*clos_var),
sub!(*ret_var),
)),
args.iter()
.map(|(var, expr)| (sub!(*var), expr.map(go_help)))
.collect(),
*called_via,
)
}
RunLowLevel { op, args, ret_var } => RunLowLevel {
op: *op,
args: args
.iter()
.map(|(var, expr)| (sub!(*var), go_help(expr)))
.collect(),
ret_var: sub!(*ret_var),
},
ForeignCall {
foreign_symbol,
args,
ret_var,
} => ForeignCall {
foreign_symbol: foreign_symbol.clone(),
args: args
.iter()
.map(|(var, expr)| (sub!(*var), go_help(expr)))
.collect(),
ret_var: sub!(*ret_var),
},
Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
return_type,
name,
captured_symbols,
recursive,
arguments,
loc_body,
}) => Closure(ClosureData {
function_type: sub!(*function_type),
closure_type: sub!(*closure_type),
closure_ext_var: sub!(*closure_ext_var),
return_type: sub!(*return_type),
name: *name,
captured_symbols: captured_symbols
.iter()
.map(|(s, v)| (*s, sub!(*v)))
.collect(),
recursive: *recursive,
arguments: arguments
.iter()
.map(|(v, mark, pat)| (sub!(*v), *mark, pat.clone()))
.collect(),
loc_body: Box::new(loc_body.map(go_help)),
}),
Record { record_var, fields } => Record {
record_var: sub!(*record_var),
fields: fields
.iter()
.map(
|(
k,
Field {
var,
region,
loc_expr,
},
)| {
(
k.clone(),
Field {
var: sub!(*var),
region: *region,
loc_expr: Box::new(loc_expr.map(go_help)),
},
)
},
)
.collect(),
},
EmptyRecord => EmptyRecord,
Access {
record_var,
ext_var,
field_var,
loc_expr,
field,
} => Access {
record_var: sub!(*record_var),
ext_var: sub!(*ext_var),
field_var: sub!(*field_var),
loc_expr: Box::new(loc_expr.map(go_help)),
field: field.clone(),
},
Accessor(AccessorData {
name,
function_var,
record_var,
closure_var,
closure_ext_var,
ext_var,
field_var,
field,
}) => Accessor(AccessorData {
name: *name,
function_var: sub!(*function_var),
record_var: sub!(*record_var),
closure_var: sub!(*closure_var),
closure_ext_var: sub!(*closure_ext_var),
ext_var: sub!(*ext_var),
field_var: sub!(*field_var),
field: field.clone(),
}),
Update {
record_var,
ext_var,
symbol,
updates,
} => Update {
record_var: sub!(*record_var),
ext_var: sub!(*ext_var),
symbol: *symbol,
updates: updates
.iter()
.map(
|(
k,
Field {
var,
region,
loc_expr,
},
)| {
(
k.clone(),
Field {
var: sub!(*var),
region: *region,
loc_expr: Box::new(loc_expr.map(go_help)),
},
)
},
)
.collect(),
},
Tag {
variant_var,
ext_var,
name,
arguments,
} => Tag {
variant_var: sub!(*variant_var),
ext_var: sub!(*ext_var),
name: name.clone(),
arguments: arguments
.iter()
.map(|(v, e)| (sub!(*v), e.map(go_help)))
.collect(),
},
ZeroArgumentTag {
closure_name,
variant_var,
ext_var,
name,
} => ZeroArgumentTag {
closure_name: *closure_name,
variant_var: sub!(*variant_var),
ext_var: sub!(*ext_var),
name: name.clone(),
},
OpaqueRef {
opaque_var,
name,
argument,
specialized_def_type,
type_arguments,
lambda_set_variables,
} => OpaqueRef {
opaque_var: sub!(*opaque_var),
name: *name,
argument: Box::new((sub!(argument.0), argument.1.map(go_help))),
// These shouldn't matter for opaques during mono, because they are only used for reporting
// and pretty-printing to the user. During mono we decay immediately into the argument.
// NB: if there are bugs, check if not substituting here is the problem!
specialized_def_type: specialized_def_type.clone(),
type_arguments: type_arguments.clone(),
lambda_set_variables: lambda_set_variables.clone(),
},
Expect(e1, e2) => Expect(Box::new(e1.map(go_help)), Box::new(e2.map(go_help))),
RuntimeError(err) => RuntimeError(err.clone()),
}
}
}
/// Deep copies the type variables in [`var`], returning a map of original -> new type variable for
/// all type variables copied.
fn deep_copy_type_vars<'a>(
arena: &'a Bump,
subs: &mut Subs,
var: Variable,
) -> Vec<'a, (Variable, Variable)> {
// Always deal with the root, so that unified variables are treated the same.
let var = subs.get_root_key_without_compacting(var);
let mut copied = Vec::with_capacity_in(16, arena);
let cloned_var = help(arena, subs, &mut copied, var);
// we have tracked all visited variables, and can now traverse them
// in one go (without looking at the UnificationTable) and clear the copy field
let mut result = Vec::with_capacity_in(copied.len(), arena);
for var in copied {
let descriptor = subs.get_ref_mut(var);
if let Some(copy) = descriptor.copy.into_variable() {
result.push((var, copy));
descriptor.copy = OptVariable::NONE;
} else {
debug_assert!(false, "{:?} marked as copied but it wasn't", var);
}
}
debug_assert!(result.contains(&(var, cloned_var)));
return result;
#[must_use]
fn help(arena: &Bump, subs: &mut Subs, visited: &mut Vec<Variable>, var: Variable) -> Variable {
use roc_types::subs::Content::*;
use roc_types::subs::FlatType::*;
// Always deal with the root, so that unified variables are treated the same.
let var = subs.get_root_key_without_compacting(var);
let desc = subs.get_ref_mut(var);
// Unlike `deep_copy_var` in solve, here we are cloning *all* flex and rigid vars.
// So we only want to short-circuit if we've already done the cloning work for a particular
// var.
if let Some(copy) = desc.copy.into_variable() {
return copy;
}
let content = desc.content;
let copy_descriptor = Descriptor {
content: Error, // we'll update this below
rank: desc.rank,
mark: desc.mark,
copy: OptVariable::NONE,
};
let copy = subs.fresh(copy_descriptor);
subs.get_ref_mut(var).copy = copy.into();
visited.push(var);
macro_rules! descend_slice {
($slice:expr) => {
for var_index in $slice {
let var = subs[var_index];
let _ = help(arena, subs, visited, var);
}
};
}
macro_rules! descend_var {
($var:expr) => {{
help(arena, subs, visited, $var)
}};
}
macro_rules! clone_var_slice {
($slice:expr) => {{
let new_arguments = VariableSubsSlice::reserve_into_subs(subs, $slice.len());
for (target_index, var_index) in (new_arguments.indices()).zip($slice) {
let var = subs[var_index];
let copy_var = subs.get_ref(var).copy.into_variable().unwrap_or(var);
subs.variables[target_index] = copy_var;
}
new_arguments
}};
}
macro_rules! perform_clone {
($do_clone:expr) => {{
// It may the case that while deep-copying nested variables of this type, we
// ended up copying the type itself (notably if it was self-referencing, in a
// recursive type). In that case, short-circuit with the known copy.
// if let Some(copy) = subs.get_ref(var).copy.into_variable() {
// return copy;
// }
// Perform the clone.
$do_clone
}};
}
// Now we recursively copy the content of the variable.
// We have already marked the variable as copied, so we
// will not repeat this work or crawl this variable again.
let new_content = match content {
// The vars for which we want to do something interesting.
FlexVar(opt_name) => FlexVar(opt_name),
FlexAbleVar(opt_name, ability) => FlexAbleVar(opt_name, ability),
RigidVar(name) => RigidVar(name),
RigidAbleVar(name, ability) => RigidAbleVar(name, ability),
// Everything else is a mechanical descent.
Structure(flat_type) => match flat_type {
EmptyRecord | EmptyTagUnion | Erroneous(_) => Structure(flat_type),
Apply(symbol, arguments) => {
descend_slice!(arguments);
perform_clone!({
let new_arguments = clone_var_slice!(arguments);
Structure(Apply(symbol, new_arguments))
})
}
Func(arguments, closure_var, ret_var) => {
descend_slice!(arguments);
let new_closure_var = descend_var!(closure_var);
let new_ret_var = descend_var!(ret_var);
perform_clone!({
let new_arguments = clone_var_slice!(arguments);
Structure(Func(new_arguments, new_closure_var, new_ret_var))
})
}
Record(fields, ext_var) => {
let new_ext_var = descend_var!(ext_var);
descend_slice!(fields.variables());
perform_clone!({
let new_variables = clone_var_slice!(fields.variables());
let new_fields = {
RecordFields {
length: fields.length,
field_names_start: fields.field_names_start,
variables_start: new_variables.start,
field_types_start: fields.field_types_start,
}
};
Structure(Record(new_fields, new_ext_var))
})
}
TagUnion(tags, ext_var) => {
let new_ext_var = descend_var!(ext_var);
for variables_slice_index in tags.variables() {
let variables_slice = subs[variables_slice_index];
descend_slice!(variables_slice);
}
perform_clone!({
let new_variable_slices =
SubsSlice::reserve_variable_slices(subs, tags.len());
let it = (new_variable_slices.indices()).zip(tags.variables());
for (target_index, index) in it {
let slice = subs[index];
let new_variables = clone_var_slice!(slice);
subs.variable_slices[target_index] = new_variables;
}
let new_union_tags =
UnionTags::from_slices(tags.tag_names(), new_variable_slices);
Structure(TagUnion(new_union_tags, new_ext_var))
})
}
RecursiveTagUnion(rec_var, tags, ext_var) => {
let new_ext_var = descend_var!(ext_var);
let new_rec_var = descend_var!(rec_var);
for variables_slice_index in tags.variables() {
let variables_slice = subs[variables_slice_index];
descend_slice!(variables_slice);
}
perform_clone!({
let new_variable_slices =
SubsSlice::reserve_variable_slices(subs, tags.len());
let it = (new_variable_slices.indices()).zip(tags.variables());
for (target_index, index) in it {
let slice = subs[index];
let new_variables = clone_var_slice!(slice);
subs.variable_slices[target_index] = new_variables;
}
let new_union_tags =
UnionTags::from_slices(tags.tag_names(), new_variable_slices);
Structure(RecursiveTagUnion(new_rec_var, new_union_tags, new_ext_var))
})
}
FunctionOrTagUnion(tag_name, symbol, ext_var) => {
let new_ext_var = descend_var!(ext_var);
perform_clone!(Structure(FunctionOrTagUnion(tag_name, symbol, new_ext_var)))
}
},
RecursionVar {
opt_name,
structure,
} => {
let new_structure = descend_var!(structure);
perform_clone!({
RecursionVar {
opt_name,
structure: new_structure,
}
})
}
Alias(symbol, arguments, real_type_var, kind) => {
let new_real_type_var = descend_var!(real_type_var);
descend_slice!(arguments.all_variables());
perform_clone!({
let new_variables = clone_var_slice!(arguments.all_variables());
let new_arguments = AliasVariables {
variables_start: new_variables.start,
..arguments
};
Alias(symbol, new_arguments, new_real_type_var, kind)
})
}
RangedNumber(typ, range_vars) => {
let new_typ = descend_var!(typ);
descend_slice!(range_vars);
perform_clone!({
let new_range_vars = clone_var_slice!(range_vars);
RangedNumber(new_typ, new_range_vars)
})
}
Error => Error,
};
subs.set_content(copy, new_content);
copy
}
}
#[cfg(test)]
mod test {
use super::deep_copy_type_vars;
use bumpalo::Bump;
use roc_module::symbol::Symbol;
use roc_types::subs::{
Content, Content::*, Descriptor, Mark, OptVariable, Rank, Subs, SubsIndex, Variable,
};
#[cfg(test)]
fn new_var(subs: &mut Subs, content: Content) -> Variable {
subs.fresh(Descriptor {
content,
rank: Rank::toplevel(),
mark: Mark::NONE,
copy: OptVariable::NONE,
})
}
#[test]
fn copy_flex_var() {
let mut subs = Subs::new();
let arena = Bump::new();
let field_name = SubsIndex::push_new(&mut subs.field_names, "a".into());
let var = new_var(&mut subs, FlexVar(Some(field_name)));
let mut copies = deep_copy_type_vars(&arena, &mut subs, var);
assert_eq!(copies.len(), 1);
let (original, new) = copies.pop().unwrap();
assert_ne!(original, new);
assert_eq!(original, var);
match subs.get_content_without_compacting(new) {
FlexVar(Some(name)) => {
assert_eq!(subs[*name].as_str(), "a");
}
it => assert!(false, "{:?}", it),
}
}
#[test]
fn copy_rigid_var() {
let mut subs = Subs::new();
let arena = Bump::new();
let field_name = SubsIndex::push_new(&mut subs.field_names, "a".into());
let var = new_var(&mut subs, RigidVar(field_name));
let mut copies = deep_copy_type_vars(&arena, &mut subs, var);
assert_eq!(copies.len(), 1);
let (original, new) = copies.pop().unwrap();
assert_ne!(original, new);
assert_eq!(original, var);
match subs.get_content_without_compacting(new) {
RigidVar(name) => {
assert_eq!(subs[*name].as_str(), "a");
}
it => assert!(false, "{:?}", it),
}
}
#[test]
fn copy_flex_able_var() {
let mut subs = Subs::new();
let arena = Bump::new();
let field_name = SubsIndex::push_new(&mut subs.field_names, "a".into());
let var = new_var(&mut subs, FlexAbleVar(Some(field_name), Symbol::UNDERSCORE));
let mut copies = deep_copy_type_vars(&arena, &mut subs, var);
assert_eq!(copies.len(), 1);
let (original, new) = copies.pop().unwrap();
assert_ne!(original, new);
assert_eq!(original, var);
match subs.get_content_without_compacting(new) {
FlexAbleVar(Some(name), Symbol::UNDERSCORE) => {
assert_eq!(subs[*name].as_str(), "a");
}
it => assert!(false, "{:?}", it),
}
}
#[test]
fn copy_rigid_able_var() {
let mut subs = Subs::new();
let arena = Bump::new();
let field_name = SubsIndex::push_new(&mut subs.field_names, "a".into());
let var = new_var(&mut subs, RigidAbleVar(field_name, Symbol::UNDERSCORE));
let mut copies = deep_copy_type_vars(&arena, &mut subs, var);
assert_eq!(copies.len(), 1);
let (original, new) = copies.pop().unwrap();
assert_ne!(original, new);
assert_eq!(original, var);
match subs.get_content_without_compacting(new) {
RigidAbleVar(name, Symbol::UNDERSCORE) => {
assert_eq!(subs[*name].as_str(), "a");
}
it => assert!(false, "{:?}", it),
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@
pub mod borrow;
pub mod code_gen_help;
mod copy;
pub mod inc_dec;
pub mod ir;
pub mod layout;

View File

@ -475,6 +475,8 @@ fn expression<'a>(
space0_before_e(term(min_indent), min_indent, EType::TIndentStart)
.parse(arena, state)?;
let region = Region::span_across(&first.region, &return_type.region);
// prepare arguments
let mut arguments = Vec::with_capacity_in(rest.len() + 1, arena);
arguments.push(first);
@ -482,7 +484,7 @@ fn expression<'a>(
let output = arena.alloc(arguments);
let result = Loc {
region: return_type.region,
region,
value: TypeAnnotation::Function(output, arena.alloc(return_type)),
};
let progress = p1.or(p2).or(p3);

View File

@ -15,7 +15,7 @@ Defs(
Newline,
],
),
typ: @33-36 Function(
typ: @18-36 Function(
[
@18-19 BoundVariable(
"a",

View File

@ -15,7 +15,7 @@ Defs(
Newline,
],
),
typ: @23-26 Function(
typ: @18-26 Function(
[
@18-19 BoundVariable(
"a",
@ -35,7 +35,7 @@ Defs(
Newline,
],
),
typ: @42-45 Function(
typ: @37-45 Function(
[
@37-38 BoundVariable(
"a",

View File

@ -10,8 +10,8 @@ Defs(
members: [
AbilityMember {
name: @9-13 "hash",
typ: @21-37 Where(
@21-24 Function(
typ: @16-37 Where(
@16-24 Function(
[
@16-17 BoundVariable(
"a",

View File

@ -10,8 +10,8 @@ Defs(
members: [
AbilityMember {
name: @8-11 "ab1",
typ: @19-33 Where(
@19-21 Function(
typ: @14-33 Where(
@14-21 Function(
[
@14-15 BoundVariable(
"a",
@ -47,8 +47,8 @@ Defs(
members: [
AbilityMember {
name: @43-46 "ab2",
typ: @54-68 Where(
@54-56 Function(
typ: @49-68 Where(
@49-56 Function(
[
@49-50 BoundVariable(
"a",

View File

@ -14,7 +14,7 @@
ann_pattern: @11-23 Identifier(
"wrappedNotEq",
),
ann_type: @34-38 Function(
ann_type: @26-38 Function(
[
@26-27 BoundVariable(
"a",

View File

@ -32,7 +32,7 @@ Defs(
RequiredValue(
@48-55 "putLine",
[],
@65-75 Function(
@58-75 Function(
[
@58-61 Apply(
"",

View File

@ -0,0 +1,83 @@
Defs(
[
@0-77 Value(
Annotation(
@0-1 Identifier(
"x",
),
@4-77 Record {
fields: [
@6-24 RequiredValue(
@6-10 "init",
[],
@13-24 Function(
[
@13-15 Record {
fields: [],
ext: None,
},
],
@19-24 Apply(
"",
"Model",
[],
),
),
),
@26-54 RequiredValue(
@26-32 "update",
[],
@35-54 Function(
[
@35-40 Apply(
"",
"Model",
[],
),
@42-45 Apply(
"",
"Str",
[],
),
],
@49-54 Apply(
"",
"Model",
[],
),
),
),
@56-75 RequiredValue(
@56-60 "view",
[],
@63-75 Function(
[
@63-68 Apply(
"",
"Model",
[],
),
],
@72-75 Apply(
"",
"Str",
[],
),
),
),
],
ext: None,
},
),
),
],
@79-81 SpaceBefore(
Num(
"42",
),
[
Newline,
Newline,
],
),
)

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