Merge remote-tracking branch 'origin/trunk' into decimal-literals

This commit is contained in:
Folkert 2021-11-21 19:31:38 +01:00
commit 5529841d68
404 changed files with 9919 additions and 6764 deletions

View file

@ -8,7 +8,7 @@ env:
jobs: jobs:
spell-check: spell-check:
name: spell check name: spell check
runs-on: [self-hosted] runs-on: [self-hosted, linux]
timeout-minutes: 10 timeout-minutes: 10
env: env:
FORCE_COLOR: 1 FORCE_COLOR: 1

View file

@ -9,7 +9,7 @@ on:
jobs: jobs:
deploy: deploy:
name: 'Deploy to Netlify' name: 'Deploy to Netlify'
runs-on: [self-hosted] runs-on: [self-hosted, linux]
steps: steps:
- uses: jsmrcaga/action-netlify-deploy@v1.6.0 - uses: jsmrcaga/action-netlify-deploy@v1.6.0
with: with:

View file

@ -51,3 +51,8 @@ Eric Newbury <enewbury@users.noreply.github.com>
Ayaz Hafiz <ayaz.hafiz.1@gmail.com> Ayaz Hafiz <ayaz.hafiz.1@gmail.com>
Johannes Maas <github@j-maas.de> Johannes Maas <github@j-maas.de>
Takeshi Sato <doublequotation@gmail.com> Takeshi Sato <doublequotation@gmail.com>
Joost Baas <joost@joostbaas.eu>
Callum Dunster <cdunster@users.noreply.github.com>
Martin Stewart <MartinSStewart@gmail.com>
James Hegedus <jthegedus@hey.com>
Cristiano Piemontese <cristiano.piemontese@vidiemme.it>

View file

@ -94,13 +94,7 @@ Install nix:
`curl -L https://nixos.org/nix/install | sh` `curl -L https://nixos.org/nix/install | sh`
If you're on MacOS and using a OS version >= 10.15: You will need to start a fresh terminal session to use nix.
`sh <(curl -L https://nixos.org/nix/install) --darwin-use-unencrypted-nix-store-volume`
You may prefer to setup up the volume manually by following nix documentation.
> You may need to restart your terminal
### Usage ### Usage

4
Cargo.lock generated
View file

@ -3467,6 +3467,7 @@ dependencies = [
"roc_std", "roc_std",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"static_assertions",
"ven_graph", "ven_graph",
"ven_pretty", "ven_pretty",
] ]
@ -3475,7 +3476,9 @@ dependencies = [
name = "roc_parse" name = "roc_parse"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ansi_term",
"bumpalo", "bumpalo",
"diff",
"encode_unicode", "encode_unicode",
"indoc", "indoc",
"pretty_assertions", "pretty_assertions",
@ -3551,6 +3554,7 @@ version = "0.1.0"
name = "roc_types" name = "roc_types"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo",
"roc_collections", "roc_collections",
"roc_module", "roc_module",
"roc_region", "roc_region",

View file

@ -12,7 +12,6 @@ members = [
"compiler/constrain", "compiler/constrain",
"compiler/unify", "compiler/unify",
"compiler/solve", "compiler/solve",
"compiler/reporting",
"compiler/fmt", "compiler/fmt",
"compiler/mono", "compiler/mono",
"compiler/test_mono", "compiler/test_mono",
@ -31,6 +30,7 @@ members = [
"ast", "ast",
"cli", "cli",
"code_markup", "code_markup",
"reporting",
"roc_std", "roc_std",
"utils", "utils",
"docs", "docs",

View file

@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
copy-dirs: copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir cli cli_utils compiler docs editor ast code_markup utils roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./ COPY --dir cli cli_utils compiler docs editor ast code_markup utils reporting roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
test-zig: test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt FROM +install-zig-llvm-valgrind-clippy-rustfmt
@ -79,17 +79,17 @@ test-rust:
# not pre-compiling the host can cause race conditions # not pre-compiling the host can cause race conditions
RUN echo "4" | cargo run --release examples/benchmarks/NQueens.roc RUN echo "4" | cargo run --release examples/benchmarks/NQueens.roc
RUN --mount=type=cache,target=$SCCACHE_DIR \ RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release --features with_sound --workspace && sccache --show-stats cargo test --locked --release --features with_sound --workspace && sccache --show-stats
# test the dev and wasm backend: they require an explicit feature flag. # test the dev and wasm backend: they require an explicit feature flag.
RUN --mount=type=cache,target=$SCCACHE_DIR \ RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release --package test_gen --no-default-features --features gen-dev && sccache --show-stats cargo test --locked --release --package test_gen --no-default-features --features gen-dev && sccache --show-stats
# gen-wasm has some multithreading problems to do with the wasmer runtime. Run it single-threaded as a separate job # gen-wasm has some multithreading problems to do with the wasmer runtime. Run it single-threaded as a separate job
RUN --mount=type=cache,target=$SCCACHE_DIR \ RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release --package test_gen --no-default-features --features gen-wasm -- --test-threads=1 && sccache --show-stats cargo test --locked --release --package test_gen --no-default-features --features gen-wasm -- --test-threads=1 && sccache --show-stats
# run i386 (32-bit linux) cli tests # run i386 (32-bit linux) cli tests
RUN echo "4" | cargo run --release --features="target-x86" -- --backend=x86_32 examples/benchmarks/NQueens.roc RUN echo "4" | cargo run --locked --release --features="target-x86" -- --backend=x86_32 examples/benchmarks/NQueens.roc
RUN --mount=type=cache,target=$SCCACHE_DIR \ RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release --features with_sound --test cli_run i386 --features="i386-cli-run" && sccache --show-stats cargo test --locked --release --features with_sound --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
verify-no-git-changes: verify-no-git-changes:
FROM +test-rust FROM +test-rust

View file

@ -20,6 +20,7 @@ use crate::{
expr2::{ClosureExtra, Expr2, ExprId, WhenBranch}, expr2::{ClosureExtra, Expr2, ExprId, WhenBranch},
record_field::RecordField, record_field::RecordField,
}, },
fun_def::FunctionDef,
pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct},
types::{Type2, TypeId}, types::{Type2, TypeId},
val_def::ValueDef, val_def::ValueDef,
@ -276,7 +277,7 @@ pub fn constrain_expr<'a>(
expr_id: expr_node_id, expr_id: expr_node_id,
closure_var, closure_var,
fn_var, fn_var,
.. called_via,
} => { } => {
// The expression that evaluates to the function being called, e.g. `foo` in // The expression that evaluates to the function being called, e.g. `foo` in
// (foo) bar baz // (foo) bar baz
@ -348,7 +349,7 @@ pub fn constrain_expr<'a>(
region, region,
); );
let category = Category::CallResult(opt_symbol); let category = Category::CallResult(opt_symbol, *called_via);
let mut and_constraints = BumpVec::with_capacity_in(4, arena); let mut and_constraints = BumpVec::with_capacity_in(4, arena);
@ -818,6 +819,126 @@ pub fn constrain_expr<'a>(
} }
} }
} }
// In an expression like
// id = \x -> x
//
// id 1
// The `def_id` refers to the definition `id = \x -> x`,
// and the body refers to `id 1`.
Expr2::LetFunction {
def_id,
body_id,
body_var: _,
} => {
let body = env.pool.get(*body_id);
let body_con = constrain_expr(arena, env, body, expected.shallow_clone(), region);
let function_def = env.pool.get(*def_id);
let (name, arguments, body_id, rigid_vars, args_constrs) = match function_def {
FunctionDef::WithAnnotation {
name,
arguments,
body_id,
rigids,
return_type: _,
} => {
// The annotation gives us arguments with proper Type2s, but the constraints we
// generate below args bound to type variables. Create fresh ones and bind them
// to the types we already know.
let mut args_constrs = BumpVec::with_capacity_in(arguments.len(), arena);
let args_vars = PoolVec::with_capacity(arguments.len() as u32, env.pool);
for (arg_ty_node_id, arg_var_node_id) in
arguments.iter_node_ids().zip(args_vars.iter_node_ids())
{
let (ty, pattern) = env.pool.get(arg_ty_node_id);
let arg_var = env.var_store.fresh();
let ty = env.pool.get(*ty);
args_constrs.push(Eq(
Type2::Variable(arg_var),
Expected::NoExpectation(ty.shallow_clone()),
Category::Storage(std::file!(), std::line!()),
// TODO: should be the actual region of the argument
region,
));
env.pool[arg_var_node_id] = (arg_var, *pattern);
}
let rigids = env.pool.get(*rigids);
let rigid_vars: BumpVec<Variable> =
BumpVec::from_iter_in(rigids.names.iter(env.pool).map(|&(_, v)| v), arena);
(name, args_vars, body_id, rigid_vars, args_constrs)
}
FunctionDef::NoAnnotation {
name,
arguments,
body_id,
return_var: _,
} => {
(
name,
arguments.shallow_clone(),
body_id,
BumpVec::new_in(arena), // The function is unannotated, so there are no rigid type vars
BumpVec::new_in(arena), // No extra constraints to generate for arguments
)
}
};
// A function definition is equivalent to a named value definition, where the
// value is a closure. So, we create a closure definition in correspondence
// with the function definition, generate type constraints for it, and demand
// that type of the function is just the type of the resolved closure.
let fn_var = env.var_store.fresh();
let fn_ty = Type2::Variable(fn_var);
let extra = ClosureExtra {
return_type: env.var_store.fresh(),
captured_symbols: PoolVec::empty(env.pool),
closure_type: env.var_store.fresh(),
closure_ext_var: env.var_store.fresh(),
};
let clos = Expr2::Closure {
args: arguments.shallow_clone(),
uniq_symbol: *name,
body_id: *body_id,
function_type: env.var_store.fresh(),
extra: env.pool.add(extra),
recursive: roc_can::expr::Recursive::Recursive,
};
let clos_con = constrain_expr(
arena,
env,
&clos,
Expected::NoExpectation(fn_ty.shallow_clone()),
region,
);
// This is the `foo` part in `foo = \...`. We want to bind the name of the
// function with its type, whose constraints we generated above.
let mut def_pattern_state = PatternState2 {
headers: BumpMap::new_in(arena),
vars: BumpVec::new_in(arena),
constraints: args_constrs,
};
def_pattern_state.headers.insert(*name, fn_ty);
def_pattern_state.vars.push(fn_var);
Let(arena.alloc(LetConstraint {
rigid_vars,
flex_vars: def_pattern_state.vars,
def_types: def_pattern_state.headers, // Binding function name -> its type
defs_constraint: Let(arena.alloc(LetConstraint {
rigid_vars: BumpVec::new_in(arena), // always empty
flex_vars: BumpVec::new_in(arena), // empty, because our functions have no arguments
def_types: BumpMap::new_in(arena), // empty, because our functions have no arguments
defs_constraint: And(def_pattern_state.constraints),
ret_constraint: clos_con,
})),
ret_constraint: body_con,
}))
}
Expr2::Update { Expr2::Update {
symbol, symbol,
updates, updates,
@ -1031,7 +1152,6 @@ pub fn constrain_expr<'a>(
exists(arena, vars, And(and_constraints)) exists(arena, vars, And(and_constraints))
} }
Expr2::LetRec { .. } => todo!(), Expr2::LetRec { .. } => todo!(),
Expr2::LetFunction { .. } => todo!(),
} }
} }
@ -1765,7 +1885,7 @@ pub mod test_constrain {
use roc_parse::parser::SyntaxError; use roc_parse::parser::SyntaxError;
use roc_region::all::Region; use roc_region::all::Region;
use roc_types::{ use roc_types::{
pretty_print::content_to_string, pretty_print::{content_to_string, name_all_type_vars},
solved_types::Solved, solved_types::Solved,
subs::{Subs, VarStore, Variable}, subs::{Subs, VarStore, Variable},
}; };
@ -1799,7 +1919,7 @@ pub mod test_constrain {
aliases, aliases,
}; };
let mut subs = Subs::new(var_store); let mut subs = Subs::new_from_varstore(var_store);
for (var, name) in rigid_variables { for (var, name) in rigid_variables {
subs.rigid_var(var, name); subs.rigid_var(var, name);
@ -1845,7 +1965,7 @@ pub mod test_constrain {
let expr2_result = str_to_expr2(&code_arena, actual, &mut env, &mut scope, region); let expr2_result = str_to_expr2(&code_arena, actual, &mut env, &mut scope, region);
match expr2_result { match expr2_result {
Ok((expr, _)) => { Ok((expr, output)) => {
let constraint = constrain_expr( let constraint = constrain_expr(
&code_arena, &code_arena,
&mut env, &mut env,
@ -1865,17 +1985,22 @@ pub mod test_constrain {
let mut var_store = VarStore::default(); let mut var_store = VarStore::default();
std::mem::swap(ref_var_store, &mut var_store); std::mem::swap(ref_var_store, &mut var_store);
let rigids = output.introduced_variables.name_by_var;
let (mut solved, _, _) = run_solve( let (mut solved, _, _) = run_solve(
&code_arena, &code_arena,
pool, pool,
Default::default(), Default::default(),
Default::default(), rigids,
constraint, constraint,
var_store, var_store,
); );
let subs = solved.inner_mut(); let subs = solved.inner_mut();
// name type vars
name_all_type_vars(var, subs);
let content = subs.get_content_without_compacting(var); let content = subs.get_content_without_compacting(var);
// Connect the ModuleId to it's IdentIds // Connect the ModuleId to it's IdentIds
@ -2132,6 +2257,102 @@ pub mod test_constrain {
) )
} }
#[test]
fn dual_arity_lambda() {
infer_eq(
indoc!(
r#"
\a, b -> Pair a b
"#
),
"a, b -> [ Pair a b ]*",
);
}
#[test]
fn anonymous_identity() {
infer_eq(
indoc!(
r#"
(\a -> a) 3.14
"#
),
"Float *",
);
}
#[test]
fn identity_of_identity() {
infer_eq(
indoc!(
r#"
(\val -> val) (\val -> val)
"#
),
"a -> a",
);
}
#[test]
fn identity_function() {
infer_eq(
indoc!(
r#"
\val -> val
"#
),
"a -> a",
);
}
#[test]
fn apply_function() {
infer_eq(
indoc!(
r#"
\f, x -> f x
"#
),
"(a -> b), a -> b",
);
}
#[test]
fn flip_function() {
infer_eq(
indoc!(
r#"
\f -> (\a, b -> f b a)
"#
),
"(a, b -> c) -> (b, a -> c)",
);
}
#[test]
fn always_function() {
infer_eq(
indoc!(
r#"
\val -> \_ -> val
"#
),
"a -> (* -> a)",
);
}
#[test]
fn pass_a_function() {
infer_eq(
indoc!(
r#"
\f -> f {}
"#
),
"({} -> a) -> a",
);
}
#[test] #[test]
fn constrain_closure() { fn constrain_closure() {
infer_eq( infer_eq(
@ -2145,4 +2366,130 @@ pub mod test_constrain {
"{}* -> Num *", "{}* -> Num *",
) )
} }
#[test]
fn recursive_identity() {
infer_eq(
indoc!(
r#"
identity = \val -> val
identity
"#
),
"a -> a",
);
}
#[test]
fn use_apply() {
infer_eq(
indoc!(
r#"
identity = \a -> a
apply = \f, x -> f x
apply identity 5
"#
),
"Num *",
);
}
#[test]
fn nested_let_function() {
infer_eq(
indoc!(
r#"
curryPair = \a ->
getB = \b -> Pair a b
getB
curryPair
"#
),
"a -> (b -> [ Pair a b ]*)",
);
}
#[test]
fn record_with_bound_var() {
infer_eq(
indoc!(
r#"
fn = \rec ->
x = rec.x
rec
fn
"#
),
"{ x : a }b -> { x : a }b",
);
}
#[test]
fn using_type_signature() {
infer_eq(
indoc!(
r#"
bar : custom -> custom
bar = \x -> x
bar
"#
),
"custom -> custom",
);
}
#[ignore = "Currently panics at 'Invalid Cycle', ast/src/lang/core/def/def.rs:1212:21"]
#[test]
fn using_type_signature2() {
infer_eq(
indoc!(
r#"
id1 : tya -> tya
id1 = \x -> x
id2 : tyb -> tyb
id2 = id1
id2
"#
),
"tyb -> tyb",
);
}
#[ignore = "Implement annotation-only decls"]
#[test]
fn type_signature_without_body() {
infer_eq(
indoc!(
r#"
foo: Str -> {}
foo "hi"
"#
),
"{}",
);
}
#[ignore = "Implement annotation-only decls"]
#[test]
fn type_signature_without_body_rigid() {
infer_eq(
indoc!(
r#"
foo : Num * -> custom
foo 2
"#
),
"custom",
);
}
} }

View file

@ -37,7 +37,11 @@ use crate::{
rigids::Rigids, rigids::Rigids,
scope::Scope, scope::Scope,
}, },
mem_pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone}, mem_pool::{
pool::{NodeId, Pool},
pool_vec::PoolVec,
shallow_clone::ShallowClone,
},
}; };
#[derive(Debug)] #[derive(Debug)]
@ -316,7 +320,7 @@ fn from_pending_alias<'a>(
} }
for loc_lowercase in vars { for loc_lowercase in vars {
if !named_rigids.contains_key(loc_lowercase.value.as_str()) { if !named_rigids.contains_key(&loc_lowercase.value) {
env.problem(Problem::PhantomTypeArgument { env.problem(Problem::PhantomTypeArgument {
alias: symbol, alias: symbol,
variable_region: loc_lowercase.region, variable_region: loc_lowercase.region,
@ -454,6 +458,10 @@ fn canonicalize_pending_def<'a>(
output.references.referenced_aliases.insert(symbol); output.references.referenced_aliases.insert(symbol);
} }
// Ensure rigid type vars and their names are known in the output.
for (name, &var) in named_rigids.iter() {
output.introduced_variables.insert_named(name.clone(), var);
}
let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool); let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool);
// bookkeeping for tail-call detection. If we're assigning to an // bookkeeping for tail-call detection. If we're assigning to an
@ -521,7 +529,7 @@ fn canonicalize_pending_def<'a>(
// parent commit for the bug this fixed! // parent commit for the bug this fixed!
let refs = References::new(); let refs = References::new();
let arguments: PoolVec<(PatternId, Type2)> = let arguments: PoolVec<(NodeId<Type2>, PatternId)> =
PoolVec::with_capacity(closure_args.len() as u32, env.pool); PoolVec::with_capacity(closure_args.len() as u32, env.pool);
let return_type: TypeId; let return_type: TypeId;
@ -558,7 +566,8 @@ fn canonicalize_pending_def<'a>(
for (node_id, ((_, pattern_id), typ)) in for (node_id, ((_, pattern_id), typ)) in
arguments.iter_node_ids().zip(it.into_iter()) arguments.iter_node_ids().zip(it.into_iter())
{ {
env.pool[node_id] = (pattern_id, typ); let typ = env.pool.add(typ);
env.pool[node_id] = (typ, pattern_id);
} }
return_type = return_type_id; return_type = return_type_id;
@ -689,14 +698,14 @@ fn canonicalize_pending_def<'a>(
// parent commit for the bug this fixed! // parent commit for the bug this fixed!
let refs = References::new(); let refs = References::new();
let arguments: PoolVec<(PatternId, Variable)> = let arguments: PoolVec<(Variable, PatternId)> =
PoolVec::with_capacity(closure_args.len() as u32, env.pool); PoolVec::with_capacity(closure_args.len() as u32, env.pool);
let it: Vec<_> = closure_args.iter(env.pool).map(|(x, y)| (*x, *y)).collect(); let it: Vec<_> = closure_args.iter(env.pool).map(|(x, y)| (*x, *y)).collect();
for (node_id, (_, pattern_id)) in arguments.iter_node_ids().zip(it.into_iter()) for (node_id, (_, pattern_id)) in arguments.iter_node_ids().zip(it.into_iter())
{ {
env.pool[node_id] = (pattern_id, env.var_store.fresh()); env.pool[node_id] = (env.var_store.fresh(), pattern_id);
} }
let function_def = FunctionDef::NoAnnotation { let function_def = FunctionDef::NoAnnotation {

View file

@ -6,8 +6,8 @@ use crate::{
mem_pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec}, mem_pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec},
}; };
use roc_can::expr::Recursive; use roc_can::expr::Recursive;
use roc_module::called_via::CalledVia;
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::operator::CalledVia;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use super::record_field::RecordField; use super::record_field::RecordField;

View file

@ -132,7 +132,7 @@ pub fn expr_to_expr2<'a>(
Str(literal) => flatten_str_literal(env, scope, literal), Str(literal) => flatten_str_literal(env, scope, literal),
List { items, .. } => { List(items) => {
let mut output = Output::default(); let mut output = Output::default();
let output_ref = &mut output; let output_ref = &mut output;
@ -185,13 +185,12 @@ pub fn expr_to_expr2<'a>(
RecordUpdate { RecordUpdate {
fields, fields,
update: loc_update, update: loc_update,
final_comments: _,
} => { } => {
let (can_update, update_out) = let (can_update, update_out) =
expr_to_expr2(env, scope, &loc_update.value, loc_update.region); expr_to_expr2(env, scope, &loc_update.value, loc_update.region);
if let Expr2::Var(symbol) = &can_update { if let Expr2::Var(symbol) = &can_update {
match canonicalize_fields(env, scope, fields) { match canonicalize_fields(env, scope, fields.items) {
Ok((can_fields, mut output)) => { Ok((can_fields, mut output)) => {
output.references.union_mut(update_out.references); output.references.union_mut(update_out.references);
@ -236,14 +235,11 @@ pub fn expr_to_expr2<'a>(
} }
} }
Record { Record(fields) => {
fields,
final_comments: _,
} => {
if fields.is_empty() { if fields.is_empty() {
(Expr2::EmptyRecord, Output::default()) (Expr2::EmptyRecord, Output::default())
} else { } else {
match canonicalize_fields(env, scope, fields) { match canonicalize_fields(env, scope, fields.items) {
Ok((can_fields, output)) => ( Ok((can_fields, output)) => (
Expr2::Record { Expr2::Record {
record_var: env.var_store.fresh(), record_var: env.var_store.fresh(),

View file

@ -15,14 +15,14 @@ use super::{
pub enum FunctionDef { pub enum FunctionDef {
WithAnnotation { WithAnnotation {
name: Symbol, // 8B name: Symbol, // 8B
arguments: PoolVec<(PatternId, Type2)>, // 8B arguments: PoolVec<(NodeId<Type2>, PatternId)>, // 8B
rigids: NodeId<Rigids>, // 4B rigids: NodeId<Rigids>, // 4B
return_type: TypeId, // 4B return_type: TypeId, // 4B
body_id: ExprId, // 4B body_id: ExprId, // 4B
}, },
NoAnnotation { NoAnnotation {
name: Symbol, // 8B name: Symbol, // 8B
arguments: PoolVec<(PatternId, Variable)>, // 8B arguments: PoolVec<(Variable, PatternId)>, // 8B
return_var: Variable, // 4B return_var: Variable, // 4B
body_id: ExprId, // 4B body_id: ExprId, // 4B
}, },

View file

@ -2,7 +2,7 @@ pub mod ast;
mod declaration; mod declaration;
pub mod def; pub mod def;
pub mod expr; pub mod expr;
mod fun_def; pub mod fun_def;
pub mod header; pub mod header;
pub mod pattern; pub mod pattern;
pub mod str; pub mod str;

View file

@ -1,4 +1,4 @@
use roc_module::{operator::CalledVia, symbol::Symbol}; use roc_module::{called_via::CalledVia, symbol::Symbol};
use roc_parse::ast::StrLiteral; use roc_parse::ast::StrLiteral;
use crate::{ use crate::{

View file

@ -3,7 +3,7 @@
#![allow(unused_imports)] #![allow(unused_imports)]
// use roc_can::expr::Output; // use roc_can::expr::Output;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, TagName}; use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::types::{Problem, RecordField}; use roc_types::types::{Problem, RecordField};
@ -20,29 +20,34 @@ pub type TypeId = NodeId<Type2>;
#[derive(Debug)] #[derive(Debug)]
pub enum Type2 { pub enum Type2 {
Variable(Variable), Variable(Variable), // 4B
Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 12B + 4B Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 12B + 4B AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
// 32B // 24B
HostExposedAlias { HostExposedAlias {
name: Symbol, // 8B name: Symbol, // 8B
arguments: PoolVec<(PoolStr, TypeId)>, // 12B arguments: PoolVec<(PoolStr, TypeId)>, // 8B
actual_var: Variable, // 4B actual_var: Variable, // 4B
actual: TypeId, // 4B actual: TypeId, // 4B
}, },
EmptyTagUnion, EmptyTagUnion,
TagUnion(PoolVec<(TagName, PoolVec<Type2>)>, TypeId), // 16B = 12B + 4B TagUnion(PoolVec<(TagName, PoolVec<Type2>)>, TypeId), // 12B = 8B + 4B
RecursiveTagUnion(Variable, PoolVec<(TagName, PoolVec<Type2>)>, TypeId), // 20B = 4B + 12B + 4B RecursiveTagUnion(Variable, PoolVec<(TagName, PoolVec<Type2>)>, TypeId), // 16B = 4B + 8B + 4B
EmptyRec, EmptyRec,
Record(PoolVec<(PoolStr, RecordField<TypeId>)>, TypeId), // 16B = 12B + 4B Record(PoolVec<(PoolStr, RecordField<TypeId>)>, TypeId), // 12B = 8B + 4B
Function(PoolVec<Type2>, TypeId, TypeId), // 20B = 12B + 4B + 4B Function(PoolVec<Type2>, TypeId, TypeId), // 16B = 8B + 4B + 4B
Apply(Symbol, PoolVec<Type2>), // 20B = 8B + 12B Apply(Symbol, PoolVec<Type2>), // 16B = 8B + 8B
Erroneous(Problem2), Erroneous(Problem2), // 24B
}
#[test]
fn type2_size() {
assert_eq!(std::mem::size_of::<Type2>(), 32); // 24B + pad
} }
#[derive(Debug)] #[derive(Debug)]
@ -171,9 +176,9 @@ pub enum Signature {
}, },
} }
pub enum Annotation2<'a> { pub enum Annotation2 {
Annotation { Annotation {
named_rigids: MutMap<&'a str, Variable>, named_rigids: MutMap<Lowercase, Variable>,
unnamed_rigids: MutSet<Variable>, unnamed_rigids: MutSet<Variable>,
symbols: MutSet<Symbol>, symbols: MutSet<Symbol>,
signature: Signature, signature: Signature,
@ -186,7 +191,7 @@ pub fn to_annotation2<'a>(
scope: &mut Scope, scope: &mut Scope,
annotation: &'a roc_parse::ast::TypeAnnotation<'a>, annotation: &'a roc_parse::ast::TypeAnnotation<'a>,
region: Region, region: Region,
) -> Annotation2<'a> { ) -> Annotation2 {
let mut references = References::default(); let mut references = References::default();
let annotation = to_type2(env, scope, &mut references, annotation, region); let annotation = to_type2(env, scope, &mut references, annotation, region);
@ -240,11 +245,7 @@ pub fn to_annotation2<'a>(
} }
} }
fn shallow_dealias<'a>( fn shallow_dealias<'a>(env: &mut Env, references: References, annotation: Type2) -> Annotation2 {
env: &mut Env,
references: References<'a>,
annotation: Type2,
) -> Annotation2<'a> {
let References { let References {
named, named,
unnamed, unnamed,
@ -288,8 +289,8 @@ fn shallow_dealias<'a>(
} }
#[derive(Default)] #[derive(Default)]
pub struct References<'a> { pub struct References {
named: MutMap<&'a str, Variable>, named: MutMap<Lowercase, Variable>,
unnamed: MutSet<Variable>, unnamed: MutSet<Variable>,
hidden: MutSet<Variable>, hidden: MutSet<Variable>,
symbols: MutSet<Symbol>, symbols: MutSet<Symbol>,
@ -298,7 +299,7 @@ pub struct References<'a> {
pub fn to_type_id<'a>( pub fn to_type_id<'a>(
env: &mut Env, env: &mut Env,
scope: &mut Scope, scope: &mut Scope,
rigids: &mut References<'a>, rigids: &mut References,
annotation: &roc_parse::ast::TypeAnnotation<'a>, annotation: &roc_parse::ast::TypeAnnotation<'a>,
region: Region, region: Region,
) -> TypeId { ) -> TypeId {
@ -310,7 +311,7 @@ pub fn to_type_id<'a>(
pub fn as_type_id<'a>( pub fn as_type_id<'a>(
env: &mut Env, env: &mut Env,
scope: &mut Scope, scope: &mut Scope,
rigids: &mut References<'a>, rigids: &mut References,
type_id: TypeId, type_id: TypeId,
annotation: &roc_parse::ast::TypeAnnotation<'a>, annotation: &roc_parse::ast::TypeAnnotation<'a>,
region: Region, region: Region,
@ -324,7 +325,7 @@ pub fn as_type_id<'a>(
pub fn to_type2<'a>( pub fn to_type2<'a>(
env: &mut Env, env: &mut Env,
scope: &mut Scope, scope: &mut Scope,
references: &mut References<'a>, references: &mut References,
annotation: &roc_parse::ast::TypeAnnotation<'a>, annotation: &roc_parse::ast::TypeAnnotation<'a>,
region: Region, region: Region,
) -> Type2 { ) -> Type2 {
@ -375,8 +376,9 @@ pub fn to_type2<'a>(
Type2::Function(arguments, closure_type_id, return_type_id) Type2::Function(arguments, closure_type_id, return_type_id)
} }
BoundVariable(v) => { BoundVariable(v) => {
// a rigid type variable // A rigid type variable. The parser should have already ensured that the name is indeed a lowercase.
match references.named.get(v) { let v = Lowercase::from(*v);
match references.named.get(&v) {
Some(var) => Type2::Variable(*var), Some(var) => Type2::Variable(*var),
None => { None => {
let var = env.var_store.fresh(); let var = env.var_store.fresh();
@ -387,6 +389,9 @@ pub fn to_type2<'a>(
} }
} }
} }
Inferred => {
unimplemented!();
}
Wildcard | Malformed(_) => { Wildcard | Malformed(_) => {
let var = env.var_store.fresh(); let var = env.var_store.fresh();
@ -401,7 +406,7 @@ pub fn to_type2<'a>(
let field_types = PoolVec::with_capacity(field_types_map.len() as u32, env.pool); let field_types = PoolVec::with_capacity(field_types_map.len() as u32, env.pool);
for (node_id, (label, field)) in field_types.iter_node_ids().zip(field_types_map) { for (node_id, (label, field)) in field_types.iter_node_ids().zip(field_types_map) {
let poolstr = PoolStr::new(label, env.pool); let poolstr = PoolStr::new(label.as_str(), env.pool);
let rec_field = match field { let rec_field = match field {
RecordField::Optional(_) => { RecordField::Optional(_) => {
@ -428,7 +433,7 @@ pub fn to_type2<'a>(
Type2::Record(field_types, ext_type) Type2::Record(field_types, ext_type)
} }
TagUnion { tags, ext, .. } => { TagUnion { tags, ext, .. } => {
let tag_types_vec = can_tags(env, scope, references, tags, region); let tag_types_vec = can_tags(env, scope, references, tags.items, region);
let tag_types = PoolVec::with_capacity(tag_types_vec.len() as u32, env.pool); let tag_types = PoolVec::with_capacity(tag_types_vec.len() as u32, env.pool);
@ -480,10 +485,10 @@ pub fn to_type2<'a>(
{ {
match loc_var.value { match loc_var.value {
BoundVariable(ident) => { BoundVariable(ident) => {
let var_name = ident; let var_name = Lowercase::from(ident);
if let Some(var) = references.named.get(&var_name) { if let Some(var) = references.named.get(&var_name) {
let poolstr = PoolStr::new(var_name, env.pool); let poolstr = PoolStr::new(var_name.as_str(), env.pool);
let type_id = env.pool.add(Type2::Variable(*var)); let type_id = env.pool.add(Type2::Variable(*var));
env.pool[var_id] = (poolstr.shallow_clone(), type_id); env.pool[var_id] = (poolstr.shallow_clone(), type_id);
@ -494,7 +499,7 @@ pub fn to_type2<'a>(
let var = env.var_store.fresh(); let var = env.var_store.fresh();
references.named.insert(var_name.clone(), var); references.named.insert(var_name.clone(), var);
let poolstr = PoolStr::new(var_name, env.pool); let poolstr = PoolStr::new(var_name.as_str(), env.pool);
let type_id = env.pool.add(Type2::Variable(var)); let type_id = env.pool.add(Type2::Variable(var));
env.pool[var_id] = (poolstr.shallow_clone(), type_id); env.pool[var_id] = (poolstr.shallow_clone(), type_id);
@ -576,10 +581,10 @@ pub fn to_type2<'a>(
fn can_assigned_fields<'a>( fn can_assigned_fields<'a>(
env: &mut Env, env: &mut Env,
scope: &mut Scope, scope: &mut Scope,
rigids: &mut References<'a>, rigids: &mut References,
fields: &&[Located<roc_parse::ast::AssignedField<'a, roc_parse::ast::TypeAnnotation<'a>>>], fields: &&[Located<roc_parse::ast::AssignedField<'a, roc_parse::ast::TypeAnnotation<'a>>>],
region: Region, region: Region,
) -> MutMap<&'a str, RecordField<Type2>> { ) -> MutMap<Lowercase, RecordField<Type2>> {
use roc_parse::ast::AssignedField::*; use roc_parse::ast::AssignedField::*;
use roc_types::types::RecordField::*; use roc_types::types::RecordField::*;
@ -602,8 +607,8 @@ fn can_assigned_fields<'a>(
let field_type = let field_type =
to_type2(env, scope, rigids, &annotation.value, annotation.region); to_type2(env, scope, rigids, &annotation.value, annotation.region);
let label = field_name.value; let label = Lowercase::from(field_name.value);
field_types.insert(label, Required(field_type)); field_types.insert(label.clone(), Required(field_type));
break 'inner label; break 'inner label;
} }
@ -611,20 +616,20 @@ fn can_assigned_fields<'a>(
let field_type = let field_type =
to_type2(env, scope, rigids, &annotation.value, annotation.region); to_type2(env, scope, rigids, &annotation.value, annotation.region);
let label = field_name.value; let label = Lowercase::from(field_name.value);
field_types.insert(label.clone(), Optional(field_type)); field_types.insert(label.clone(), Optional(field_type));
break 'inner label; break 'inner label;
} }
LabelOnly(loc_field_name) => { LabelOnly(loc_field_name) => {
// Interpret { a, b } as { a : a, b : b } // Interpret { a, b } as { a : a, b : b }
let field_name = loc_field_name.value; let field_name = Lowercase::from(loc_field_name.value);
let field_type = { let field_type = {
if let Some(var) = rigids.named.get(&field_name) { if let Some(var) = rigids.named.get(&field_name) {
Type2::Variable(*var) Type2::Variable(*var)
} else { } else {
let field_var = env.var_store.fresh(); let field_var = env.var_store.fresh();
rigids.named.insert(field_name, field_var); rigids.named.insert(field_name.clone(), field_var);
Type2::Variable(field_var) Type2::Variable(field_var)
} }
}; };
@ -664,7 +669,7 @@ fn can_assigned_fields<'a>(
fn can_tags<'a>( fn can_tags<'a>(
env: &mut Env, env: &mut Env,
scope: &mut Scope, scope: &mut Scope,
rigids: &mut References<'a>, rigids: &mut References,
tags: &'a [Located<roc_parse::ast::Tag<'a>>], tags: &'a [Located<roc_parse::ast::Tag<'a>>],
region: Region, region: Region,
) -> Vec<(TagName, PoolVec<Type2>)> { ) -> Vec<(TagName, PoolVec<Type2>)> {
@ -748,7 +753,7 @@ enum TypeApply {
fn to_type_apply<'a>( fn to_type_apply<'a>(
env: &mut Env, env: &mut Env,
scope: &mut Scope, scope: &mut Scope,
rigids: &mut References<'a>, rigids: &mut References,
module_name: &str, module_name: &str,
ident: &str, ident: &str,
type_arguments: &[Located<roc_parse::ast::TypeAnnotation<'a>>], type_arguments: &[Located<roc_parse::ast::TypeAnnotation<'a>>],

View file

@ -1,7 +1,7 @@
use crate::mem_pool::pool::{NodeId, Pool}; use crate::mem_pool::pool::{NodeId, Pool};
use bumpalo::{collections::Vec as BumpVec, Bump}; use bumpalo::{collections::Vec as BumpVec, Bump};
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, ModuleName}; use roc_module::ident::{Ident, Lowercase, ModuleName};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
@ -134,11 +134,8 @@ impl<'a> Env<'a> {
)), )),
} }
} else { } else {
match self match self.dep_idents.get(&module_id) {
.dep_idents Some(exposed_ids) => match exposed_ids.get_id(&ident) {
.get(&module_id)
.and_then(|exposed_ids| exposed_ids.get_id(&ident))
{
Some(ident_id) => { Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id); let symbol = Symbol::new(module_id, *ident_id);
@ -146,11 +143,28 @@ impl<'a> Env<'a> {
Ok(symbol) Ok(symbol)
} }
None => Err(RuntimeError::ValueNotExposed { None => {
let exposed_values = exposed_ids
.idents()
.filter(|(_, ident)| {
ident.as_ref().starts_with(|c: char| c.is_lowercase())
})
.map(|(_, ident)| Lowercase::from(ident.as_ref()))
.collect();
Err(RuntimeError::ValueNotExposed {
module_name, module_name,
ident, ident,
region, region,
}), exposed_values,
})
}
},
None => {
panic!(
"Module {} exists, but is not recorded in dep_idents",
module_name
)
}
} }
} }
} }

View file

@ -7,6 +7,7 @@ use crate::mem_pool::{
pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone, pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone,
}; };
use roc_collections::all::WyHash; use roc_collections::all::WyHash;
use roc_module::ident::Lowercase;
use roc_types::subs::Variable; use roc_types::subs::Variable;
#[derive(Debug)] #[derive(Debug)]
@ -18,7 +19,7 @@ pub struct Rigids {
#[allow(clippy::needless_collect)] #[allow(clippy::needless_collect)]
impl Rigids { impl Rigids {
pub fn new( pub fn new(
named: HashMap<&str, Variable, BuildHasherDefault<WyHash>>, named: HashMap<Lowercase, Variable, BuildHasherDefault<WyHash>>,
unnamed: HashSet<Variable, BuildHasherDefault<WyHash>>, unnamed: HashSet<Variable, BuildHasherDefault<WyHash>>,
pool: &mut Pool, pool: &mut Pool,
) -> Self { ) -> Self {
@ -26,7 +27,7 @@ impl Rigids {
let mut temp_names = Vec::new(); let mut temp_names = Vec::new();
temp_names.extend(named.iter().map(|(name, var)| (Some(*name), *var))); temp_names.extend(named.iter().map(|(name, var)| (Some(name.as_str()), *var)));
temp_names.extend(unnamed.iter().map(|var| (None, *var))); temp_names.extend(unnamed.iter().map(|var| (None, *var)));

View file

@ -61,7 +61,7 @@ roc_load = { path = "../compiler/load" }
roc_gen_llvm = { path = "../compiler/gen_llvm", optional = true } roc_gen_llvm = { path = "../compiler/gen_llvm", optional = true }
roc_build = { path = "../compiler/build", default-features = false } roc_build = { path = "../compiler/build", default-features = false }
roc_fmt = { path = "../compiler/fmt" } roc_fmt = { path = "../compiler/fmt" }
roc_reporting = { path = "../compiler/reporting" } roc_reporting = { path = "../reporting" }
roc_editor = { path = "../editor", optional = true } roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" } roc_linker = { path = "../linker" }
clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] } clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }

View file

@ -55,6 +55,7 @@ pub fn build_file<'a>(
link_type: LinkType, link_type: LinkType,
surgically_link: bool, surgically_link: bool,
precompiled: bool, precompiled: bool,
target_valgrind: bool,
) -> Result<BuiltFile, LoadingProblem<'a>> { ) -> Result<BuiltFile, LoadingProblem<'a>> {
let compilation_start = SystemTime::now(); let compilation_start = SystemTime::now();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
@ -116,6 +117,7 @@ pub fn build_file<'a>(
.keys() .keys()
.map(|x| x.as_str(&loaded.interns).to_string()) .map(|x| x.as_str(&loaded.interns).to_string())
.collect(), .collect(),
target_valgrind,
); );
// TODO try to move as much of this linking as possible to the precompiled // TODO try to move as much of this linking as possible to the precompiled
@ -280,6 +282,7 @@ pub fn build_file<'a>(
}) })
} }
#[allow(clippy::too_many_arguments)]
fn spawn_rebuild_thread( fn spawn_rebuild_thread(
opt_level: OptLevel, opt_level: OptLevel,
surgically_link: bool, surgically_link: bool,
@ -288,9 +291,12 @@ fn spawn_rebuild_thread(
binary_path: PathBuf, binary_path: PathBuf,
target: &Triple, target: &Triple,
exported_symbols: Vec<String>, exported_symbols: Vec<String>,
target_valgrind: bool,
) -> std::thread::JoinHandle<u128> { ) -> std::thread::JoinHandle<u128> {
let thread_local_target = target.clone(); let thread_local_target = target.clone();
std::thread::spawn(move || { std::thread::spawn(move || {
print!("🔨 Rebuilding host... ");
let rebuild_host_start = SystemTime::now(); let rebuild_host_start = SystemTime::now();
if !precompiled { if !precompiled {
if surgically_link { if surgically_link {
@ -299,6 +305,7 @@ fn spawn_rebuild_thread(
&thread_local_target, &thread_local_target,
host_input_path.as_path(), host_input_path.as_path(),
exported_symbols, exported_symbols,
target_valgrind,
) )
.unwrap(); .unwrap();
} else { } else {
@ -307,6 +314,7 @@ fn spawn_rebuild_thread(
&thread_local_target, &thread_local_target,
host_input_path.as_path(), host_input_path.as_path(),
None, None,
target_valgrind,
); );
} }
} }
@ -316,6 +324,9 @@ fn spawn_rebuild_thread(
std::fs::copy(prehost, binary_path.as_path()).unwrap(); std::fs::copy(prehost, binary_path.as_path()).unwrap();
} }
let rebuild_host_end = rebuild_host_start.elapsed().unwrap(); let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
println!("Done!");
rebuild_host_end.as_millis() rebuild_host_end.as_millis()
}) })
} }

View file

@ -33,6 +33,7 @@ pub const FLAG_BACKEND: &str = "backend";
pub const FLAG_TIME: &str = "time"; pub const FLAG_TIME: &str = "time";
pub const FLAG_LINK: &str = "roc-linker"; pub const FLAG_LINK: &str = "roc-linker";
pub const FLAG_PRECOMPILED: &str = "precompiled-host"; pub const FLAG_PRECOMPILED: &str = "precompiled-host";
pub const FLAG_VALGRIND: &str = "valgrind";
pub const ROC_FILE: &str = "ROC_FILE"; pub const ROC_FILE: &str = "ROC_FILE";
pub const BACKEND: &str = "BACKEND"; pub const BACKEND: &str = "BACKEND";
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES"; pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
@ -100,6 +101,12 @@ pub fn build_app<'a>() -> App<'a> {
.about("Assumes the host has been precompiled and skips recompiling the host.") .about("Assumes the host has been precompiled and skips recompiling the host.")
.required(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) .subcommand(App::new(CMD_REPL)
.about("Launch the interactive Read Eval Print Loop (REPL)") .about("Launch the interactive Read Eval Print Loop (REPL)")
@ -258,6 +265,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
}; };
let surgically_link = matches.is_present(FLAG_LINK); let surgically_link = matches.is_present(FLAG_LINK);
let precompiled = matches.is_present(FLAG_PRECOMPILED); let precompiled = matches.is_present(FLAG_PRECOMPILED);
if surgically_link && !roc_linker::supported(&link_type, &target) { if surgically_link && !roc_linker::supported(&link_type, &target) {
panic!( panic!(
"Link type, {:?}, with target, {}, not supported by roc linker", "Link type, {:?}, with target, {}, not supported by roc linker",
@ -287,6 +295,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
}); });
let src_dir = path.parent().unwrap().canonicalize().unwrap(); let src_dir = path.parent().unwrap().canonicalize().unwrap();
let target_valgrind = matches.is_present(FLAG_VALGRIND);
let res_binary_path = build_file( let res_binary_path = build_file(
&arena, &arena,
&target, &target,
@ -298,6 +307,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
link_type, link_type,
surgically_link, surgically_link,
precompiled, precompiled,
target_valgrind,
); );
match res_binary_path { match res_binary_path {

View file

@ -2,12 +2,12 @@ use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use libloading::Library; use libloading::Library;
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type}; use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
use roc_module::called_via::CalledVia;
use roc_module::ident::TagName; use roc_module::ident::TagName;
use roc_module::operator::CalledVia;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::ProcLayout; use roc_mono::ir::ProcLayout;
use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant}; use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant};
use roc_parse::ast::{AssignedField, Expr, StrLiteral}; use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable};
@ -133,10 +133,7 @@ fn jit_to_ast_help<'a>(
), ),
Layout::Builtin(Builtin::EmptyList) => { Layout::Builtin(Builtin::EmptyList) => {
Ok(run_jit_function!(lib, main_fn_name, &'static str, |_| { Ok(run_jit_function!(lib, main_fn_name, &'static str, |_| {
Expr::List { Expr::List(Collection::empty())
items: &[],
final_comments: &[],
}
})) }))
} }
Layout::Builtin(Builtin::List(elem_layout)) => Ok(run_jit_function!( Layout::Builtin(Builtin::List(elem_layout)) => Ok(run_jit_function!(
@ -431,10 +428,7 @@ fn ptr_to_ast<'a>(
num_to_ast(env, number_literal_to_ast(env.arena, num), content) num_to_ast(env, number_literal_to_ast(env.arena, num), content)
} }
Layout::Builtin(Builtin::EmptyList) => Expr::List { Layout::Builtin(Builtin::EmptyList) => Expr::List(Collection::empty()),
items: &[],
final_comments: &[],
},
Layout::Builtin(Builtin::List(elem_layout)) => { Layout::Builtin(Builtin::List(elem_layout)) => {
// Turn the (ptr, len) wrapper struct into actual ptr and len values. // Turn the (ptr, len) wrapper struct into actual ptr and len values.
let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) }; let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) };
@ -522,10 +516,7 @@ fn list_to_ast<'a>(
let output = output.into_bump_slice(); let output = output.into_bump_slice();
Expr::List { Expr::List(Collection::with_items(output))
items: output,
final_comments: &[],
}
} }
fn single_tag_union_to_ast<'a>( fn single_tag_union_to_ast<'a>(
@ -621,10 +612,7 @@ fn struct_to_ast<'a>(
let output = env.arena.alloc([loc_field]); let output = env.arena.alloc([loc_field]);
Expr::Record { Expr::Record(Collection::with_items(output))
fields: output,
final_comments: &[],
}
} else { } else {
debug_assert_eq!(sorted_fields.len(), field_layouts.len()); debug_assert_eq!(sorted_fields.len(), field_layouts.len());
@ -658,10 +646,7 @@ fn struct_to_ast<'a>(
let output = output.into_bump_slice(); let output = output.into_bump_slice();
Expr::Record { Expr::Record(Collection::with_items(output))
fields: output,
final_comments: &[],
}
} }
} }
@ -735,10 +720,7 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
region: Region::zero(), region: Region::zero(),
}; };
Expr::Record { Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field])))
fields: arena.alloc([loc_assigned_field]),
final_comments: arena.alloc([]),
}
} }
FlatType::TagUnion(tags, _) if tags.len() == 1 => { FlatType::TagUnion(tags, _) if tags.len() == 1 => {
let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags);
@ -850,10 +832,7 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a>
region: Region::zero(), region: Region::zero(),
}; };
Expr::Record { Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field])))
fields: arena.alloc([loc_assigned_field]),
final_comments: &[],
}
} }
FlatType::TagUnion(tags, _) if tags.len() == 1 => { FlatType::TagUnion(tags, _) if tags.len() == 1 => {
let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags);
@ -972,10 +951,7 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
region: Region::zero(), region: Region::zero(),
}; };
Expr::Record { Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field])))
fields: arena.alloc([loc_assigned_field]),
final_comments: arena.alloc([]),
}
} }
FlatType::TagUnion(tags, _) => { FlatType::TagUnion(tags, _) => {
// This was a single-tag union that got unwrapped at runtime. // This was a single-tag union that got unwrapped at runtime.

View file

@ -218,8 +218,8 @@ pub fn gen_and_eval<'a>(
// Verify the module // Verify the module
if let Err(errors) = env.module.verify() { if let Err(errors) = env.module.verify() {
panic!( panic!(
"Errors defining module: {}\n\nUncomment things nearby to see more details.", "Errors defining module:\n{}\n\nUncomment things nearby to see more details.",
errors errors.to_string()
); );
} }

View file

@ -53,7 +53,14 @@ mod cli_run {
expected_ending: &str, expected_ending: &str,
use_valgrind: bool, use_valgrind: bool,
) { ) {
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat()); let mut all_flags = vec![];
all_flags.extend_from_slice(flags);
if use_valgrind {
all_flags.extend_from_slice(&["--valgrind"]);
}
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], &all_flags[..]].concat());
if !compile_out.stderr.is_empty() { if !compile_out.stderr.is_empty() {
panic!("{}", compile_out.stderr); panic!("{}", compile_out.stderr);
} }
@ -111,8 +118,7 @@ mod cli_run {
} }
valgrind_out valgrind_out
} else { } else if let Some(input_file) = input_file {
if let Some(input_file) = input_file {
run_cmd( run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(), file.with_file_name(executable_filename).to_str().unwrap(),
stdin, stdin,
@ -124,7 +130,6 @@ mod cli_run {
stdin, stdin,
&[], &[],
) )
}
}; };
if !&out.stdout.ends_with(expected_ending) { if !&out.stdout.ends_with(expected_ending) {
panic!( panic!(
@ -796,7 +801,7 @@ fn read_wasi_stdout(wasi_env: wasmer_wasi::WasiEnv) -> String {
let mut buf = String::new(); let mut buf = String::new();
stdout.read_to_string(&mut buf).unwrap(); stdout.read_to_string(&mut buf).unwrap();
return buf; buf
} }
_ => todo!(), _ => todo!(),
} }

61
cli_utils/Cargo.lock generated
View file

@ -4,12 +4,12 @@ version = 3
[[package]] [[package]]
name = "ab_glyph" name = "ab_glyph"
version = "0.2.11" version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af0ac006645f86f20f6c6fa4dcaef920bf803df819123626f9440e35835e7d80" checksum = "20b228f2c198f98d4337ceb560333fb12cbb2f4948a953bf8c57d09deb219603"
dependencies = [ dependencies = [
"ab_glyph_rasterizer", "ab_glyph_rasterizer",
"owned_ttf_parser 0.12.1", "owned_ttf_parser 0.13.2",
] ]
[[package]] [[package]]
@ -298,9 +298,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.71" version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -1121,9 +1121,9 @@ dependencies = [
[[package]] [[package]]
name = "glyph_brush" name = "glyph_brush"
version = "0.7.2" version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e3f00b8574a76fb6c50890c48da03946ca50e4372a2778737922666a2238221" checksum = "21932fbf719272848eec4583740d978203c6e7da4c4e203358f5b95946c97409"
dependencies = [ dependencies = [
"glyph_brush_draw_cache", "glyph_brush_draw_cache",
"glyph_brush_layout", "glyph_brush_layout",
@ -1135,9 +1135,9 @@ dependencies = [
[[package]] [[package]]
name = "glyph_brush_draw_cache" name = "glyph_brush_draw_cache"
version = "0.1.4" version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac2c82074cafb68b9e459c50c655f7eedcb92d6ee7166813802934bc6fc29fa3" checksum = "6010675390f6889e09a21e2c8b575b3ee25667ea8237a8d59423f73cb8c28610"
dependencies = [ dependencies = [
"ab_glyph", "ab_glyph",
"crossbeam-channel", "crossbeam-channel",
@ -1409,9 +1409,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.106" version = "0.2.107"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673" checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -1912,11 +1912,11 @@ dependencies = [
[[package]] [[package]]
name = "owned_ttf_parser" name = "owned_ttf_parser"
version = "0.12.1" version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60ac8dda2e5cc09bf6480e3b3feff9783db251710c922ae9369a429c51efdeb0" checksum = "65ee3f72636e6f164cc41c9f9057f4e58c4e13507699ea7f5e5242b64b8198ee"
dependencies = [ dependencies = [
"ttf-parser 0.12.3", "ttf-parser 0.13.2",
] ]
[[package]] [[package]]
@ -2398,8 +2398,6 @@ name = "roc_build"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"im",
"im-rc",
"inkwell 0.1.0", "inkwell 0.1.0",
"libloading 0.7.1", "libloading 0.7.1",
"roc_builtins", "roc_builtins",
@ -2440,8 +2438,6 @@ name = "roc_can"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"im",
"im-rc",
"roc_builtins", "roc_builtins",
"roc_collections", "roc_collections",
"roc_module", "roc_module",
@ -2459,8 +2455,6 @@ dependencies = [
"bumpalo", "bumpalo",
"clap 3.0.0-beta.5", "clap 3.0.0-beta.5",
"const_format", "const_format",
"im",
"im-rc",
"inkwell 0.1.0", "inkwell 0.1.0",
"libloading 0.7.1", "libloading 0.7.1",
"mimalloc", "mimalloc",
@ -2562,8 +2556,6 @@ dependencies = [
"fs_extra", "fs_extra",
"futures", "futures",
"glyph_brush", "glyph_brush",
"im",
"im-rc",
"libc", "libc",
"log", "log",
"nonempty", "nonempty",
@ -2599,8 +2591,6 @@ name = "roc_fmt"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"im",
"im-rc",
"roc_collections", "roc_collections",
"roc_module", "roc_module",
"roc_parse", "roc_parse",
@ -2612,8 +2602,6 @@ name = "roc_gen_dev"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"im",
"im-rc",
"object 0.26.2", "object 0.26.2",
"roc_builtins", "roc_builtins",
"roc_collections", "roc_collections",
@ -2632,20 +2620,13 @@ name = "roc_gen_llvm"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"im",
"im-rc",
"inkwell 0.1.0", "inkwell 0.1.0",
"morphic_lib", "morphic_lib",
"roc_builtins", "roc_builtins",
"roc_collections", "roc_collections",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_problem",
"roc_region",
"roc_solve",
"roc_std", "roc_std",
"roc_types",
"roc_unify",
"target-lexicon", "target-lexicon",
] ]
@ -2654,6 +2635,7 @@ name = "roc_gen_wasm"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"roc_builtins",
"roc_collections", "roc_collections",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
@ -2726,7 +2708,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"hashbrown 0.11.2", "hashbrown 0.11.2",
"linked-hash-map",
"morphic_lib", "morphic_lib",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
@ -2737,7 +2718,7 @@ dependencies = [
"roc_std", "roc_std",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"ven_ena", "static_assertions",
"ven_graph", "ven_graph",
"ven_pretty", "ven_pretty",
] ]
@ -2773,8 +2754,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"distance", "distance",
"im",
"im-rc",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_module", "roc_module",
@ -2983,9 +2962,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.69" version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e466864e431129c7e0d3476b92f20458e5879919a0596c6472738d9fa2d342f8" checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -3294,9 +3273,9 @@ checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc"
[[package]] [[package]]
name = "ttf-parser" name = "ttf-parser"
version = "0.12.3" version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6" checksum = "3e835d06ed78a500d3d0e431a20c18ff5544b3f6e11376e834370cfd35e8948e"
[[package]] [[package]]
name = "twox-hash" name = "twox-hash"

View file

@ -22,7 +22,7 @@ roc_load = { path = "../load" }
roc_gen_llvm = { path = "../gen_llvm", optional = true } roc_gen_llvm = { path = "../gen_llvm", optional = true }
roc_gen_wasm = { path = "../gen_wasm", optional = true } roc_gen_wasm = { path = "../gen_wasm", optional = true }
roc_gen_dev = { path = "../gen_dev", default-features = false } roc_gen_dev = { path = "../gen_dev", default-features = false }
roc_reporting = { path = "../reporting" } roc_reporting = { path = "../../reporting" }
roc_std = { path = "../../roc_std" } roc_std = { path = "../../roc_std" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }
libloading = "0.7.1" libloading = "0.7.1"

View file

@ -86,6 +86,7 @@ pub fn build_zig_host_native(
target: &str, target: &str,
opt_level: OptLevel, opt_level: OptLevel,
shared_lib_path: Option<&Path>, shared_lib_path: Option<&Path>,
_target_valgrind: bool,
) -> Output { ) -> Output {
let mut command = Command::new("zig"); let mut command = Command::new("zig");
command command
@ -118,6 +119,15 @@ pub fn build_zig_host_native(
"-target", "-target",
target, target,
]); ]);
// use single threaded testing for cli_run and enable this code if valgrind fails with unhandled instruction bytes, see #1963.
/*if target_valgrind {
command.args(&[
"-mcpu",
"x86_64"
]);
}*/
if matches!(opt_level, OptLevel::Optimize) { if matches!(opt_level, OptLevel::Optimize) {
command.args(&["-O", "ReleaseSafe"]); command.args(&["-O", "ReleaseSafe"]);
} }
@ -135,6 +145,8 @@ pub fn build_zig_host_native(
_target: &str, _target: &str,
opt_level: OptLevel, opt_level: OptLevel,
shared_lib_path: Option<&Path>, shared_lib_path: Option<&Path>,
// For compatibility with the non-macOS def above. Keep these in sync.
_target_valgrind: bool,
) -> Output { ) -> Output {
use serde_json::Value; use serde_json::Value;
@ -339,6 +351,7 @@ pub fn rebuild_host(
target: &Triple, target: &Triple,
host_input_path: &Path, host_input_path: &Path,
shared_lib_path: Option<&Path>, shared_lib_path: Option<&Path>,
target_valgrind: bool,
) { ) {
let c_host_src = host_input_path.with_file_name("host.c"); let c_host_src = host_input_path.with_file_name("host.c");
let c_host_dest = host_input_path.with_file_name("c_host.o"); let c_host_dest = host_input_path.with_file_name("c_host.o");
@ -394,6 +407,7 @@ pub fn rebuild_host(
"native", "native",
opt_level, opt_level,
shared_lib_path, shared_lib_path,
target_valgrind,
) )
} }
Architecture::X86_32(_) => { Architecture::X86_32(_) => {
@ -407,6 +421,7 @@ pub fn rebuild_host(
"i386-linux-musl", "i386-linux-musl",
opt_level, opt_level,
shared_lib_path, shared_lib_path,
target_valgrind,
) )
} }
@ -421,6 +436,7 @@ pub fn rebuild_host(
target_triple_str(target), target_triple_str(target),
opt_level, opt_level,
shared_lib_path, shared_lib_path,
target_valgrind,
) )
} }
_ => panic!("Unsupported architecture {:?}", target.architecture), _ => panic!("Unsupported architecture {:?}", target.architecture),
@ -863,6 +879,7 @@ fn get_macos_version() -> String {
.expect("Failed to convert output of command 'sw_vers -productVersion' into a utf8 string"); .expect("Failed to convert output of command 'sw_vers -productVersion' into a utf8 string");
full_version_string full_version_string
.trim_end()
.split('.') .split('.')
.take(2) .take(2)
.collect::<Vec<&str>>() .collect::<Vec<&str>>()

View file

@ -862,94 +862,44 @@ pub fn listSwap(
return newList; return newList;
} }
pub fn listTakeFirst( pub fn listSublist(
list: RocList, list: RocList,
alignment: u32, alignment: u32,
element_width: usize, element_width: usize,
take_count: usize, start: usize,
) callconv(.C) RocList { len: usize,
if (list.bytes) |source_ptr| {
if (take_count == 0) {
return RocList.empty();
}
const in_len = list.len();
const out_len = std.math.min(take_count, in_len);
const output = RocList.allocate(alignment, out_len, element_width);
const target_ptr = output.bytes orelse unreachable;
@memcpy(target_ptr, source_ptr, out_len * element_width);
utils.decref(list.bytes, in_len * element_width, alignment);
return output;
} else {
return RocList.empty();
}
}
pub fn listTakeLast(
list: RocList,
alignment: u32,
element_width: usize,
take_count: usize,
dec: Dec, dec: Dec,
) callconv(.C) RocList { ) callconv(.C) RocList {
if (take_count == 0) { if (len == 0) {
return RocList.empty(); return RocList.empty();
} }
if (list.bytes) |source_ptr| { if (list.bytes) |source_ptr| {
const size = list.len(); const size = list.len();
if (size <= take_count) {
return list; if (start >= size) {
}
const drop_count = size - take_count;
return listDrop(
list,
alignment,
element_width,
drop_count,
dec,
);
} else {
return RocList.empty(); return RocList.empty();
} }
}
pub fn listDrop( const keep_len = std.math.min(len, size - start);
list: RocList, const drop_len = std.math.max(start, 0);
alignment: u32,
element_width: usize,
drop_count: usize,
dec: Dec,
) callconv(.C) RocList {
if (list.bytes) |source_ptr| {
const size = list.len();
const keep_count = size - drop_count;
var i: usize = 0; var i: usize = 0;
const iterations = std.math.min(drop_count, size); while (i < drop_len) : (i += 1) {
while (i < iterations) : (i += 1) {
const element = source_ptr + i * element_width; const element = source_ptr + i * element_width;
dec(element); dec(element);
} }
if (drop_count >= size) { const output = RocList.allocate(alignment, keep_len, element_width);
return RocList.empty();
}
const output = RocList.allocate(alignment, keep_count, element_width);
const target_ptr = output.bytes orelse unreachable; const target_ptr = output.bytes orelse unreachable;
@memcpy(target_ptr, source_ptr + drop_count * element_width, keep_count * element_width); @memcpy(target_ptr, source_ptr + start * element_width, keep_len * element_width);
utils.decref(list.bytes, size * element_width, alignment); utils.decref(list.bytes, size * element_width, alignment);
return output; return output;
} else {
return RocList.empty();
} }
return RocList.empty();
} }
pub fn listDropAt( pub fn listDropAt(
@ -1162,6 +1112,36 @@ pub fn listAny(
return false; return false;
} }
pub fn listAll(
list: RocList,
caller: Caller1,
data: Opaque,
inc_n_data: IncN,
data_is_owned: bool,
element_width: usize,
) callconv(.C) bool {
if (list.bytes) |source_ptr| {
const size = list.len();
if (data_is_owned) {
inc_n_data(data, size);
}
var i: usize = 0;
while (i < size) : (i += 1) {
var satisfied = false;
const element = source_ptr + i * element_width;
caller(data, element, @ptrCast(?[*]u8, &satisfied));
if (!satisfied) {
return false;
}
}
return true;
}
return true;
}
// SWAP ELEMENTS // SWAP ELEMENTS
inline fn swapHelp(width: usize, temporary: [*]u8, ptr1: [*]u8, ptr2: [*]u8) void { inline fn swapHelp(width: usize, temporary: [*]u8, ptr1: [*]u8, ptr2: [*]u8) void {

View file

@ -45,14 +45,13 @@ comptime {
exportListFn(list.listReverse, "reverse"); exportListFn(list.listReverse, "reverse");
exportListFn(list.listSortWith, "sort_with"); exportListFn(list.listSortWith, "sort_with");
exportListFn(list.listConcat, "concat"); exportListFn(list.listConcat, "concat");
exportListFn(list.listTakeFirst, "take_first"); exportListFn(list.listSublist, "sublist");
exportListFn(list.listTakeLast, "take_last");
exportListFn(list.listDrop, "drop");
exportListFn(list.listDropAt, "drop_at"); exportListFn(list.listDropAt, "drop_at");
exportListFn(list.listSet, "set"); exportListFn(list.listSet, "set");
exportListFn(list.listSetInPlace, "set_in_place"); exportListFn(list.listSetInPlace, "set_in_place");
exportListFn(list.listSwap, "swap"); exportListFn(list.listSwap, "swap");
exportListFn(list.listAny, "any"); exportListFn(list.listAny, "any");
exportListFn(list.listAll, "all");
exportListFn(list.listFindUnsafe, "find_unsafe"); exportListFn(list.listFindUnsafe, "find_unsafe");
} }
@ -128,6 +127,7 @@ comptime {
exportStrFn(str.repeat, "repeat"); exportStrFn(str.repeat, "repeat");
exportStrFn(str.strTrim, "trim"); exportStrFn(str.strTrim, "trim");
exportStrFn(str.strTrimLeft, "trim_left"); exportStrFn(str.strTrimLeft, "trim_left");
exportStrFn(str.strTrimRight, "trim_right");
inline for (INTEGERS) |T| { inline for (INTEGERS) |T| {
str.exportFromInt(T, ROC_BUILTINS ++ "." ++ STR ++ ".from_int."); str.exportFromInt(T, ROC_BUILTINS ++ "." ++ STR ++ ".from_int.");

View file

@ -1584,6 +1584,41 @@ pub fn strTrimLeft(string: RocStr) callconv(.C) RocStr {
return RocStr.empty(); return RocStr.empty();
} }
pub fn strTrimRight(string: RocStr) callconv(.C) RocStr {
if (string.str_bytes) |bytes_ptr| {
const trailing_bytes = countTrailingWhitespaceBytes(string);
const original_len = string.len();
if (original_len == trailing_bytes) {
string.deinit();
return RocStr.empty();
}
const new_len = original_len - trailing_bytes;
const small_or_shared = new_len <= SMALL_STR_MAX_LENGTH or !string.isRefcountOne();
if (small_or_shared) {
return RocStr.init(string.asU8ptr(), new_len);
}
// nonempty, large, and unique:
var i: usize = 0;
while (i < new_len) : (i += 1) {
const dest = bytes_ptr + i;
const source = dest;
@memcpy(dest, source, 1);
}
var new_string = string;
new_string.str_len = new_len;
return new_string;
}
return RocStr.empty();
}
fn countLeadingWhitespaceBytes(string: RocStr) usize { fn countLeadingWhitespaceBytes(string: RocStr) usize {
var byte_count: usize = 0; var byte_count: usize = 0;
@ -1820,6 +1855,77 @@ test "strTrimLeft: small to small" {
try expect(trimmed.isSmallStr()); try expect(trimmed.isSmallStr());
} }
test "strTrimRight: empty" {
const trimmedEmpty = strTrimRight(RocStr.empty());
try expect(trimmedEmpty.eq(RocStr.empty()));
}
test "strTrimRight: blank" {
const original_bytes = " ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.deinit();
const trimmed = strTrimRight(original);
try expect(trimmed.eq(RocStr.empty()));
}
test "strTrimRight: large to large" {
const original_bytes = " hello giant world ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.deinit();
try expect(!original.isSmallStr());
const expected_bytes = " hello giant world";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.deinit();
try expect(!expected.isSmallStr());
const trimmed = strTrimRight(original);
try expect(trimmed.eq(expected));
}
test "strTrimRight: large to small" {
const original_bytes = " hello world ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.deinit();
try expect(!original.isSmallStr());
const expected_bytes = " hello world";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.deinit();
try expect(expected.isSmallStr());
const trimmed = strTrimRight(original);
try expect(trimmed.eq(expected));
try expect(trimmed.isSmallStr());
}
test "strTrimRight: small to small" {
const original_bytes = " hello world ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.deinit();
try expect(original.isSmallStr());
const expected_bytes = " hello world";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.deinit();
try expect(expected.isSmallStr());
const trimmed = strTrimRight(original);
try expect(trimmed.eq(expected));
try expect(trimmed.isSmallStr());
}
test "ReverseUtf8View: hello world" { test "ReverseUtf8View: hello world" {
const original_bytes = "hello world"; const original_bytes = "hello world";
const expected_bytes = "dlrow olleh"; const expected_bytes = "dlrow olleh";

View file

@ -691,6 +691,10 @@ all : List elem, (elem -> Bool) -> Bool
## any of the elements satisfy it. ## any of the elements satisfy it.
any : List elem, (elem -> Bool) -> Bool 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. ## Returns the first element of the list satisfying a predicate function.
## If no satisfying element is found, an `Err NotFound` is returned. ## If no satisfying element is found, an `Err NotFound` is returned.
find : List elem, (elem -> Bool) -> Result elem [ NotFound ]* find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*

View file

@ -149,6 +149,7 @@ pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range";
pub const STR_REPEAT: &str = "roc_builtins.str.repeat"; pub const STR_REPEAT: &str = "roc_builtins.str.repeat";
pub const STR_TRIM: &str = "roc_builtins.str.trim"; pub const STR_TRIM: &str = "roc_builtins.str.trim";
pub const STR_TRIM_LEFT: &str = "roc_builtins.str.trim_left"; pub const STR_TRIM_LEFT: &str = "roc_builtins.str.trim_left";
pub const STR_TRIM_RIGHT: &str = "roc_builtins.str.trim_right";
pub const DICT_HASH: &str = "roc_builtins.dict.hash"; pub const DICT_HASH: &str = "roc_builtins.dict.hash";
pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str"; pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str";
@ -183,9 +184,7 @@ pub const LIST_CONTAINS: &str = "roc_builtins.list.contains";
pub const LIST_REPEAT: &str = "roc_builtins.list.repeat"; pub const LIST_REPEAT: &str = "roc_builtins.list.repeat";
pub const LIST_APPEND: &str = "roc_builtins.list.append"; pub const LIST_APPEND: &str = "roc_builtins.list.append";
pub const LIST_PREPEND: &str = "roc_builtins.list.prepend"; pub const LIST_PREPEND: &str = "roc_builtins.list.prepend";
pub const LIST_TAKE_FIRST: &str = "roc_builtins.list.take_first"; pub const LIST_SUBLIST: &str = "roc_builtins.list.sublist";
pub const LIST_TAKE_LAST: &str = "roc_builtins.list.take_last";
pub const LIST_DROP: &str = "roc_builtins.list.drop";
pub const LIST_DROP_AT: &str = "roc_builtins.list.drop_at"; pub const LIST_DROP_AT: &str = "roc_builtins.list.drop_at";
pub const LIST_SWAP: &str = "roc_builtins.list.swap"; pub const LIST_SWAP: &str = "roc_builtins.list.swap";
pub const LIST_SINGLE: &str = "roc_builtins.list.single"; pub const LIST_SINGLE: &str = "roc_builtins.list.single";
@ -197,6 +196,7 @@ pub const LIST_CONCAT: &str = "roc_builtins.list.concat";
pub const LIST_SET: &str = "roc_builtins.list.set"; pub const LIST_SET: &str = "roc_builtins.list.set";
pub const LIST_SET_IN_PLACE: &str = "roc_builtins.list.set_in_place"; pub const LIST_SET_IN_PLACE: &str = "roc_builtins.list.set_in_place";
pub const LIST_ANY: &str = "roc_builtins.list.any"; 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_FIND_UNSAFE: &str = "roc_builtins.list.find_unsafe";
pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64"; pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64";

View file

@ -639,6 +639,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(str_type()) Box::new(str_type())
); );
// trimRight : Str -> Str
add_top_level_function_type!(
Symbol::STR_TRIM_RIGHT,
vec![str_type()],
Box::new(str_type())
);
// trim : Str -> Str // trim : Str -> Str
add_top_level_function_type!(Symbol::STR_TRIM, vec![str_type()], Box::new(str_type())); add_top_level_function_type!(Symbol::STR_TRIM, vec![str_type()], Box::new(str_type()));
@ -1008,6 +1015,25 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(list_type(flex(TVAR1))), Box::new(list_type(flex(TVAR1))),
); );
// split : List elem, Nat -> { before: List elem, others: List elem }
add_top_level_function_type!(
Symbol::LIST_SPLIT,
vec![list_type(flex(TVAR1)), nat_type(),],
Box::new(SolvedType::Record {
fields: vec![
(
"before".into(),
RecordField::Required(list_type(flex(TVAR1)))
),
(
"others".into(),
RecordField::Required(list_type(flex(TVAR1)))
),
],
ext: Box::new(SolvedType::EmptyRecord),
},),
);
// drop : List elem, Nat -> List elem // drop : List elem, Nat -> List elem
add_top_level_function_type!( add_top_level_function_type!(
Symbol::LIST_DROP, Symbol::LIST_DROP,
@ -1102,6 +1128,16 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(bool_type()), Box::new(bool_type()),
); );
// all: List elem, (elem -> Bool) -> Bool
add_top_level_function_type!(
Symbol::LIST_ALL,
vec![
list_type(flex(TVAR1)),
closure(vec![flex(TVAR1)], TVAR2, Box::new(bool_type())),
],
Box::new(bool_type()),
);
// sortWith : List a, (a, a -> Ordering) -> List a // sortWith : List a, (a, a -> Ordering) -> List a
add_top_level_function_type!( add_top_level_function_type!(
Symbol::LIST_SORT_WITH, Symbol::LIST_SORT_WITH,
@ -1133,6 +1169,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
) )
} }
// intersperse : List elem, elem -> List elem
add_top_level_function_type!(
Symbol::LIST_INTERSPERSE,
vec![list_type(flex(TVAR1)), flex(TVAR1)],
Box::new(list_type(flex(TVAR1))),
);
// Dict module // Dict module
// len : Dict * * -> Nat // len : Dict * * -> Nat
@ -1381,6 +1424,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(bool_type()), Box::new(bool_type()),
); );
// isErr : Result * * -> bool
add_top_level_function_type!(
Symbol::RESULT_IS_ERR,
vec![result_type(flex(TVAR1), flex(TVAR3))],
Box::new(bool_type()),
);
types types
} }

View file

@ -417,7 +417,7 @@ fn can_annotation_help(
TagUnion { tags, ext, .. } => { TagUnion { tags, ext, .. } => {
let tag_types = can_tags( let tag_types = can_tags(
env, env,
tags, tags.items,
region, region,
scope, scope,
var_store, var_store,
@ -459,6 +459,9 @@ fn can_annotation_help(
Type::Variable(var) Type::Variable(var)
} }
Inferred => {
unimplemented!();
}
Malformed(string) => { Malformed(string) => {
malformed(env, region, string); malformed(env, region, string);

View file

@ -1,11 +1,11 @@
use crate::def::Def; use crate::def::Def;
use crate::expr::{ClosureData, Expr::*}; use crate::expr::{ClosureData, Expr::*};
use crate::expr::{Expr, Recursive, WhenBranch}; use crate::expr::{Expr, Field, Recursive, WhenBranch};
use crate::pattern::Pattern; use crate::pattern::Pattern;
use roc_collections::all::SendMap; use roc_collections::all::SendMap;
use roc_module::ident::TagName; use roc_module::called_via::CalledVia;
use roc_module::ident::{Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::operator::CalledVia;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
@ -69,6 +69,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
STR_REPEAT => str_repeat, STR_REPEAT => str_repeat,
STR_TRIM => str_trim, STR_TRIM => str_trim,
STR_TRIM_LEFT => str_trim_left, STR_TRIM_LEFT => str_trim_left,
STR_TRIM_RIGHT => str_trim_right,
LIST_LEN => list_len, LIST_LEN => list_len,
LIST_GET => list_get, LIST_GET => list_get,
LIST_SET => list_set, LIST_SET => list_set,
@ -95,6 +96,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_TAKE_FIRST => list_take_first, LIST_TAKE_FIRST => list_take_first,
LIST_TAKE_LAST => list_take_last, LIST_TAKE_LAST => list_take_last,
LIST_SUBLIST => list_sublist, LIST_SUBLIST => list_sublist,
LIST_SPLIT => list_split,
LIST_INTERSPERSE => list_intersperse,
LIST_DROP => list_drop, LIST_DROP => list_drop,
LIST_DROP_AT => list_drop_at, LIST_DROP_AT => list_drop_at,
LIST_DROP_FIRST => list_drop_first, LIST_DROP_FIRST => list_drop_first,
@ -110,6 +113,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_WALK_UNTIL => list_walk_until, LIST_WALK_UNTIL => list_walk_until,
LIST_SORT_WITH => list_sort_with, LIST_SORT_WITH => list_sort_with,
LIST_ANY => list_any, LIST_ANY => list_any,
LIST_ALL => list_all,
LIST_FIND => list_find, LIST_FIND => list_find,
DICT_LEN => dict_len, DICT_LEN => dict_len,
DICT_EMPTY => dict_empty, DICT_EMPTY => dict_empty,
@ -193,6 +197,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
RESULT_AFTER => result_after, RESULT_AFTER => result_after,
RESULT_WITH_DEFAULT => result_with_default, RESULT_WITH_DEFAULT => result_with_default,
RESULT_IS_OK => result_is_ok, RESULT_IS_OK => result_is_ok,
RESULT_IS_ERR => result_is_err,
} }
} }
@ -1294,6 +1299,11 @@ fn str_trim_left(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::StrTrimLeft, var_store) lowlevel_1(symbol, LowLevel::StrTrimLeft, var_store)
} }
/// Str.trimRight : Str -> Str
fn str_trim_right(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::StrTrimRight, var_store)
}
/// Str.repeat : Str, Nat -> Str /// Str.repeat : Str, Nat -> Str
fn str_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def { fn str_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
let str_var = var_store.fresh(); let str_var = var_store.fresh();
@ -2020,11 +2030,13 @@ fn list_swap(symbol: Symbol, var_store: &mut VarStore) -> Def {
fn list_take_first(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_take_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
let len_var = var_store.fresh(); let len_var = var_store.fresh();
let zero = int(len_var, Variable::NATURAL, 0);
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::ListTakeFirst, op: LowLevel::ListSublist,
args: vec![ args: vec![
(list_var, Var(Symbol::ARG_1)), (list_var, Var(Symbol::ARG_1)),
(len_var, zero),
(len_var, Var(Symbol::ARG_2)), (len_var, Var(Symbol::ARG_2)),
], ],
ret_var: list_var, ret_var: list_var,
@ -2044,10 +2056,40 @@ fn list_take_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
let len_var = var_store.fresh(); let len_var = var_store.fresh();
let zero = int(len_var, Variable::NATURAL, 0);
let bool_var = var_store.fresh();
let get_list_len = RunLowLevel {
op: LowLevel::ListLen,
args: vec![(list_var, Var(Symbol::ARG_1))],
ret_var: len_var,
};
let get_sub = RunLowLevel {
op: LowLevel::NumSubWrap,
args: vec![(len_var, get_list_len), (len_var, Var(Symbol::ARG_2))],
ret_var: len_var,
};
let get_start = If {
cond_var: bool_var,
branch_var: len_var,
branches: vec![(
no_region(RunLowLevel {
op: LowLevel::NumGt,
args: vec![(len_var, get_sub.clone()), (len_var, zero.clone())],
ret_var: bool_var,
}),
no_region(get_sub),
)],
final_else: Box::new(no_region(zero)),
};
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::ListTakeLast, op: LowLevel::ListSublist,
args: vec![ args: vec![
(list_var, Var(Symbol::ARG_1)), (list_var, Var(Symbol::ARG_1)),
(len_var, get_start),
(len_var, Var(Symbol::ARG_2)), (len_var, Var(Symbol::ARG_2)),
], ],
ret_var: list_var, ret_var: list_var,
@ -2089,15 +2131,13 @@ fn list_sublist(symbol: Symbol, var_store: &mut VarStore) -> Def {
field: "len".into(), field: "len".into(),
}; };
let body_drop = RunLowLevel { let body = RunLowLevel {
op: LowLevel::ListDrop, op: LowLevel::ListSublist,
args: vec![(list_var, Var(sym_list)), (start_var, get_start)], args: vec![
ret_var: list_var, (list_var, Var(sym_list)),
}; (start_var, get_start),
(len_var, get_len),
let body_take = RunLowLevel { ],
op: LowLevel::ListTakeFirst,
args: vec![(list_var, body_drop), (len_var, get_len)],
ret_var: list_var, ret_var: list_var,
}; };
@ -2105,28 +2145,229 @@ fn list_sublist(symbol: Symbol, var_store: &mut VarStore) -> Def {
symbol, symbol,
vec![(list_var, sym_list), (rec_var, sym_rec)], vec![(list_var, sym_list), (rec_var, sym_rec)],
var_store, var_store,
body_take, body,
list_var, list_var,
) )
} }
/// List.intersperse : List elem, elem -> List elem
fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let sep_var = var_store.fresh();
let list_sym = Symbol::ARG_1;
let sep_sym = Symbol::ARG_2;
let clos_var = var_store.fresh();
let clos_acc_var = var_store.fresh();
let clos_sym = Symbol::LIST_INTERSPERSE_CLOS;
let clos_acc_sym = Symbol::ARG_3;
let clos_elem_sym = Symbol::ARG_4;
let int_var = var_store.fresh();
let zero = int(int_var, Variable::NATURAL, 0);
// \acc, elem -> acc |> List.append sep |> List.append elem
let clos = Closure(ClosureData {
function_type: clos_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: clos_acc_var,
name: clos_sym,
recursive: Recursive::NotRecursive,
captured_symbols: vec![(sep_sym, sep_var)],
arguments: vec![
(clos_acc_var, no_region(Pattern::Identifier(clos_acc_sym))),
(sep_var, no_region(Pattern::Identifier(clos_elem_sym))),
],
loc_body: {
let append_sep = RunLowLevel {
op: LowLevel::ListAppend,
args: vec![(clos_acc_var, Var(clos_acc_sym)), (sep_var, Var(sep_sym))],
ret_var: clos_acc_var,
};
Box::new(no_region(RunLowLevel {
op: LowLevel::ListAppend,
args: vec![(clos_acc_var, append_sep), (sep_var, Var(clos_elem_sym))],
ret_var: clos_acc_var,
}))
},
});
// List.walk [] l (\acc, elem -> acc |> List.append sep |> List.append elem)
let acc = RunLowLevel {
op: LowLevel::ListWalk,
args: vec![
(list_var, Var(list_sym)),
(
clos_acc_var,
List {
elem_var: sep_var,
loc_elems: vec![],
},
),
(clos_var, clos),
],
ret_var: clos_acc_var,
};
let body = RunLowLevel {
op: LowLevel::ListDropAt,
args: vec![(clos_acc_var, acc), (int_var, zero)],
ret_var: clos_acc_var,
};
defn(
symbol,
vec![(list_var, list_sym), (sep_var, sep_sym)],
var_store,
body,
clos_acc_var,
)
}
/// List.split : List elem, Nat -> { before: List elem, others: List elem }
fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let index_var = var_store.fresh();
let list_sym = Symbol::ARG_1;
let index_sym = Symbol::ARG_2;
let clos_sym = Symbol::LIST_SPLIT_CLOS;
let clos_start_sym = Symbol::ARG_3;
let clos_len_sym = Symbol::ARG_4;
let clos_fun_var = var_store.fresh();
let clos_start_var = var_store.fresh();
let clos_len_var = var_store.fresh();
let clos_ret_var = var_store.fresh();
let ret_var = var_store.fresh();
let zero = int(index_var, Variable::NATURAL, 0);
let clos = Closure(ClosureData {
function_type: clos_fun_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: clos_ret_var,
name: clos_sym,
recursive: Recursive::NotRecursive,
captured_symbols: vec![(list_sym, clos_ret_var)],
arguments: vec![
(
clos_start_var,
no_region(Pattern::Identifier(clos_start_sym)),
),
(clos_len_var, no_region(Pattern::Identifier(clos_len_sym))),
],
loc_body: {
Box::new(no_region(RunLowLevel {
op: LowLevel::ListSublist,
args: vec![
(clos_ret_var, Var(list_sym)),
(clos_start_var, Var(clos_start_sym)),
(clos_len_var, Var(clos_len_sym)),
],
ret_var: clos_ret_var,
}))
},
});
let fun = Box::new((
clos_fun_var,
no_region(clos),
var_store.fresh(),
clos_ret_var,
));
let get_before = Call(
fun.clone(),
vec![
(index_var, no_region(zero)),
(index_var, no_region(Var(index_sym))),
],
CalledVia::Space,
);
let get_list_len = RunLowLevel {
op: LowLevel::ListLen,
args: vec![(list_var, Var(list_sym))],
ret_var: index_var,
};
let get_others_len = RunLowLevel {
op: LowLevel::NumSubWrap,
args: vec![(index_var, get_list_len), (index_var, Var(index_sym))],
ret_var: index_var,
};
let get_others = Call(
fun,
vec![
(index_var, no_region(Var(index_sym))),
(index_var, no_region(get_others_len)),
],
CalledVia::Space,
);
let before = Field {
var: clos_ret_var,
region: Region::zero(),
loc_expr: Box::new(no_region(get_before)),
};
let others = Field {
var: clos_ret_var,
region: Region::zero(),
loc_expr: Box::new(no_region(get_others)),
};
let body = record(
vec![("before".into(), before), ("others".into(), others)],
var_store,
);
defn(
symbol,
vec![(list_var, list_sym), (index_var, index_sym)],
var_store,
body,
ret_var,
)
}
/// List.drop : List elem, Nat -> List elem /// List.drop : List elem, Nat -> List elem
fn list_drop(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_drop(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
let index_var = var_store.fresh(); let len_var = var_store.fresh();
let get_list_len = RunLowLevel {
op: LowLevel::ListLen,
args: vec![(list_var, Var(Symbol::ARG_1))],
ret_var: len_var,
};
let get_len = RunLowLevel {
op: LowLevel::NumSubWrap,
args: vec![(len_var, get_list_len), (len_var, Var(Symbol::ARG_2))],
ret_var: len_var,
};
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::ListDrop, op: LowLevel::ListSublist,
args: vec![ args: vec![
(list_var, Var(Symbol::ARG_1)), (list_var, Var(Symbol::ARG_1)),
(index_var, Var(Symbol::ARG_2)), (len_var, Var(Symbol::ARG_2)),
(len_var, get_len),
], ],
ret_var: list_var, ret_var: list_var,
}; };
defn( defn(
symbol, symbol,
vec![(list_var, Symbol::ARG_1), (index_var, Symbol::ARG_2)], vec![(list_var, Symbol::ARG_1), (len_var, Symbol::ARG_2)],
var_store, var_store,
body, body,
list_var, list_var,
@ -2805,6 +3046,11 @@ fn list_any(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListAny, var_store) lowlevel_2(symbol, LowLevel::ListAny, var_store)
} }
/// List.all: List elem, (elem -> Bool) -> Bool
fn list_all(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListAll, var_store)
}
/// List.find : List elem, (elem -> Bool) -> Result elem [ NotFound ]* /// List.find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*
fn list_find(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_find(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list = Symbol::ARG_1; let list = Symbol::ARG_1;
@ -3992,6 +4238,83 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
let ret_var = var_store.fresh();
let result_var = var_store.fresh();
let mut branches = vec![];
{
// ok branch
let tag_name = TagName::Global("Ok".into());
let pattern = Pattern::AppliedTag {
whole_var: result_var,
ext_var: var_store.fresh(),
tag_name,
arguments: vec![(var_store.fresh(), no_region(Pattern::Underscore))],
};
let false_expr = Tag {
variant_var: var_store.fresh(),
ext_var: var_store.fresh(),
name: TagName::Global("False".into()),
arguments: vec![],
};
let branch = WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(false_expr),
guard: None,
};
branches.push(branch);
}
{
// err branch
let tag_name = TagName::Global("Err".into());
let pattern = Pattern::AppliedTag {
whole_var: result_var,
ext_var: var_store.fresh(),
tag_name,
arguments: vec![(var_store.fresh(), no_region(Pattern::Underscore))],
};
let true_expr = Tag {
variant_var: var_store.fresh(),
ext_var: var_store.fresh(),
name: TagName::Global("True".into()),
arguments: vec![],
};
let branch = WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(true_expr),
guard: None,
};
branches.push(branch);
}
let body = When {
cond_var: result_var,
expr_var: ret_var,
region: Region::zero(),
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
branches,
};
defn(
symbol,
vec![(result_var, Symbol::ARG_1)],
var_store,
body,
ret_var,
)
}
fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def { fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def {
let ret_var = var_store.fresh(); let ret_var = var_store.fresh();
let result_var = var_store.fresh(); let result_var = var_store.fresh();
@ -4182,17 +4505,17 @@ fn tag(name: &'static str, args: Vec<Expr>, var_store: &mut VarStore) -> Expr {
} }
} }
// #[inline(always)] #[inline(always)]
// fn record(fields: Vec<(Lowercase, Field)>, var_store: &mut VarStore) -> Expr { fn record(fields: Vec<(Lowercase, Field)>, var_store: &mut VarStore) -> Expr {
// let mut send_map = SendMap::default(); let mut send_map = SendMap::default();
// for (k, v) in fields { for (k, v) in fields {
// send_map.insert(k, v); send_map.insert(k, v);
// } }
// Expr::Record { Expr::Record {
// record_var: var_store.fresh(), record_var: var_store.fresh(),
// fields: send_map, fields: send_map,
// } }
// } }
#[inline(always)] #[inline(always)]
fn defn( fn defn(

View file

@ -1,6 +1,6 @@
use crate::procedure::References; use crate::procedure::References;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, ModuleName}; use roc_module::ident::{Ident, Lowercase, ModuleName};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
@ -99,11 +99,8 @@ impl<'a> Env<'a> {
)), )),
} }
} else { } else {
match self match self.dep_idents.get(&module_id) {
.dep_idents Some(exposed_ids) => match exposed_ids.get_id(&ident) {
.get(&module_id)
.and_then(|exposed_ids| exposed_ids.get_id(&ident))
{
Some(ident_id) => { Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id); let symbol = Symbol::new(module_id, *ident_id);
@ -111,11 +108,28 @@ impl<'a> Env<'a> {
Ok(symbol) Ok(symbol)
} }
None => Err(RuntimeError::ValueNotExposed { None => {
let exposed_values = exposed_ids
.idents()
.filter(|(_, ident)| {
ident.as_ref().starts_with(|c: char| c.is_lowercase())
})
.map(|(_, ident)| Lowercase::from(ident.as_ref()))
.collect();
Err(RuntimeError::ValueNotExposed {
module_name, module_name,
ident, ident,
region, region,
}), exposed_values,
})
}
},
None => {
panic!(
"Module {} exists, but is not recorded in dep_idents",
module_name
)
}
} }
} }
} }

View file

@ -10,9 +10,9 @@ use crate::pattern::{canonicalize_pattern, Pattern};
use crate::procedure::References; use crate::procedure::References;
use crate::scope::Scope; use crate::scope::Scope;
use roc_collections::all::{ImSet, MutMap, MutSet, SendMap}; use roc_collections::all::{ImSet, MutMap, MutSet, SendMap};
use roc_module::called_via::CalledVia;
use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::operator::CalledVia;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_parse::ast::{self, EscapedChar, StrLiteral}; use roc_parse::ast::{self, EscapedChar, StrLiteral};
use roc_parse::pattern::PatternType::*; use roc_parse::pattern::PatternType::*;
@ -228,14 +228,11 @@ pub fn canonicalize_expr<'a>(
(answer, Output::default()) (answer, Output::default())
} }
ast::Expr::Record { ast::Expr::Record(fields) => {
fields,
final_comments: _,
} => {
if fields.is_empty() { if fields.is_empty() {
(EmptyRecord, Output::default()) (EmptyRecord, Output::default())
} else { } else {
match canonicalize_fields(env, var_store, scope, region, fields) { match canonicalize_fields(env, var_store, scope, region, fields.items) {
Ok((can_fields, output)) => ( Ok((can_fields, output)) => (
Record { Record {
record_var: var_store.fresh(), record_var: var_store.fresh(),
@ -261,12 +258,11 @@ pub fn canonicalize_expr<'a>(
ast::Expr::RecordUpdate { ast::Expr::RecordUpdate {
fields, fields,
update: loc_update, update: loc_update,
final_comments: _,
} => { } => {
let (can_update, update_out) = let (can_update, update_out) =
canonicalize_expr(env, var_store, scope, loc_update.region, &loc_update.value); canonicalize_expr(env, var_store, scope, loc_update.region, &loc_update.value);
if let Var(symbol) = &can_update.value { if let Var(symbol) = &can_update.value {
match canonicalize_fields(env, var_store, scope, region, fields) { match canonicalize_fields(env, var_store, scope, region, fields.items) {
Ok((can_fields, mut output)) => { Ok((can_fields, mut output)) => {
output.references = output.references.union(update_out.references); output.references = output.references.union(update_out.references);
@ -307,9 +303,7 @@ pub fn canonicalize_expr<'a>(
} }
} }
ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal), ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal),
ast::Expr::List { ast::Expr::List(loc_elems) => {
items: loc_elems, ..
} => {
if loc_elems.is_empty() { if loc_elems.is_empty() {
( (
List { List {
@ -1717,7 +1711,7 @@ fn desugar_str_segments(var_store: &mut VarStore, segments: Vec<StrSegment>) ->
(var_store.fresh(), loc_new_expr), (var_store.fresh(), loc_new_expr),
(var_store.fresh(), loc_expr), (var_store.fresh(), loc_expr),
], ],
CalledVia::Space, CalledVia::StringInterpolation,
); );
loc_expr = Located::new(0, 0, 0, 0, expr); loc_expr = Located::new(0, 0, 0, 0, expr);

View file

@ -2,9 +2,9 @@
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_module::called_via::BinOp::Pizza;
use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::ModuleName; use roc_module::ident::ModuleName;
use roc_module::operator::BinOp::Pizza;
use roc_module::operator::{BinOp, CalledVia};
use roc_parse::ast::Expr::{self, *}; use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::{AssignedField, Def, WhenBranch}; use roc_parse::ast::{AssignedField, Def, WhenBranch};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
@ -144,77 +144,46 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
arena.alloc(Located { region, value }) arena.alloc(Located { region, value })
} }
List { List(items) => {
items,
final_comments,
} => {
let mut new_items = Vec::with_capacity_in(items.len(), arena); let mut new_items = Vec::with_capacity_in(items.len(), arena);
for item in items.iter() { for item in items.iter() {
new_items.push(desugar_expr(arena, item)); new_items.push(desugar_expr(arena, item));
} }
let new_items = new_items.into_bump_slice(); let new_items = new_items.into_bump_slice();
let value: Expr<'a> = List { let value: Expr<'a> = List(items.replace_items(new_items));
items: new_items,
final_comments,
};
arena.alloc(Located { arena.alloc(Located {
region: loc_expr.region, region: loc_expr.region,
value, value,
}) })
} }
Record { Record(fields) => arena.alloc(Located {
fields, region: loc_expr.region,
final_comments, value: Record(fields.map_items(arena, |field| {
} => {
let mut new_fields = Vec::with_capacity_in(fields.len(), arena);
for field in fields.iter() {
let value = desugar_field(arena, &field.value); let value = desugar_field(arena, &field.value);
Located {
new_fields.push(Located {
value, value,
region: field.region, region: field.region,
});
} }
})),
}),
let new_fields = new_fields.into_bump_slice(); RecordUpdate { fields, update } => {
arena.alloc(Located {
region: loc_expr.region,
value: Record {
fields: new_fields,
final_comments,
},
})
}
RecordUpdate {
fields,
update,
final_comments,
} => {
// NOTE the `update` field is always a `Var { .. }` and does not need to be desugared // NOTE the `update` field is always a `Var { .. }` and does not need to be desugared
let mut new_fields = Vec::with_capacity_in(fields.len(), arena); let new_fields = fields.map_items(arena, |field| {
for field in fields.iter() {
let value = desugar_field(arena, &field.value); let value = desugar_field(arena, &field.value);
Located {
new_fields.push(Located {
value, value,
region: field.region, region: field.region,
});
} }
});
let new_fields = new_fields.into_bump_slice();
arena.alloc(Located { arena.alloc(Located {
region: loc_expr.region, region: loc_expr.region,
value: RecordUpdate { value: RecordUpdate {
update: *update, update: *update,
fields: new_fields, fields: new_fields,
final_comments,
}, },
}) })
} }
@ -308,7 +277,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
}) })
} }
UnaryOp(loc_arg, loc_op) => { UnaryOp(loc_arg, loc_op) => {
use roc_module::operator::UnaryOp::*; use roc_module::called_via::UnaryOp::*;
let region = loc_op.region; let region = loc_op.region;
let op = loc_op.value; let op = loc_op.value;
@ -506,7 +475,7 @@ fn binop_step<'a>(
op_stack: &mut Vec<Located<BinOp>>, op_stack: &mut Vec<Located<BinOp>>,
next_op: Located<BinOp>, next_op: Located<BinOp>,
) -> Step<'a> { ) -> Step<'a> {
use roc_module::operator::Associativity::*; use roc_module::called_via::Associativity::*;
use std::cmp::Ordering; use std::cmp::Ordering;
match op_stack.pop() { match op_stack.pop() {

View file

@ -246,7 +246,7 @@ pub fn canonicalize_pattern<'a>(
let mut destructs = Vec::with_capacity(patterns.len()); let mut destructs = Vec::with_capacity(patterns.len());
let mut opt_erroneous = None; let mut opt_erroneous = None;
for loc_pattern in *patterns { for loc_pattern in patterns.iter() {
match loc_pattern.value { match loc_pattern.value {
Identifier(label) => { Identifier(label) => {
match scope.introduce( match scope.introduce(

View file

@ -254,7 +254,7 @@ pub fn constrain_expr(
exists(vec![*elem_var], And(constraints)) exists(vec![*elem_var], And(constraints))
} }
} }
Call(boxed, loc_args, _application_style) => { Call(boxed, loc_args, called_via) => {
let (fn_var, loc_fn, closure_var, ret_var) = &**boxed; let (fn_var, loc_fn, closure_var, ret_var) = &**boxed;
// The expression that evaluates to the function being called, e.g. `foo` in // The expression that evaluates to the function being called, e.g. `foo` in
// (foo) bar baz // (foo) bar baz
@ -317,7 +317,7 @@ pub fn constrain_expr(
region, region,
); );
let category = Category::CallResult(opt_symbol); let category = Category::CallResult(opt_symbol, *called_via);
exists( exists(
vars, vars,

View file

@ -1,6 +1,6 @@
use crate::spaces::{fmt_comments_only, fmt_spaces, newline, NewlineAt, INDENT}; use crate::spaces::{fmt_comments_only, fmt_spaces, newline, NewlineAt, INDENT};
use bumpalo::collections::String; use bumpalo::collections::String;
use roc_parse::ast::{AssignedField, Collection, Expr, Tag, TypeAnnotation}; use roc_parse::ast::{AssignedField, Expr, Tag, TypeAnnotation};
use roc_region::all::Located; use roc_region::all::Located;
/// Does an AST node need parens around it? /// Does an AST node need parens around it?
@ -81,9 +81,9 @@ where
} }
macro_rules! format_sequence { macro_rules! format_sequence {
($buf: expr, $indent:expr, $start:expr, $end:expr, $items:expr, $final_comments:expr, $newline:expr, $t:ident) => { ($buf: expr, $indent:expr, $start:expr, $end:expr, $items:expr, $newline:expr, $t:ident) => {
let is_multiline = let is_multiline = $items.iter().any(|item| item.value.is_multiline())
$items.iter().any(|item| item.value.is_multiline()) || !$final_comments.is_empty(); || !$items.final_comments().is_empty();
if is_multiline { if is_multiline {
let braces_indent = $indent + INDENT; let braces_indent = $indent + INDENT;
@ -138,7 +138,12 @@ macro_rules! format_sequence {
} }
} }
} }
fmt_comments_only($buf, $final_comments.iter(), NewlineAt::Top, item_indent); fmt_comments_only(
$buf,
$items.final_comments().iter(),
NewlineAt::Top,
item_indent,
);
newline($buf, braces_indent); newline($buf, braces_indent);
$buf.push($end); $buf.push($end);
} else { } else {
@ -175,7 +180,7 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
true true
} }
Wildcard | BoundVariable(_) | Malformed(_) => false, Wildcard | Inferred | BoundVariable(_) | Malformed(_) => false,
Function(args, result) => { Function(args, result) => {
(&result.value).is_multiline() (&result.value).is_multiline()
|| args.iter().any(|loc_arg| (&loc_arg.value).is_multiline()) || args.iter().any(|loc_arg| (&loc_arg.value).is_multiline())
@ -192,11 +197,7 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
fields.items.iter().any(|field| field.value.is_multiline()) fields.items.iter().any(|field| field.value.is_multiline())
} }
TagUnion { TagUnion { tags, ext } => {
tags,
ext,
final_comments: _,
} => {
match ext { match ext {
Some(ann) if ann.value.is_multiline() => return true, Some(ann) if ann.value.is_multiline() => return true,
_ => {} _ => {}
@ -278,37 +279,18 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
} }
BoundVariable(v) => buf.push_str(v), BoundVariable(v) => buf.push_str(v),
Wildcard => buf.push('*'), Wildcard => buf.push('*'),
Inferred => buf.push('_'),
TagUnion { TagUnion { tags, ext } => {
tags, format_sequence!(buf, indent, '[', ']', tags, newlines, Tag);
ext,
final_comments,
} => {
format_sequence!(buf, indent, '[', ']', tags, final_comments, newlines, Tag);
if let Some(loc_ext_ann) = *ext { if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent); loc_ext_ann.value.format(buf, indent);
} }
} }
Record { Record { fields, ext } => {
fields: format_sequence!(buf, indent, '{', '}', fields, newlines, AssignedField);
Collection {
items,
final_comments,
},
ext,
} => {
format_sequence!(
buf,
indent,
'{',
'}',
items,
final_comments,
newlines,
AssignedField
);
if let Some(loc_ext_ann) = *ext { if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent); loc_ext_ann.value.format(buf, indent);

View file

@ -3,9 +3,11 @@ use crate::def::fmt_def;
use crate::pattern::fmt_pattern; use crate::pattern::fmt_pattern;
use crate::spaces::{add_spaces, fmt_comments_only, fmt_spaces, newline, NewlineAt, INDENT}; use crate::spaces::{add_spaces, fmt_comments_only, fmt_spaces, newline, NewlineAt, INDENT};
use bumpalo::collections::String; use bumpalo::collections::String;
use roc_module::operator::{self, BinOp}; use roc_module::called_via::{self, BinOp};
use roc_parse::ast::StrSegment; use roc_parse::ast::StrSegment;
use roc_parse::ast::{AssignedField, Base, CommentOrNewline, Expr, Pattern, WhenBranch}; use roc_parse::ast::{
AssignedField, Base, Collection, CommentOrNewline, Expr, Pattern, WhenBranch,
};
use roc_region::all::Located; use roc_region::all::Located;
impl<'a> Formattable<'a> for Expr<'a> { impl<'a> Formattable<'a> for Expr<'a> {
@ -39,7 +41,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
// These expressions always have newlines // These expressions always have newlines
Defs(_, _) | When(_, _) => true, Defs(_, _) | When(_, _) => true,
List { items, .. } => items.iter().any(|loc_expr| loc_expr.is_multiline()), List(items) => items.iter().any(|loc_expr| loc_expr.is_multiline()),
Str(literal) => { Str(literal) => {
use roc_parse::ast::StrLiteral::*; use roc_parse::ast::StrLiteral::*;
@ -98,7 +100,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
.any(|loc_pattern| loc_pattern.is_multiline()) .any(|loc_pattern| loc_pattern.is_multiline())
} }
Record { fields, .. } => fields.iter().any(|loc_field| loc_field.is_multiline()), Record(fields) => fields.iter().any(|loc_field| loc_field.is_multiline()),
RecordUpdate { fields, .. } => fields.iter().any(|loc_field| loc_field.is_multiline()), RecordUpdate { fields, .. } => fields.iter().any(|loc_field| loc_field.is_multiline()),
} }
} }
@ -250,18 +252,11 @@ impl<'a> Formattable<'a> for Expr<'a> {
buf.push_str(string); buf.push_str(string);
} }
Record { Record(fields) => {
fields, fmt_record(buf, None, *fields, indent);
final_comments,
} => {
fmt_record(buf, None, fields, final_comments, indent);
} }
RecordUpdate { RecordUpdate { update, fields } => {
fields, fmt_record(buf, Some(*update), *fields, indent);
update,
final_comments,
} => {
fmt_record(buf, Some(*update), fields, final_comments, indent);
} }
Closure(loc_patterns, loc_ret) => { Closure(loc_patterns, loc_ret) => {
fmt_closure(buf, loc_patterns, loc_ret, indent); fmt_closure(buf, loc_patterns, loc_ret, indent);
@ -295,19 +290,16 @@ impl<'a> Formattable<'a> for Expr<'a> {
fmt_if(buf, branches, final_else, self.is_multiline(), indent); fmt_if(buf, branches, final_else, self.is_multiline(), indent);
} }
When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent), When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent),
List { List(items) => {
items, fmt_list(buf, *items, indent);
final_comments,
} => {
fmt_list(buf, items, final_comments, indent);
} }
BinOps(lefts, right) => fmt_bin_ops(buf, lefts, right, false, parens, indent), BinOps(lefts, right) => fmt_bin_ops(buf, lefts, right, false, parens, indent),
UnaryOp(sub_expr, unary_op) => { UnaryOp(sub_expr, unary_op) => {
match &unary_op.value { match &unary_op.value {
operator::UnaryOp::Negate => { called_via::UnaryOp::Negate => {
buf.push('-'); buf.push('-');
} }
operator::UnaryOp::Not => { called_via::UnaryOp::Not => {
buf.push('!'); buf.push('!');
} }
} }
@ -362,26 +354,26 @@ fn format_str_segment<'a>(seg: &StrSegment<'a>, buf: &mut String<'a>, indent: u1
fn push_op(buf: &mut String, op: BinOp) { fn push_op(buf: &mut String, op: BinOp) {
match op { match op {
operator::BinOp::Caret => buf.push('^'), called_via::BinOp::Caret => buf.push('^'),
operator::BinOp::Star => buf.push('*'), called_via::BinOp::Star => buf.push('*'),
operator::BinOp::Slash => buf.push('/'), called_via::BinOp::Slash => buf.push('/'),
operator::BinOp::DoubleSlash => buf.push_str("//"), called_via::BinOp::DoubleSlash => buf.push_str("//"),
operator::BinOp::Percent => buf.push('%'), called_via::BinOp::Percent => buf.push('%'),
operator::BinOp::DoublePercent => buf.push_str("%%"), called_via::BinOp::DoublePercent => buf.push_str("%%"),
operator::BinOp::Plus => buf.push('+'), called_via::BinOp::Plus => buf.push('+'),
operator::BinOp::Minus => buf.push('-'), called_via::BinOp::Minus => buf.push('-'),
operator::BinOp::Equals => buf.push_str("=="), called_via::BinOp::Equals => buf.push_str("=="),
operator::BinOp::NotEquals => buf.push_str("!="), called_via::BinOp::NotEquals => buf.push_str("!="),
operator::BinOp::LessThan => buf.push('<'), called_via::BinOp::LessThan => buf.push('<'),
operator::BinOp::GreaterThan => buf.push('>'), called_via::BinOp::GreaterThan => buf.push('>'),
operator::BinOp::LessThanOrEq => buf.push_str("<="), called_via::BinOp::LessThanOrEq => buf.push_str("<="),
operator::BinOp::GreaterThanOrEq => buf.push_str(">="), called_via::BinOp::GreaterThanOrEq => buf.push_str(">="),
operator::BinOp::And => buf.push_str("&&"), called_via::BinOp::And => buf.push_str("&&"),
operator::BinOp::Or => buf.push_str("||"), called_via::BinOp::Or => buf.push_str("||"),
operator::BinOp::Pizza => buf.push_str("|>"), called_via::BinOp::Pizza => buf.push_str("|>"),
operator::BinOp::Assignment => unreachable!(), called_via::BinOp::Assignment => unreachable!(),
operator::BinOp::HasType => unreachable!(), called_via::BinOp::HasType => unreachable!(),
operator::BinOp::Backpassing => unreachable!(), called_via::BinOp::Backpassing => unreachable!(),
} }
} }
@ -416,12 +408,9 @@ fn fmt_bin_ops<'a>(
loc_right_side.format_with_options(buf, apply_needs_parens, Newlines::Yes, indent); loc_right_side.format_with_options(buf, apply_needs_parens, Newlines::Yes, indent);
} }
fn fmt_list<'a>( fn fmt_list<'a>(buf: &mut String<'a>, items: Collection<'a, &'a Located<Expr<'a>>>, indent: u16) {
buf: &mut String<'a>, let loc_items = items.items;
loc_items: &[&Located<Expr<'a>>], let final_comments = items.final_comments();
final_comments: &'a [CommentOrNewline<'a>],
indent: u16,
) {
if loc_items.is_empty() && final_comments.iter().all(|c| c.is_newline()) { if loc_items.is_empty() && final_comments.iter().all(|c| c.is_newline()) {
buf.push_str("[]"); buf.push_str("[]");
} else { } else {
@ -917,10 +906,11 @@ fn fmt_backpassing<'a>(
fn fmt_record<'a>( fn fmt_record<'a>(
buf: &mut String<'a>, buf: &mut String<'a>,
update: Option<&'a Located<Expr<'a>>>, update: Option<&'a Located<Expr<'a>>>,
loc_fields: &[Located<AssignedField<'a, Expr<'a>>>], fields: Collection<'a, Located<AssignedField<'a, Expr<'a>>>>,
final_comments: &'a [CommentOrNewline<'a>],
indent: u16, indent: u16,
) { ) {
let loc_fields = fields.items;
let final_comments = fields.final_comments();
if loc_fields.is_empty() && final_comments.iter().all(|c| c.is_newline()) { if loc_fields.is_empty() && final_comments.iter().all(|c| c.is_newline()) {
buf.push_str("{}"); buf.push_str("{}");
} else { } else {

View file

@ -1,6 +1,6 @@
use crate::spaces::{fmt_spaces, INDENT}; use crate::spaces::{fmt_spaces, INDENT};
use bumpalo::collections::{String, Vec}; use bumpalo::collections::String;
use roc_parse::ast::Module; use roc_parse::ast::{Collection, Module};
use roc_parse::header::{AppHeader, ExposesEntry, ImportsEntry, InterfaceHeader, PlatformHeader}; use roc_parse::header::{AppHeader, ExposesEntry, ImportsEntry, InterfaceHeader, PlatformHeader};
use roc_region::all::Located; use roc_region::all::Located;
@ -64,7 +64,7 @@ pub fn fmt_interface_header<'a>(buf: &mut String<'a>, header: &'a InterfaceHeade
fmt_spaces(buf, header.after_imports.iter(), indent); fmt_spaces(buf, header.after_imports.iter(), indent);
} }
fmt_imports(buf, &header.imports, indent); fmt_imports(buf, header.imports, indent);
} }
pub fn fmt_app_header<'a>(buf: &mut String<'a>, header: &'a AppHeader<'a>) { pub fn fmt_app_header<'a>(buf: &mut String<'a>, header: &'a AppHeader<'a>) {
@ -76,7 +76,7 @@ pub fn fmt_app_header<'a>(buf: &mut String<'a>, header: &'a AppHeader<'a>) {
buf.push_str("imports"); buf.push_str("imports");
fmt_spaces(buf, header.before_imports.iter(), indent); fmt_spaces(buf, header.before_imports.iter(), indent);
fmt_imports(buf, &header.imports, indent); fmt_imports(buf, header.imports, indent);
fmt_spaces(buf, header.after_imports.iter(), indent); fmt_spaces(buf, header.after_imports.iter(), indent);
} }
@ -86,7 +86,7 @@ pub fn fmt_platform_header<'a>(_buf: &mut String<'a>, _header: &'a PlatformHeade
fn fmt_imports<'a>( fn fmt_imports<'a>(
buf: &mut String<'a>, buf: &mut String<'a>,
loc_entries: &'a Vec<'a, Located<ImportsEntry<'a>>>, loc_entries: Collection<'a, Located<ImportsEntry<'a>>>,
indent: u16, indent: u16,
) { ) {
buf.push('['); buf.push('[');
@ -112,7 +112,7 @@ fn fmt_imports<'a>(
fn fmt_exposes<'a>( fn fmt_exposes<'a>(
buf: &mut String<'a>, buf: &mut String<'a>,
loc_entries: &'a Vec<'a, Located<ExposesEntry<'a, &'a str>>>, loc_entries: &'a Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
indent: u16, indent: u16,
) { ) {
buf.push('['); buf.push('[');

View file

@ -70,7 +70,7 @@ This is the general procedure I follow with some helpful links:
A good place to look for missing features is in the test files for generation in [test_gen](https://github.com/rtfeldman/roc/tree/trunk/compiler/test_gen). Any test that is not enabled for the `gen-dev` feature still needs to be added to the dev backend. Eventually all features should be enabled for the dev backend. A good place to look for missing features is in the test files for generation in [test_gen](https://github.com/rtfeldman/roc/tree/trunk/compiler/test_gen). Any test that is not enabled for the `gen-dev` feature still needs to be added to the dev backend. Eventually all features should be enabled for the dev backend.
1. Pick/write the simplest test case you can find for the new feature. 1. Pick/write the simplest test case you can find for the new feature.
Just add `feature = "gen-dev"` to the `cfg` line for the test case. Just add `feature = "gen-dev"` to the `cfg` line for the test case.
1. Uncomment the code to print out procedures [from here](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/tests/helpers/eval.rs) and run the test. 1. Uncomment the code to print out procedures [from here](https://github.com/rtfeldman/roc/blob/b03ed18553569314a420d5bf1fb0ead4b6b5ecda/compiler/test_gen/src/helpers/dev.rs#L76) and run the test.
It should fail and print out the mono ir for this test case. It should fail and print out the mono ir for this test case.
Seeing the actual mono ir tends to be very helpful for complex additions. Seeing the actual mono ir tends to be very helpful for complex additions.
1. Generally it will fail in one of the match statements in the [Backend](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/lib.rs) trait. 1. Generally it will fail in one of the match statements in the [Backend](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/lib.rs) trait.

View file

@ -446,6 +446,10 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
unimplemented!("stack offsets over 32k are not yet implement for AArch64"); unimplemented!("stack offsets over 32k are not yet implement for AArch64");
} }
} }
#[inline(always)]
fn neg_reg64_reg64(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _src: AArch64GeneralReg) {
unimplemented!("neg is not yet implement for AArch64");
}
#[inline(always)] #[inline(always)]
fn sub_reg64_reg64_imm32( fn sub_reg64_reg64_imm32(
@ -486,6 +490,26 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
unimplemented!("registers equality not implemented yet for AArch64"); unimplemented!("registers equality not implemented yet for AArch64");
} }
#[inline(always)]
fn neq_reg64_reg64_reg64(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
) {
unimplemented!("registers non-equality not implemented yet for AArch64");
}
#[inline(always)]
fn lt_reg64_reg64_reg64(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
) {
unimplemented!("registers less than not implemented yet for AArch64");
}
#[inline(always)] #[inline(always)]
fn ret(buf: &mut Vec<'_, u8>) { fn ret(buf: &mut Vec<'_, u8>) {
ret_reg64(buf, AArch64GeneralReg::LR) ret_reg64(buf, AArch64GeneralReg::LR)

View file

@ -149,6 +149,7 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg); fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
fn neg_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg);
fn imul_reg64_reg64_reg64( fn imul_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
dst: GeneralReg, dst: GeneralReg,
@ -171,6 +172,20 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
src2: GeneralReg, src2: GeneralReg,
); );
fn neq_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src1: GeneralReg,
src2: GeneralReg,
);
fn lt_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src1: GeneralReg,
src2: GeneralReg,
);
fn ret(buf: &mut Vec<'_, u8>); fn ret(buf: &mut Vec<'_, u8>);
} }
@ -786,6 +801,23 @@ impl<
} }
} }
fn build_num_neg(
&mut self,
dst: &Symbol,
src: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String> {
match layout {
Layout::Builtin(Builtin::Int64) => {
let dst_reg = self.claim_general_reg(dst)?;
let src_reg = self.load_to_general_reg(src)?;
ASM::neg_reg64_reg64(&mut self.buf, dst_reg, src_reg);
Ok(())
}
x => Err(format!("NumNeg: layout, {:?}, not implemented yet", x)),
}
}
fn build_num_sub( fn build_num_sub(
&mut self, &mut self,
dst: &Symbol, dst: &Symbol,
@ -824,6 +856,44 @@ impl<
} }
} }
fn build_neq(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
arg_layout: &Layout<'a>,
) -> Result<(), String> {
match arg_layout {
Layout::Builtin(Builtin::Int64) => {
let dst_reg = self.claim_general_reg(dst)?;
let src1_reg = self.load_to_general_reg(src1)?;
let src2_reg = self.load_to_general_reg(src2)?;
ASM::neq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
Ok(())
}
x => Err(format!("NumNeq: layout, {:?}, not implemented yet", x)),
}
}
fn build_num_lt(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
arg_layout: &Layout<'a>,
) -> Result<(), String> {
match arg_layout {
Layout::Builtin(Builtin::Int64) => {
let dst_reg = self.claim_general_reg(dst)?;
let src1_reg = self.load_to_general_reg(src1)?;
let src2_reg = self.load_to_general_reg(src2)?;
ASM::lt_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
Ok(())
}
x => Err(format!("NumLt: layout, {:?}, not implemented yet", x)),
}
}
fn create_struct( fn create_struct(
&mut self, &mut self,
sym: &Symbol, sym: &Symbol,

View file

@ -1053,6 +1053,11 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
mov_base64_offset32_reg64(buf, X86_64GeneralReg::RSP, offset, src) mov_base64_offset32_reg64(buf, X86_64GeneralReg::RSP, offset, src)
} }
#[inline(always)]
fn neg_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) {
mov_reg64_reg64(buf, dst, src);
neg_reg64(buf, dst);
}
#[inline(always)] #[inline(always)]
fn sub_reg64_reg64_imm32( fn sub_reg64_reg64_imm32(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
@ -1091,6 +1096,28 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
sete_reg64(buf, dst); sete_reg64(buf, dst);
} }
#[inline(always)]
fn neq_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: X86_64GeneralReg,
src1: X86_64GeneralReg,
src2: X86_64GeneralReg,
) {
cmp_reg64_reg64(buf, src1, src2);
setne_reg64(buf, dst);
}
#[inline(always)]
fn lt_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: X86_64GeneralReg,
src1: X86_64GeneralReg,
src2: X86_64GeneralReg,
) {
cmp_reg64_reg64(buf, src1, src2);
setl_reg64(buf, dst);
}
#[inline(always)] #[inline(always)]
fn ret(buf: &mut Vec<'_, u8>) { fn ret(buf: &mut Vec<'_, u8>) {
ret(buf); ret(buf);
@ -1448,9 +1475,9 @@ fn neg_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
buf.extend(&[rex, 0xF7, 0xD8 + reg_mod]); buf.extend(&[rex, 0xF7, 0xD8 + reg_mod]);
} }
/// `SETE r/m64` -> Set Byte on Condition - zero/equal (ZF=1) // helper function for `set*` instructions
#[inline(always)] #[inline(always)]
fn sete_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) { fn set_reg64_help(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg, value: u8) {
// XOR needs 3 bytes, actual SETE instruction need 3 or 4 bytes // XOR needs 3 bytes, actual SETE instruction need 3 or 4 bytes
buf.reserve(7); buf.reserve(7);
@ -1458,10 +1485,10 @@ fn sete_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
let reg_mod = reg as u8 % 8; let reg_mod = reg as u8 % 8;
use X86_64GeneralReg::*; use X86_64GeneralReg::*;
match reg { match reg {
RAX | RCX | RDX | RBX => buf.extend(&[0x0F, 0x94, 0xC0 + reg_mod]), RAX | RCX | RDX | RBX => buf.extend(&[0x0F, value, 0xC0 + reg_mod]),
RSP | RBP | RSI | RDI => buf.extend(&[REX, 0x0F, 0x94, 0xC0 + reg_mod]), RSP | RBP | RSI | RDI => buf.extend(&[REX, 0x0F, value, 0xC0 + reg_mod]),
R8 | R9 | R10 | R11 | R12 | R13 | R14 | R15 => { R8 | R9 | R10 | R11 | R12 | R13 | R14 | R15 => {
buf.extend(&[REX + 1, 0x0F, 0x94, 0xC0 + reg_mod]) buf.extend(&[REX + 1, 0x0F, value, 0xC0 + reg_mod])
} }
} }
@ -1470,6 +1497,24 @@ fn sete_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
and_reg64_imm8(buf, reg, 1); and_reg64_imm8(buf, reg, 1);
} }
/// `SETE r/m64` -> Set Byte on Condition - zero/equal (ZF=1)
#[inline(always)]
fn sete_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
set_reg64_help(buf, reg, 0x94);
}
/// `SETNE r/m64` -> Set byte if not equal (ZF=0).
#[inline(always)]
fn setne_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
set_reg64_help(buf, reg, 0x95);
}
/// `SETL r/m64` -> Set byte if less (SF≠ OF).
#[inline(always)]
fn setl_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
set_reg64_help(buf, reg, 0x9c);
}
/// `RET` -> Near return to calling procedure. /// `RET` -> Near return to calling procedure.
#[inline(always)] #[inline(always)]
fn ret(buf: &mut Vec<'_, u8>) { fn ret(buf: &mut Vec<'_, u8>) {
@ -1576,6 +1621,34 @@ mod tests {
} }
} }
#[test]
fn test_sub_reg64_reg64() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for ((dst, src), expected) in &[
(
(X86_64GeneralReg::RAX, X86_64GeneralReg::RAX),
[0x48, 0x29, 0xC0],
),
(
(X86_64GeneralReg::RAX, X86_64GeneralReg::R15),
[0x4C, 0x29, 0xF8],
),
(
(X86_64GeneralReg::R15, X86_64GeneralReg::RAX),
[0x49, 0x29, 0xC7],
),
(
(X86_64GeneralReg::R15, X86_64GeneralReg::R15),
[0x4D, 0x29, 0xFF],
),
] {
buf.clear();
sub_reg64_reg64(&mut buf, *dst, *src);
assert_eq!(expected, &buf[..]);
}
}
#[test] #[test]
fn test_addsd_freg64_freg64() { fn test_addsd_freg64_freg64() {
let arena = bumpalo::Bump::new(); let arena = bumpalo::Bump::new();
@ -2026,7 +2099,7 @@ mod tests {
} }
#[test] #[test]
fn test_sete_reg64() { fn test_set_reg64_help() {
let arena = bumpalo::Bump::new(); let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena]; let mut buf = bumpalo::vec![in &arena];
@ -2039,7 +2112,7 @@ mod tests {
], ],
); );
buf.clear(); buf.clear();
sete_reg64(&mut buf, reg); set_reg64_help(&mut buf, reg, 0x94); // sete_reg64
assert_eq!(expected, &buf[..]); assert_eq!(expected, &buf[..]);
// tests for 8 bytes in the output buffer // tests for 8 bytes in the output buffer
@ -2064,7 +2137,7 @@ mod tests {
), ),
] { ] {
buf.clear(); buf.clear();
sete_reg64(&mut buf, *reg); set_reg64_help(&mut buf, *reg, 0x94); // sete_reg64
assert_eq!(expected, &buf[..]); assert_eq!(expected, &buf[..]);
} }
} }

View file

@ -227,88 +227,18 @@ where
ret_layout, ret_layout,
.. ..
} => { } => {
// For most builtins instead of calling a function, we can just inline the low level. // If this function is just a lowlevel wrapper, then inline it
match *func_sym { if let Some(lowlevel) = LowLevel::from_inlined_wrapper(*func_sym) {
Symbol::NUM_ABS => self.build_run_low_level( self.build_run_low_level(
sym, sym,
&LowLevel::NumAbs, &lowlevel,
arguments, arguments,
arg_layouts, arg_layouts,
ret_layout, ret_layout,
), )
Symbol::NUM_ADD => self.build_run_low_level( } else if func_sym
sym,
&LowLevel::NumAdd,
arguments,
arg_layouts,
ret_layout,
),
Symbol::NUM_ACOS => self.build_run_low_level(
sym,
&LowLevel::NumAcos,
arguments,
arg_layouts,
ret_layout,
),
Symbol::NUM_ASIN => self.build_run_low_level(
sym,
&LowLevel::NumAsin,
arguments,
arg_layouts,
ret_layout,
),
Symbol::NUM_ATAN => self.build_run_low_level(
sym,
&LowLevel::NumAtan,
arguments,
arg_layouts,
ret_layout,
),
Symbol::NUM_MUL => self.build_run_low_level(
sym,
&LowLevel::NumMul,
arguments,
arg_layouts,
ret_layout,
),
Symbol::NUM_POW_INT => self.build_run_low_level(
sym,
&LowLevel::NumPowInt,
arguments,
arg_layouts,
ret_layout,
),
Symbol::NUM_SUB => self.build_run_low_level(
sym,
&LowLevel::NumSub,
arguments,
arg_layouts,
ret_layout,
),
Symbol::NUM_ROUND => self.build_run_low_level(
sym,
&LowLevel::NumRound,
arguments,
arg_layouts,
ret_layout,
),
Symbol::BOOL_EQ => self.build_run_low_level(
sym,
&LowLevel::Eq,
arguments,
arg_layouts,
ret_layout,
),
Symbol::STR_CONCAT => self.build_run_low_level(
sym,
&LowLevel::StrConcat,
arguments,
arg_layouts,
ret_layout,
),
x if x
.module_string(&self.env().interns) .module_string(&self.env().interns)
.starts_with(ModuleName::APP) => .starts_with(ModuleName::APP)
{ {
let fn_name = LayoutIds::default() let fn_name = LayoutIds::default()
.get(*func_sym, layout) .get(*func_sym, layout)
@ -316,8 +246,11 @@ where
// Now that the arguments are needed, load them if they are literals. // Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(arguments)?; self.load_literal_symbols(arguments)?;
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout) self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
} } else {
x => Err(format!("the function, {:?}, is not yet implemented", x)), Err(format!(
"the function, {:?}, is not yet implemented",
func_sym
))
} }
} }
@ -435,6 +368,18 @@ where
); );
self.build_num_mul(sym, &args[0], &args[1], ret_layout) self.build_num_mul(sym, &args[0], &args[1], ret_layout)
} }
LowLevel::NumNeg => {
debug_assert_eq!(
1,
args.len(),
"NumNeg: expected to have exactly one argument"
);
debug_assert_eq!(
arg_layouts[0], *ret_layout,
"NumNeg: expected to have the same argument and return layout"
);
self.build_num_neg(sym, &args[0], ret_layout)
}
LowLevel::NumPowInt => self.build_fn_call( LowLevel::NumPowInt => self.build_fn_call(
sym, sym,
bitcode::NUM_POW_INT[IntWidth::I64].to_string(), bitcode::NUM_POW_INT[IntWidth::I64].to_string(),
@ -471,6 +416,40 @@ where
); );
self.build_eq(sym, &args[0], &args[1], &arg_layouts[0]) self.build_eq(sym, &args[0], &args[1], &arg_layouts[0])
} }
LowLevel::NotEq => {
debug_assert_eq!(
2,
args.len(),
"NotEq: expected to have exactly two argument"
);
debug_assert_eq!(
arg_layouts[0], arg_layouts[1],
"NotEq: expected all arguments of to have the same layout"
);
debug_assert_eq!(
Layout::Builtin(Builtin::Int1),
*ret_layout,
"NotEq: expected to have return layout of type I1"
);
self.build_neq(sym, &args[0], &args[1], &arg_layouts[0])
}
LowLevel::NumLt => {
debug_assert_eq!(
2,
args.len(),
"NumLt: expected to have exactly two argument"
);
debug_assert_eq!(
arg_layouts[0], arg_layouts[1],
"NumLt: expected all arguments of to have the same layout"
);
debug_assert_eq!(
Layout::Builtin(Builtin::Int1),
*ret_layout,
"NumLt: expected to have return layout of type I1"
);
self.build_num_lt(sym, &args[0], &args[1], &arg_layouts[0])
}
LowLevel::NumRound => self.build_fn_call( LowLevel::NumRound => self.build_fn_call(
sym, sym,
bitcode::NUM_ROUND[FloatWidth::F64].to_string(), bitcode::NUM_ROUND[FloatWidth::F64].to_string(),
@ -526,6 +505,14 @@ where
layout: &Layout<'a>, layout: &Layout<'a>,
) -> Result<(), String>; ) -> Result<(), String>;
/// build_num_neg stores the negated value of src into dst.
fn build_num_neg(
&mut self,
dst: &Symbol,
src: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String>;
/// build_num_sub stores the `src1 - src2` difference into dst. /// build_num_sub stores the `src1 - src2` difference into dst.
fn build_num_sub( fn build_num_sub(
&mut self, &mut self,
@ -544,6 +531,24 @@ where
arg_layout: &Layout<'a>, arg_layout: &Layout<'a>,
) -> Result<(), String>; ) -> Result<(), String>;
/// build_neq stores the result of `src1 != src2` into dst.
fn build_neq(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
arg_layout: &Layout<'a>,
) -> Result<(), String>;
/// build_num_lt stores the result of `src1 < src2` into dst.
fn build_num_lt(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
arg_layout: &Layout<'a>,
) -> Result<(), String>;
/// literal_map gets the map from symbol to literal, used for lazy loading and literal folding. /// literal_map gets the map from symbol to literal, used for lazy loading and literal folding.
fn literal_map(&mut self) -> &mut MutMap<Symbol, Literal<'a>>; fn literal_map(&mut self) -> &mut MutMap<Symbol, Literal<'a>>;
@ -785,7 +790,7 @@ where
match call_type { match call_type {
CallType::ByName { .. } => {} CallType::ByName { .. } => {}
CallType::LowLevel { .. } => {} CallType::LowLevel { .. } => {}
CallType::HigherOrderLowLevel { .. } => {} CallType::HigherOrder { .. } => {}
CallType::Foreign { .. } => {} CallType::Foreign { .. } => {}
} }
} }

View file

@ -128,20 +128,19 @@ fn build_has_tag_id_help<'a, 'ctx, 'env>(
[tag_id, tag_value_ptr] => { [tag_id, tag_value_ptr] => {
let tag_type = basic_type_from_layout(env, &Layout::Union(union_layout)); let tag_type = basic_type_from_layout(env, &Layout::Union(union_layout));
let argument_cast = env let tag_value = env.builder.build_pointer_cast(
.builder tag_value_ptr.into_pointer_value(),
.build_bitcast(
*tag_value_ptr,
tag_type.ptr_type(AddressSpace::Generic), tag_type.ptr_type(AddressSpace::Generic),
"load_opaque", "load_opaque_get_tag_id",
) );
.into_pointer_value();
let tag_value = env.builder.build_load(argument_cast, "get_value");
let actual_tag_id = { let actual_tag_id = {
let tag_id_i64 = let tag_id_i64 = crate::llvm::build::get_tag_id(
crate::llvm::build::get_tag_id(env, function_value, &union_layout, tag_value); env,
function_value,
&union_layout,
tag_value.into(),
);
env.builder env.builder
.build_int_cast(tag_id_i64, env.context.i16_type(), "to_i16") .build_int_cast(tag_id_i64, env.context.i16_type(), "to_i16")
@ -155,18 +154,11 @@ fn build_has_tag_id_help<'a, 'ctx, 'env>(
); );
let tag_data_ptr = { let tag_data_ptr = {
let data_index = env let ptr = env
.context .builder
.i64_type() .build_struct_gep(tag_value, TAG_DATA_INDEX, "get_data_ptr")
.const_int(TAG_DATA_INDEX as u64, false); .unwrap();
let ptr = unsafe {
env.builder.build_gep(
tag_value_ptr.into_pointer_value(),
&[data_index],
"get_data_ptr",
)
};
env.builder.build_bitcast(ptr, i8_ptr_type, "to_opaque") env.builder.build_bitcast(ptr, i8_ptr_type, "to_opaque")
}; };
@ -191,6 +183,7 @@ pub fn build_transform_caller<'a, 'ctx, 'env>(
function: FunctionValue<'ctx>, function: FunctionValue<'ctx>,
closure_data_layout: LambdaSet<'a>, closure_data_layout: LambdaSet<'a>,
argument_layouts: &[Layout<'a>], argument_layouts: &[Layout<'a>],
result_layout: Layout<'a>,
) -> FunctionValue<'ctx> { ) -> FunctionValue<'ctx> {
let fn_name: &str = &format!( let fn_name: &str = &format!(
"{}_zig_function_caller", "{}_zig_function_caller",
@ -204,6 +197,7 @@ pub fn build_transform_caller<'a, 'ctx, 'env>(
function, function,
closure_data_layout, closure_data_layout,
argument_layouts, argument_layouts,
result_layout,
fn_name, fn_name,
), ),
} }
@ -214,6 +208,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
roc_function: FunctionValue<'ctx>, roc_function: FunctionValue<'ctx>,
closure_data_layout: LambdaSet<'a>, closure_data_layout: LambdaSet<'a>,
argument_layouts: &[Layout<'a>], argument_layouts: &[Layout<'a>],
result_layout: Layout<'a>,
fn_name: &str, fn_name: &str,
) -> FunctionValue<'ctx> { ) -> FunctionValue<'ctx> {
debug_assert!(argument_layouts.len() <= 7); debug_assert!(argument_layouts.len() <= 7);
@ -260,12 +255,22 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
for (argument_ptr, layout) in arguments.iter().zip(argument_layouts) { for (argument_ptr, layout) in arguments.iter().zip(argument_layouts) {
let basic_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic); let basic_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic);
let argument = if layout.is_passed_by_reference() {
env.builder
.build_pointer_cast(
argument_ptr.into_pointer_value(),
basic_type,
"cast_ptr_to_tag_build_transform_caller_help",
)
.into()
} else {
let argument_cast = env let argument_cast = env
.builder .builder
.build_bitcast(*argument_ptr, basic_type, "load_opaque") .build_bitcast(*argument_ptr, basic_type, "load_opaque_1")
.into_pointer_value(); .into_pointer_value();
let argument = env.builder.build_load(argument_cast, "load_opaque"); env.builder.build_load(argument_cast, "load_opaque_2")
};
arguments_cast.push(argument); arguments_cast.push(argument);
} }
@ -288,31 +293,19 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
} }
} }
let call = { let result = crate::llvm::build::call_roc_function(
env.builder env,
.build_call(roc_function, arguments_cast.as_slice(), "tmp") roc_function,
}; &result_layout,
arguments_cast.as_slice(),
call.set_call_convention(FAST_CALL_CONV); );
let result = call
.try_as_basic_value()
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."));
let result_u8_ptr = function_value let result_u8_ptr = function_value
.get_nth_param(argument_layouts.len() as u32 + 1) .get_nth_param(argument_layouts.len() as u32 + 1)
.unwrap(); .unwrap()
let result_ptr = env
.builder
.build_bitcast(
result_u8_ptr,
result.get_type().ptr_type(AddressSpace::Generic),
"write_result",
)
.into_pointer_value(); .into_pointer_value();
env.builder.build_store(result_ptr, result); crate::llvm::build::store_roc_value_opaque(env, result_layout, result_u8_ptr, result);
env.builder.build_return(None); env.builder.build_return(None);
env.builder.position_at_end(block); env.builder.position_at_end(block);
@ -414,12 +407,18 @@ fn build_rc_wrapper<'a, 'ctx, 'env>(
let value_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic); let value_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic);
let value = if layout.is_passed_by_reference() {
env.builder
.build_pointer_cast(value_ptr, value_type, "cast_ptr_to_tag_build_rc_wrapper")
.into()
} else {
let value_cast = env let value_cast = env
.builder .builder
.build_bitcast(value_ptr, value_type, "load_opaque") .build_bitcast(value_ptr, value_type, "load_opaque")
.into_pointer_value(); .into_pointer_value();
let value = env.builder.build_load(value_cast, "load_opaque"); env.builder.build_load(value_cast, "load_opaque")
};
match rc_operation { match rc_operation {
Mode::Inc => { Mode::Inc => {

File diff suppressed because it is too large Load diff

View file

@ -2,9 +2,9 @@ use crate::debug_info_init;
use crate::llvm::bitcode::call_bitcode_fn; use crate::llvm::bitcode::call_bitcode_fn;
use crate::llvm::build::tag_pointer_clear_tag_id; use crate::llvm::build::tag_pointer_clear_tag_id;
use crate::llvm::build::Env; use crate::llvm::build::Env;
use crate::llvm::build::{cast_block_of_memory_to_tag, get_tag_id, FAST_CALL_CONV, TAG_DATA_INDEX}; use crate::llvm::build::{get_tag_id, FAST_CALL_CONV, TAG_DATA_INDEX};
use crate::llvm::build_str; use crate::llvm::build_str;
use crate::llvm::convert::basic_type_from_layout; use crate::llvm::convert::{basic_type_from_layout, basic_type_from_layout_1};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use inkwell::values::{ use inkwell::values::{
BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue, BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
@ -339,7 +339,8 @@ fn build_hash_tag<'a, 'ctx, 'env>(
None => { None => {
let seed_type = env.context.i64_type(); let seed_type = env.context.i64_type();
let arg_type = basic_type_from_layout(env, layout); let arg_type = basic_type_from_layout_1(env, layout);
dbg!(layout, arg_type);
let function_value = crate::llvm::refcounting::build_header_help( let function_value = crate::llvm::refcounting::build_header_help(
env, env,
@ -423,14 +424,6 @@ fn hash_tag<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify"); let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block); env.builder.position_at_end(block);
let struct_layout = Layout::Struct(field_layouts);
let wrapper_type = basic_type_from_layout(env, &struct_layout);
debug_assert!(wrapper_type.is_struct_type());
let as_struct =
cast_block_of_memory_to_tag(env.builder, tag.into_struct_value(), wrapper_type);
// hash the tag id // hash the tag id
let hash_bytes = store_and_use_as_u8_ptr( let hash_bytes = store_and_use_as_u8_ptr(
env, env,
@ -440,7 +433,6 @@ fn hash_tag<'a, 'ctx, 'env>(
.into(), .into(),
&tag_id_layout, &tag_id_layout,
); );
let seed = hash_bitcode_fn( let seed = hash_bitcode_fn(
env, env,
seed, seed,
@ -449,14 +441,9 @@ fn hash_tag<'a, 'ctx, 'env>(
); );
// hash the tag data // hash the tag data
let answer = build_hash_struct( let tag = tag.into_pointer_value();
env, let answer =
layout_ids, hash_ptr_to_struct(env, layout_ids, union_layout, field_layouts, seed, tag);
field_layouts,
WhenRecursive::Unreachable,
seed,
as_struct,
);
merge_phi.add_incoming(&[(&answer, block)]); merge_phi.add_incoming(&[(&answer, block)]);
env.builder.build_unconditional_branch(merge_block); env.builder.build_unconditional_branch(merge_block);
@ -793,7 +780,15 @@ fn hash_list<'a, 'ctx, 'env>(
env.builder.build_store(result, answer); env.builder.build_store(result, answer);
}; };
incrementing_elem_loop(env, parent, ptr, length, "current_index", loop_fn); incrementing_elem_loop(
env,
parent,
*element_layout,
ptr,
length,
"current_index",
loop_fn,
);
env.builder.build_unconditional_branch(done_block); env.builder.build_unconditional_branch(done_block);
@ -822,12 +817,12 @@ fn hash_ptr_to_struct<'a, 'ctx, 'env>(
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
use inkwell::types::BasicType; use inkwell::types::BasicType;
let wrapper_type = basic_type_from_layout(env, &Layout::Union(*union_layout)); let wrapper_type = basic_type_from_layout_1(env, &Layout::Union(*union_layout));
// cast the opaque pointer to a pointer of the correct shape // cast the opaque pointer to a pointer of the correct shape
let wrapper_ptr = env let wrapper_ptr = env
.builder .builder
.build_bitcast(tag, wrapper_type, "opaque_to_correct") .build_bitcast(tag, wrapper_type, "hash_ptr_to_struct_opaque_to_correct")
.into_pointer_value(); .into_pointer_value();
let struct_ptr = env let struct_ptr = env

View file

@ -17,6 +17,8 @@ use morphic_lib::UpdateMode;
use roc_builtins::bitcode; use roc_builtins::bitcode;
use roc_mono::layout::{Builtin, Layout, LayoutIds}; use roc_mono::layout::{Builtin, Layout, LayoutIds};
use super::build::{load_roc_value, store_roc_value};
pub fn pass_update_mode<'a, 'ctx, 'env>( pub fn pass_update_mode<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
update_mode: UpdateMode, update_mode: UpdateMode,
@ -53,9 +55,13 @@ pub fn call_bitcode_fn_returns_list<'a, 'ctx, 'env>(
fn pass_element_as_opaque<'a, 'ctx, 'env>( fn pass_element_as_opaque<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
element: BasicValueEnum<'ctx>, element: BasicValueEnum<'ctx>,
layout: Layout<'a>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let element_ptr = env.builder.build_alloca(element.get_type(), "element"); let element_type = basic_type_from_layout(env, &layout);
env.builder.build_store(element_ptr, element); let element_ptr = env
.builder
.build_alloca(element_type, "element_to_pass_as_opaque");
store_roc_value(env, layout, element_ptr, element);
env.builder.build_bitcast( env.builder.build_bitcast(
element_ptr, element_ptr,
@ -106,7 +112,7 @@ pub fn list_single<'a, 'ctx, 'env>(
env, env,
&[ &[
env.alignment_intvalue(element_layout), env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element), pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout), layout_width(env, element_layout),
], ],
bitcode::LIST_SINGLE, bitcode::LIST_SINGLE,
@ -128,7 +134,7 @@ pub fn list_repeat<'a, 'ctx, 'env>(
&[ &[
list_len.into(), list_len.into(),
env.alignment_intvalue(element_layout), env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element), pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout), layout_width(env, element_layout),
inc_element_fn.as_global_value().as_pointer_value().into(), inc_element_fn.as_global_value().as_pointer_value().into(),
], ],
@ -216,10 +222,11 @@ pub fn list_get_unsafe<'a, 'ctx, 'env>(
// Assume the bounds have already been checked earlier // Assume the bounds have already been checked earlier
// (e.g. by List.get or List.first, which wrap List.#getUnsafe) // (e.g. by List.get or List.first, which wrap List.#getUnsafe)
let elem_ptr = let elem_ptr = unsafe {
unsafe { builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "elem") }; builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "list_get_element")
};
let result = builder.build_load(elem_ptr, "List.get"); let result = load_roc_value(env, **elem_layout, elem_ptr, "list_get_load_element");
increment_refcount_layout(env, parent, layout_ids, 1, result, elem_layout); increment_refcount_layout(env, parent, layout_ids, 1, result, elem_layout);
@ -247,7 +254,7 @@ pub fn list_append<'a, 'ctx, 'env>(
&[ &[
pass_list_cc(env, original_wrapper.into()), pass_list_cc(env, original_wrapper.into()),
env.alignment_intvalue(element_layout), env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element), pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout), layout_width(env, element_layout),
pass_update_mode(env, update_mode), pass_update_mode(env, update_mode),
], ],
@ -267,7 +274,7 @@ pub fn list_prepend<'a, 'ctx, 'env>(
&[ &[
pass_list_cc(env, original_wrapper.into()), pass_list_cc(env, original_wrapper.into()),
env.alignment_intvalue(element_layout), env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element), pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout), layout_width(env, element_layout),
], ],
bitcode::LIST_PREPEND, bitcode::LIST_PREPEND,
@ -297,31 +304,13 @@ pub fn list_swap<'a, 'ctx, 'env>(
) )
} }
/// List.takeFirst : List elem, Nat -> List elem /// List.sublist : List elem, { start : Nat, len : Nat } -> List elem
pub fn list_take_first<'a, 'ctx, 'env>( pub fn list_sublist<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
original_wrapper: StructValue<'ctx>,
count: IntValue<'ctx>,
element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
call_bitcode_fn_returns_list(
env,
&[
pass_list_cc(env, original_wrapper.into()),
env.alignment_intvalue(element_layout),
layout_width(env, element_layout),
count.into(),
],
bitcode::LIST_TAKE_FIRST,
)
}
/// List.takeLast : List elem, Nat -> List elem
pub fn list_take_last<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
original_wrapper: StructValue<'ctx>, original_wrapper: StructValue<'ctx>,
count: IntValue<'ctx>, start: IntValue<'ctx>,
len: IntValue<'ctx>,
element_layout: &Layout<'a>, element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout); let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
@ -331,32 +320,11 @@ pub fn list_take_last<'a, 'ctx, 'env>(
pass_list_cc(env, original_wrapper.into()), pass_list_cc(env, original_wrapper.into()),
env.alignment_intvalue(element_layout), env.alignment_intvalue(element_layout),
layout_width(env, element_layout), layout_width(env, element_layout),
count.into(), start.into(),
len.into(),
dec_element_fn.as_global_value().as_pointer_value().into(), dec_element_fn.as_global_value().as_pointer_value().into(),
], ],
bitcode::LIST_TAKE_LAST, bitcode::LIST_SUBLIST,
)
}
/// List.drop : List elem, Nat -> List elem
pub fn list_drop<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
original_wrapper: StructValue<'ctx>,
count: IntValue<'ctx>,
element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
call_bitcode_fn_returns_list(
env,
&[
pass_list_cc(env, original_wrapper.into()),
env.alignment_intvalue(element_layout),
layout_width(env, element_layout),
count.into(),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_DROP,
) )
} }
@ -406,7 +374,7 @@ pub fn list_set<'a, 'ctx, 'env>(
&[ &[
bytes.into(), bytes.into(),
index.into(), index.into(),
pass_element_as_opaque(env, element), pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout), layout_width(env, element_layout),
dec_element_fn.as_global_value().as_pointer_value().into(), dec_element_fn.as_global_value().as_pointer_value().into(),
], ],
@ -419,7 +387,7 @@ pub fn list_set<'a, 'ctx, 'env>(
length.into(), length.into(),
env.alignment_intvalue(element_layout), env.alignment_intvalue(element_layout),
index.into(), index.into(),
pass_element_as_opaque(env, element), pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout), layout_width(env, element_layout),
dec_element_fn.as_global_value().as_pointer_value().into(), dec_element_fn.as_global_value().as_pointer_value().into(),
], ],
@ -595,7 +563,7 @@ pub fn list_contains<'a, 'ctx, 'env>(
env, env,
&[ &[
pass_list_cc(env, list), pass_list_cc(env, list),
pass_element_as_opaque(env, element), pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout), layout_width(env, element_layout),
eq_fn, eq_fn,
], ],
@ -958,6 +926,27 @@ pub fn list_any<'a, 'ctx, 'env>(
) )
} }
/// List.all : List elem, \(elem -> Bool) -> Bool
pub fn list_all<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
roc_function_call: RocFunctionCall<'ctx>,
list: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
call_bitcode_fn(
env,
&[
pass_list_cc(env, list),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
layout_width(env, element_layout),
],
bitcode::LIST_ALL,
)
}
/// List.findUnsafe : List elem, (elem -> Bool) -> { value: elem, found: bool } /// List.findUnsafe : List elem, (elem -> Bool) -> { value: elem, found: bool }
pub fn list_find_unsafe<'a, 'ctx, 'env>( pub fn list_find_unsafe<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
@ -1154,6 +1143,7 @@ where
pub fn incrementing_elem_loop<'a, 'ctx, 'env, LoopFn>( pub fn incrementing_elem_loop<'a, 'ctx, 'env, LoopFn>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
element_layout: Layout<'a>,
ptr: PointerValue<'ctx>, ptr: PointerValue<'ctx>,
len: IntValue<'ctx>, len: IntValue<'ctx>,
index_name: &str, index_name: &str,
@ -1166,9 +1156,14 @@ where
incrementing_index_loop(env, parent, len, index_name, |index| { incrementing_index_loop(env, parent, len, index_name, |index| {
// The pointer to the element in the list // The pointer to the element in the list
let elem_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index], "load_index") }; let element_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index], "load_index") };
let elem = builder.build_load(elem_ptr, "get_elem"); let elem = load_roc_value(
env,
element_layout,
element_ptr,
"incrementing_element_loop_load",
);
loop_fn(index, elem); loop_fn(index, elem);
}) })

View file

@ -269,6 +269,16 @@ pub fn str_trim_left<'a, 'ctx, 'env>(
call_bitcode_fn(env, &[str_i128.into()], bitcode::STR_TRIM_LEFT) call_bitcode_fn(env, &[str_i128.into()], bitcode::STR_TRIM_LEFT)
} }
/// Str.trimRight : Str -> Str
pub fn str_trim_right<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
str_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
let str_i128 = str_symbol_to_c_abi(env, scope, str_symbol);
call_bitcode_fn(env, &[str_i128.into()], bitcode::STR_TRIM_RIGHT)
}
/// Str.fromInt : Int -> Str /// Str.fromInt : Int -> Str
pub fn str_from_int<'a, 'ctx, 'env>( pub fn str_from_int<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,

View file

@ -1,10 +1,8 @@
use crate::llvm::bitcode::call_bitcode_fn; use crate::llvm::bitcode::call_bitcode_fn;
use crate::llvm::build::{ use crate::llvm::build::{get_tag_id, tag_pointer_clear_tag_id, Env, FAST_CALL_CONV};
cast_block_of_memory_to_tag, get_tag_id, tag_pointer_clear_tag_id, Env, FAST_CALL_CONV,
};
use crate::llvm::build_list::{list_len, load_list_ptr}; use crate::llvm::build_list::{list_len, load_list_ptr};
use crate::llvm::build_str::str_equal; use crate::llvm::build_str::str_equal;
use crate::llvm::convert::basic_type_from_layout; use crate::llvm::convert::{basic_type_from_layout, basic_type_from_layout_1};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use inkwell::types::BasicType; use inkwell::types::BasicType;
use inkwell::values::{ use inkwell::values::{
@ -751,7 +749,7 @@ fn build_tag_eq<'a, 'ctx, 'env>(
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value, Some(function_value) => function_value,
None => { None => {
let arg_type = basic_type_from_layout(env, tag_layout); let arg_type = basic_type_from_layout_1(env, tag_layout);
let function_value = crate::llvm::refcounting::build_header_help( let function_value = crate::llvm::refcounting::build_header_help(
env, env,
@ -844,9 +842,29 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
match union_layout { match union_layout {
NonRecursive(tags) => { NonRecursive(tags) => {
let ptr_equal = env.builder.build_int_compare(
IntPredicate::EQ,
env.builder
.build_ptr_to_int(tag1.into_pointer_value(), env.ptr_int(), "pti"),
env.builder
.build_ptr_to_int(tag2.into_pointer_value(), env.ptr_int(), "pti"),
"compare_pointers",
);
let compare_tag_ids = ctx.append_basic_block(parent, "compare_tag_ids");
env.builder
.build_conditional_branch(ptr_equal, return_true, compare_tag_ids);
env.builder.position_at_end(compare_tag_ids);
let id1 = get_tag_id(env, parent, union_layout, tag1); let id1 = get_tag_id(env, parent, union_layout, tag1);
let id2 = get_tag_id(env, parent, union_layout, tag2); let id2 = get_tag_id(env, parent, union_layout, tag2);
// clear the tag_id so we get a pointer to the actual data
let tag1 = tag1.into_pointer_value();
let tag2 = tag2.into_pointer_value();
let compare_tag_fields = ctx.append_basic_block(parent, "compare_tag_fields"); let compare_tag_fields = ctx.append_basic_block(parent, "compare_tag_fields");
let same_tag = let same_tag =
@ -866,30 +884,14 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify"); let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block); env.builder.position_at_end(block);
// TODO drop tag id? let answer = eq_ptr_to_struct(
let struct_layout = Layout::Struct(field_layouts);
let wrapper_type = basic_type_from_layout(env, &struct_layout);
debug_assert!(wrapper_type.is_struct_type());
let struct1 = cast_block_of_memory_to_tag(
env.builder,
tag1.into_struct_value(),
wrapper_type,
);
let struct2 = cast_block_of_memory_to_tag(
env.builder,
tag2.into_struct_value(),
wrapper_type,
);
let answer = build_struct_eq(
env, env,
layout_ids, layout_ids,
union_layout,
Some(when_recursive.clone()),
field_layouts, field_layouts,
when_recursive.clone(), tag1,
struct1, tag2,
struct2,
); );
env.builder.build_return(Some(&answer)); env.builder.build_return(Some(&answer));
@ -946,8 +948,15 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify"); let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block); env.builder.position_at_end(block);
let answer = let answer = eq_ptr_to_struct(
eq_ptr_to_struct(env, layout_ids, union_layout, field_layouts, tag1, tag2); env,
layout_ids,
union_layout,
None,
field_layouts,
tag1,
tag2,
);
env.builder.build_return(Some(&answer)); env.builder.build_return(Some(&answer));
@ -1003,6 +1012,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env, env,
layout_ids, layout_ids,
union_layout, union_layout,
None,
other_fields, other_fields,
tag1.into_pointer_value(), tag1.into_pointer_value(),
tag2.into_pointer_value(), tag2.into_pointer_value(),
@ -1093,8 +1103,15 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify"); let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block); env.builder.position_at_end(block);
let answer = let answer = eq_ptr_to_struct(
eq_ptr_to_struct(env, layout_ids, union_layout, field_layouts, tag1, tag2); env,
layout_ids,
union_layout,
None,
field_layouts,
tag1,
tag2,
);
env.builder.build_return(Some(&answer)); env.builder.build_return(Some(&answer));
@ -1128,6 +1145,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env, env,
layout_ids, layout_ids,
union_layout, union_layout,
None,
field_layouts, field_layouts,
tag1.into_pointer_value(), tag1.into_pointer_value(),
tag2.into_pointer_value(), tag2.into_pointer_value(),
@ -1142,6 +1160,7 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
union_layout: &UnionLayout<'a>, union_layout: &UnionLayout<'a>,
opt_when_recursive: Option<WhenRecursive<'a>>,
field_layouts: &'a [Layout<'a>], field_layouts: &'a [Layout<'a>],
tag1: PointerValue<'ctx>, tag1: PointerValue<'ctx>,
tag2: PointerValue<'ctx>, tag2: PointerValue<'ctx>,
@ -1184,7 +1203,7 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
env, env,
layout_ids, layout_ids,
field_layouts, field_layouts,
WhenRecursive::Loop(*union_layout), opt_when_recursive.unwrap_or(WhenRecursive::Loop(*union_layout)),
struct1, struct1,
struct2, struct2,
) )

View file

@ -76,6 +76,66 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
} }
} }
pub fn basic_type_from_layout_1<'a, 'ctx, 'env>(
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
layout: &Layout<'_>,
) -> BasicTypeEnum<'ctx> {
use Layout::*;
match layout {
Struct(sorted_fields) => basic_type_from_record(env, sorted_fields),
LambdaSet(lambda_set) => {
basic_type_from_layout_1(env, &lambda_set.runtime_representation())
}
Union(union_layout) => {
use UnionLayout::*;
let tag_id_type = basic_type_from_layout(env, &union_layout.tag_id_layout());
match union_layout {
NonRecursive(tags) => {
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes);
let struct_type = env.context.struct_type(&[data, tag_id_type], false);
struct_type.ptr_type(AddressSpace::Generic).into()
}
Recursive(tags)
| NullableWrapped {
other_tags: tags, ..
} => {
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes);
if union_layout.stores_tag_id_as_data(env.ptr_bytes) {
env.context
.struct_type(&[data, tag_id_type], false)
.ptr_type(AddressSpace::Generic)
.into()
} else {
data.ptr_type(AddressSpace::Generic).into()
}
}
NullableUnwrapped { other_fields, .. } => {
let block = block_of_memory_slices(env.context, &[other_fields], env.ptr_bytes);
block.ptr_type(AddressSpace::Generic).into()
}
NonNullableUnwrapped(fields) => {
let block = block_of_memory_slices(env.context, &[fields], env.ptr_bytes);
block.ptr_type(AddressSpace::Generic).into()
}
}
}
RecursivePointer => {
// TODO make this dynamic
env.context
.i64_type()
.ptr_type(AddressSpace::Generic)
.as_basic_type_enum()
}
Builtin(builtin) => basic_type_from_builtin(env, builtin),
}
}
pub fn basic_type_from_builtin<'a, 'ctx, 'env>( pub fn basic_type_from_builtin<'a, 'ctx, 'env>(
env: &crate::llvm::build::Env<'a, 'ctx, 'env>, env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
builtin: &Builtin<'_>, builtin: &Builtin<'_>,

View file

@ -1,11 +1,11 @@
use crate::debug_info_init; use crate::debug_info_init;
use crate::llvm::bitcode::call_void_bitcode_fn; use crate::llvm::bitcode::call_void_bitcode_fn;
use crate::llvm::build::{ use crate::llvm::build::{
add_func, cast_basic_basic, cast_block_of_memory_to_tag, get_tag_id, get_tag_id_non_recursive, add_func, cast_basic_basic, get_tag_id, tag_pointer_clear_tag_id, use_roc_value, Env,
tag_pointer_clear_tag_id, Env, FAST_CALL_CONV, TAG_DATA_INDEX, FAST_CALL_CONV, TAG_DATA_INDEX, TAG_ID_INDEX,
}; };
use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list}; use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list};
use crate::llvm::convert::{basic_type_from_layout, ptr_int}; use crate::llvm::convert::{basic_type_from_layout, basic_type_from_layout_1, ptr_int};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use inkwell::basic_block::BasicBlock; use inkwell::basic_block::BasicBlock;
use inkwell::context::Context; use inkwell::context::Context;
@ -139,8 +139,10 @@ impl<'ctx> PointerToRefcount<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function"); let block = env.builder.get_insert_block().expect("to be in a function");
let parent = block.get_parent().unwrap(); let parent = block.get_parent().unwrap();
let modify_block = env.context.append_basic_block(parent, "inc_str_modify"); let modify_block = env
let cont_block = env.context.append_basic_block(parent, "inc_str_cont"); .context
.append_basic_block(parent, "inc_refcount_modify");
let cont_block = env.context.append_basic_block(parent, "inc_refcount_cont");
env.builder env.builder
.build_conditional_branch(is_static_allocation, cont_block, modify_block); .build_conditional_branch(is_static_allocation, cont_block, modify_block);
@ -181,7 +183,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
env.module, env.module,
fn_name, fn_name,
fn_type, fn_type,
Linkage::Private, Linkage::Internal,
FAST_CALL_CONV, // Because it's an internal-only function, it should use the fast calling convention. FAST_CALL_CONV, // Because it's an internal-only function, it should use the fast calling convention.
); );
@ -349,18 +351,25 @@ fn modify_refcount_struct_help<'a, 'ctx, 'env>(
for (i, field_layout) in layouts.iter().enumerate() { for (i, field_layout) in layouts.iter().enumerate() {
if field_layout.contains_refcounted() { if field_layout.contains_refcounted() {
let field_ptr = env let raw_value = env
.builder .builder
.build_extract_value(wrapper_struct, i as u32, "decrement_struct_field") .build_extract_value(wrapper_struct, i as u32, "decrement_struct_field")
.unwrap(); .unwrap();
let field_value = use_roc_value(
env,
*field_layout,
raw_value,
"load_struct_tag_field_for_decrement",
);
modify_refcount_layout_help( modify_refcount_layout_help(
env, env,
parent, parent,
layout_ids, layout_ids,
mode.to_call_mode(fn_val), mode.to_call_mode(fn_val),
when_recursive, when_recursive,
field_ptr, field_value,
field_layout, field_layout,
); );
} }
@ -753,7 +762,15 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
); );
}; };
incrementing_elem_loop(env, parent, ptr, len, "modify_rc_index", loop_fn); incrementing_elem_loop(
env,
parent,
*element_layout,
ptr,
len,
"modify_rc_index",
loop_fn,
);
} }
let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper); let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper);
@ -1289,7 +1306,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
.build_bitcast( .build_bitcast(
value_ptr, value_ptr,
wrapper_type.ptr_type(AddressSpace::Generic), wrapper_type.ptr_type(AddressSpace::Generic),
"opaque_to_correct", "opaque_to_correct_recursive_decrement",
) )
.into_pointer_value(); .into_pointer_value();
@ -1602,7 +1619,7 @@ fn modify_refcount_union<'a, 'ctx, 'env>(
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value, Some(function_value) => function_value,
None => { None => {
let basic_type = basic_type_from_layout(env, &layout); let basic_type = basic_type_from_layout_1(env, &layout);
let function_value = build_header(env, basic_type, mode, &fn_name); let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_union_help( modify_refcount_union_help(
@ -1647,18 +1664,24 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
// Add args to scope // Add args to scope
let arg_symbol = Symbol::ARG_1; let arg_symbol = Symbol::ARG_1;
let arg_val = fn_val.get_param_iter().next().unwrap(); let arg_ptr = fn_val.get_param_iter().next().unwrap().into_pointer_value();
arg_val.set_name(arg_symbol.as_str(&env.interns)); arg_ptr.set_name(arg_symbol.as_str(&env.interns));
let parent = fn_val; let parent = fn_val;
let before_block = env.builder.get_insert_block().expect("to be in a function"); let before_block = env.builder.get_insert_block().expect("to be in a function");
let wrapper_struct = arg_val.into_struct_value();
// read the tag_id // read the tag_id
let tag_id = get_tag_id_non_recursive(env, wrapper_struct); let tag_id_ptr = env
.builder
.build_struct_gep(arg_ptr, TAG_ID_INDEX, "tag_id_ptr")
.unwrap();
let tag_id = env
.builder
.build_load(tag_id_ptr, "load_tag_id")
.into_int_value();
let tag_id_u8 = env let tag_id_u8 = env
.builder .builder
@ -1686,12 +1709,16 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
let wrapper_type = basic_type_from_layout(env, &Layout::Struct(field_layouts)); let wrapper_type = basic_type_from_layout(env, &Layout::Struct(field_layouts));
debug_assert!(wrapper_type.is_struct_type()); debug_assert!(wrapper_type.is_struct_type());
let data_bytes = env let opaque_tag_data_ptr = env
.builder .builder
.build_extract_value(wrapper_struct, TAG_DATA_INDEX, "read_tag_id") .build_struct_gep(arg_ptr, TAG_DATA_INDEX, "field_ptr")
.unwrap() .unwrap();
.into_struct_value();
let wrapper_struct = cast_block_of_memory_to_tag(env.builder, data_bytes, wrapper_type); let cast_tag_data_pointer = env.builder.build_pointer_cast(
opaque_tag_data_ptr,
wrapper_type.ptr_type(AddressSpace::Generic),
"cast_to_concrete_tag",
);
for (i, field_layout) in field_layouts.iter().enumerate() { for (i, field_layout) in field_layouts.iter().enumerate() {
if let Layout::RecursivePointer = field_layout { if let Layout::RecursivePointer = field_layout {
@ -1699,16 +1726,22 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
} else if field_layout.contains_refcounted() { } else if field_layout.contains_refcounted() {
let field_ptr = env let field_ptr = env
.builder .builder
.build_extract_value(wrapper_struct, i as u32, "modify_tag_field") .build_struct_gep(cast_tag_data_pointer, i as u32, "modify_tag_field")
.unwrap(); .unwrap();
let field_value = if field_layout.is_passed_by_reference() {
field_ptr.into()
} else {
env.builder.build_load(field_ptr, "field_value")
};
modify_refcount_layout_help( modify_refcount_layout_help(
env, env,
parent, parent,
layout_ids, layout_ids,
mode.to_call_mode(fn_val), mode.to_call_mode(fn_val),
when_recursive, when_recursive,
field_ptr, field_value,
field_layout, field_layout,
); );
} }

Binary file not shown.

View file

@ -2,12 +2,13 @@ use bumpalo::{self, collections::Vec};
use code_builder::Align; use code_builder::Align;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
use roc_mono::layout::{Layout, LayoutIds}; use roc_mono::layout::{Builtin, Layout, LayoutIds};
use crate::layout::WasmLayout; use crate::layout::WasmLayout;
use crate::low_level::{build_call_low_level, LowlevelBuildResult}; use crate::low_level::{decode_low_level, LowlevelBuildResult};
use crate::storage::{Storage, StoredValue, StoredValueKind}; use crate::storage::{Storage, StoredValue, StoredValueKind};
use crate::wasm_module::linking::{ use crate::wasm_module::linking::{
DataSymbol, LinkingSection, RelocationSection, WasmObjectSymbol, WASM_SYM_BINDING_WEAK, DataSymbol, LinkingSection, RelocationSection, WasmObjectSymbol, WASM_SYM_BINDING_WEAK,
@ -22,8 +23,8 @@ use crate::wasm_module::{
LocalId, Signature, SymInfo, ValueType, LocalId, Signature, SymInfo, ValueType,
}; };
use crate::{ use crate::{
copy_memory, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, MEMORY_NAME, PTR_TYPE, copy_memory, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, MEMORY_NAME, PTR_SIZE,
STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME, PTR_TYPE, STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME,
}; };
/// The memory address where the constants data will be loaded during module instantiation. /// The memory address where the constants data will be loaded during module instantiation.
@ -294,16 +295,17 @@ impl<'a> WasmBackend<'a> {
_ => { _ => {
self.storage.load_symbols(&mut self.code_builder, &[*sym]); self.storage.load_symbols(&mut self.code_builder, &[*sym]);
self.code_builder.br(self.block_depth); // jump to end of function (for stack frame pop)
} }
} }
// jump to the "stack frame pop" code at the end of the function
self.code_builder.br(self.block_depth - 1);
Ok(()) Ok(())
} }
Stmt::Switch { Stmt::Switch {
cond_symbol, cond_symbol,
cond_layout: _, cond_layout,
branches, branches,
default_branch, default_branch,
ret_layout: _, ret_layout: _,
@ -321,21 +323,45 @@ impl<'a> WasmBackend<'a> {
cond_storage, cond_storage,
); );
// create (number_of_branches - 1) new blocks. // create a block for each branch except the default
for _ in 0..branches.len() { for _ in 0..branches.len() {
self.start_block(BlockType::NoResult) self.start_block(BlockType::NoResult)
} }
let is_bool = matches!(cond_layout, Layout::Builtin(Builtin::Int1));
let cond_type = WasmLayout::new(cond_layout).value_type();
// then, we jump whenever the value under scrutiny is equal to the value of a branch // then, we jump whenever the value under scrutiny is equal to the value of a branch
for (i, (value, _, _)) in branches.iter().enumerate() { for (i, (value, _, _)) in branches.iter().enumerate() {
// put the cond_symbol on the top of the stack // put the cond_symbol on the top of the stack
self.storage self.storage
.load_symbols(&mut self.code_builder, &[*cond_symbol]); .load_symbols(&mut self.code_builder, &[*cond_symbol]);
if is_bool {
// We already have a bool, don't need to compare against a const to get one
if *value == 0 {
self.code_builder.i32_eqz();
}
} else {
match cond_type {
ValueType::I32 => {
self.code_builder.i32_const(*value as i32); self.code_builder.i32_const(*value as i32);
// compare the 2 topmost values
self.code_builder.i32_eq(); self.code_builder.i32_eq();
}
ValueType::I64 => {
self.code_builder.i64_const(*value as i64);
self.code_builder.i64_eq();
}
ValueType::F32 => {
self.code_builder.f32_const(f32::from_bits(*value as u32));
self.code_builder.f32_eq();
}
ValueType::F64 => {
self.code_builder.f64_const(f64::from_bits(*value as u64));
self.code_builder.f64_eq();
}
}
}
// "break" out of `i` surrounding blocks // "break" out of `i` surrounding blocks
self.code_builder.br_if(i as u32); self.code_builder.br_if(i as u32);
@ -418,6 +444,13 @@ impl<'a> WasmBackend<'a> {
Ok(()) Ok(())
} }
Stmt::Refcounting(_modify, following) => {
// TODO: actually deal with refcounting. For hello world, we just skipped it.
self.build_stmt(following, ret_layout)?;
Ok(())
}
x => Err(format!("statement not yet implemented: {:?}", x)), x => Err(format!("statement not yet implemented: {:?}", x)),
} }
} }
@ -444,6 +477,11 @@ impl<'a> WasmBackend<'a> {
arguments, arguments,
}) => match call_type { }) => match call_type {
CallType::ByName { name: func_sym, .. } => { CallType::ByName { name: func_sym, .. } => {
// If this function is just a lowlevel wrapper, then inline it
if let Some(lowlevel) = LowLevel::from_inlined_wrapper(*func_sym) {
return self.build_low_level(lowlevel, arguments, *sym, wasm_layout);
}
let mut wasm_args_tmp: Vec<Symbol>; let mut wasm_args_tmp: Vec<Symbol>;
let (wasm_args, has_return_val) = match wasm_layout { let (wasm_args, has_return_val) = match wasm_layout {
WasmLayout::StackMemory { .. } => { WasmLayout::StackMemory { .. } => {
@ -486,10 +524,59 @@ impl<'a> WasmBackend<'a> {
} }
CallType::LowLevel { op: lowlevel, .. } => { CallType::LowLevel { op: lowlevel, .. } => {
let return_layout = WasmLayout::new(layout); self.build_low_level(*lowlevel, arguments, *sym, wasm_layout)
self.storage.load_symbols(&mut self.code_builder, arguments); }
let build_result = build_call_low_level( x => Err(format!("the call type, {:?}, is not yet implemented", x)),
},
Expr::Struct(fields) => self.create_struct(sym, layout, fields),
Expr::StructAtIndex {
index,
field_layouts,
structure,
} => {
if let StoredValue::StackMemory { location, .. } = self.storage.get(structure) {
let (local_id, mut offset) =
location.local_and_offset(self.storage.stack_frame_pointer);
for field in field_layouts.iter().take(*index as usize) {
offset += field.stack_size(PTR_SIZE);
}
self.storage.copy_value_from_memory(
&mut self.code_builder,
*sym,
local_id,
offset,
);
} else {
unreachable!("Unexpected storage for {:?}", structure)
}
Ok(())
}
x => Err(format!("Expression is not yet implemented {:?}", x)),
}
}
fn build_low_level(
&mut self,
lowlevel: LowLevel,
arguments: &'a [Symbol],
return_sym: Symbol,
return_layout: WasmLayout,
) -> Result<(), String> {
// Load symbols using the "fast calling convention" that Zig uses instead of the C ABI we normally use.
// It's only different from the C ABI for small structs, and we are using Zig for all of those cases.
// This is a workaround for a bug in Zig. If later versions fix it, we can change to the C ABI.
self.storage.load_symbols_fastcc(
&mut self.code_builder,
arguments,
return_sym,
&return_layout,
);
let build_result = decode_low_level(
&mut self.code_builder, &mut self.code_builder,
&mut self.storage, &mut self.storage,
lowlevel, lowlevel,
@ -501,7 +588,7 @@ impl<'a> WasmBackend<'a> {
match build_result { match build_result {
Done => Ok(()), Done => Ok(()),
BuiltinCall(name) => { BuiltinCall(name) => {
self.call_imported_builtin(name, arguments, &return_layout); self.call_zig_builtin(name, arguments, &return_layout);
Ok(()) Ok(())
} }
NotImplemented => Err(format!( NotImplemented => Err(format!(
@ -510,14 +597,6 @@ impl<'a> WasmBackend<'a> {
)), )),
} }
} }
x => Err(format!("the call type, {:?}, is not yet implemented", x)),
},
Expr::Struct(fields) => self.create_struct(sym, layout, fields),
x => Err(format!("Expression is not yet implemented {:?}", x)),
}
}
fn load_literal( fn load_literal(
&mut self, &mut self,
@ -578,8 +657,8 @@ impl<'a> WasmBackend<'a> {
self.lookup_string_constant(string, sym, layout); self.lookup_string_constant(string, sym, layout);
self.code_builder.get_local(local_id); self.code_builder.get_local(local_id);
self.code_builder.insert_memory_relocation(linker_sym_index); self.code_builder
self.code_builder.i32_const(elements_addr as i32); .i32_const_mem_addr(elements_addr, linker_sym_index);
self.code_builder.i32_store(Align::Bytes4, offset); self.code_builder.i32_store(Align::Bytes4, offset);
self.code_builder.get_local(local_id); self.code_builder.get_local(local_id);
@ -701,12 +780,10 @@ impl<'a> WasmBackend<'a> {
Ok(()) Ok(())
} }
fn call_imported_builtin( /// Generate a call instruction to a Zig builtin function.
&mut self, /// And if we haven't seen it before, add an Import and linker data for it.
name: &'a str, /// Zig calls use LLVM's "fast" calling convention rather than our usual C ABI.
arguments: &[Symbol], fn call_zig_builtin(&mut self, name: &'a str, arguments: &[Symbol], ret_layout: &WasmLayout) {
ret_layout: &WasmLayout,
) {
let (fn_index, linker_symbol_index) = match self.builtin_sym_index_map.get(name) { let (fn_index, linker_symbol_index) = match self.builtin_sym_index_map.get(name) {
Some(sym_idx) => match &self.linker_symbols[*sym_idx] { Some(sym_idx) => match &self.linker_symbols[*sym_idx] {
SymInfo::Function(WasmObjectSymbol::Imported { index, .. }) => { SymInfo::Function(WasmObjectSymbol::Imported { index, .. }) => {
@ -716,11 +793,29 @@ impl<'a> WasmBackend<'a> {
}, },
None => { None => {
let mut param_types = Vec::with_capacity_in(arguments.len(), self.env.arena); let mut param_types = Vec::with_capacity_in(1 + arguments.len(), self.env.arena);
param_types.extend(arguments.iter().map(|a| self.storage.get(a).value_type()));
let ret_type = if ret_layout.is_stack_memory() {
param_types.push(ValueType::I32);
None
} else {
Some(ret_layout.value_type())
};
// Zig's "fast calling convention" packs structs into CPU registers (stack machine slots) if possible.
// If they're small enough they can go into an I32 or I64. If they're big, they're pointers (I32).
for arg in arguments {
param_types.push(match self.storage.get(arg) {
StoredValue::StackMemory { size, .. } if *size > 4 && *size <= 8 => {
ValueType::I64
}
stored => stored.value_type(),
});
}
let signature_index = self.module.types.insert(Signature { let signature_index = self.module.types.insert(Signature {
param_types, param_types,
ret_type: Some(ret_layout.value_type()), // TODO: handle builtins with no return value ret_type,
}); });
let import_index = self.module.import.entries.len() as u32; let import_index = self.module.import.entries.len() as u32;
@ -731,14 +826,15 @@ impl<'a> WasmBackend<'a> {
}; };
self.module.import.entries.push(import); self.module.import.entries.push(import);
let sym_idx = self.linker_symbols.len() as u32; let sym_idx = self.linker_symbols.len();
let sym_info = SymInfo::Function(WasmObjectSymbol::Imported { let sym_info = SymInfo::Function(WasmObjectSymbol::Imported {
flags: WASM_SYM_UNDEFINED, flags: WASM_SYM_UNDEFINED,
index: import_index, index: import_index,
}); });
self.linker_symbols.push(sym_info); self.linker_symbols.push(sym_info);
self.builtin_sym_index_map.insert(name, sym_idx);
(import_index, sym_idx) (import_index, sym_idx as u32)
} }
}; };
self.code_builder.call( self.code_builder.call(

View file

@ -7,6 +7,7 @@ pub mod wasm_module;
use bumpalo::{self, collections::Vec, Bump}; use bumpalo::{self, collections::Vec, Bump};
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::ir::{Proc, ProcLayout};
use roc_mono::layout::LayoutIds; use roc_mono::layout::LayoutIds;
@ -36,7 +37,7 @@ pub fn build_module<'a>(
env: &'a Env, env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<std::vec::Vec<u8>, String> { ) -> Result<std::vec::Vec<u8>, String> {
let mut wasm_module = build_module_help(env, procedures)?; let (mut wasm_module, _) = build_module_help(env, procedures)?;
let mut buffer = std::vec::Vec::with_capacity(4096); let mut buffer = std::vec::Vec::with_capacity(4096);
wasm_module.serialize_mut(&mut buffer); wasm_module.serialize_mut(&mut buffer);
Ok(buffer) Ok(buffer)
@ -45,36 +46,54 @@ pub fn build_module<'a>(
pub fn build_module_help<'a>( pub fn build_module_help<'a>(
env: &'a Env, env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<WasmModule<'a>, String> { ) -> Result<(WasmModule<'a>, u32), String> {
let mut layout_ids = LayoutIds::default(); let mut layout_ids = LayoutIds::default();
let mut proc_symbols = Vec::with_capacity_in(procedures.len(), env.arena); let mut generated_procs = Vec::with_capacity_in(procedures.len(), env.arena);
let mut generated_symbols = Vec::with_capacity_in(procedures.len(), env.arena);
let mut linker_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena); let mut linker_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena);
let mut exports = Vec::with_capacity_in(procedures.len(), env.arena); let mut exports = Vec::with_capacity_in(4, env.arena);
let mut main_fn_index = None;
// Collect the symbols & names for the procedures // Collect the symbols & names for the procedures,
for (i, (sym, layout)) in procedures.keys().enumerate() { // and filter out procs we're going to inline
proc_symbols.push(*sym); let mut fn_index: u32 = 0;
for ((sym, layout), proc) in procedures.into_iter() {
if LowLevel::from_inlined_wrapper(sym).is_some() {
continue;
}
generated_procs.push(proc);
generated_symbols.push(sym);
let fn_name = layout_ids let fn_name = layout_ids
.get_toplevel(*sym, layout) .get_toplevel(sym, &layout)
.to_symbol_string(*sym, &env.interns); .to_symbol_string(sym, &env.interns);
if env.exposed_to_host.contains(sym) { if env.exposed_to_host.contains(&sym) {
main_fn_index = Some(fn_index);
exports.push(Export { exports.push(Export {
name: fn_name.clone(), name: fn_name.clone(),
ty: ExportType::Func, ty: ExportType::Func,
index: i as u32, index: fn_index,
}); });
} }
let linker_sym = SymInfo::for_function(i as u32, fn_name); let linker_sym = SymInfo::for_function(fn_index, fn_name);
linker_symbols.push(linker_sym); linker_symbols.push(linker_sym);
fn_index += 1;
} }
// Main loop: Build the Wasm module // Build the Wasm module
let (mut module, linker_symbols) = { let (mut module, linker_symbols) = {
let mut backend = WasmBackend::new(env, layout_ids, proc_symbols, linker_symbols, exports); let mut backend = WasmBackend::new(
for ((sym, _), proc) in procedures.into_iter() { env,
layout_ids,
generated_symbols.clone(),
linker_symbols,
exports,
);
for (proc, sym) in generated_procs.into_iter().zip(generated_symbols) {
backend.build_proc(proc, sym)?; backend.build_proc(proc, sym)?;
} }
(backend.module, backend.linker_symbols) (backend.module, backend.linker_symbols)
@ -83,7 +102,7 @@ pub fn build_module_help<'a>(
let symbol_table = LinkingSubSection::SymbolTable(linker_symbols); let symbol_table = LinkingSubSection::SymbolTable(linker_symbols);
module.linking.subsections.push(symbol_table); module.linking.subsections.push(symbol_table);
Ok(module) Ok((module, main_fn_index.unwrap()))
} }
pub struct CopyMemoryConfig { pub struct CopyMemoryConfig {

View file

@ -15,10 +15,10 @@ pub enum LowlevelBuildResult {
NotImplemented, NotImplemented,
} }
pub fn build_call_low_level<'a>( pub fn decode_low_level<'a>(
code_builder: &mut CodeBuilder<'a>, code_builder: &mut CodeBuilder<'a>,
storage: &mut Storage<'a>, storage: &mut Storage<'a>,
lowlevel: &LowLevel, lowlevel: LowLevel,
args: &'a [Symbol], args: &'a [Symbol],
ret_layout: &WasmLayout, ret_layout: &WasmLayout,
) -> LowlevelBuildResult { ) -> LowlevelBuildResult {
@ -27,16 +27,39 @@ pub fn build_call_low_level<'a>(
let panic_ret_type = || panic!("Invalid return layout for {:?}: {:?}", lowlevel, ret_layout); let panic_ret_type = || panic!("Invalid return layout for {:?}: {:?}", lowlevel, ret_layout);
match lowlevel { match lowlevel {
StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt StrConcat => return BuiltinCall(bitcode::STR_CONCAT),
| StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 | StrTrimLeft StrJoinWith => return NotImplemented, // needs Array
| StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | StrTrim | ListLen StrIsEmpty => {
| ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat code_builder.i64_const(i64::MIN);
code_builder.i64_eq();
}
StrStartsWith => return BuiltinCall(bitcode::STR_STARTS_WITH),
StrStartsWithCodePt => return BuiltinCall(bitcode::STR_STARTS_WITH_CODE_PT),
StrEndsWith => return BuiltinCall(bitcode::STR_ENDS_WITH),
StrSplit => return NotImplemented, // needs Array
StrCountGraphemes => return NotImplemented, // test needs Array
StrFromInt => return NotImplemented, // choose builtin based on storage size
StrFromUtf8 => return NotImplemented, // needs Array
StrTrimLeft => return BuiltinCall(bitcode::STR_TRIM_LEFT),
StrTrimRight => return BuiltinCall(bitcode::STR_TRIM_RIGHT),
StrFromUtf8Range => return NotImplemented, // needs Array
StrToUtf8 => return NotImplemented, // needs Array
StrRepeat => return BuiltinCall(bitcode::STR_REPEAT),
StrFromFloat => {
// linker errors for __ashlti3, __fixunsdfti, __multi3, __udivti3, __umodti3
// https://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html
// https://gcc.gnu.org/onlinedocs/gccint/Soft-float-library-routines.html
return NotImplemented;
}
StrTrim => return BuiltinCall(bitcode::STR_TRIM),
ListLen | ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat
| ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap | ListMap2 | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap | ListMap2
| ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil
| ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListTakeFirst | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListSublist
| ListTakeLast | ListDrop | ListDropAt | ListSwap | ListAny | ListFindUnsafe | DictSize | ListDropAt | ListSwap | ListAny | ListAll | ListFindUnsafe | DictSize | DictEmpty
| DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues
| DictValues | DictUnion | DictIntersection | DictDifference | DictWalk | SetFromList => { | DictUnion | DictIntersection | DictDifference | DictWalk | SetFromList => {
return NotImplemented; return NotImplemented;
} }
@ -129,6 +152,9 @@ pub fn build_call_low_level<'a>(
NumIsMultipleOf => return NotImplemented, NumIsMultipleOf => return NotImplemented,
NumAbs => match ret_layout.value_type() { NumAbs => match ret_layout.value_type() {
I32 => { I32 => {
let arg_storage = storage.get(&args[0]).to_owned();
storage.ensure_value_has_local(code_builder, args[0], arg_storage);
storage.load_symbols(code_builder, args);
code_builder.i32_const(0); code_builder.i32_const(0);
storage.load_symbols(code_builder, args); storage.load_symbols(code_builder, args);
code_builder.i32_sub(); code_builder.i32_sub();
@ -138,6 +164,9 @@ pub fn build_call_low_level<'a>(
code_builder.select(); code_builder.select();
} }
I64 => { I64 => {
let arg_storage = storage.get(&args[0]).to_owned();
storage.ensure_value_has_local(code_builder, args[0], arg_storage);
storage.load_symbols(code_builder, args);
code_builder.i64_const(0); code_builder.i64_const(0);
storage.load_symbols(code_builder, args); storage.load_symbols(code_builder, args);
code_builder.i64_sub(); code_builder.i64_sub();

View file

@ -5,7 +5,7 @@ use roc_collections::all::MutMap;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use crate::layout::WasmLayout; use crate::layout::WasmLayout;
use crate::wasm_module::{CodeBuilder, LocalId, ValueType, VirtualMachineSymbolState}; use crate::wasm_module::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState};
use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_SIZE, PTR_TYPE}; use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_SIZE, PTR_TYPE};
pub enum StoredValueKind { pub enum StoredValueKind {
@ -33,7 +33,7 @@ impl StackMemoryLocation {
pub enum StoredValue { pub enum StoredValue {
/// A value stored implicitly in the VM stack (primitives only) /// A value stored implicitly in the VM stack (primitives only)
VirtualMachineStack { VirtualMachineStack {
vm_state: VirtualMachineSymbolState, vm_state: VmSymbolState,
value_type: ValueType, value_type: ValueType,
size: u32, size: u32,
}, },
@ -126,7 +126,7 @@ impl<'a> Storage<'a> {
} }
} }
_ => StoredValue::VirtualMachineStack { _ => StoredValue::VirtualMachineStack {
vm_state: VirtualMachineSymbolState::NotYetPushed, vm_state: VmSymbolState::NotYetPushed,
value_type: *value_type, value_type: *value_type,
size: *size, size: *size,
}, },
@ -194,17 +194,10 @@ impl<'a> Storage<'a> {
}) })
} }
/// Load symbols to the top of the VM stack /// Load a single symbol using the C Calling Convention
/// Avoid calling this method in a loop with one symbol at a time! It will work, /// *Private* because external code should always load symbols in bulk (see load_symbols)
/// but it generates very inefficient Wasm code. fn load_symbol_ccc(&mut self, code_builder: &mut CodeBuilder, sym: Symbol) {
pub fn load_symbols(&mut self, code_builder: &mut CodeBuilder, symbols: &[Symbol]) { let storage = self.get(&sym).to_owned();
if code_builder.verify_stack_match(symbols) {
// The symbols were already at the top of the stack, do nothing!
// This should be quite common due to the structure of the Mono IR
return;
}
for sym in symbols.iter() {
let storage = self.get(sym).to_owned();
match storage { match storage {
StoredValue::VirtualMachineStack { StoredValue::VirtualMachineStack {
vm_state, vm_state,
@ -212,13 +205,12 @@ impl<'a> Storage<'a> {
size, size,
} => { } => {
let next_local_id = self.get_next_local_id(); let next_local_id = self.get_next_local_id();
let maybe_next_vm_state = let maybe_next_vm_state = code_builder.load_symbol(sym, vm_state, next_local_id);
code_builder.load_symbol(*sym, vm_state, next_local_id);
match maybe_next_vm_state { match maybe_next_vm_state {
// The act of loading the value changed the VM state, so update it // The act of loading the value changed the VM state, so update it
Some(next_vm_state) => { Some(next_vm_state) => {
self.symbol_storage_map.insert( self.symbol_storage_map.insert(
*sym, sym,
StoredValue::VirtualMachineStack { StoredValue::VirtualMachineStack {
vm_state: next_vm_state, vm_state: next_vm_state,
value_type, value_type,
@ -231,7 +223,7 @@ impl<'a> Storage<'a> {
// it was not in a convenient position in the VM stack. // it was not in a convenient position in the VM stack.
self.local_types.push(value_type); self.local_types.push(value_type);
self.symbol_storage_map.insert( self.symbol_storage_map.insert(
*sym, sym,
StoredValue::Local { StoredValue::Local {
local_id: next_local_id, local_id: next_local_id,
value_type, value_type,
@ -241,24 +233,88 @@ impl<'a> Storage<'a> {
} }
} }
} }
StoredValue::Local { local_id, .. }
| StoredValue::StackMemory { StoredValue::Local { local_id, .. } => {
location: StackMemoryLocation::PointerArg(local_id),
..
} => {
code_builder.get_local(local_id); code_builder.get_local(local_id);
code_builder.set_top_symbol(*sym); code_builder.set_top_symbol(sym);
} }
StoredValue::StackMemory { StoredValue::StackMemory { location, .. } => {
location: StackMemoryLocation::FrameOffset(offset), let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer);
.. code_builder.get_local(local_id);
} => { if offset != 0 {
code_builder.get_local(self.stack_frame_pointer.unwrap());
code_builder.i32_const(offset as i32); code_builder.i32_const(offset as i32);
code_builder.i32_add(); code_builder.i32_add();
code_builder.set_top_symbol(*sym);
} }
code_builder.set_top_symbol(sym);
}
}
}
/// Load symbols to the top of the VM stack
/// Avoid calling this method in a loop with one symbol at a time! It will work,
/// but it generates very inefficient Wasm code.
pub fn load_symbols(&mut self, code_builder: &mut CodeBuilder, symbols: &[Symbol]) {
if code_builder.verify_stack_match(symbols) {
// The symbols were already at the top of the stack, do nothing!
// This should be quite common due to the structure of the Mono IR
return;
}
for sym in symbols.iter() {
self.load_symbol_ccc(code_builder, *sym);
}
}
/// Load symbols in a way compatible with LLVM's "fast calling convention"
/// A bug in Zig means it always uses this for Wasm even when we specify C calling convention.
/// It squashes small structs into primitive values where possible, avoiding stack memory
/// in favour of CPU registers (or VM stack values, which eventually become CPU registers).
/// We need to convert some of our structs from our internal C-like representation to work with Zig.
/// We are sticking to C ABI for better compatibility on the platform side.
pub fn load_symbols_fastcc(
&mut self,
code_builder: &mut CodeBuilder,
symbols: &[Symbol],
return_symbol: Symbol,
return_layout: &WasmLayout,
) {
// Note: we are not doing verify_stack_match in this case so we may generate more code.
// We would need more bookkeeping in CodeBuilder to track which representation is on the stack!
if return_layout.is_stack_memory() {
// Load the address where the return value should be written
// Apparently for return values we still use a pointer to stack memory
self.load_symbol_ccc(code_builder, return_symbol);
};
for sym in symbols {
if let StoredValue::StackMemory {
location,
size,
alignment_bytes,
} = self.get(sym)
{
if *size == 0 {
unimplemented!("Passing zero-sized values is not implemented yet");
} else if *size > 8 {
return self.load_symbol_ccc(code_builder, *sym);
}
let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer);
code_builder.get_local(local_id);
let align = Align::from(*alignment_bytes);
if *size == 1 {
code_builder.i32_load8_u(align, offset);
} else if *size == 2 {
code_builder.i32_load16_u(align, offset);
} else if *size <= 4 {
code_builder.i32_load(align, offset);
} else {
code_builder.i64_load(align, offset);
}
} else {
self.load_symbol_ccc(code_builder, *sym);
} }
} }
} }
@ -319,6 +375,67 @@ impl<'a> Storage<'a> {
} }
} }
/// Generate code to copy a StoredValue from an arbitrary memory location
/// (defined by a pointer and offset).
pub fn copy_value_from_memory(
&mut self,
code_builder: &mut CodeBuilder,
to_symbol: Symbol,
from_ptr: LocalId,
from_offset: u32,
) -> u32 {
let to_storage = self.get(&to_symbol).to_owned();
match to_storage {
StoredValue::StackMemory {
location,
size,
alignment_bytes,
} => {
let (to_ptr, to_offset) = location.local_and_offset(self.stack_frame_pointer);
copy_memory(
code_builder,
CopyMemoryConfig {
from_ptr,
from_offset,
to_ptr,
to_offset,
size,
alignment_bytes,
},
);
size
}
StoredValue::VirtualMachineStack {
value_type, size, ..
}
| StoredValue::Local {
value_type, size, ..
} => {
use crate::wasm_module::Align::*;
code_builder.get_local(from_ptr);
match (value_type, size) {
(ValueType::I64, 8) => code_builder.i64_load(Bytes8, from_offset),
(ValueType::I32, 4) => code_builder.i32_load(Bytes4, from_offset),
(ValueType::I32, 2) => code_builder.i32_load16_s(Bytes2, from_offset),
(ValueType::I32, 1) => code_builder.i32_load8_s(Bytes1, from_offset),
(ValueType::F32, 4) => code_builder.f32_load(Bytes4, from_offset),
(ValueType::F64, 8) => code_builder.f64_load(Bytes8, from_offset),
_ => {
panic!("Cannot store {:?} with alignment of {:?}", value_type, size);
}
};
if let StoredValue::Local { local_id, .. } = to_storage {
code_builder.set_local(local_id);
}
size
}
}
}
/// Generate code to copy from one StoredValue to another /// Generate code to copy from one StoredValue to another
/// Copies the _entire_ value. For struct fields etc., see `copy_value_to_memory` /// Copies the _entire_ value. For struct fields etc., see `copy_value_to_memory`
pub fn clone_value( pub fn clone_value(
@ -422,7 +539,7 @@ impl<'a> Storage<'a> {
} = storage } = storage
{ {
let local_id = self.get_next_local_id(); let local_id = self.get_next_local_id();
if vm_state != VirtualMachineSymbolState::NotYetPushed { if vm_state != VmSymbolState::NotYetPushed {
code_builder.load_symbol(symbol, vm_state, local_id); code_builder.load_symbol(symbol, vm_state, local_id);
code_builder.set_local(local_id); code_builder.set_local(local_id);
} }

View file

@ -1,7 +1,6 @@
use bumpalo::collections::vec::Vec; use bumpalo::collections::vec::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use core::panic; use core::panic;
use std::fmt::Debug;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -10,6 +9,13 @@ use super::opcodes::{OpCode, OpCode::*};
use super::serialize::{SerialBuffer, Serialize}; use super::serialize::{SerialBuffer, Serialize};
use crate::{round_up_to_alignment, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID}; use crate::{round_up_to_alignment, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID};
const ENABLE_DEBUG_LOG: bool = false;
macro_rules! log_instruction {
($($x: expr),+) => {
if ENABLE_DEBUG_LOG { println!($($x,)*); }
};
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LocalId(pub u32); pub struct LocalId(pub u32);
@ -29,6 +35,7 @@ impl Serialize for ValueType {
} }
} }
#[derive(PartialEq, Eq, Debug)]
pub enum BlockType { pub enum BlockType {
NoResult, NoResult,
Value(ValueType), Value(ValueType),
@ -43,6 +50,31 @@ impl BlockType {
} }
} }
/// A control block in our model of the VM
/// Child blocks cannot "see" values from their parent block
struct VmBlock<'a> {
/// opcode indicating what kind of block this is
opcode: OpCode,
/// the stack of values for this block
value_stack: Vec<'a, Symbol>,
/// whether this block pushes a result value to its parent
has_result: bool,
}
impl std::fmt::Debug for VmBlock<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"{:?} {}",
self.opcode,
if self.has_result {
"Result"
} else {
"NoResult"
}
))
}
}
/// Wasm memory alignment. (Rust representation matches Wasm encoding) /// Wasm memory alignment. (Rust representation matches Wasm encoding)
#[repr(u8)] #[repr(u8)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -73,7 +105,7 @@ impl From<u32> for Align {
} }
#[derive(Debug, Clone, PartialEq, Copy)] #[derive(Debug, Clone, PartialEq, Copy)]
pub enum VirtualMachineSymbolState { pub enum VmSymbolState {
/// Value doesn't exist yet /// Value doesn't exist yet
NotYetPushed, NotYetPushed,
@ -113,6 +145,8 @@ macro_rules! instruction_memargs {
#[derive(Debug)] #[derive(Debug)]
pub struct CodeBuilder<'a> { pub struct CodeBuilder<'a> {
arena: &'a Bump,
/// The main container for the instructions /// The main container for the instructions
code: Vec<'a, u8>, code: Vec<'a, u8>,
@ -135,8 +169,8 @@ pub struct CodeBuilder<'a> {
inner_length: Vec<'a, u8>, inner_length: Vec<'a, u8>,
/// Our simulation model of the Wasm stack machine /// Our simulation model of the Wasm stack machine
/// Keeps track of where Symbol values are in the VM stack /// Nested blocks of instructions. A child block can't "see" the stack of its parent block
vm_stack: Vec<'a, Symbol>, vm_block_stack: Vec<'a, VmBlock<'a>>,
/// Linker info to help combine the Roc module with builtin & platform modules, /// Linker info to help combine the Roc module with builtin & platform modules,
/// e.g. to modify call instructions when function indices change /// e.g. to modify call instructions when function indices change
@ -146,13 +180,22 @@ pub struct CodeBuilder<'a> {
#[allow(clippy::new_without_default)] #[allow(clippy::new_without_default)]
impl<'a> CodeBuilder<'a> { impl<'a> CodeBuilder<'a> {
pub fn new(arena: &'a Bump) -> Self { pub fn new(arena: &'a Bump) -> Self {
let mut vm_block_stack = Vec::with_capacity_in(8, arena);
let function_block = VmBlock {
opcode: BLOCK,
has_result: true,
value_stack: Vec::with_capacity_in(8, arena),
};
vm_block_stack.push(function_block);
CodeBuilder { CodeBuilder {
arena,
code: Vec::with_capacity_in(1024, arena), code: Vec::with_capacity_in(1024, arena),
insertions: Vec::with_capacity_in(32, arena), insertions: Vec::with_capacity_in(32, arena),
insert_bytes: Vec::with_capacity_in(64, arena), insert_bytes: Vec::with_capacity_in(64, arena),
preamble: Vec::with_capacity_in(32, arena), preamble: Vec::with_capacity_in(32, arena),
inner_length: Vec::with_capacity_in(5, arena), inner_length: Vec::with_capacity_in(5, arena),
vm_stack: Vec::with_capacity_in(32, arena), vm_block_stack,
relocations: Vec::with_capacity_in(32, arena), relocations: Vec::with_capacity_in(32, arena),
} }
} }
@ -167,45 +210,49 @@ impl<'a> CodeBuilder<'a> {
***********************************************************/ ***********************************************************/
/// Set the Symbol that is at the top of the VM stack right now fn current_stack(&self) -> &Vec<'a, Symbol> {
/// We will use this later when we need to load the Symbol let block = self.vm_block_stack.last().unwrap();
pub fn set_top_symbol(&mut self, sym: Symbol) -> VirtualMachineSymbolState { &block.value_stack
let len = self.vm_stack.len();
let pushed_at = self.code.len();
if len == 0 {
panic!(
"trying to set symbol with nothing on stack, code = {:?}",
self.code
);
} }
self.vm_stack[len - 1] = sym; fn current_stack_mut(&mut self) -> &mut Vec<'a, Symbol> {
let block = self.vm_block_stack.last_mut().unwrap();
&mut block.value_stack
}
VirtualMachineSymbolState::Pushed { pushed_at } /// Set the Symbol that is at the top of the VM stack right now
/// We will use this later when we need to load the Symbol
pub fn set_top_symbol(&mut self, sym: Symbol) -> VmSymbolState {
let current_stack = &mut self.vm_block_stack.last_mut().unwrap().value_stack;
let pushed_at = self.code.len();
let top_symbol: &mut Symbol = current_stack.last_mut().unwrap();
*top_symbol = sym;
VmSymbolState::Pushed { pushed_at }
} }
/// Verify if a sequence of symbols is at the top of the stack /// Verify if a sequence of symbols is at the top of the stack
pub fn verify_stack_match(&self, symbols: &[Symbol]) -> bool { pub fn verify_stack_match(&self, symbols: &[Symbol]) -> bool {
let current_stack = self.current_stack();
let n_symbols = symbols.len(); let n_symbols = symbols.len();
let stack_depth = self.vm_stack.len(); let stack_depth = current_stack.len();
if n_symbols > stack_depth { if n_symbols > stack_depth {
return false; return false;
} }
let offset = stack_depth - n_symbols; let offset = stack_depth - n_symbols;
for (i, sym) in symbols.iter().enumerate() { for (i, sym) in symbols.iter().enumerate() {
if self.vm_stack[offset + i] != *sym { if current_stack[offset + i] != *sym {
return false; return false;
} }
} }
true true
} }
fn add_insertion(&mut self, insert_at: usize, opcode: u8, immediate: u32) { fn add_insertion(&mut self, insert_at: usize, opcode: OpCode, immediate: u32) {
let start = self.insert_bytes.len(); let start = self.insert_bytes.len();
self.insert_bytes.push(opcode); self.insert_bytes.push(opcode as u8);
self.insert_bytes.encode_u32(immediate); self.insert_bytes.encode_u32(immediate);
self.insertions.push(Insertion { self.insertions.push(Insertion {
@ -213,6 +260,13 @@ impl<'a> CodeBuilder<'a> {
start, start,
end: self.insert_bytes.len(), end: self.insert_bytes.len(),
}); });
log_instruction!(
"**insert {:?} {} at byte offset {}**",
opcode,
immediate,
insert_at
);
} }
/// Load a Symbol that is stored in the VM stack /// Load a Symbol that is stored in the VM stack
@ -225,41 +279,56 @@ impl<'a> CodeBuilder<'a> {
pub fn load_symbol( pub fn load_symbol(
&mut self, &mut self,
symbol: Symbol, symbol: Symbol,
vm_state: VirtualMachineSymbolState, vm_state: VmSymbolState,
next_local_id: LocalId, next_local_id: LocalId,
) -> Option<VirtualMachineSymbolState> { ) -> Option<VmSymbolState> {
use VirtualMachineSymbolState::*; use VmSymbolState::*;
match vm_state { match vm_state {
NotYetPushed => panic!("Symbol {:?} has no value yet. Nothing to load.", symbol), NotYetPushed => unreachable!("Symbol {:?} has no value yet. Nothing to load.", symbol),
Pushed { pushed_at } => { Pushed { pushed_at } => {
let &top = self.vm_stack.last().unwrap(); match self.current_stack().last() {
if top == symbol { Some(top_symbol) if *top_symbol == symbol => {
// We're lucky, the symbol is already on top of the VM stack // We're lucky, the symbol is already on top of the current block's stack.
// No code to generate! (This reduces code size by up to 25% in tests.) // No code to generate! (This reduces code size by up to 25% in tests.)
// Just let the caller know what happened // Just let the caller know what happened
Some(Popped { pushed_at }) Some(Popped { pushed_at })
}
_ => {
// Symbol is not on top of the stack.
// We should have saved it to a local, so go back and do that now.
// It should still be on the stack in the block where it was assigned. Remove it.
let mut found = false;
for block in self.vm_block_stack.iter_mut() {
if let Some(found_index) =
block.value_stack.iter().position(|&s| s == symbol)
{
block.value_stack.remove(found_index);
found = true;
}
}
// Go back to the code position where it was pushed, and save it to a local
if found {
self.add_insertion(pushed_at, SETLOCAL, next_local_id.0);
} else { } else {
// Symbol is not on top of the stack. Find it. if ENABLE_DEBUG_LOG {
if let Some(found_index) = self.vm_stack.iter().rposition(|&s| s == symbol) { println!(
// Insert a local.set where the value was created "{:?} has been popped implicitly. Leaving it on the stack.",
self.add_insertion(pushed_at, SETLOCAL as u8, next_local_id.0); symbol
);
}
self.add_insertion(pushed_at, TEELOCAL, next_local_id.0);
}
// Take the value out of the stack where local.set was inserted // Recover the value again at the current position
self.vm_stack.remove(found_index);
// Insert a local.get at the current position
self.get_local(next_local_id); self.get_local(next_local_id);
self.vm_stack.push(symbol); self.set_top_symbol(symbol);
// This Symbol is no longer stored in the VM stack, but in a local // This Symbol is no longer stored in the VM stack, but in a local
None None
} else {
panic!(
"{:?} has state {:?} but not found in VM stack",
symbol, vm_state
);
} }
} }
} }
@ -267,11 +336,11 @@ impl<'a> CodeBuilder<'a> {
Popped { pushed_at } => { Popped { pushed_at } => {
// This Symbol is being used for a second time // This Symbol is being used for a second time
// Insert a local.tee where it was pushed, so we don't interfere with the first usage // Insert a local.tee where it was pushed, so we don't interfere with the first usage
self.add_insertion(pushed_at, TEELOCAL as u8, next_local_id.0); self.add_insertion(pushed_at, TEELOCAL, next_local_id.0);
// Insert a local.get at the current position // Insert a local.get at the current position
self.get_local(next_local_id); self.get_local(next_local_id);
self.vm_stack.push(symbol); self.set_top_symbol(symbol);
// This symbol has been promoted to a Local // This symbol has been promoted to a Local
// Tell the caller it no longer has a VirtualMachineSymbolState // Tell the caller it no longer has a VirtualMachineSymbolState
@ -282,7 +351,7 @@ impl<'a> CodeBuilder<'a> {
/********************************************************** /**********************************************************
FINALIZE AND SERIALIZE FUNCTION HEADER
***********************************************************/ ***********************************************************/
@ -375,6 +444,12 @@ impl<'a> CodeBuilder<'a> {
self.insertions.sort_by_key(|ins| ins.at); self.insertions.sort_by_key(|ins| ins.at);
} }
/**********************************************************
SERIALIZE
***********************************************************/
/// Serialize all byte vectors in the right order /// Serialize all byte vectors in the right order
/// Also update relocation offsets relative to the base offset (code section body start) /// Also update relocation offsets relative to the base offset (code section body start)
pub fn serialize_with_relocs<T: SerialBuffer>( pub fn serialize_with_relocs<T: SerialBuffer>(
@ -433,38 +508,78 @@ impl<'a> CodeBuilder<'a> {
/// Base method for generating instructions /// Base method for generating instructions
/// Emits the opcode and simulates VM stack push/pop /// Emits the opcode and simulates VM stack push/pop
fn inst(&mut self, opcode: OpCode, pops: usize, push: bool) { fn inst_base(&mut self, opcode: OpCode, pops: usize, push: bool) {
let new_len = self.vm_stack.len() - pops as usize; let current_stack = self.current_stack_mut();
self.vm_stack.truncate(new_len); let new_len = current_stack.len() - pops as usize;
current_stack.truncate(new_len);
if push { if push {
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE); current_stack.push(Symbol::WASM_TMP);
} }
self.code.push(opcode as u8); self.code.push(opcode as u8);
} }
fn inst_imm8(&mut self, opcode: OpCode, pops: usize, push: bool, immediate: u8) { /// Plain instruction without any immediates
self.inst(opcode, pops, push); fn inst(&mut self, opcode: OpCode, pops: usize, push: bool) {
self.code.push(immediate); self.inst_base(opcode, pops, push);
log_instruction!(
"{:10}\t\t{:?}",
format!("{:?}", opcode),
self.current_stack()
);
} }
// public for use in test code /// Block instruction
pub fn inst_imm32(&mut self, opcode: OpCode, pops: usize, push: bool, immediate: u32) { fn inst_block(&mut self, opcode: OpCode, pops: usize, block_type: BlockType) {
self.inst(opcode, pops, push); self.inst_base(opcode, pops, false);
self.code.push(block_type.as_byte());
// Start a new block with a fresh value stack
self.vm_block_stack.push(VmBlock {
opcode,
value_stack: Vec::with_capacity_in(8, self.arena),
has_result: block_type != BlockType::NoResult,
});
log_instruction!(
"{:10} {:?}\t{:?}",
format!("{:?}", opcode),
block_type,
&self.vm_block_stack
);
}
fn inst_imm32(&mut self, opcode: OpCode, pops: usize, push: bool, immediate: u32) {
self.inst_base(opcode, pops, push);
self.code.encode_u32(immediate); self.code.encode_u32(immediate);
log_instruction!(
"{:10}\t{}\t{:?}",
format!("{:?}", opcode),
immediate,
self.current_stack()
);
} }
fn inst_mem(&mut self, opcode: OpCode, pops: usize, push: bool, align: Align, offset: u32) { fn inst_mem(&mut self, opcode: OpCode, pops: usize, push: bool, align: Align, offset: u32) {
self.inst(opcode, pops, push); self.inst_base(opcode, pops, push);
self.code.push(align as u8); self.code.push(align as u8);
self.code.encode_u32(offset); self.code.encode_u32(offset);
log_instruction!(
"{:10} {:?} {}\t{:?}",
format!("{:?}", opcode),
align,
offset,
self.current_stack()
);
} }
/// Insert a linker relocation for a memory address /// Insert a const reference to a memory address
pub fn insert_memory_relocation(&mut self, symbol_index: u32) { pub fn i32_const_mem_addr(&mut self, addr: u32, symbol_index: u32) {
self.inst_base(I32CONST, 0, true);
let offset = self.code.len() as u32;
self.code.encode_padded_u32(addr);
self.relocations.push(RelocationEntry::Offset { self.relocations.push(RelocationEntry::Offset {
type_id: OffsetRelocType::MemoryAddrLeb, type_id: OffsetRelocType::MemoryAddrLeb,
offset: self.code.len() as u32, offset,
symbol_index, symbol_index,
addend: 0, addend: 0,
}); });
@ -484,22 +599,38 @@ impl<'a> CodeBuilder<'a> {
instruction_no_args!(nop, NOP, 0, false); instruction_no_args!(nop, NOP, 0, false);
pub fn block(&mut self, ty: BlockType) { pub fn block(&mut self, ty: BlockType) {
self.inst_imm8(BLOCK, 0, false, ty.as_byte()); self.inst_block(BLOCK, 0, ty);
} }
pub fn loop_(&mut self, ty: BlockType) { pub fn loop_(&mut self, ty: BlockType) {
self.inst_imm8(LOOP, 0, false, ty.as_byte()); self.inst_block(LOOP, 0, ty);
} }
pub fn if_(&mut self, ty: BlockType) { pub fn if_(&mut self, ty: BlockType) {
self.inst_imm8(IF, 1, false, ty.as_byte()); self.inst_block(IF, 1, ty);
}
pub fn else_(&mut self) {
// Reuse the 'then' block but clear its value stack
self.current_stack_mut().clear();
self.inst(ELSE, 0, false);
} }
instruction_no_args!(else_, ELSE, 0, false); pub fn end(&mut self) {
instruction_no_args!(end, END, 0, false); self.inst_base(END, 0, false);
let ended_block = self.vm_block_stack.pop().unwrap();
if ended_block.has_result {
let result = ended_block.value_stack.last().unwrap();
self.current_stack_mut().push(*result)
}
log_instruction!("END \t\t{:?}", &self.vm_block_stack);
}
pub fn br(&mut self, levels: u32) { pub fn br(&mut self, levels: u32) {
self.inst_imm32(BR, 0, false, levels); self.inst_imm32(BR, 0, false, levels);
} }
pub fn br_if(&mut self, levels: u32) { pub fn br_if(&mut self, levels: u32) {
// In dynamic execution, br_if can pop 2 values if condition is true and the target block has a result.
// But our stack model is for *static* analysis and we need it to be correct at the next instruction,
// where the branch was not taken. So we only pop 1 value, the condition.
self.inst_imm32(BRIF, 1, false, levels); self.inst_imm32(BRIF, 1, false, levels);
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -516,30 +647,26 @@ impl<'a> CodeBuilder<'a> {
n_args: usize, n_args: usize,
has_return_val: bool, has_return_val: bool,
) { ) {
let stack_depth = self.vm_stack.len(); self.inst_base(CALL, n_args, has_return_val);
if n_args > stack_depth {
panic!(
"Trying to call to call function {:?} with {:?} values but only {:?} on the VM stack\n{:?}",
function_index, n_args, stack_depth, self
);
}
self.vm_stack.truncate(stack_depth - n_args);
if has_return_val {
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
}
self.code.push(CALL as u8);
// Write the index of the function to be called.
// Also make a RelocationEntry so the linker can see that this byte offset relates to a function by name.
// Here we initialise the offset to an index of self.code. After completing the function, we'll add
// other factors to make it relative to the code section. (All insertions will be known then.)
let offset = self.code.len() as u32; let offset = self.code.len() as u32;
self.code.encode_padded_u32(function_index); self.code.encode_padded_u32(function_index);
// Make a RelocationEntry so the linker can see that this byte offset relates to a function by name.
// Here we initialise the offset to an index of self.code. After completing the function, we'll add
// other factors to make it relative to the code section. (All insertions will be known then.)
self.relocations.push(RelocationEntry::Index { self.relocations.push(RelocationEntry::Index {
type_id: IndexRelocType::FunctionIndexLeb, type_id: IndexRelocType::FunctionIndexLeb,
offset, offset,
symbol_index, symbol_index,
}); });
log_instruction!(
"{:10}\t{}\t{:?}",
format!("{:?}", CALL),
function_index,
self.current_stack()
);
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -591,26 +718,44 @@ impl<'a> CodeBuilder<'a> {
instruction_memargs!(i64_store32, I64STORE32, 2, false); instruction_memargs!(i64_store32, I64STORE32, 2, false);
pub fn memory_size(&mut self) { pub fn memory_size(&mut self) {
self.inst_imm8(CURRENTMEMORY, 0, true, 0); self.inst(CURRENTMEMORY, 0, true);
self.code.push(0);
} }
pub fn memory_grow(&mut self) { pub fn memory_grow(&mut self) {
self.inst_imm8(GROWMEMORY, 1, true, 0); self.inst(GROWMEMORY, 1, true);
self.code.push(0);
}
fn log_const<T>(&self, opcode: OpCode, x: T)
where
T: std::fmt::Debug + std::fmt::Display,
{
log_instruction!(
"{:10}\t{}\t{:?}",
format!("{:?}", opcode),
x,
self.current_stack()
);
} }
pub fn i32_const(&mut self, x: i32) { pub fn i32_const(&mut self, x: i32) {
self.inst(I32CONST, 0, true); self.inst_base(I32CONST, 0, true);
self.code.encode_i32(x); self.code.encode_i32(x);
self.log_const(I32CONST, x);
} }
pub fn i64_const(&mut self, x: i64) { pub fn i64_const(&mut self, x: i64) {
self.inst(I64CONST, 0, true); self.inst_base(I64CONST, 0, true);
self.code.encode_i64(x); self.code.encode_i64(x);
self.log_const(I64CONST, x);
} }
pub fn f32_const(&mut self, x: f32) { pub fn f32_const(&mut self, x: f32) {
self.inst(F32CONST, 0, true); self.inst_base(F32CONST, 0, true);
self.code.encode_f32(x); self.code.encode_f32(x);
self.log_const(F32CONST, x);
} }
pub fn f64_const(&mut self, x: f64) { pub fn f64_const(&mut self, x: f64) {
self.inst(F64CONST, 0, true); self.inst_base(F64CONST, 0, true);
self.code.encode_f64(x); self.code.encode_f64(x);
self.log_const(F64CONST, x);
} }
// TODO: Consider creating unified methods for numerical ops like 'eq' and 'add', // TODO: Consider creating unified methods for numerical ops like 'eq' and 'add',

View file

@ -4,8 +4,6 @@ pub mod opcodes;
pub mod sections; pub mod sections;
pub mod serialize; pub mod serialize;
pub use code_builder::{ pub use code_builder::{Align, BlockType, CodeBuilder, LocalId, ValueType, VmSymbolState};
Align, BlockType, CodeBuilder, LocalId, ValueType, VirtualMachineSymbolState,
};
pub use linking::{LinkingSubSection, SymInfo}; pub use linking::{LinkingSubSection, SymInfo};
pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature, WasmModule}; pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature, WasmModule};

View file

@ -1,5 +1,5 @@
#[repr(u8)] #[repr(u8)]
#[derive(Debug)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OpCode { pub enum OpCode {
UNREACHABLE = 0x00, UNREACHABLE = 0x00,
NOP = 0x01, NOP = 0x01,

View file

@ -29,6 +29,8 @@ pub enum SectionId {
Element = 9, Element = 9,
Code = 10, Code = 10,
Data = 11, Data = 11,
/// DataCount section is unused. Only needed for single-pass validation of
/// memory.init and data.drop, which we don't use
DataCount = 12, DataCount = 12,
} }
@ -239,8 +241,7 @@ impl<'a> Serialize for ImportSection<'a> {
#[derive(Debug)] #[derive(Debug)]
pub struct FunctionSection<'a> { pub struct FunctionSection<'a> {
/// Private. See WasmModule::add_function_signature pub signature_indices: Vec<'a, u32>,
signature_indices: Vec<'a, u32>,
} }
impl<'a> FunctionSection<'a> { impl<'a> FunctionSection<'a> {
@ -525,42 +526,6 @@ impl Serialize for DataSection<'_> {
} }
} }
/*******************************************************************
*
* Data Count section
*
* Pre-declares the number of segments in the Data section.
* This helps the runtime to validate the module in a single pass.
* The order of sections is DataCount -> Code -> Data
*
*******************************************************************/
#[derive(Debug)]
struct DataCountSection {
count: u32,
}
impl DataCountSection {
fn new(data_section: &DataSection<'_>) -> Self {
let count = data_section
.segments
.iter()
.filter(|seg| !seg.init.is_empty())
.count() as u32;
DataCountSection { count }
}
}
impl Serialize for DataCountSection {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
if self.count > 0 {
let header_indices = write_section_header(buffer, SectionId::DataCount);
buffer.encode_u32(self.count);
update_section_size(buffer, header_indices);
}
}
}
/******************************************************************* /*******************************************************************
* *
* Module * Module
@ -658,11 +623,6 @@ impl<'a> WasmModule<'a> {
counter.serialize_and_count(buffer, &self.start); counter.serialize_and_count(buffer, &self.start);
counter.serialize_and_count(buffer, &self.element); counter.serialize_and_count(buffer, &self.element);
// Data Count section forward-declares the size of the Data section
// so that Code section can be validated in one pass
let data_count_section = DataCountSection::new(&self.data);
counter.serialize_and_count(buffer, &data_count_section);
// Code section is the only one with relocations so we can stop counting // Code section is the only one with relocations so we can stop counting
let code_section_index = counter.section_index; let code_section_index = counter.section_index;
let code_section_body_index = self let code_section_body_index = self

View file

@ -18,7 +18,7 @@ roc_unify = { path = "../unify" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
roc_reporting = { path = "../reporting" } roc_reporting = { path = "../../reporting" }
morphic_lib = { path = "../../vendor/morphic_lib" } morphic_lib = { path = "../../vendor/morphic_lib" }
ven_pretty = { path = "../../vendor/pretty" } ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }

View file

@ -235,16 +235,12 @@ fn generate_entry_doc<'a>(
fn type_to_docs(in_func_type_ann: bool, type_annotation: ast::TypeAnnotation) -> TypeAnnotation { fn type_to_docs(in_func_type_ann: bool, type_annotation: ast::TypeAnnotation) -> TypeAnnotation {
match type_annotation { match type_annotation {
ast::TypeAnnotation::TagUnion { ast::TypeAnnotation::TagUnion { tags, ext } => {
tags,
ext,
final_comments: _,
} => {
let mut tags_to_render: Vec<Tag> = Vec::new(); let mut tags_to_render: Vec<Tag> = Vec::new();
let mut any_tags_are_private = false; let mut any_tags_are_private = false;
for tag in tags { for tag in tags.iter() {
match tag_to_doc(in_func_type_ann, tag.value) { match tag_to_doc(in_func_type_ann, tag.value) {
None => { None => {
any_tags_are_private = true; any_tags_are_private = true;

View file

@ -5,8 +5,8 @@ use roc_can::expr::{ClosureData, Expr, Recursive};
use roc_can::pattern::Pattern; use roc_can::pattern::Pattern;
use roc_can::scope::Scope; use roc_can::scope::Scope;
use roc_collections::all::{MutSet, SendMap}; use roc_collections::all::{MutSet, SendMap};
use roc_module::called_via::CalledVia;
use roc_module::ident::TagName; use roc_module::ident::TagName;
use roc_module::operator::CalledVia;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};

View file

@ -19,8 +19,7 @@ use roc_module::symbol::{
Symbol, Symbol,
}; };
use roc_mono::ir::{ use roc_mono::ir::{
CapturedSymbols, EntryPoint, ExternalSpecializations, PartialProc, PendingSpecialization, Proc, CapturedSymbols, EntryPoint, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs,
ProcLayout, Procs,
}; };
use roc_mono::layout::{Layout, LayoutCache, LayoutProblem}; use roc_mono::layout::{Layout, LayoutCache, LayoutProblem};
use roc_parse::ast::{self, StrLiteral, TypeAnnotation}; use roc_parse::ast::{self, StrLiteral, TypeAnnotation};
@ -356,7 +355,7 @@ struct ModuleCache<'a> {
constrained: MutMap<ModuleId, ConstrainedModule>, constrained: MutMap<ModuleId, ConstrainedModule>,
typechecked: MutMap<ModuleId, TypeCheckedModule<'a>>, typechecked: MutMap<ModuleId, TypeCheckedModule<'a>>,
found_specializations: MutMap<ModuleId, FoundSpecializationsModule<'a>>, found_specializations: MutMap<ModuleId, FoundSpecializationsModule<'a>>,
external_specializations_requested: MutMap<ModuleId, ExternalSpecializations<'a>>, external_specializations_requested: MutMap<ModuleId, Vec<ExternalSpecializations>>,
/// Various information /// Various information
imports: MutMap<ModuleId, MutSet<ModuleId>>, imports: MutMap<ModuleId, MutSet<ModuleId>>,
@ -587,7 +586,7 @@ fn start_phase<'a>(
.module_cache .module_cache
.external_specializations_requested .external_specializations_requested
.remove(&module_id) .remove(&module_id)
.unwrap_or_else(|| ExternalSpecializations::new_in(arena)); .unwrap_or_default();
let FoundSpecializationsModule { let FoundSpecializationsModule {
module_id, module_id,
@ -831,7 +830,7 @@ enum Msg<'a> {
module_id: ModuleId, module_id: ModuleId,
ident_ids: IdentIds, ident_ids: IdentIds,
layout_cache: LayoutCache<'a>, layout_cache: LayoutCache<'a>,
external_specializations_requested: BumpMap<ModuleId, ExternalSpecializations<'a>>, external_specializations_requested: BumpMap<ModuleId, ExternalSpecializations>,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
problems: Vec<roc_mono::ir::MonoProblem>, problems: Vec<roc_mono::ir::MonoProblem>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
@ -911,9 +910,6 @@ struct State<'a> {
/// pending specializations in the same thread. /// pending specializations in the same thread.
pub needs_specialization: MutSet<ModuleId>, pub needs_specialization: MutSet<ModuleId>,
pub all_pending_specializations:
MutMap<Symbol, MutMap<ProcLayout<'a>, PendingSpecialization<'a>>>,
pub specializations_in_flight: u32, pub specializations_in_flight: u32,
pub timings: MutMap<ModuleId, ModuleTiming>, pub timings: MutMap<ModuleId, ModuleTiming>,
@ -1054,7 +1050,7 @@ enum BuildTask<'a> {
subs: Subs, subs: Subs,
procs_base: ProcsBase<'a>, procs_base: ProcsBase<'a>,
layout_cache: LayoutCache<'a>, layout_cache: LayoutCache<'a>,
specializations_we_must_make: ExternalSpecializations<'a>, specializations_we_must_make: Vec<ExternalSpecializations>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
}, },
} }
@ -1538,7 +1534,6 @@ where
unsolved_modules: MutMap::default(), unsolved_modules: MutMap::default(),
timings: MutMap::default(), timings: MutMap::default(),
needs_specialization: MutSet::default(), needs_specialization: MutSet::default(),
all_pending_specializations: MutMap::default(),
specializations_in_flight: 0, specializations_in_flight: 0,
layout_caches: std::vec::Vec::with_capacity(num_cpus::get()), layout_caches: std::vec::Vec::with_capacity(num_cpus::get()),
procs: Procs::new_in(arena), procs: Procs::new_in(arena),
@ -2067,17 +2062,6 @@ fn update<'a>(
log!("found specializations for {:?}", module_id); log!("found specializations for {:?}", module_id);
let subs = solved_subs.into_inner(); let subs = solved_subs.into_inner();
for (symbol, specs) in &procs_base.specializations_for_host {
let existing = match state.all_pending_specializations.entry(*symbol) {
Vacant(entry) => entry.insert(MutMap::default()),
Occupied(entry) => entry.into_mut(),
};
for (layout, pend) in specs {
existing.insert(*layout, pend.clone());
}
}
state state
.module_cache .module_cache
.top_level_thunks .top_level_thunks
@ -2171,11 +2155,11 @@ fn update<'a>(
.external_specializations_requested .external_specializations_requested
.entry(module_id) .entry(module_id)
{ {
Vacant(entry) => entry.insert(ExternalSpecializations::new_in(arena)), Vacant(entry) => entry.insert(vec![]),
Occupied(entry) => entry.into_mut(), Occupied(entry) => entry.into_mut(),
}; };
existing.extend(requested); existing.push(requested);
} }
msg_tx msg_tx
@ -2198,11 +2182,11 @@ fn update<'a>(
.external_specializations_requested .external_specializations_requested
.entry(module_id) .entry(module_id)
{ {
Vacant(entry) => entry.insert(ExternalSpecializations::new_in(arena)), Vacant(entry) => entry.insert(vec![]),
Occupied(entry) => entry.into_mut(), Occupied(entry) => entry.into_mut(),
}; };
existing.extend(requested); existing.push(requested);
} }
start_tasks(arena, &mut state, work, injector, worker_listeners)?; start_tasks(arena, &mut state, work, injector, worker_listeners)?;
@ -2608,8 +2592,8 @@ fn parse_header<'a>(
opt_shorthand, opt_shorthand,
header_src, header_src,
packages: &[], packages: &[],
exposes: header.exposes.into_bump_slice(), exposes: header.exposes.items,
imports: header.imports.into_bump_slice(), imports: header.imports.items,
to_platform: None, to_platform: None,
}; };
@ -2642,8 +2626,8 @@ fn parse_header<'a>(
opt_shorthand, opt_shorthand,
header_src, header_src,
packages, packages,
exposes: header.provides.into_bump_slice(), exposes: header.provides.items,
imports: header.imports.into_bump_slice(), imports: header.imports.items,
to_platform: Some(header.to.value.clone()), to_platform: Some(header.to.value.clone()),
}; };
@ -3236,7 +3220,7 @@ fn send_header_two<'a>(
let extra = HeaderFor::PkgConfig { let extra = HeaderFor::PkgConfig {
config_shorthand: shorthand, config_shorthand: shorthand,
platform_main_type: requires[0].value.clone(), platform_main_type: requires[0].value,
main_for_host, main_for_host,
}; };
@ -3409,8 +3393,7 @@ fn fabricate_pkg_config_module<'a>(
header_src: &'a str, header_src: &'a str,
module_timing: ModuleTiming, module_timing: ModuleTiming,
) -> (ModuleId, Msg<'a>) { ) -> (ModuleId, Msg<'a>) {
let provides: &'a [Located<ExposesEntry<'a, &'a str>>] = let provides: &'a [Located<ExposesEntry<'a, &'a str>>] = header.provides.items;
header.provides.clone().into_bump_slice();
let info = PlatformHeaderInfo { let info = PlatformHeaderInfo {
filename, filename,
@ -3420,8 +3403,8 @@ fn fabricate_pkg_config_module<'a>(
app_module_id, app_module_id,
packages: &[], packages: &[],
provides, provides,
requires: arena.alloc([header.requires.signature.clone()]), requires: arena.alloc([header.requires.signature]),
imports: header.imports.clone().into_bump_slice(), imports: header.imports.items,
}; };
send_header_two( send_header_two(
@ -3467,7 +3450,7 @@ fn fabricate_effects_module<'a>(
{ {
let mut module_ids = (*module_ids).lock(); let mut module_ids = (*module_ids).lock();
for exposed in header.exposes { for exposed in header.exposes.iter() {
if let ExposesEntry::Exposed(module_name) = exposed.value { if let ExposesEntry::Exposed(module_name) = exposed.value {
module_ids.get_or_insert(&PQModuleName::Qualified( module_ids.get_or_insert(&PQModuleName::Qualified(
shorthand, shorthand,
@ -3886,7 +3869,7 @@ fn exposed_from_import<'a>(entry: &ImportsEntry<'a>) -> (QualifiedModuleName<'a>
Module(module_name, exposes) => { Module(module_name, exposes) => {
let mut exposed = Vec::with_capacity(exposes.len()); let mut exposed = Vec::with_capacity(exposes.len());
for loc_entry in exposes { for loc_entry in exposes.iter() {
exposed.push(ident_from_exposed(&loc_entry.value)); exposed.push(ident_from_exposed(&loc_entry.value));
} }
@ -3901,7 +3884,7 @@ fn exposed_from_import<'a>(entry: &ImportsEntry<'a>) -> (QualifiedModuleName<'a>
Package(package_name, module_name, exposes) => { Package(package_name, module_name, exposes) => {
let mut exposed = Vec::with_capacity(exposes.len()); let mut exposed = Vec::with_capacity(exposes.len());
for loc_entry in exposes { for loc_entry in exposes.iter() {
exposed.push(ident_from_exposed(&loc_entry.value)); exposed.push(ident_from_exposed(&loc_entry.value));
} }
@ -3937,7 +3920,7 @@ fn make_specializations<'a>(
mut subs: Subs, mut subs: Subs,
procs_base: ProcsBase<'a>, procs_base: ProcsBase<'a>,
mut layout_cache: LayoutCache<'a>, mut layout_cache: LayoutCache<'a>,
specializations_we_must_make: ExternalSpecializations<'a>, specializations_we_must_make: Vec<ExternalSpecializations>,
mut module_timing: ModuleTiming, mut module_timing: ModuleTiming,
ptr_bytes: u32, ptr_bytes: u32,
) -> Msg<'a> { ) -> Msg<'a> {
@ -3973,7 +3956,7 @@ fn make_specializations<'a>(
&mut mono_env, &mut mono_env,
procs, procs,
specializations_we_must_make, specializations_we_must_make,
procs_base.specializations_for_host, procs_base.host_specializations,
&mut layout_cache, &mut layout_cache,
); );
@ -4005,27 +3988,11 @@ struct ProcsBase<'a> {
partial_procs: BumpMap<Symbol, PartialProc<'a>>, partial_procs: BumpMap<Symbol, PartialProc<'a>>,
module_thunks: &'a [Symbol], module_thunks: &'a [Symbol],
/// A host-exposed function must be specialized; it's a seed for subsequent specializations /// A host-exposed function must be specialized; it's a seed for subsequent specializations
specializations_for_host: BumpMap<Symbol, MutMap<ProcLayout<'a>, PendingSpecialization<'a>>>, host_specializations: roc_mono::ir::HostSpecializations,
runtime_errors: BumpMap<Symbol, &'a str>, runtime_errors: BumpMap<Symbol, &'a str>,
imported_module_thunks: &'a [Symbol], imported_module_thunks: &'a [Symbol],
} }
impl<'a> ProcsBase<'a> {
fn add_specialization_for_host(
&mut self,
symbol: Symbol,
layout: ProcLayout<'a>,
pending: PendingSpecialization<'a>,
) {
let all_pending = self
.specializations_for_host
.entry(symbol)
.or_insert_with(|| HashMap::with_capacity_and_hasher(1, default_hasher()));
all_pending.insert(layout, pending);
}
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn build_pending_specializations<'a>( fn build_pending_specializations<'a>(
arena: &'a Bump, arena: &'a Bump,
@ -4047,7 +4014,7 @@ fn build_pending_specializations<'a>(
let mut procs_base = ProcsBase { let mut procs_base = ProcsBase {
partial_procs: BumpMap::default(), partial_procs: BumpMap::default(),
module_thunks: &[], module_thunks: &[],
specializations_for_host: BumpMap::default(), host_specializations: roc_mono::ir::HostSpecializations::new(),
runtime_errors: BumpMap::default(), runtime_errors: BumpMap::default(),
imported_module_thunks, imported_module_thunks,
}; };
@ -4135,7 +4102,7 @@ fn add_def_to_module<'a>(
match def.loc_pattern.value { match def.loc_pattern.value {
Identifier(symbol) => { Identifier(symbol) => {
let is_exposed = exposed_to_host.contains_key(&symbol); let is_host_exposed = exposed_to_host.contains_key(&symbol);
match def.loc_expr.value { match def.loc_expr.value {
Closure(ClosureData { Closure(ClosureData {
@ -4153,19 +4120,19 @@ fn add_def_to_module<'a>(
// register it as such. Otherwise, since it // register it as such. Otherwise, since it
// never gets called by Roc code, it will never // never gets called by Roc code, it will never
// get specialized! // get specialized!
if is_exposed { if is_host_exposed {
let layout = match layout_cache.raw_from_var( let layout_result =
mono_env.arena, layout_cache.raw_from_var(mono_env.arena, annotation, mono_env.subs);
annotation,
mono_env.subs, // cannot specialize when e.g. main's type contains type variables
) { if let Err(e) = layout_result {
Ok(l) => l, match e {
Err(LayoutProblem::Erroneous) => { LayoutProblem::Erroneous => {
let message = "top level function has erroneous type"; let message = "top level function has erroneous type";
procs.runtime_errors.insert(symbol, message); procs.runtime_errors.insert(symbol, message);
return; return;
} }
Err(LayoutProblem::UnresolvedTypeVar(v)) => { LayoutProblem::UnresolvedTypeVar(v) => {
let message = format!( let message = format!(
"top level function has unresolved type variable {:?}", "top level function has unresolved type variable {:?}",
v v
@ -4175,20 +4142,15 @@ fn add_def_to_module<'a>(
.insert(symbol, mono_env.arena.alloc(message)); .insert(symbol, mono_env.arena.alloc(message));
return; return;
} }
}; }
}
let pending = PendingSpecialization::from_exposed_function( procs.host_specializations.insert_host_exposed(
mono_env.arena,
mono_env.subs, mono_env.subs,
symbol,
def.annotation, def.annotation,
annotation, annotation,
); );
procs.add_specialization_for_host(
symbol,
ProcLayout::from_raw(mono_env.arena, layout),
pending,
);
} }
let partial_proc = PartialProc::from_named_function( let partial_proc = PartialProc::from_named_function(
@ -4208,28 +4170,25 @@ fn add_def_to_module<'a>(
// mark this symbols as a top-level thunk before any other work on the procs // mark this symbols as a top-level thunk before any other work on the procs
module_thunks.push(symbol); module_thunks.push(symbol);
let annotation = def.expr_var;
// If this is an exposed symbol, we need to // If this is an exposed symbol, we need to
// register it as such. Otherwise, since it // register it as such. Otherwise, since it
// never gets called by Roc code, it will never // never gets called by Roc code, it will never
// get specialized! // get specialized!
if is_exposed { if is_host_exposed {
let annotation = def.expr_var; let layout_result =
layout_cache.raw_from_var(mono_env.arena, annotation, mono_env.subs);
let top_level = match layout_cache.from_var( // cannot specialize when e.g. main's type contains type variables
mono_env.arena, if let Err(e) = layout_result {
annotation, match e {
mono_env.subs, LayoutProblem::Erroneous => {
) {
Ok(l) => {
// remember, this is a 0-argument thunk
ProcLayout::new(mono_env.arena, &[], l)
}
Err(LayoutProblem::Erroneous) => {
let message = "top level function has erroneous type"; let message = "top level function has erroneous type";
procs.runtime_errors.insert(symbol, message); procs.runtime_errors.insert(symbol, message);
return; return;
} }
Err(LayoutProblem::UnresolvedTypeVar(v)) => { LayoutProblem::UnresolvedTypeVar(v) => {
let message = format!( let message = format!(
"top level function has unresolved type variable {:?}", "top level function has unresolved type variable {:?}",
v v
@ -4239,20 +4198,19 @@ fn add_def_to_module<'a>(
.insert(symbol, mono_env.arena.alloc(message)); .insert(symbol, mono_env.arena.alloc(message));
return; return;
} }
}; }
}
let pending = PendingSpecialization::from_exposed_function( procs.host_specializations.insert_host_exposed(
mono_env.arena,
mono_env.subs, mono_env.subs,
symbol,
def.annotation, def.annotation,
annotation, annotation,
); );
procs.add_specialization_for_host(symbol, top_level, pending);
} }
let proc = PartialProc { let proc = PartialProc {
annotation: def.expr_var, annotation,
// This is a 0-arity thunk, so it has no arguments. // This is a 0-arity thunk, so it has no arguments.
pattern_symbols: &[], pattern_symbols: &[],
// This is a top-level definition, so it cannot capture anything // This is a top-level definition, so it cannot capture anything

View file

@ -12,6 +12,10 @@ pub enum CalledVia {
/// Calling with a unary operator, e.g. (!foo bar baz) or (-foo bar baz) /// Calling with a unary operator, e.g. (!foo bar baz) or (-foo bar baz)
UnaryOp(UnaryOp), UnaryOp(UnaryOp),
/// This call is the result of desugaring string interpolation,
/// e.g. "\(first) \(last)" is transformed into Str.concat (Str.concat first " ") last.
StringInterpolation,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]

View file

@ -2,10 +2,10 @@
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)] #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
pub mod called_via;
pub mod ident; pub mod ident;
pub mod low_level; pub mod low_level;
pub mod module_err; pub mod module_err;
pub mod operator;
pub mod symbol; pub mod symbol;
#[macro_use] #[macro_use]

View file

@ -1,3 +1,5 @@
use crate::symbol::Symbol;
/// Low-level operations that get translated directly into e.g. LLVM instructions. /// Low-level operations that get translated directly into e.g. LLVM instructions.
/// These are always wrapped when exposed to end users, and can only make it /// These are always wrapped when exposed to end users, and can only make it
/// into an Expr when added directly by can::builtins /// into an Expr when added directly by can::builtins
@ -19,6 +21,7 @@ pub enum LowLevel {
StrFromFloat, StrFromFloat,
StrTrim, StrTrim,
StrTrimLeft, StrTrimLeft,
StrTrimRight,
ListLen, ListLen,
ListGetUnsafe, ListGetUnsafe,
ListSet, ListSet,
@ -43,12 +46,11 @@ pub enum LowLevel {
ListKeepOks, ListKeepOks,
ListKeepErrs, ListKeepErrs,
ListSortWith, ListSortWith,
ListTakeFirst, ListSublist,
ListTakeLast,
ListDrop,
ListDropAt, ListDropAt,
ListSwap, ListSwap,
ListAny, ListAny,
ListAll,
ListFindUnsafe, ListFindUnsafe,
DictSize, DictSize,
DictEmpty, DictEmpty,
@ -115,106 +117,6 @@ pub enum LowLevel {
ExpectTrue, ExpectTrue,
} }
macro_rules! first_order {
() => {
StrConcat
| StrJoinWith
| StrIsEmpty
| StrStartsWith
| StrStartsWithCodePt
| StrEndsWith
| StrSplit
| StrCountGraphemes
| StrFromInt
| StrFromUtf8
| StrFromUtf8Range
| StrToUtf8
| StrRepeat
| StrTrim
| StrTrimLeft
| StrFromFloat
| ListLen
| ListGetUnsafe
| ListSet
| ListTakeFirst
| ListTakeLast
| ListDrop
| ListDropAt
| ListSingle
| ListRepeat
| ListReverse
| ListConcat
| ListContains
| ListAppend
| ListPrepend
| ListJoin
| ListRange
| ListSwap
| DictSize
| DictEmpty
| DictInsert
| DictRemove
| DictContains
| DictGetUnsafe
| DictKeys
| DictValues
| DictUnion
| DictIntersection
| DictDifference
| SetFromList
| NumAdd
| NumAddWrap
| NumAddChecked
| NumSub
| NumSubWrap
| NumSubChecked
| NumMul
| NumMulWrap
| NumMulChecked
| NumGt
| NumGte
| NumLt
| NumLte
| NumCompare
| NumDivUnchecked
| NumDivCeilUnchecked
| NumRemUnchecked
| NumIsMultipleOf
| NumAbs
| NumNeg
| NumSin
| NumCos
| NumSqrtUnchecked
| NumLogUnchecked
| NumRound
| NumToFloat
| NumPow
| NumCeiling
| NumPowInt
| NumFloor
| NumIsFinite
| NumAtan
| NumAcos
| NumAsin
| NumBitwiseAnd
| NumBitwiseXor
| NumBitwiseOr
| NumShiftLeftBy
| NumShiftRightBy
| NumBytesToU16
| NumBytesToU32
| NumShiftRightZfBy
| NumIntCast
| Eq
| NotEq
| And
| Or
| Not
| Hash
| ExpectTrue
};
}
macro_rules! higher_order { macro_rules! higher_order {
() => { () => {
ListMap ListMap
@ -230,6 +132,7 @@ macro_rules! higher_order {
| ListKeepErrs | ListKeepErrs
| ListSortWith | ListSortWith
| ListAny | ListAny
| ListAll
| ListFindUnsafe | ListFindUnsafe
| DictWalk | DictWalk
}; };
@ -241,17 +144,13 @@ impl LowLevel {
pub fn is_higher_order(&self) -> bool { pub fn is_higher_order(&self) -> bool {
use LowLevel::*; use LowLevel::*;
match self { matches!(self, higher_order!())
first_order!() => false,
higher_order!() => true,
}
} }
pub fn function_argument_position(&self) -> usize { pub fn function_argument_position(&self) -> usize {
use LowLevel::*; use LowLevel::*;
match self { match self {
first_order!() => unreachable!(),
ListMap => 1, ListMap => 1,
ListMap2 => 2, ListMap2 => 2,
ListMap3 => 3, ListMap3 => 3,
@ -265,8 +164,131 @@ impl LowLevel {
ListKeepErrs => 1, ListKeepErrs => 1,
ListSortWith => 1, ListSortWith => 1,
ListAny => 1, ListAny => 1,
ListAll => 1,
ListFindUnsafe => 1, ListFindUnsafe => 1,
DictWalk => 2, DictWalk => 2,
_ => unreachable!(),
}
}
/// Used in dev backends to inline some lowlevel wrapper functions
/// For wrappers that contain logic, we return None to prevent inlining
/// (Mention each explicitly rather than using `_`, to show they have not been forgotten)
pub fn from_inlined_wrapper(symbol: Symbol) -> Option<LowLevel> {
use LowLevel::*;
match symbol {
Symbol::STR_CONCAT => Some(StrConcat),
Symbol::STR_JOIN_WITH => Some(StrJoinWith),
Symbol::STR_IS_EMPTY => Some(StrIsEmpty),
Symbol::STR_STARTS_WITH => Some(StrStartsWith),
Symbol::STR_STARTS_WITH_CODE_PT => Some(StrStartsWithCodePt),
Symbol::STR_ENDS_WITH => Some(StrEndsWith),
Symbol::STR_SPLIT => Some(StrSplit),
Symbol::STR_COUNT_GRAPHEMES => Some(StrCountGraphemes),
Symbol::STR_FROM_INT => Some(StrFromInt),
Symbol::STR_FROM_UTF8 => None,
Symbol::STR_FROM_UTF8_RANGE => None,
Symbol::STR_TO_UTF8 => Some(StrToUtf8),
Symbol::STR_REPEAT => Some(StrRepeat),
Symbol::STR_FROM_FLOAT => Some(StrFromFloat),
Symbol::STR_TRIM => Some(StrTrim),
Symbol::STR_TRIM_LEFT => Some(StrTrimLeft),
Symbol::STR_TRIM_RIGHT => Some(StrTrimRight),
Symbol::LIST_LEN => Some(ListLen),
Symbol::LIST_GET => None,
Symbol::LIST_SET => None,
Symbol::LIST_SINGLE => Some(ListSingle),
Symbol::LIST_REPEAT => Some(ListRepeat),
Symbol::LIST_REVERSE => Some(ListReverse),
Symbol::LIST_CONCAT => Some(ListConcat),
Symbol::LIST_CONTAINS => Some(ListContains),
Symbol::LIST_APPEND => Some(ListAppend),
Symbol::LIST_PREPEND => Some(ListPrepend),
Symbol::LIST_JOIN => Some(ListJoin),
Symbol::LIST_RANGE => Some(ListRange),
Symbol::LIST_MAP => Some(ListMap),
Symbol::LIST_MAP2 => Some(ListMap2),
Symbol::LIST_MAP3 => Some(ListMap3),
Symbol::LIST_MAP4 => Some(ListMap4),
Symbol::LIST_MAP_WITH_INDEX => Some(ListMapWithIndex),
Symbol::LIST_KEEP_IF => Some(ListKeepIf),
Symbol::LIST_WALK => Some(ListWalk),
Symbol::LIST_WALK_UNTIL => Some(ListWalkUntil),
Symbol::LIST_WALK_BACKWARDS => Some(ListWalkBackwards),
Symbol::LIST_KEEP_OKS => Some(ListKeepOks),
Symbol::LIST_KEEP_ERRS => Some(ListKeepErrs),
Symbol::LIST_SORT_WITH => Some(ListSortWith),
Symbol::LIST_SUBLIST => Some(ListSublist),
Symbol::LIST_DROP_AT => Some(ListDropAt),
Symbol::LIST_SWAP => Some(ListSwap),
Symbol::LIST_ANY => Some(ListAny),
Symbol::LIST_ALL => Some(ListAll),
Symbol::LIST_FIND => None,
Symbol::DICT_LEN => Some(DictSize),
Symbol::DICT_EMPTY => Some(DictEmpty),
Symbol::DICT_INSERT => Some(DictInsert),
Symbol::DICT_REMOVE => Some(DictRemove),
Symbol::DICT_CONTAINS => Some(DictContains),
Symbol::DICT_GET => None,
Symbol::DICT_KEYS => Some(DictKeys),
Symbol::DICT_VALUES => Some(DictValues),
Symbol::DICT_UNION => Some(DictUnion),
Symbol::DICT_INTERSECTION => Some(DictIntersection),
Symbol::DICT_DIFFERENCE => Some(DictDifference),
Symbol::DICT_WALK => Some(DictWalk),
Symbol::SET_FROM_LIST => Some(SetFromList),
Symbol::NUM_ADD => Some(NumAdd),
Symbol::NUM_ADD_WRAP => Some(NumAddWrap),
Symbol::NUM_ADD_CHECKED => None,
Symbol::NUM_SUB => Some(NumSub),
Symbol::NUM_SUB_WRAP => Some(NumSubWrap),
Symbol::NUM_SUB_CHECKED => None,
Symbol::NUM_MUL => Some(NumMul),
Symbol::NUM_MUL_WRAP => Some(NumMulWrap),
Symbol::NUM_MUL_CHECKED => None,
Symbol::NUM_GT => Some(NumGt),
Symbol::NUM_GTE => Some(NumGte),
Symbol::NUM_LT => Some(NumLt),
Symbol::NUM_LTE => Some(NumLte),
Symbol::NUM_COMPARE => Some(NumCompare),
Symbol::NUM_DIV_FLOAT => None,
Symbol::NUM_DIV_CEIL => None,
Symbol::NUM_REM => None,
Symbol::NUM_IS_MULTIPLE_OF => Some(NumIsMultipleOf),
Symbol::NUM_ABS => Some(NumAbs),
Symbol::NUM_NEG => Some(NumNeg),
Symbol::NUM_SIN => Some(NumSin),
Symbol::NUM_COS => Some(NumCos),
Symbol::NUM_SQRT => None,
Symbol::NUM_LOG => None,
Symbol::NUM_ROUND => Some(NumRound),
Symbol::NUM_TO_FLOAT => Some(NumToFloat),
Symbol::NUM_POW => Some(NumPow),
Symbol::NUM_CEILING => Some(NumCeiling),
Symbol::NUM_POW_INT => Some(NumPowInt),
Symbol::NUM_FLOOR => Some(NumFloor),
// => Some(NumIsFinite),
Symbol::NUM_ATAN => Some(NumAtan),
Symbol::NUM_ACOS => Some(NumAcos),
Symbol::NUM_ASIN => Some(NumAsin),
Symbol::NUM_BYTES_TO_U16 => None,
Symbol::NUM_BYTES_TO_U32 => None,
Symbol::NUM_BITWISE_AND => Some(NumBitwiseAnd),
Symbol::NUM_BITWISE_XOR => Some(NumBitwiseXor),
Symbol::NUM_BITWISE_OR => Some(NumBitwiseOr),
Symbol::NUM_SHIFT_LEFT => Some(NumShiftLeftBy),
Symbol::NUM_SHIFT_RIGHT => Some(NumShiftRightBy),
Symbol::NUM_SHIFT_RIGHT_ZERO_FILL => Some(NumShiftRightZfBy),
Symbol::NUM_INT_CAST => Some(NumIntCast),
Symbol::BOOL_EQ => Some(Eq),
Symbol::BOOL_NEQ => Some(NotEq),
Symbol::BOOL_AND => Some(And),
Symbol::BOOL_OR => Some(Or),
Symbol::BOOL_NOT => Some(Not),
// => Some(Hash),
// => Some(ExpectTrue),
_ => None,
} }
} }
} }

View file

@ -875,8 +875,11 @@ define_builtins! {
// used by the dev backend to store the pointer to where to store large return types // used by the dev backend to store the pointer to where to store large return types
23 RET_POINTER: "#ret_pointer" 23 RET_POINTER: "#ret_pointer"
// used in wasm dev backend to mark values in the VM stack that have no other Symbol // used in wasm dev backend to mark temporary values in the VM stack
24 WASM_ANONYMOUS_STACK_VALUE: "#wasm_anonymous_stack_value" 24 WASM_TMP: "#wasm_tmp"
// the _ used in mono when a specialized symbol is deleted
25 REMOVED_SPECIALIZATION: "#removed_specialization"
} }
1 NUM: "Num" => { 1 NUM: "Num" => {
0 NUM_NUM: "Num" imported // the Num.Num type alias 0 NUM_NUM: "Num" imported // the Num.Num type alias
@ -989,12 +992,16 @@ define_builtins! {
} }
2 BOOL: "Bool" => { 2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
1 BOOL_AND: "and" 1 BOOL_FALSE: "False" imported // Bool.Bool = [ False, True ]
2 BOOL_OR: "or" // NB: not strictly needed; used for finding global tag names in error suggestions
3 BOOL_NOT: "not" 2 BOOL_TRUE: "True" imported // Bool.Bool = [ False, True ]
4 BOOL_XOR: "xor" // NB: not strictly needed; used for finding global tag names in error suggestions
5 BOOL_EQ: "isEq" 3 BOOL_AND: "and"
6 BOOL_NEQ: "isNotEq" 4 BOOL_OR: "or"
5 BOOL_NOT: "not"
6 BOOL_XOR: "xor"
7 BOOL_EQ: "isEq"
8 BOOL_NEQ: "isNotEq"
} }
3 STR: "Str" => { 3 STR: "Str" => {
0 STR_STR: "Str" imported // the Str.Str type alias 0 STR_STR: "Str" imported // the Str.Str type alias
@ -1019,6 +1026,7 @@ define_builtins! {
19 STR_REPEAT: "repeat" 19 STR_REPEAT: "repeat"
20 STR_TRIM: "trim" 20 STR_TRIM: "trim"
21 STR_TRIM_LEFT: "trimLeft" 21 STR_TRIM_LEFT: "trimLeft"
22 STR_TRIM_RIGHT: "trimRight"
} }
4 LIST: "List" => { 4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias 0 LIST_LIST: "List" imported // the List.List type alias
@ -1071,14 +1079,24 @@ define_builtins! {
47 LIST_FIND: "find" 47 LIST_FIND: "find"
48 LIST_FIND_RESULT: "#find_result" // symbol used in the definition of List.find 48 LIST_FIND_RESULT: "#find_result" // symbol used in the definition of List.find
49 LIST_SUBLIST: "sublist" 49 LIST_SUBLIST: "sublist"
50 LIST_INTERSPERSE: "intersperse"
51 LIST_INTERSPERSE_CLOS: "#intersperseClos"
52 LIST_SPLIT: "split"
53 LIST_SPLIT_CLOS: "#splitClos"
54 LIST_ALL: "all"
} }
5 RESULT: "Result" => { 5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias 0 RESULT_RESULT: "Result" imported // the Result.Result type alias
1 RESULT_MAP: "map" 1 RESULT_OK: "Ok" imported // Result.Result a e = [ Ok a, Err e ]
2 RESULT_MAP_ERR: "mapErr" // NB: not strictly needed; used for finding global tag names in error suggestions
3 RESULT_WITH_DEFAULT: "withDefault" 2 RESULT_ERR: "Err" imported // Result.Result a e = [ Ok a, Err e ]
4 RESULT_AFTER: "after" // NB: not strictly needed; used for finding global tag names in error suggestions
5 RESULT_IS_OK: "isOk" 3 RESULT_MAP: "map"
4 RESULT_MAP_ERR: "mapErr"
5 RESULT_WITH_DEFAULT: "withDefault"
6 RESULT_AFTER: "after"
7 RESULT_IS_OK: "isOk"
8 RESULT_IS_ERR: "isErr"
} }
6 DICT: "Dict" => { 6 DICT: "Dict" => {
0 DICT_DICT: "Dict" imported // the Dict.Dict type alias 0 DICT_DICT: "Dict" imported // the Dict.Dict type alias

View file

@ -20,3 +20,4 @@ morphic_lib = { path = "../../vendor/morphic_lib" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }
hashbrown = { version = "0.11.2", features = [ "bumpalo" ] } hashbrown = { version = "0.11.2", features = [ "bumpalo" ] }
ven_graph = { path = "../../vendor/pathfinding" } ven_graph = { path = "../../vendor/pathfinding" }
static_assertions = "1.1.0"

View file

@ -10,8 +10,8 @@ use roc_module::symbol::Symbol;
use std::convert::TryFrom; use std::convert::TryFrom;
use crate::ir::{ use crate::ir::{
Call, CallType, Expr, HostExposedLayouts, ListLiteralElement, Literal, ModifyRc, OptLevel, Call, CallType, Expr, HigherOrderLowLevel, HostExposedLayouts, ListLiteralElement, Literal,
Proc, Stmt, ModifyRc, OptLevel, Proc, Stmt,
}; };
use crate::layout::{Builtin, Layout, ListLayout, RawFunctionLayout, UnionLayout}; use crate::layout::{Builtin, Layout, ListLayout, RawFunctionLayout, UnionLayout};
@ -24,7 +24,7 @@ pub const STATIC_LIST_NAME: ConstName = ConstName(b"THIS IS A STATIC LIST");
const ENTRY_POINT_NAME: &[u8] = b"mainForHost"; const ENTRY_POINT_NAME: &[u8] = b"mainForHost";
pub fn func_name_bytes(proc: &Proc) -> [u8; SIZE] { 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) func_name_bytes_help(proc.name, proc.args.iter().map(|x| x.0), &proc.ret_layout)
} }
const DEBUG: bool = false; const DEBUG: bool = false;
@ -53,7 +53,7 @@ impl TagUnionId {
pub fn func_name_bytes_help<'a, I>( pub fn func_name_bytes_help<'a, I>(
symbol: Symbol, symbol: Symbol,
argument_layouts: I, argument_layouts: I,
return_layout: Layout<'a>, return_layout: &Layout<'a>,
) -> [u8; SIZE] ) -> [u8; SIZE]
where where
I: Iterator<Item = Layout<'a>>, I: Iterator<Item = Layout<'a>>,
@ -162,13 +162,13 @@ where
match layout { match layout {
RawFunctionLayout::Function(_, _, _) => { RawFunctionLayout::Function(_, _, _) => {
let it = top_level.arguments.iter().copied(); let it = top_level.arguments.iter().copied();
let bytes = func_name_bytes_help(*symbol, it, top_level.result); let bytes = func_name_bytes_help(*symbol, it, &top_level.result);
host_exposed_functions.push((bytes, top_level.arguments)); host_exposed_functions.push((bytes, top_level.arguments));
} }
RawFunctionLayout::ZeroArgumentThunk(_) => { RawFunctionLayout::ZeroArgumentThunk(_) => {
let it = std::iter::once(Layout::Struct(&[])); let it = std::iter::once(Layout::Struct(&[]));
let bytes = func_name_bytes_help(*symbol, it, top_level.result); let bytes = func_name_bytes_help(*symbol, it, &top_level.result);
host_exposed_functions.push((bytes, top_level.arguments)); host_exposed_functions.push((bytes, top_level.arguments));
} }
@ -196,7 +196,7 @@ where
let roc_main_bytes = func_name_bytes_help( let roc_main_bytes = func_name_bytes_help(
entry_point.symbol, entry_point.symbol,
entry_point.layout.arguments.iter().copied(), entry_point.layout.arguments.iter().copied(),
entry_point.layout.result, &entry_point.layout.result,
); );
let roc_main = FuncName(&roc_main_bytes); let roc_main = FuncName(&roc_main_bytes);
@ -660,7 +660,7 @@ fn call_spec(
let arg_value_id = build_tuple_value(builder, env, block, call.arguments)?; let arg_value_id = build_tuple_value(builder, env, block, call.arguments)?;
let it = arg_layouts.iter().copied(); let it = arg_layouts.iter().copied();
let bytes = func_name_bytes_help(*symbol, it, *ret_layout); let bytes = func_name_bytes_help(*symbol, it, ret_layout);
let name = FuncName(&bytes); let name = FuncName(&bytes);
let module = MOD_APP; let module = MOD_APP;
builder.add_call(block, spec_var, module, name, arg_value_id) builder.add_call(block, spec_var, module, name, arg_value_id)
@ -688,7 +688,7 @@ fn call_spec(
*update_mode, *update_mode,
call.arguments, call.arguments,
), ),
HigherOrderLowLevel { HigherOrder(HigherOrderLowLevel {
specialization_id, specialization_id,
closure_env_layout, closure_env_layout,
update_mode, update_mode,
@ -698,7 +698,7 @@ fn call_spec(
function_name, function_name,
function_env, function_env,
.. ..
} => { }) => {
use crate::low_level::HigherOrder::*; use crate::low_level::HigherOrder::*;
let array = specialization_id.to_bytes(); let array = specialization_id.to_bytes();
@ -708,7 +708,7 @@ fn call_spec(
let update_mode_var = UpdateModeVar(&mode); let update_mode_var = UpdateModeVar(&mode);
let it = arg_layouts.iter().copied(); let it = arg_layouts.iter().copied();
let bytes = func_name_bytes_help(*function_name, it, *ret_layout); let bytes = func_name_bytes_help(*function_name, it, ret_layout);
let name = FuncName(&bytes); let name = FuncName(&bytes);
let module = MOD_APP; let module = MOD_APP;
@ -1093,6 +1093,25 @@ fn call_spec(
add_loop(builder, block, state_type, init_state, loop_body) add_loop(builder, block, state_type, init_state, loop_body)
} }
ListAll { xs } => {
let list = env.symbols[xs];
let loop_body = |builder: &mut FuncDefBuilder, block, _state| {
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let element = builder.add_bag_get(block, bag)?;
let new_state = call_function!(builder, block, [element]);
Ok(new_state)
};
let state_layout = Layout::Builtin(Builtin::Int1);
let state_type = layout_spec(builder, &state_layout)?;
let init_state = new_num(builder, block)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListFindUnsafe { xs } => { ListFindUnsafe { xs } => {
let list = env.symbols[xs]; let list = env.symbols[xs];
@ -1477,14 +1496,14 @@ fn expr_spec<'a>(
} }
UnionLayout::Recursive(_) => builder.add_make_tuple(block, &[cell_id, data_id])?, UnionLayout::Recursive(_) => builder.add_make_tuple(block, &[cell_id, data_id])?,
UnionLayout::NullableWrapped { nullable_id, .. } => { UnionLayout::NullableWrapped { nullable_id, .. } => {
if *tag_id == *nullable_id as u8 { if *tag_id == *nullable_id as _ {
data_id data_id
} else { } else {
builder.add_make_tuple(block, &[cell_id, data_id])? builder.add_make_tuple(block, &[cell_id, data_id])?
} }
} }
UnionLayout::NullableUnwrapped { nullable_id, .. } => { UnionLayout::NullableUnwrapped { nullable_id, .. } => {
if *tag_id == *nullable_id as u8 { if *tag_id == *nullable_id as _ {
data_id data_id
} else { } else {
builder.add_make_tuple(block, &[cell_id, data_id])? builder.add_make_tuple(block, &[cell_id, data_id])?

View file

@ -1,4 +1,4 @@
use crate::ir::{Expr, JoinPointId, Param, Proc, ProcLayout, Stmt}; use crate::ir::{Expr, HigherOrderLowLevel, JoinPointId, Param, Proc, ProcLayout, Stmt};
use crate::layout::Layout; use crate::layout::Layout;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
@ -560,7 +560,7 @@ impl<'a> BorrowInfState<'a> {
arg_layouts, arg_layouts,
.. ..
} => { } => {
let top_level = ProcLayout::new(self.arena, arg_layouts, *ret_layout); let top_level = ProcLayout::new(self.arena, arg_layouts, **ret_layout);
// get the borrow signature of the applied function // get the borrow signature of the applied function
let ps = param_map let ps = param_map
@ -593,14 +593,14 @@ impl<'a> BorrowInfState<'a> {
self.own_args_using_bools(arguments, ps); self.own_args_using_bools(arguments, ps);
} }
HigherOrderLowLevel { HigherOrder(HigherOrderLowLevel {
op, op,
arg_layouts, arg_layouts,
ret_layout, ret_layout,
function_name, function_name,
function_env, function_env,
.. ..
} => { }) => {
use crate::low_level::HigherOrder::*; use crate::low_level::HigherOrder::*;
let closure_layout = ProcLayout { let closure_layout = ProcLayout {
@ -619,6 +619,7 @@ impl<'a> BorrowInfState<'a> {
| ListKeepOks { xs } | ListKeepOks { xs }
| ListKeepErrs { xs } | ListKeepErrs { xs }
| ListAny { xs } | ListAny { xs }
| ListAll { xs }
| ListFindUnsafe { xs } => { | ListFindUnsafe { xs } => {
// own the list if the function wants to own the element // own the list if the function wants to own the element
if !function_ps[0].borrow { if !function_ps[0].borrow {
@ -795,7 +796,7 @@ impl<'a> BorrowInfState<'a> {
Stmt::Ret(z), Stmt::Ret(z),
) = (v, b) ) = (v, b)
{ {
let top_level = ProcLayout::new(self.arena, arg_layouts, *ret_layout); let top_level = ProcLayout::new(self.arena, arg_layouts, **ret_layout);
if self.current_proc == *g && x == *z { if self.current_proc == *g && x == *z {
// anonymous functions (for which the ps may not be known) // anonymous functions (for which the ps may not be known)
@ -941,6 +942,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
StrConcat => arena.alloc_slice_copy(&[owned, borrowed]), StrConcat => arena.alloc_slice_copy(&[owned, borrowed]),
StrTrim => arena.alloc_slice_copy(&[owned]), StrTrim => arena.alloc_slice_copy(&[owned]),
StrTrimLeft => arena.alloc_slice_copy(&[owned]), StrTrimLeft => arena.alloc_slice_copy(&[owned]),
StrTrimRight => arena.alloc_slice_copy(&[owned]),
StrSplit => arena.alloc_slice_copy(&[borrowed, borrowed]), StrSplit => arena.alloc_slice_copy(&[borrowed, borrowed]),
ListSingle => arena.alloc_slice_copy(&[irrelevant]), ListSingle => arena.alloc_slice_copy(&[irrelevant]),
ListRepeat => arena.alloc_slice_copy(&[irrelevant, borrowed]), ListRepeat => arena.alloc_slice_copy(&[irrelevant, borrowed]),
@ -952,7 +954,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListMap2 => arena.alloc_slice_copy(&[owned, owned, function, closure_data]), ListMap2 => arena.alloc_slice_copy(&[owned, owned, function, closure_data]),
ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, function, closure_data]), ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, function, closure_data]),
ListMap4 => arena.alloc_slice_copy(&[owned, owned, owned, owned, function, closure_data]), ListMap4 => arena.alloc_slice_copy(&[owned, owned, owned, owned, function, closure_data]),
ListKeepIf | ListKeepOks | ListKeepErrs | ListAny => { ListKeepIf | ListKeepOks | ListKeepErrs | ListAny | ListAll => {
arena.alloc_slice_copy(&[owned, function, closure_data]) arena.alloc_slice_copy(&[owned, function, closure_data])
} }
ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]), ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]),
@ -966,9 +968,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
// TODO when we have lists with capacity (if ever) // TODO when we have lists with capacity (if ever)
// List.append should own its first argument // List.append should own its first argument
ListAppend => arena.alloc_slice_copy(&[owned, owned]), ListAppend => arena.alloc_slice_copy(&[owned, owned]),
ListTakeFirst => arena.alloc_slice_copy(&[owned, irrelevant]), ListSublist => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
ListTakeLast => arena.alloc_slice_copy(&[owned, irrelevant]),
ListDrop => arena.alloc_slice_copy(&[owned, irrelevant]),
ListDropAt => arena.alloc_slice_copy(&[owned, irrelevant]), ListDropAt => arena.alloc_slice_copy(&[owned, irrelevant]),
ListSwap => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), ListSwap => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
@ -1046,7 +1046,7 @@ fn call_info_call<'a>(call: &crate::ir::Call<'a>, info: &mut CallInfo<'a>) {
} }
Foreign { .. } => {} Foreign { .. } => {}
LowLevel { .. } => {} LowLevel { .. } => {}
HigherOrderLowLevel { .. } => {} HigherOrder(_) => {}
} }
} }

View file

@ -3,7 +3,7 @@ use crate::ir::{
BranchInfo, DestructType, Env, Expr, FloatPrecision, IntPrecision, JoinPointId, Literal, Param, BranchInfo, DestructType, Env, Expr, FloatPrecision, IntPrecision, JoinPointId, Literal, Param,
Pattern, Procs, Stmt, Pattern, Procs, Stmt,
}; };
use crate::layout::{Builtin, Layout, LayoutCache, UnionLayout}; use crate::layout::{Builtin, Layout, LayoutCache, TagIdIntType, UnionLayout};
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::TagName; use roc_module::ident::TagName;
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
@ -81,7 +81,7 @@ enum GuardedTest<'a> {
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
enum Test<'a> { enum Test<'a> {
IsCtor { IsCtor {
tag_id: u8, tag_id: TagIdIntType,
tag_name: TagName, tag_name: TagName,
union: crate::exhaustive::Union, union: crate::exhaustive::Union,
arguments: Vec<(Pattern<'a>, Layout<'a>)>, arguments: Vec<(Pattern<'a>, Layout<'a>)>,
@ -92,7 +92,7 @@ enum Test<'a> {
IsStr(Box<str>), IsStr(Box<str>),
IsBit(bool), IsBit(bool),
IsByte { IsByte {
tag_id: u8, tag_id: TagIdIntType,
num_alts: usize, num_alts: usize,
}, },
} }
@ -419,10 +419,9 @@ fn gather_edges<'a>(
let check = guarded_tests_are_complete(&relevant_tests); let check = guarded_tests_are_complete(&relevant_tests);
// TODO remove clone
let all_edges = relevant_tests let all_edges = relevant_tests
.into_iter() .into_iter()
.map(|t| edges_for(path, branches.clone(), t)) .map(|t| edges_for(path, &branches, t))
.collect(); .collect();
let fallbacks = if check { let fallbacks = if check {
@ -562,7 +561,7 @@ fn test_at_path<'a>(
}, },
BitLiteral { value, .. } => IsBit(*value), BitLiteral { value, .. } => IsBit(*value),
EnumLiteral { tag_id, union, .. } => IsByte { EnumLiteral { tag_id, union, .. } => IsByte {
tag_id: *tag_id, tag_id: *tag_id as _,
num_alts: union.alternatives.len(), num_alts: union.alternatives.len(),
}, },
IntLiteral(v, precision) => IsInt(*v, *precision), IntLiteral(v, precision) => IsInt(*v, *precision),
@ -583,7 +582,7 @@ fn test_at_path<'a>(
// understanding: if the test is successful, where could we go? // understanding: if the test is successful, where could we go?
fn edges_for<'a>( fn edges_for<'a>(
path: &[PathInstruction], path: &[PathInstruction],
branches: Vec<Branch<'a>>, branches: &[Branch<'a>],
test: GuardedTest<'a>, test: GuardedTest<'a>,
) -> (GuardedTest<'a>, Vec<Branch<'a>>) { ) -> (GuardedTest<'a>, Vec<Branch<'a>>) {
let mut new_branches = Vec::new(); let mut new_branches = Vec::new();
@ -864,7 +863,7 @@ fn to_relevant_branch_help<'a>(
EnumLiteral { tag_id, .. } => match test { EnumLiteral { tag_id, .. } => match test {
IsByte { IsByte {
tag_id: test_id, .. tag_id: test_id, ..
} if tag_id == *test_id => { } if tag_id == *test_id as _ => {
start.extend(end); start.extend(end);
Some(Branch { Some(Branch {
goal: branch.goal, goal: branch.goal,
@ -1055,9 +1054,19 @@ fn small_defaults(branches: &[Branch], path: &[PathInstruction]) -> usize {
} }
fn small_branching_factor(branches: &[Branch], path: &[PathInstruction]) -> usize { fn small_branching_factor(branches: &[Branch], path: &[PathInstruction]) -> usize {
let (edges, fallback) = gather_edges(branches.to_vec(), path); // a specialized version of gather_edges that just counts the number of options
edges.len() + (if fallback.is_empty() { 0 } else { 1 }) let relevant_tests = tests_at_path(path, branches);
let check = guarded_tests_are_complete(&relevant_tests);
let fallbacks = if check {
false
} else {
branches.iter().any(|b| is_irrelevant_to(path, b))
};
relevant_tests.len() + (if !fallbacks { 0 } else { 1 })
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -1171,7 +1180,7 @@ pub fn optimize_when<'a>(
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct PathInstruction { struct PathInstruction {
index: u64, index: u64,
tag_id: u8, tag_id: TagIdIntType,
} }
fn path_to_expr_help<'a>( fn path_to_expr_help<'a>(
@ -1198,7 +1207,7 @@ fn path_to_expr_help<'a>(
union_layout: *union_layout, union_layout: *union_layout,
}; };
let inner_layout = union_layout.layout_at(*tag_id as u8, index as usize); let inner_layout = union_layout.layout_at(*tag_id as TagIdIntType, index as usize);
symbol = env.unique_symbol(); symbol = env.unique_symbol();
stores.push((symbol, inner_layout, inner_expr)); stores.push((symbol, inner_layout, inner_expr));
@ -1317,7 +1326,9 @@ fn test_to_equality<'a>(
Test::IsByte { Test::IsByte {
tag_id: test_byte, .. tag_id: test_byte, ..
} => { } => {
let lhs = Expr::Literal(Literal::Byte(test_byte)); debug_assert!(test_byte <= (u8::MAX as u16));
let lhs = Expr::Literal(Literal::Byte(test_byte as u8));
let lhs_symbol = env.unique_symbol(); let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int8), lhs)); stores.push((lhs_symbol, Layout::Builtin(Builtin::Int8), lhs));
@ -1504,13 +1515,13 @@ enum ConstructorKnown<'a> {
Both { Both {
scrutinee: Symbol, scrutinee: Symbol,
layout: Layout<'a>, layout: Layout<'a>,
pass: u8, pass: TagIdIntType,
fail: u8, fail: TagIdIntType,
}, },
OnlyPass { OnlyPass {
scrutinee: Symbol, scrutinee: Symbol,
layout: Layout<'a>, layout: Layout<'a>,
tag_id: u8, tag_id: TagIdIntType,
}, },
Neither, Neither,
} }
@ -1530,7 +1541,7 @@ impl<'a> ConstructorKnown<'a> {
layout: *cond_layout, layout: *cond_layout,
scrutinee: cond_symbol, scrutinee: cond_symbol,
pass: *tag_id, pass: *tag_id,
fail: (*tag_id == 0) as u8, fail: (*tag_id == 0) as _,
} }
} else { } else {
ConstructorKnown::OnlyPass { ConstructorKnown::OnlyPass {
@ -1774,7 +1785,7 @@ fn decide_to_branching<'a>(
BranchInfo::Constructor { BranchInfo::Constructor {
scrutinee: inner_cond_symbol, scrutinee: inner_cond_symbol,
layout: inner_cond_layout, layout: inner_cond_layout,
tag_id: tag_id_sum as u8, tag_id: tag_id_sum as u8 as _,
} }
} else { } else {
BranchInfo::None BranchInfo::None

View file

@ -1,4 +1,4 @@
use crate::ir::DestructType; use crate::{ir::DestructType, layout::TagIdIntType};
use roc_collections::all::{Index, MutMap}; use roc_collections::all::{Index, MutMap};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
@ -35,7 +35,7 @@ pub enum RenderAs {
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)] #[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
pub struct TagId(pub u8); pub struct TagId(pub TagIdIntType);
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Ctor { pub struct Ctor {
@ -71,8 +71,12 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern {
StrLiteral(v) => Literal(Literal::Str(v.clone())), StrLiteral(v) => Literal(Literal::Str(v.clone())),
// To make sure these are exhaustive, we have to "fake" a union here // To make sure these are exhaustive, we have to "fake" a union here
BitLiteral { value, union, .. } => Ctor(union.clone(), TagId(*value as u8), vec![]), BitLiteral { value, union, .. } => {
EnumLiteral { tag_id, union, .. } => Ctor(union.clone(), TagId(*tag_id), vec![]), Ctor(union.clone(), TagId(*value as TagIdIntType), vec![])
}
EnumLiteral { tag_id, union, .. } => {
Ctor(union.clone(), TagId(*tag_id as TagIdIntType), vec![])
}
Underscore => Anything, Underscore => Anything,
Identifier(_) => Anything, Identifier(_) => Anything,

View file

@ -1,5 +1,7 @@
use crate::borrow::{ParamMap, BORROWED, OWNED}; use crate::borrow::{ParamMap, BORROWED, OWNED};
use crate::ir::{Expr, JoinPointId, ModifyRc, Param, Proc, ProcLayout, Stmt}; use crate::ir::{
CallType, Expr, HigherOrderLowLevel, JoinPointId, ModifyRc, Param, Proc, ProcLayout, Stmt,
};
use crate::layout::Layout; use crate::layout::Layout;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
@ -463,7 +465,7 @@ impl<'a> Context<'a> {
&*self.arena.alloc(Stmt::Let(z, v, l, b)) &*self.arena.alloc(Stmt::Let(z, v, l, b))
} }
HigherOrderLowLevel { HigherOrder(HigherOrderLowLevel {
op, op,
closure_env_layout, closure_env_layout,
specialization_id, specialization_id,
@ -473,7 +475,7 @@ impl<'a> Context<'a> {
function_name, function_name,
function_env, function_env,
.. ..
} => { }) => {
// setup // setup
use crate::low_level::HigherOrder::*; use crate::low_level::HigherOrder::*;
@ -481,7 +483,7 @@ impl<'a> Context<'a> {
($borrows:expr) => { ($borrows:expr) => {
Expr::Call(crate::ir::Call { Expr::Call(crate::ir::Call {
call_type: if let Some(OWNED) = $borrows.map(|p| p.borrow) { call_type: if let Some(OWNED) = $borrows.map(|p| p.borrow) {
HigherOrderLowLevel { let higher_order = HigherOrderLowLevel {
op: *op, op: *op,
closure_env_layout: *closure_env_layout, closure_env_layout: *closure_env_layout,
function_owns_closure_data: true, function_owns_closure_data: true,
@ -491,7 +493,9 @@ impl<'a> Context<'a> {
function_env: *function_env, function_env: *function_env,
arg_layouts, arg_layouts,
ret_layout: *ret_layout, ret_layout: *ret_layout,
} };
CallType::HigherOrder(self.arena.alloc(higher_order))
} else { } else {
call_type call_type
}, },
@ -532,6 +536,7 @@ impl<'a> Context<'a> {
| ListKeepOks { xs } | ListKeepOks { xs }
| ListKeepErrs { xs } | ListKeepErrs { xs }
| ListAny { xs } | ListAny { xs }
| ListAll { xs }
| ListFindUnsafe { xs } => { | ListFindUnsafe { xs } => {
let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA]; let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA];
@ -663,7 +668,7 @@ impl<'a> Context<'a> {
arg_layouts, arg_layouts,
.. ..
} => { } => {
let top_level = ProcLayout::new(self.arena, arg_layouts, *ret_layout); let top_level = ProcLayout::new(self.arena, arg_layouts, **ret_layout);
// get the borrow signature // get the borrow signature
let ps = self let ps = self

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,16 @@ use std::collections::hash_map::Entry;
use std::collections::HashMap; use std::collections::HashMap;
use ven_pretty::{DocAllocator, DocBuilder}; use ven_pretty::{DocAllocator, DocBuilder};
pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::<u8>() * 8) as usize; // if your changes cause this number to go down, great!
// please change it to the lower number.
// if it went up, maybe check that the change is really required
static_assertions::assert_eq_size!([u8; 3 * 8], Builtin);
static_assertions::assert_eq_size!([u8; 4 * 8], Layout);
static_assertions::assert_eq_size!([u8; 3 * 8], UnionLayout);
static_assertions::assert_eq_size!([u8; 3 * 8], LambdaSet);
pub type TagIdIntType = u16;
pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::<TagIdIntType>() * 8) as usize;
const GENERATE_NULLABLE: bool = true; const GENERATE_NULLABLE: bool = true;
/// If a (Num *) gets translated to a Layout, this is the numeric type it defaults to. /// If a (Num *) gets translated to a Layout, this is the numeric type it defaults to.
@ -209,7 +218,7 @@ pub enum UnionLayout<'a> {
/// e.g. `FingerTree a : [ Empty, Single a, More (Some a) (FingerTree (Tuple a)) (Some a) ]` /// e.g. `FingerTree a : [ Empty, Single a, More (Some a) (FingerTree (Tuple a)) (Some a) ]`
/// see also: https://youtu.be/ip92VMpf_-A?t=164 /// see also: https://youtu.be/ip92VMpf_-A?t=164
NullableWrapped { NullableWrapped {
nullable_id: i64, nullable_id: u16,
other_tags: &'a [&'a [Layout<'a>]], other_tags: &'a [&'a [Layout<'a>]],
}, },
/// A recursive tag union where the non-nullable variant does NOT store the tag id /// A recursive tag union where the non-nullable variant does NOT store the tag id
@ -247,7 +256,7 @@ impl<'a> UnionLayout<'a> {
} }
} }
pub fn layout_at(self, tag_id: u8, index: usize) -> Layout<'a> { pub fn layout_at(self, tag_id: TagIdIntType, index: usize) -> Layout<'a> {
let result = match self { let result = match self {
UnionLayout::NonRecursive(tag_layouts) => { UnionLayout::NonRecursive(tag_layouts) => {
let field_layouts = tag_layouts[tag_id as usize]; let field_layouts = tag_layouts[tag_id as usize];
@ -265,9 +274,9 @@ impl<'a> UnionLayout<'a> {
nullable_id, nullable_id,
other_tags, other_tags,
} => { } => {
debug_assert_ne!(nullable_id, tag_id as i64); debug_assert_ne!(nullable_id, tag_id);
let tag_index = if (tag_id as i64) < nullable_id { let tag_index = if tag_id < nullable_id {
tag_id tag_id
} else { } else {
tag_id - 1 tag_id - 1
@ -370,12 +379,12 @@ impl<'a> UnionLayout<'a> {
} }
} }
pub fn tag_is_null(&self, tag_id: u8) -> bool { pub fn tag_is_null(&self, tag_id: TagIdIntType) -> bool {
match self { match self {
UnionLayout::NonRecursive(_) UnionLayout::NonRecursive(_)
| UnionLayout::NonNullableUnwrapped(_) | UnionLayout::NonNullableUnwrapped(_)
| UnionLayout::Recursive(_) => false, | UnionLayout::Recursive(_) => false,
UnionLayout::NullableWrapped { nullable_id, .. } => *nullable_id == tag_id as i64, UnionLayout::NullableWrapped { nullable_id, .. } => *nullable_id == tag_id,
UnionLayout::NullableUnwrapped { nullable_id, .. } => *nullable_id == (tag_id != 0), UnionLayout::NullableUnwrapped { nullable_id, .. } => *nullable_id == (tag_id != 0),
} }
} }
@ -431,7 +440,7 @@ pub enum ClosureRepresentation<'a> {
Union { Union {
alphabetic_order_fields: &'a [Layout<'a>], alphabetic_order_fields: &'a [Layout<'a>],
tag_name: TagName, tag_name: TagName,
tag_id: u8, tag_id: TagIdIntType,
union_layout: UnionLayout<'a>, union_layout: UnionLayout<'a>,
}, },
/// The closure is represented as a struct. The layouts are sorted /// The closure is represented as a struct. The layouts are sorted
@ -489,7 +498,7 @@ impl<'a> LambdaSet<'a> {
.unwrap(); .unwrap();
ClosureRepresentation::Union { ClosureRepresentation::Union {
tag_id: index as u8, tag_id: index as TagIdIntType,
alphabetic_order_fields: fields, alphabetic_order_fields: fields,
tag_name: TagName::Closure(function_symbol), tag_name: TagName::Closure(function_symbol),
union_layout: *union, union_layout: *union,
@ -863,6 +872,16 @@ impl<'a> Layout<'a> {
false false
} }
pub fn is_passed_by_reference(&self) -> bool {
match self {
Layout::Union(UnionLayout::NonRecursive(_)) => true,
Layout::LambdaSet(lambda_set) => {
lambda_set.runtime_representation().is_passed_by_reference()
}
_ => false,
}
}
pub fn stack_size(&self, pointer_size: u32) -> u32 { pub fn stack_size(&self, pointer_size: u32) -> u32 {
let width = self.stack_size_without_alignment(pointer_size); let width = self.stack_size_without_alignment(pointer_size);
let alignment = self.alignment_bytes(pointer_size); let alignment = self.alignment_bytes(pointer_size);
@ -941,16 +960,16 @@ impl<'a> Layout<'a> {
}) })
.max(); .max();
match max_alignment {
Some(align) => {
let tag_id_builtin = variant.tag_id_builtin(); let tag_id_builtin = variant.tag_id_builtin();
match max_alignment {
round_up_to_alignment( Some(align) => round_up_to_alignment(
align, align.max(tag_id_builtin.alignment_bytes(pointer_size)),
tag_id_builtin.alignment_bytes(pointer_size), tag_id_builtin.alignment_bytes(pointer_size),
) ),
None => {
// none of the tags had any payload, but the tag id still contains information
tag_id_builtin.alignment_bytes(pointer_size)
} }
None => 0,
} }
} }
Recursive(_) Recursive(_)
@ -1475,7 +1494,7 @@ fn layout_from_flat_type<'a>(
if GENERATE_NULLABLE { if GENERATE_NULLABLE {
for (index, (_name, variables)) in tags_vec.iter().enumerate() { for (index, (_name, variables)) in tags_vec.iter().enumerate() {
if variables.is_empty() { if variables.is_empty() {
nullable = Some(index as i64); nullable = Some(index as TagIdIntType);
break; break;
} }
} }
@ -1483,7 +1502,7 @@ fn layout_from_flat_type<'a>(
env.insert_seen(rec_var); env.insert_seen(rec_var);
for (index, (_name, variables)) in tags_vec.into_iter().enumerate() { for (index, (_name, variables)) in tags_vec.into_iter().enumerate() {
if matches!(nullable, Some(i) if i == index as i64) { if matches!(nullable, Some(i) if i == index as TagIdIntType) {
// don't add the nullable case // don't add the nullable case
continue; continue;
} }
@ -1633,7 +1652,7 @@ pub enum WrappedVariant<'a> {
sorted_tag_layouts: Vec<'a, (TagName, &'a [Layout<'a>])>, sorted_tag_layouts: Vec<'a, (TagName, &'a [Layout<'a>])>,
}, },
NullableWrapped { NullableWrapped {
nullable_id: i64, nullable_id: TagIdIntType,
nullable_name: TagName, nullable_name: TagName,
sorted_tag_layouts: Vec<'a, (TagName, &'a [Layout<'a>])>, sorted_tag_layouts: Vec<'a, (TagName, &'a [Layout<'a>])>,
}, },
@ -1650,7 +1669,7 @@ pub enum WrappedVariant<'a> {
} }
impl<'a> WrappedVariant<'a> { impl<'a> WrappedVariant<'a> {
pub fn tag_name_to_id(&self, tag_name: &TagName) -> (u8, &'a [Layout<'a>]) { pub fn tag_name_to_id(&self, tag_name: &TagName) -> (TagIdIntType, &'a [Layout<'a>]) {
use WrappedVariant::*; use WrappedVariant::*;
match self { match self {
@ -1662,7 +1681,7 @@ impl<'a> WrappedVariant<'a> {
.expect("tag name is not in its own type"); .expect("tag name is not in its own type");
debug_assert!(tag_id < 256); debug_assert!(tag_id < 256);
(tag_id as u8, *argument_layouts) (tag_id as TagIdIntType, *argument_layouts)
} }
NullableWrapped { NullableWrapped {
nullable_id, nullable_id,
@ -1672,7 +1691,7 @@ impl<'a> WrappedVariant<'a> {
// assumption: the nullable_name is not included in sorted_tag_layouts // assumption: the nullable_name is not included in sorted_tag_layouts
if tag_name == nullable_name { if tag_name == nullable_name {
(*nullable_id as u8, &[] as &[_]) (*nullable_id as TagIdIntType, &[] as &[_])
} else { } else {
let (mut tag_id, (_, argument_layouts)) = sorted_tag_layouts let (mut tag_id, (_, argument_layouts)) = sorted_tag_layouts
.iter() .iter()
@ -1685,7 +1704,7 @@ impl<'a> WrappedVariant<'a> {
} }
debug_assert!(tag_id < 256); debug_assert!(tag_id < 256);
(tag_id as u8, *argument_layouts) (tag_id as TagIdIntType, *argument_layouts)
} }
} }
NullableUnwrapped { NullableUnwrapped {
@ -1695,11 +1714,11 @@ impl<'a> WrappedVariant<'a> {
other_fields, other_fields,
} => { } => {
if tag_name == nullable_name { if tag_name == nullable_name {
(*nullable_id as u8, &[] as &[_]) (*nullable_id as TagIdIntType, &[] as &[_])
} else { } else {
debug_assert_eq!(other_name, tag_name); debug_assert_eq!(other_name, tag_name);
(!*nullable_id as u8, *other_fields) (!*nullable_id as TagIdIntType, *other_fields)
} }
} }
NonNullableUnwrapped { fields, .. } => (0, fields), NonNullableUnwrapped { fields, .. } => (0, fields),
@ -1855,14 +1874,14 @@ fn union_sorted_tags_help_new<'a>(
let mut answer = Vec::with_capacity_in(tags_vec.len(), arena); let mut answer = Vec::with_capacity_in(tags_vec.len(), arena);
let mut has_any_arguments = false; let mut has_any_arguments = false;
let mut nullable: Option<(i64, TagName)> = None; let mut nullable: Option<(TagIdIntType, TagName)> = None;
// only recursive tag unions can be nullable // only recursive tag unions can be nullable
let is_recursive = opt_rec_var.is_some(); let is_recursive = opt_rec_var.is_some();
if is_recursive && GENERATE_NULLABLE { if is_recursive && GENERATE_NULLABLE {
for (index, (name, variables)) in tags_vec.iter().enumerate() { for (index, (name, variables)) in tags_vec.iter().enumerate() {
if variables.is_empty() { if variables.is_empty() {
nullable = Some((index as i64, (*name).clone())); nullable = Some((index as TagIdIntType, (*name).clone()));
break; break;
} }
} }
@ -2073,7 +2092,7 @@ pub fn union_sorted_tags_help<'a>(
if is_recursive && GENERATE_NULLABLE { if is_recursive && GENERATE_NULLABLE {
for (index, (name, variables)) in tags_vec.iter().enumerate() { for (index, (name, variables)) in tags_vec.iter().enumerate() {
if variables.is_empty() { if variables.is_empty() {
nullable = Some((index as i64, name.clone())); nullable = Some((index as TagIdIntType, name.clone()));
break; break;
} }
} }
@ -2674,3 +2693,27 @@ impl<'a> std::convert::TryFrom<&Layout<'a>> for ListLayout<'a> {
} }
} }
} }
#[cfg(test)]
mod test {
use super::*;
#[test]
fn width_and_alignment_union_empty_struct() {
let lambda_set = LambdaSet {
set: &[(Symbol::LIST_MAP, &[])],
representation: &Layout::Struct(&[]),
};
let a = &[Layout::Struct(&[])] as &[_];
let b = &[Layout::LambdaSet(lambda_set)] as &[_];
let tt = [a, b];
let layout = Layout::Union(UnionLayout::NonRecursive(&tt));
// at the moment, the tag id uses an I64, so
let ptr_width = 8;
assert_eq!(layout.stack_size(ptr_width), 8);
assert_eq!(layout.alignment_bytes(ptr_width), 8);
}
}

View file

@ -50,6 +50,9 @@ pub enum HigherOrder {
ListAny { ListAny {
xs: Symbol, xs: Symbol,
}, },
ListAll {
xs: Symbol,
},
ListFindUnsafe { ListFindUnsafe {
xs: Symbol, xs: Symbol,
}, },
@ -77,6 +80,7 @@ impl HigherOrder {
HigherOrder::ListFindUnsafe { .. } => 1, HigherOrder::ListFindUnsafe { .. } => 1,
HigherOrder::DictWalk { .. } => 2, HigherOrder::DictWalk { .. } => 2,
HigherOrder::ListAny { .. } => 1, HigherOrder::ListAny { .. } => 1,
HigherOrder::ListAll { .. } => 1,
} }
} }
} }
@ -100,9 +104,7 @@ enum FirstOrder {
ListLen, ListLen,
ListGetUnsafe, ListGetUnsafe,
ListSet, ListSet,
ListTakeFirst, ListSublist,
ListTakeLast,
ListDrop,
ListDropAt, ListDropAt,
ListSingle, ListSingle,
ListRepeat, ListRepeat,

View file

@ -1,6 +1,6 @@
use crate::inc_dec::{collect_stmt, occurring_variables_expr, JPLiveVarMap, LiveVarSet}; use crate::inc_dec::{collect_stmt, occurring_variables_expr, JPLiveVarMap, LiveVarSet};
use crate::ir::{BranchInfo, Call, Expr, ListLiteralElement, Proc, Stmt}; use crate::ir::{BranchInfo, Call, Expr, ListLiteralElement, Proc, Stmt};
use crate::layout::{Layout, UnionLayout}; use crate::layout::{Layout, TagIdIntType, UnionLayout};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::all::MutSet; use roc_collections::all::MutSet;
@ -27,17 +27,17 @@ pub fn insert_reset_reuse<'a, 'i>(
#[derive(Debug)] #[derive(Debug)]
struct CtorInfo<'a> { struct CtorInfo<'a> {
id: u8, id: TagIdIntType,
layout: UnionLayout<'a>, layout: UnionLayout<'a>,
} }
fn may_reuse(tag_layout: UnionLayout, tag_id: u8, other: &CtorInfo) -> bool { fn may_reuse(tag_layout: UnionLayout, tag_id: TagIdIntType, other: &CtorInfo) -> bool {
if tag_layout != other.layout { if tag_layout != other.layout {
return false; return false;
} }
// we should not get here if the tag we matched on is represented as NULL // we should not get here if the tag we matched on is represented as NULL
debug_assert!(!tag_layout.tag_is_null(other.id)); debug_assert!(!tag_layout.tag_is_null(other.id as _));
// furthermore, we can only use the memory if the tag we're creating is non-NULL // furthermore, we can only use the memory if the tag we're creating is non-NULL
!tag_layout.tag_is_null(tag_id) !tag_layout.tag_is_null(tag_id)

View file

@ -17,3 +17,5 @@ pretty_assertions = "1.0.0"
indoc = "1.0.3" indoc = "1.0.3"
quickcheck = "1.0.3" quickcheck = "1.0.3"
quickcheck_macros = "1.0.0" quickcheck_macros = "1.0.0"
diff = "0.1.12"
ansi_term = "0.12.1"

View file

@ -1,32 +1,12 @@
use std::fmt::Debug;
use crate::header::{AppHeader, ImportsEntry, InterfaceHeader, PlatformHeader, TypedIdent}; use crate::header::{AppHeader, ImportsEntry, InterfaceHeader, PlatformHeader, TypedIdent};
use crate::ident::Ident; use crate::ident::Ident;
use bumpalo::collections::String; use bumpalo::collections::{String, Vec};
use bumpalo::Bump; use bumpalo::Bump;
use roc_module::operator::{BinOp, CalledVia, UnaryOp}; use roc_module::called_via::{BinOp, CalledVia, UnaryOp};
use roc_region::all::{Loc, Position, Region}; use roc_region::all::{Loc, Position, Region};
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Collection<'a, T> {
pub items: &'a [T],
pub final_comments: &'a [CommentOrNewline<'a>],
}
impl<'a, T> Collection<'a, T> {
pub fn empty() -> Collection<'a, T> {
Collection {
items: &[],
final_comments: &[],
}
}
pub fn with_items(items: &'a [T]) -> Collection<'a, T> {
Collection {
items,
final_comments: &[],
}
}
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Module<'a> { pub enum Module<'a> {
Interface { header: InterfaceHeader<'a> }, Interface { header: InterfaceHeader<'a> },
@ -115,21 +95,14 @@ pub enum Expr<'a> {
AccessorFunction(&'a str), AccessorFunction(&'a str),
// Collection Literals // Collection Literals
List { List(Collection<'a, &'a Loc<Expr<'a>>>),
items: &'a [&'a Loc<Expr<'a>>],
final_comments: &'a [CommentOrNewline<'a>],
},
RecordUpdate { RecordUpdate {
update: &'a Loc<Expr<'a>>, update: &'a Loc<Expr<'a>>,
fields: &'a [Loc<AssignedField<'a, Expr<'a>>>], fields: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
final_comments: &'a &'a [CommentOrNewline<'a>],
}, },
Record { Record(Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>),
fields: &'a [Loc<AssignedField<'a, Expr<'a>>>],
final_comments: &'a [CommentOrNewline<'a>],
},
// Lookups // Lookups
Var { Var {
@ -257,18 +230,19 @@ pub enum TypeAnnotation<'a> {
/// The row type variable in an open record, e.g. the `r` in `{ name: Str }r`. /// The row type variable in an open record, e.g. the `r` in `{ name: Str }r`.
/// This is None if it's a closed record annotation like `{ name: Str }`. /// This is None if it's a closed record annotation like `{ name: Str }`.
ext: Option<&'a Loc<TypeAnnotation<'a>>>, ext: Option<&'a Loc<TypeAnnotation<'a>>>,
// final_comments: &'a [CommentOrNewline<'a>],
}, },
/// A tag union, e.g. `[ /// A tag union, e.g. `[
TagUnion { TagUnion {
tags: &'a [Loc<Tag<'a>>],
/// The row type variable in an open tag union, e.g. the `a` in `[ Foo, Bar ]a`. /// The row type variable in an open tag union, e.g. the `a` in `[ Foo, Bar ]a`.
/// This is None if it's a closed tag union like `[ Foo, Bar]`. /// This is None if it's a closed tag union like `[ Foo, Bar]`.
ext: Option<&'a Loc<TypeAnnotation<'a>>>, ext: Option<&'a Loc<TypeAnnotation<'a>>>,
final_comments: &'a [CommentOrNewline<'a>], tags: Collection<'a, Loc<Tag<'a>>>,
}, },
/// '_', indicating the compiler should infer the type
Inferred,
/// The `*` type variable, e.g. in (List *) /// The `*` type variable, e.g. in (List *)
Wildcard, Wildcard,
@ -357,10 +331,11 @@ pub enum Pattern<'a> {
GlobalTag(&'a str), GlobalTag(&'a str),
PrivateTag(&'a str), PrivateTag(&'a str),
Apply(&'a Loc<Pattern<'a>>, &'a [Loc<Pattern<'a>>]), Apply(&'a Loc<Pattern<'a>>, &'a [Loc<Pattern<'a>>]),
/// This is Loc<Pattern> rather than Loc<str> so we can record comments /// This is Loc<Pattern> rather than Loc<str> so we can record comments
/// around the destructured names, e.g. { x ### x does stuff ###, y } /// around the destructured names, e.g. { x ### x does stuff ###, y }
/// In practice, these patterns will always be Identifier /// In practice, these patterns will always be Identifier
RecordDestructure(&'a [Loc<Pattern<'a>>]), RecordDestructure(Collection<'a, Loc<Pattern<'a>>>),
/// A required field pattern, e.g. { x: Just 0 } -> ... /// A required field pattern, e.g. { x: Just 0 } -> ...
/// Can only occur inside of a RecordDestructure /// Can only occur inside of a RecordDestructure
@ -519,6 +494,126 @@ impl<'a> Pattern<'a> {
} }
} }
} }
#[derive(Copy, Clone)]
pub struct Collection<'a, T> {
pub items: &'a [T],
// Use a pointer to a slice (rather than just a slice), in order to avoid bloating
// Ast variants. The final_comments field is rarely accessed in the hot path, so
// this shouldn't matter much for perf.
// Use an Option, so it's possible to initialize without allocating.
final_comments: Option<&'a &'a [CommentOrNewline<'a>]>,
}
impl<'a, T> Collection<'a, T> {
pub fn empty() -> Collection<'a, T> {
Collection {
items: &[],
final_comments: None,
}
}
pub fn with_items(items: &'a [T]) -> Collection<'a, T> {
Collection {
items,
final_comments: None,
}
}
pub fn with_items_and_comments(
arena: &'a Bump,
items: &'a [T],
comments: &'a [CommentOrNewline<'a>],
) -> Collection<'a, T> {
Collection {
items,
final_comments: if comments.is_empty() {
None
} else {
Some(arena.alloc(comments))
},
}
}
pub fn replace_items<V>(&self, new_items: &'a [V]) -> Collection<'a, V> {
Collection {
items: new_items,
final_comments: self.final_comments,
}
}
pub fn ptrify_items(&self, arena: &'a Bump) -> Collection<'a, &'a T> {
let mut allocated = Vec::with_capacity_in(self.len(), arena);
for parsed_elem in self.items {
allocated.push(parsed_elem);
}
self.replace_items(allocated.into_bump_slice())
}
pub fn map_items<V: 'a>(&self, arena: &'a Bump, f: impl Fn(&'a T) -> V) -> Collection<'a, V> {
let mut allocated = Vec::with_capacity_in(self.len(), arena);
for parsed_elem in self.items {
allocated.push(f(parsed_elem));
}
self.replace_items(allocated.into_bump_slice())
}
pub fn map_items_result<V: 'a, E>(
&self,
arena: &'a Bump,
f: impl Fn(&T) -> Result<V, E>,
) -> Result<Collection<'a, V>, E> {
let mut allocated = Vec::with_capacity_in(self.len(), arena);
for parsed_elem in self.items {
allocated.push(f(parsed_elem)?);
}
Ok(self.replace_items(allocated.into_bump_slice()))
}
pub fn final_comments(&self) -> &'a [CommentOrNewline<'a>] {
if let Some(final_comments) = self.final_comments {
*final_comments
} else {
&[]
}
}
pub fn iter(&self) -> impl Iterator<Item = &'a T> {
self.items.iter()
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
}
impl<'a, T: PartialEq> PartialEq for Collection<'a, T> {
fn eq(&self, other: &Self) -> bool {
self.items == other.items && self.final_comments() == other.final_comments()
}
}
impl<'a, T: Debug> Debug for Collection<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.final_comments().is_empty() {
f.debug_list().entries(self.items.iter()).finish()
} else {
f.debug_struct("Collection")
.field("items", &self.items)
.field("final_comments", &self.final_comments())
.finish()
}
}
}
pub trait Spaceable<'a> { pub trait Spaceable<'a> {
fn before(&'a self, _: &'a [CommentOrNewline<'a>]) -> Self; fn before(&'a self, _: &'a [CommentOrNewline<'a>]) -> Self;

View file

@ -1,17 +1,19 @@
use crate::ast::{AssignedField, CommentOrNewline, Def, Expr, Pattern, Spaceable, TypeAnnotation}; use crate::ast::{
AssignedField, Collection, CommentOrNewline, Def, Expr, Pattern, Spaceable, TypeAnnotation,
};
use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e}; use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e};
use crate::ident::{lowercase_ident, parse_ident, Ident}; use crate::ident::{lowercase_ident, parse_ident, Ident};
use crate::keyword; use crate::keyword;
use crate::parser::{ use crate::parser::{
self, backtrackable, optional, sep_by1, sep_by1_e, specialize, specialize_ref, then, self, backtrackable, optional, sep_by1, sep_by1_e, specialize, specialize_ref, then,
trailing_sep_by0, word1, word2, EExpr, EInParens, ELambda, EPattern, ERecord, EString, Either, trailing_sep_by0, word1, word2, EExpect, EExpr, EIf, EInParens, ELambda, EList, ENumber,
Expect, If, List, Number, ParseResult, Parser, State, Type, When, EPattern, ERecord, EString, EType, EWhen, Either, ParseResult, Parser, State,
}; };
use crate::pattern::loc_closure_param; use crate::pattern::loc_closure_param;
use crate::type_annotation; use crate::type_annotation;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_module::operator::{BinOp, CalledVia, UnaryOp}; use roc_module::called_via::{BinOp, CalledVia, UnaryOp};
use roc_region::all::{Located, Position, Region}; use roc_region::all::{Located, Position, Region};
use crate::parser::Progress::{self, *}; use crate::parser::Progress::{self, *};
@ -801,7 +803,7 @@ fn parse_defs_end<'a>(
Err((NoProgress, _, _)) => { Err((NoProgress, _, _)) => {
let start = state.get_position(); let start = state.get_position();
match crate::parser::keyword_e(crate::keyword::EXPECT, Expect::Expect) match crate::parser::keyword_e(crate::keyword::EXPECT, EExpect::Expect)
.parse(arena, state) .parse(arena, state)
{ {
Err((_, _, _)) => { Err((_, _, _)) => {
@ -861,8 +863,8 @@ fn parse_defs_end<'a>(
space0_before_e( space0_before_e(
type_annotation::located_help(min_indent + 1), type_annotation::located_help(min_indent + 1),
min_indent + 1, min_indent + 1,
Type::TSpace, EType::TSpace,
Type::TIndentStart, EType::TIndentStart,
), ),
) )
.parse(arena, state)?; .parse(arena, state)?;
@ -1099,8 +1101,8 @@ fn parse_expr_operator<'a>(
space0_before_e( space0_before_e(
type_annotation::located_help(indented_more), type_annotation::located_help(indented_more),
min_indent, min_indent,
Type::TSpace, EType::TSpace,
Type::TIndentStart, EType::TIndentStart,
), ),
) )
.parse(arena, state)?; .parse(arena, state)?;
@ -1126,8 +1128,8 @@ fn parse_expr_operator<'a>(
space0_before_e( space0_before_e(
type_annotation::located_help(indented_more), type_annotation::located_help(indented_more),
min_indent, min_indent,
Type::TSpace, EType::TSpace,
Type::TIndentStart, EType::TIndentStart,
), ),
); );
@ -1446,20 +1448,14 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
Expr::ParensAround(sub_expr) => expr_to_pattern_help(arena, sub_expr), Expr::ParensAround(sub_expr) => expr_to_pattern_help(arena, sub_expr),
Expr::Record { Expr::Record(fields) => {
fields, let patterns = fields.map_items_result(arena, |loc_assigned_field| {
final_comments: _,
} => {
let mut loc_patterns = Vec::with_capacity_in(fields.len(), arena);
for loc_assigned_field in fields.iter() {
let region = loc_assigned_field.region; let region = loc_assigned_field.region;
let value = assigned_expr_field_to_pattern_help(arena, &loc_assigned_field.value)?; let value = assigned_expr_field_to_pattern_help(arena, &loc_assigned_field.value)?;
Ok(Located { region, value })
})?;
loc_patterns.push(Located { region, value }); Ok(Pattern::RecordDestructure(patterns))
}
Ok(Pattern::RecordDestructure(loc_patterns.into_bump_slice()))
} }
Expr::Float(string) => Ok(Pattern::FloatLiteral(string)), Expr::Float(string) => Ok(Pattern::FloatLiteral(string)),
@ -1651,21 +1647,21 @@ mod when {
pub fn expr_help<'a>( pub fn expr_help<'a>(
min_indent: u16, min_indent: u16,
options: ExprParseOptions, options: ExprParseOptions,
) -> impl Parser<'a, Expr<'a>, When<'a>> { ) -> impl Parser<'a, Expr<'a>, EWhen<'a>> {
then( then(
and!( and!(
when_with_indent(), when_with_indent(),
skip_second!( skip_second!(
space0_around_ee( space0_around_ee(
specialize_ref(When::Condition, move |arena, state| { specialize_ref(EWhen::Condition, move |arena, state| {
parse_loc_expr_with_options(min_indent, options, arena, state) parse_loc_expr_with_options(min_indent, options, arena, state)
}), }),
min_indent, min_indent,
When::Space, EWhen::Space,
When::IndentCondition, EWhen::IndentCondition,
When::IndentIs, EWhen::IndentIs,
), ),
parser::keyword_e(keyword::IS, When::Is) parser::keyword_e(keyword::IS, EWhen::Is)
) )
), ),
move |arena, state, progress, (case_indent, loc_condition)| { move |arena, state, progress, (case_indent, loc_condition)| {
@ -1673,7 +1669,7 @@ mod when {
return Err(( return Err((
progress, progress,
// TODO maybe pass case_indent here? // TODO maybe pass case_indent here?
When::PatternAlignment(5, state.line, state.column), EWhen::PatternAlignment(5, state.line, state.column),
state, state,
)); ));
} }
@ -1693,9 +1689,9 @@ mod when {
} }
/// Parsing when with indentation. /// Parsing when with indentation.
fn when_with_indent<'a>() -> impl Parser<'a, u16, When<'a>> { fn when_with_indent<'a>() -> impl Parser<'a, u16, EWhen<'a>> {
move |arena, state: State<'a>| { move |arena, state: State<'a>| {
parser::keyword_e(keyword::WHEN, When::When) parser::keyword_e(keyword::WHEN, EWhen::When)
.parse(arena, state) .parse(arena, state)
.map(|(progress, (), state)| (progress, state.indent_col, state)) .map(|(progress, (), state)| (progress, state.indent_col, state))
} }
@ -1704,7 +1700,7 @@ mod when {
fn branches<'a>( fn branches<'a>(
min_indent: u16, min_indent: u16,
options: ExprParseOptions, options: ExprParseOptions,
) -> impl Parser<'a, Vec<'a, &'a WhenBranch<'a>>, When<'a>> { ) -> impl Parser<'a, Vec<'a, &'a WhenBranch<'a>>, EWhen<'a>> {
move |arena, state: State<'a>| { move |arena, state: State<'a>| {
let when_indent = state.indent_col; let when_indent = state.indent_col;
@ -1741,7 +1737,7 @@ mod when {
let indent = pattern_indent_level - indent_col; let indent = pattern_indent_level - indent_col;
Err(( Err((
MadeProgress, MadeProgress,
When::PatternAlignment(indent, state.line, state.column), EWhen::PatternAlignment(indent, state.line, state.column),
state, state,
)) ))
} }
@ -1799,7 +1795,7 @@ mod when {
(Col, Vec<'a, Located<Pattern<'a>>>), (Col, Vec<'a, Located<Pattern<'a>>>),
Option<Located<Expr<'a>>>, Option<Located<Expr<'a>>>,
), ),
When<'a>, EWhen<'a>,
> { > {
let options = ExprParseOptions { let options = ExprParseOptions {
check_for_arrow: false, check_for_arrow: false,
@ -1810,16 +1806,16 @@ mod when {
one_of![ one_of![
map!( map!(
skip_first!( skip_first!(
parser::keyword_e(keyword::IF, When::IfToken), parser::keyword_e(keyword::IF, EWhen::IfToken),
// TODO we should require space before the expression but not after // TODO we should require space before the expression but not after
space0_around_ee( space0_around_ee(
specialize_ref(When::IfGuard, move |arena, state| { specialize_ref(EWhen::IfGuard, move |arena, state| {
parse_loc_expr_with_options(min_indent + 1, options, arena, state) parse_loc_expr_with_options(min_indent + 1, options, arena, state)
}), }),
min_indent, min_indent,
When::Space, EWhen::Space,
When::IndentIfGuard, EWhen::IndentIfGuard,
When::IndentArrow, EWhen::IndentArrow,
) )
), ),
Some Some
@ -1831,17 +1827,17 @@ mod when {
fn branch_single_alternative<'a>( fn branch_single_alternative<'a>(
min_indent: u16, min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, When<'a>> { ) -> impl Parser<'a, Located<Pattern<'a>>, EWhen<'a>> {
move |arena, state| { move |arena, state| {
let (_, spaces, state) = let (_, spaces, state) =
backtrackable(space0_e(min_indent, When::Space, When::IndentPattern)) backtrackable(space0_e(min_indent, EWhen::Space, EWhen::IndentPattern))
.parse(arena, state)?; .parse(arena, state)?;
let (_, loc_pattern, state) = space0_after_e( let (_, loc_pattern, state) = space0_after_e(
specialize(When::Pattern, crate::pattern::loc_pattern_help(min_indent)), specialize(EWhen::Pattern, crate::pattern::loc_pattern_help(min_indent)),
min_indent, min_indent,
When::Space, EWhen::Space,
When::IndentPattern, EWhen::IndentPattern,
) )
.parse(arena, state)?; .parse(arena, state)?;
@ -1862,12 +1858,12 @@ mod when {
fn branch_alternatives_help<'a>( fn branch_alternatives_help<'a>(
min_indent: u16, min_indent: u16,
pattern_indent_level: Option<u16>, pattern_indent_level: Option<u16>,
) -> impl Parser<'a, (Col, Vec<'a, Located<Pattern<'a>>>), When<'a>> { ) -> impl Parser<'a, (Col, Vec<'a, Located<Pattern<'a>>>), EWhen<'a>> {
move |arena, state: State<'a>| { move |arena, state: State<'a>| {
let initial = state; let initial = state;
// put no restrictions on the indent after the spaces; we'll check it manually // put no restrictions on the indent after the spaces; we'll check it manually
match space0_e(0, When::Space, When::IndentPattern).parse(arena, state) { match space0_e(0, EWhen::Space, EWhen::IndentPattern).parse(arena, state) {
Err((MadeProgress, fail, _)) => Err((NoProgress, fail, initial)), Err((MadeProgress, fail, _)) => Err((NoProgress, fail, initial)),
Err((NoProgress, fail, _)) => Err((NoProgress, fail, initial)), Err((NoProgress, fail, _)) => Err((NoProgress, fail, initial)),
Ok((_progress, spaces, state)) => { Ok((_progress, spaces, state)) => {
@ -1876,7 +1872,7 @@ mod when {
// this branch is indented too much // this branch is indented too much
Err(( Err((
NoProgress, NoProgress,
When::IndentPattern(state.line, state.column), EWhen::IndentPattern(state.line, state.column),
initial, initial,
)) ))
} }
@ -1884,7 +1880,7 @@ mod when {
let indent = wanted - state.column; let indent = wanted - state.column;
Err(( Err((
NoProgress, NoProgress,
When::PatternAlignment(indent, state.line, state.column), EWhen::PatternAlignment(indent, state.line, state.column),
initial, initial,
)) ))
} }
@ -1896,7 +1892,7 @@ mod when {
let pattern_indent_col = state.column; let pattern_indent_col = state.column;
let parser = sep_by1( let parser = sep_by1(
word1(b'|', When::Bar), word1(b'|', EWhen::Bar),
branch_single_alternative(pattern_indent + 1), branch_single_alternative(pattern_indent + 1),
); );
@ -1930,16 +1926,16 @@ mod when {
} }
/// Parsing the righthandside of a branch in a when conditional. /// Parsing the righthandside of a branch in a when conditional.
fn branch_result<'a>(indent: u16) -> impl Parser<'a, Located<Expr<'a>>, When<'a>> { fn branch_result<'a>(indent: u16) -> impl Parser<'a, Located<Expr<'a>>, EWhen<'a>> {
skip_first!( skip_first!(
word2(b'-', b'>', When::Arrow), word2(b'-', b'>', EWhen::Arrow),
space0_before_e( space0_before_e(
specialize_ref(When::Branch, move |arena, state| parse_loc_expr( specialize_ref(EWhen::Branch, move |arena, state| parse_loc_expr(
indent, arena, state indent, arena, state
)), )),
indent, indent,
When::Space, EWhen::Space,
When::IndentBranch, EWhen::IndentBranch,
) )
) )
} }
@ -1947,38 +1943,38 @@ mod when {
fn if_branch<'a>( fn if_branch<'a>(
min_indent: u16, min_indent: u16,
) -> impl Parser<'a, (Located<Expr<'a>>, Located<Expr<'a>>), If<'a>> { ) -> impl Parser<'a, (Located<Expr<'a>>, Located<Expr<'a>>), EIf<'a>> {
move |arena, state| { move |arena, state| {
// NOTE: only parse spaces before the expression // NOTE: only parse spaces before the expression
let (_, cond, state) = space0_around_ee( let (_, cond, state) = space0_around_ee(
specialize_ref(If::Condition, move |arena, state| { specialize_ref(EIf::Condition, move |arena, state| {
parse_loc_expr(min_indent, arena, state) parse_loc_expr(min_indent, arena, state)
}), }),
min_indent, min_indent,
If::Space, EIf::Space,
If::IndentCondition, EIf::IndentCondition,
If::IndentThenToken, EIf::IndentThenToken,
) )
.parse(arena, state) .parse(arena, state)
.map_err(|(_, f, s)| (MadeProgress, f, s))?; .map_err(|(_, f, s)| (MadeProgress, f, s))?;
let (_, _, state) = parser::keyword_e(keyword::THEN, If::Then) let (_, _, state) = parser::keyword_e(keyword::THEN, EIf::Then)
.parse(arena, state) .parse(arena, state)
.map_err(|(_, f, s)| (MadeProgress, f, s))?; .map_err(|(_, f, s)| (MadeProgress, f, s))?;
let (_, then_branch, state) = space0_around_ee( let (_, then_branch, state) = space0_around_ee(
specialize_ref(If::ThenBranch, move |arena, state| { specialize_ref(EIf::ThenBranch, move |arena, state| {
parse_loc_expr(min_indent, arena, state) parse_loc_expr(min_indent, arena, state)
}), }),
min_indent, min_indent,
If::Space, EIf::Space,
If::IndentThenBranch, EIf::IndentThenBranch,
If::IndentElseToken, EIf::IndentElseToken,
) )
.parse(arena, state) .parse(arena, state)
.map_err(|(_, f, s)| (MadeProgress, f, s))?; .map_err(|(_, f, s)| (MadeProgress, f, s))?;
let (_, _, state) = parser::keyword_e(keyword::ELSE, If::Else) let (_, _, state) = parser::keyword_e(keyword::ELSE, EIf::Else)
.parse(arena, state) .parse(arena, state)
.map_err(|(_, f, s)| (MadeProgress, f, s))?; .map_err(|(_, f, s)| (MadeProgress, f, s))?;
@ -1989,26 +1985,26 @@ fn if_branch<'a>(
fn expect_help<'a>( fn expect_help<'a>(
min_indent: u16, min_indent: u16,
options: ExprParseOptions, options: ExprParseOptions,
) -> impl Parser<'a, Expr<'a>, Expect<'a>> { ) -> impl Parser<'a, Expr<'a>, EExpect<'a>> {
move |arena: &'a Bump, state: State<'a>| { move |arena: &'a Bump, state: State<'a>| {
let start = state.get_position(); let start = state.get_position();
let (_, _, state) = let (_, _, state) =
parser::keyword_e(keyword::EXPECT, Expect::Expect).parse(arena, state)?; parser::keyword_e(keyword::EXPECT, EExpect::Expect).parse(arena, state)?;
let (_, condition, state) = space0_before_e( let (_, condition, state) = space0_before_e(
specialize_ref(Expect::Condition, move |arena, state| { specialize_ref(EExpect::Condition, move |arena, state| {
parse_loc_expr_with_options(start.col + 1, options, arena, state) parse_loc_expr_with_options(start.col + 1, options, arena, state)
}), }),
start.col + 1, start.col + 1,
Expect::Space, EExpect::Space,
Expect::IndentCondition, EExpect::IndentCondition,
) )
.parse(arena, state) .parse(arena, state)
.map_err(|(_, f, s)| (MadeProgress, f, s))?; .map_err(|(_, f, s)| (MadeProgress, f, s))?;
let parse_cont = specialize_ref( let parse_cont = specialize_ref(
Expect::Continuation, EExpect::Continuation,
space0_before_e( space0_before_e(
move |a, s| parse_loc_expr(min_indent, a, s), move |a, s| parse_loc_expr(min_indent, a, s),
min_indent, min_indent,
@ -2028,9 +2024,9 @@ fn expect_help<'a>(
fn if_expr_help<'a>( fn if_expr_help<'a>(
min_indent: u16, min_indent: u16,
options: ExprParseOptions, options: ExprParseOptions,
) -> impl Parser<'a, Expr<'a>, If<'a>> { ) -> impl Parser<'a, Expr<'a>, EIf<'a>> {
move |arena: &'a Bump, state| { move |arena: &'a Bump, state| {
let (_, _, state) = parser::keyword_e(keyword::IF, If::If).parse(arena, state)?; let (_, _, state) = parser::keyword_e(keyword::IF, EIf::If).parse(arena, state)?;
let mut branches = Vec::with_capacity_in(1, arena); let mut branches = Vec::with_capacity_in(1, arena);
@ -2044,8 +2040,8 @@ fn if_expr_help<'a>(
// try to parse another `if` // try to parse another `if`
// NOTE this drops spaces between the `else` and the `if` // NOTE this drops spaces between the `else` and the `if`
let optional_if = and!( let optional_if = and!(
backtrackable(space0_e(min_indent, If::Space, If::IndentIf)), backtrackable(space0_e(min_indent, EIf::Space, EIf::IndentIf)),
parser::keyword_e(keyword::IF, If::If) parser::keyword_e(keyword::IF, EIf::If)
); );
match optional_if.parse(arena, state) { match optional_if.parse(arena, state) {
@ -2058,12 +2054,12 @@ fn if_expr_help<'a>(
}; };
let (_, else_branch, state) = space0_before_e( let (_, else_branch, state) = space0_before_e(
specialize_ref(If::ElseBranch, move |arena, state| { specialize_ref(EIf::ElseBranch, move |arena, state| {
parse_loc_expr_with_options(min_indent, options, arena, state) parse_loc_expr_with_options(min_indent, options, arena, state)
}), }),
min_indent, min_indent,
If::Space, EIf::Space,
If::IndentElseBranch, EIf::IndentElseBranch,
) )
.parse(arena, state_final_else) .parse(arena, state_final_else)
.map_err(|(_, f, s)| (MadeProgress, f, s))?; .map_err(|(_, f, s)| (MadeProgress, f, s))?;
@ -2147,33 +2143,26 @@ fn ident_to_expr<'a>(arena: &'a Bump, src: Ident<'a>) -> Expr<'a> {
} }
} }
fn list_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, List<'a>> { fn list_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EList<'a>> {
move |arena, state| { move |arena, state| {
let (_, elements, state) = collection_trailing_sep_e!( let (_, elements, state) = collection_trailing_sep_e!(
word1(b'[', List::Open), word1(b'[', EList::Open),
specialize_ref(List::Expr, move |a, s| parse_loc_expr_no_multi_backpassing( specialize_ref(
min_indent, a, s EList::Expr,
)), move |a, s| parse_loc_expr_no_multi_backpassing(min_indent, a, s)
word1(b',', List::End), ),
word1(b']', List::End), word1(b',', EList::End),
word1(b']', EList::End),
min_indent, min_indent,
List::Open, EList::Open,
List::Space, EList::Space,
List::IndentEnd, EList::IndentEnd,
Expr::SpaceBefore Expr::SpaceBefore
) )
.parse(arena, state)?; .parse(arena, state)?;
let mut allocated = Vec::with_capacity_in(elements.items.len(), arena); let elements = elements.ptrify_items(arena);
let expr = Expr::List(elements);
for parsed_elem in elements.items {
allocated.push(parsed_elem);
}
let expr = Expr::List {
items: allocated.into_bump_slice(),
final_comments: elements.final_comments,
};
Ok((MadeProgress, expr, state)) Ok((MadeProgress, expr, state))
} }
@ -2311,13 +2300,17 @@ fn record_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EExpr<'
let mut value = match opt_update { let mut value = match opt_update {
Some(update) => Expr::RecordUpdate { Some(update) => Expr::RecordUpdate {
update: &*arena.alloc(update), update: &*arena.alloc(update),
fields: loc_assigned_fields_with_comments.value.0.into_bump_slice(), fields: Collection::with_items_and_comments(
final_comments: arena.alloc(loc_assigned_fields_with_comments.value.1), arena,
}, loc_assigned_fields_with_comments.value.0.into_bump_slice(),
None => Expr::Record { arena.alloc(loc_assigned_fields_with_comments.value.1),
fields: loc_assigned_fields_with_comments.value.0.into_bump_slice(), ),
final_comments: loc_assigned_fields_with_comments.value.1,
}, },
None => Expr::Record(Collection::with_items_and_comments(
arena,
loc_assigned_fields_with_comments.value.0.into_bump_slice(),
loc_assigned_fields_with_comments.value.1,
)),
}; };
// there can be field access, e.g. `{ x : 4 }.x` // there can be field access, e.g. `{ x : 4 }.x`
@ -2341,7 +2334,7 @@ fn string_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EString<'a>> {
map!(crate::string_literal::parse(), Expr::Str) map!(crate::string_literal::parse(), Expr::Str)
} }
fn positive_number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, Number> { fn positive_number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> {
map!( map!(
crate::number_literal::positive_number_literal(), crate::number_literal::positive_number_literal(),
|literal| { |literal| {
@ -2364,7 +2357,7 @@ fn positive_number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, Number> {
) )
} }
fn number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, Number> { fn number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> {
map!(crate::number_literal::number_literal(), |literal| { map!(crate::number_literal::number_literal(), |literal| {
use crate::number_literal::NumLiteral::*; use crate::number_literal::NumLiteral::*;

View file

@ -38,7 +38,7 @@ pub enum PackageOrPath<'a> {
Path(StrLiteral<'a>), Path(StrLiteral<'a>),
} }
#[derive(Clone, PartialEq, Eq, Debug, Hash)] #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub struct ModuleName<'a>(&'a str); pub struct ModuleName<'a>(&'a str);
impl<'a> From<ModuleName<'a>> for &'a str { impl<'a> From<ModuleName<'a>> for &'a str {
@ -60,8 +60,8 @@ impl<'a> ModuleName<'a> {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct InterfaceHeader<'a> { pub struct InterfaceHeader<'a> {
pub name: Loc<ModuleName<'a>>, pub name: Loc<ModuleName<'a>>,
pub exposes: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>, pub exposes: Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>, pub imports: Collection<'a, Loc<ImportsEntry<'a>>>,
// Potential comments and newlines - these will typically all be empty. // Potential comments and newlines - these will typically all be empty.
pub before_header: &'a [CommentOrNewline<'a>], pub before_header: &'a [CommentOrNewline<'a>],
@ -82,8 +82,8 @@ pub enum To<'a> {
pub struct AppHeader<'a> { pub struct AppHeader<'a> {
pub name: Loc<StrLiteral<'a>>, pub name: Loc<StrLiteral<'a>>,
pub packages: Collection<'a, Loc<PackageEntry<'a>>>, pub packages: Collection<'a, Loc<PackageEntry<'a>>>,
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>, pub imports: Collection<'a, Loc<ImportsEntry<'a>>>,
pub provides: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>, pub provides: Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub to: Loc<To<'a>>, pub to: Loc<To<'a>>,
// Potential comments and newlines - these will typically all be empty. // Potential comments and newlines - these will typically all be empty.
@ -117,7 +117,7 @@ pub struct PackageHeader<'a> {
pub after_imports: &'a [CommentOrNewline<'a>], pub after_imports: &'a [CommentOrNewline<'a>],
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub enum PlatformRigid<'a> { pub enum PlatformRigid<'a> {
Entry { rigid: &'a str, alias: &'a str }, Entry { rigid: &'a str, alias: &'a str },
@ -137,7 +137,7 @@ impl<'a> Spaceable<'a> for PlatformRigid<'a> {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct PlatformRequires<'a> { pub struct PlatformRequires<'a> {
pub rigids: Vec<'a, Loc<PlatformRigid<'a>>>, pub rigids: Collection<'a, Loc<PlatformRigid<'a>>>,
pub signature: Loc<TypedIdent<'a>>, pub signature: Loc<TypedIdent<'a>>,
} }
@ -145,10 +145,10 @@ pub struct PlatformRequires<'a> {
pub struct PlatformHeader<'a> { pub struct PlatformHeader<'a> {
pub name: Loc<PackageName<'a>>, pub name: Loc<PackageName<'a>>,
pub requires: PlatformRequires<'a>, pub requires: PlatformRequires<'a>,
pub exposes: Vec<'a, Loc<ExposesEntry<'a, ModuleName<'a>>>>, pub exposes: Collection<'a, Loc<ExposesEntry<'a, ModuleName<'a>>>>,
pub packages: Collection<'a, Loc<PackageEntry<'a>>>, pub packages: Collection<'a, Loc<PackageEntry<'a>>>,
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>, pub imports: Collection<'a, Loc<ImportsEntry<'a>>>,
pub provides: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>, pub provides: Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub effects: Effects<'a>, pub effects: Effects<'a>,
// Potential comments and newlines - these will typically all be empty. // Potential comments and newlines - these will typically all be empty.
@ -177,7 +177,7 @@ pub struct Effects<'a> {
pub entries: &'a [Loc<TypedIdent<'a>>], pub entries: &'a [Loc<TypedIdent<'a>>],
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub enum ExposesEntry<'a, T> { pub enum ExposesEntry<'a, T> {
/// e.g. `Task` /// e.g. `Task`
Exposed(T), Exposed(T),
@ -196,16 +196,19 @@ impl<'a, T> Spaceable<'a> for ExposesEntry<'a, T> {
} }
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub enum ImportsEntry<'a> { pub enum ImportsEntry<'a> {
/// e.g. `Task` or `Task.{ Task, after }` /// e.g. `Task` or `Task.{ Task, after }`
Module(ModuleName<'a>, Vec<'a, Loc<ExposesEntry<'a, &'a str>>>), Module(
ModuleName<'a>,
Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
),
/// e.g. `base.Task` or `base.Task.{ after }` or `base.{ Task.{ Task, after } }` /// e.g. `base.Task` or `base.Task.{ after }` or `base.{ Task.{ Task, after } }`
Package( Package(
&'a str, &'a str,
ModuleName<'a>, ModuleName<'a>,
Vec<'a, Loc<ExposesEntry<'a, &'a str>>>, Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
), ),
// Spaces // Spaces
@ -224,7 +227,7 @@ impl<'a> ExposesEntry<'a, &'a str> {
} }
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub enum TypedIdent<'a> { pub enum TypedIdent<'a> {
/// e.g. /// e.g.
/// ///

View file

@ -220,11 +220,11 @@ fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
let opt_imports: Option<( let opt_imports: Option<(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ImportsEntry<'a>>>, Collection<'a, Located<ImportsEntry<'a>>>,
)> = opt_imports; )> = opt_imports;
let ((before_imports, after_imports), imports) = let ((before_imports, after_imports), imports) =
opt_imports.unwrap_or_else(|| ((&[] as _, &[] as _), Vec::new_in(arena))); opt_imports.unwrap_or_else(|| ((&[] as _, &[] as _), Collection::empty()));
let provides: ProvidesTo<'a> = provides; // rustc must be told the type here let provides: ProvidesTo<'a> = provides; // rustc must be told the type here
let header = AppHeader { let header = AppHeader {
@ -303,7 +303,7 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> {
#[derive(Debug)] #[derive(Debug)]
struct ProvidesTo<'a> { struct ProvidesTo<'a> {
entries: Vec<'a, Located<ExposesEntry<'a, &'a str>>>, entries: Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
to: Located<To<'a>>, to: Located<To<'a>>,
before_provides_keyword: &'a [CommentOrNewline<'a>], before_provides_keyword: &'a [CommentOrNewline<'a>],
@ -362,7 +362,7 @@ fn provides_without_to<'a>() -> impl Parser<
'a, 'a,
( (
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ExposesEntry<'a, &'a str>>>, Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
), ),
EProvides<'a>, EProvides<'a>,
> { > {
@ -376,14 +376,16 @@ fn provides_without_to<'a>() -> impl Parser<
EProvides::IndentProvides, EProvides::IndentProvides,
EProvides::IndentListStart EProvides::IndentListStart
), ),
collection_e!( collection_trailing_sep_e!(
word1(b'[', EProvides::ListStart), word1(b'[', EProvides::ListStart),
exposes_entry(EProvides::Identifier), exposes_entry(EProvides::Identifier),
word1(b',', EProvides::ListEnd), word1(b',', EProvides::ListEnd),
word1(b']', EProvides::ListEnd), word1(b']', EProvides::ListEnd),
min_indent, min_indent,
EProvides::Open,
EProvides::Space, EProvides::Space,
EProvides::IndentListEnd EProvides::IndentListEnd,
ExposesEntry::SpaceBefore
) )
) )
} }
@ -442,15 +444,17 @@ fn platform_requires<'a>() -> impl Parser<'a, PlatformRequires<'a>, ERequires<'a
#[inline(always)] #[inline(always)]
fn requires_rigids<'a>( fn requires_rigids<'a>(
min_indent: u16, min_indent: u16,
) -> impl Parser<'a, Vec<'a, Located<PlatformRigid<'a>>>, ERequires<'a>> { ) -> impl Parser<'a, Collection<'a, Located<PlatformRigid<'a>>>, ERequires<'a>> {
collection_e!( collection_trailing_sep_e!(
word1(b'{', ERequires::ListStart), word1(b'{', ERequires::ListStart),
specialize(|_, r, c| ERequires::Rigid(r, c), loc!(requires_rigid())), specialize(|_, r, c| ERequires::Rigid(r, c), loc!(requires_rigid())),
word1(b',', ERequires::ListEnd), word1(b',', ERequires::ListEnd),
word1(b'}', ERequires::ListEnd), word1(b'}', ERequires::ListEnd),
min_indent, min_indent,
ERequires::Open,
ERequires::Space, ERequires::Space,
ERequires::IndentListEnd ERequires::IndentListEnd,
PlatformRigid::SpaceBefore
) )
} }
@ -487,7 +491,7 @@ fn exposes_values<'a>() -> impl Parser<
'a, 'a,
( (
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ExposesEntry<'a, &'a str>>>, Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
), ),
EExposes, EExposes,
> { > {
@ -502,14 +506,16 @@ fn exposes_values<'a>() -> impl Parser<
EExposes::IndentExposes, EExposes::IndentExposes,
EExposes::IndentListStart EExposes::IndentListStart
), ),
collection_e!( collection_trailing_sep_e!(
word1(b'[', EExposes::ListStart), word1(b'[', EExposes::ListStart),
exposes_entry(EExposes::Identifier), exposes_entry(EExposes::Identifier),
word1(b',', EExposes::ListEnd), word1(b',', EExposes::ListEnd),
word1(b']', EExposes::ListEnd), word1(b']', EExposes::ListEnd),
min_indent, min_indent,
EExposes::Open,
EExposes::Space, EExposes::Space,
EExposes::IndentListEnd EExposes::IndentListEnd,
ExposesEntry::SpaceBefore
) )
) )
} }
@ -539,7 +545,7 @@ fn exposes_modules<'a>() -> impl Parser<
'a, 'a,
( (
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ExposesEntry<'a, ModuleName<'a>>>>, Collection<'a, Located<ExposesEntry<'a, ModuleName<'a>>>>,
), ),
EExposes, EExposes,
> { > {
@ -554,14 +560,16 @@ fn exposes_modules<'a>() -> impl Parser<
EExposes::IndentExposes, EExposes::IndentExposes,
EExposes::IndentListStart EExposes::IndentListStart
), ),
collection_e!( collection_trailing_sep_e!(
word1(b'[', EExposes::ListStart), word1(b'[', EExposes::ListStart),
exposes_module(EExposes::Identifier), exposes_module(EExposes::Identifier),
word1(b',', EExposes::ListEnd), word1(b',', EExposes::ListEnd),
word1(b']', EExposes::ListEnd), word1(b']', EExposes::ListEnd),
min_indent, min_indent,
EExposes::Open,
EExposes::Space, EExposes::Space,
EExposes::IndentListEnd EExposes::IndentListEnd,
ExposesEntry::SpaceBefore
) )
) )
} }
@ -631,7 +639,7 @@ fn imports<'a>() -> impl Parser<
'a, 'a,
( (
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ImportsEntry<'a>>>, Collection<'a, Located<ImportsEntry<'a>>>,
), ),
EImports, EImports,
> { > {
@ -646,14 +654,16 @@ fn imports<'a>() -> impl Parser<
EImports::IndentImports, EImports::IndentImports,
EImports::IndentListStart EImports::IndentListStart
), ),
collection_e!( collection_trailing_sep_e!(
word1(b'[', EImports::ListStart), word1(b'[', EImports::ListStart),
loc!(imports_entry()), loc!(imports_entry()),
word1(b',', EImports::ListEnd), word1(b',', EImports::ListEnd),
word1(b']', EImports::ListEnd), word1(b']', EImports::ListEnd),
min_indent, min_indent,
EImports::Open,
EImports::Space, EImports::Space,
EImports::IndentListEnd EImports::IndentListEnd,
ImportsEntry::SpaceBefore
) )
) )
} }
@ -687,14 +697,16 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>, EEffects<'a>> {
space0_e(min_indent, EEffects::Space, EEffects::IndentListStart) space0_e(min_indent, EEffects::Space, EEffects::IndentListStart)
) )
.parse(arena, state)?; .parse(arena, state)?;
let (_, entries, state) = collection_e!( let (_, entries, state) = collection_trailing_sep_e!(
word1(b'{', EEffects::ListStart), word1(b'{', EEffects::ListStart),
specialize(EEffects::TypedIdent, loc!(typed_ident())), specialize(EEffects::TypedIdent, loc!(typed_ident())),
word1(b',', EEffects::ListEnd), word1(b',', EEffects::ListEnd),
word1(b'}', EEffects::ListEnd), word1(b'}', EEffects::ListEnd),
min_indent, min_indent,
EEffects::Open,
EEffects::Space, EEffects::Space,
EEffects::IndentListEnd EEffects::IndentListEnd,
TypedIdent::SpaceBefore
) )
.parse(arena, state)?; .parse(arena, state)?;
@ -706,7 +718,7 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>, EEffects<'a>> {
spaces_after_type_name, spaces_after_type_name,
effect_shortname: type_shortname, effect_shortname: type_shortname,
effect_type_name: type_name, effect_type_name: type_name,
entries: entries.into_bump_slice(), entries: entries.items,
}, },
state, state,
)) ))
@ -768,7 +780,7 @@ fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>, EImports> {
type Temp<'a> = ( type Temp<'a> = (
(Option<&'a str>, ModuleName<'a>), (Option<&'a str>, ModuleName<'a>),
Option<Vec<'a, Located<ExposesEntry<'a, &'a str>>>>, Option<Collection<'a, Located<ExposesEntry<'a, &'a str>>>>,
); );
map_with_arena!( map_with_arena!(
@ -785,19 +797,21 @@ fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>, EImports> {
// e.g. `.{ Task, after}` // e.g. `.{ Task, after}`
maybe!(skip_first!( maybe!(skip_first!(
word1(b'.', EImports::ExposingDot), word1(b'.', EImports::ExposingDot),
collection_e!( collection_trailing_sep_e!(
word1(b'{', EImports::SetStart), word1(b'{', EImports::SetStart),
exposes_entry(EImports::Identifier), exposes_entry(EImports::Identifier),
word1(b',', EImports::SetEnd), word1(b',', EImports::SetEnd),
word1(b'}', EImports::SetEnd), word1(b'}', EImports::SetEnd),
min_indent, min_indent,
EImports::Open,
EImports::Space, EImports::Space,
EImports::IndentSetEnd EImports::IndentSetEnd,
ExposesEntry::SpaceBefore
) )
)) ))
), ),
|arena, ((opt_shortname, module_name), opt_values): Temp<'a>| { |_arena, ((opt_shortname, module_name), opt_values): Temp<'a>| {
let exposed_values = opt_values.unwrap_or_else(|| Vec::new_in(arena)); let exposed_values = opt_values.unwrap_or_else(Collection::empty);
match opt_shortname { match opt_shortname {
Some(shortname) => ImportsEntry::Package(shortname, module_name, exposed_values), Some(shortname) => ImportsEntry::Package(shortname, module_name, exposed_values),

View file

@ -1,5 +1,5 @@
use crate::ast::Base; use crate::ast::Base;
use crate::parser::{Number, ParseResult, Parser, Progress, State}; use crate::parser::{ENumber, ParseResult, Parser, Progress, State};
pub enum NumLiteral<'a> { pub enum NumLiteral<'a> {
Float(&'a str), Float(&'a str),
@ -11,7 +11,7 @@ pub enum NumLiteral<'a> {
}, },
} }
pub fn positive_number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, Number> { pub fn positive_number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, ENumber> {
move |_arena, state: State<'a>| { move |_arena, state: State<'a>| {
match state.bytes.get(0) { match state.bytes.get(0) {
Some(first_byte) if (*first_byte as char).is_ascii_digit() => { Some(first_byte) if (*first_byte as char).is_ascii_digit() => {
@ -19,13 +19,13 @@ pub fn positive_number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, Number>
} }
_ => { _ => {
// this is not a number at all // this is not a number at all
Err((Progress::NoProgress, Number::End, state)) Err((Progress::NoProgress, ENumber::End, state))
} }
} }
} }
} }
pub fn number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, Number> { pub fn number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, ENumber> {
move |_arena, state: State<'a>| { move |_arena, state: State<'a>| {
match state.bytes.get(0) { match state.bytes.get(0) {
Some(first_byte) if *first_byte == b'-' => { Some(first_byte) if *first_byte == b'-' => {
@ -37,7 +37,7 @@ pub fn number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, Number> {
} }
_ => { _ => {
// this is not a number at all // this is not a number at all
Err((Progress::NoProgress, Number::End, state)) Err((Progress::NoProgress, ENumber::End, state))
} }
} }
} }
@ -47,7 +47,7 @@ fn parse_number_base<'a>(
is_negated: bool, is_negated: bool,
bytes: &'a [u8], bytes: &'a [u8],
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, NumLiteral<'a>, Number> { ) -> ParseResult<'a, NumLiteral<'a>, ENumber> {
match bytes.get(0..2) { match bytes.get(0..2) {
Some(b"0b") => chomp_number_base(Base::Binary, is_negated, &bytes[2..], state), Some(b"0b") => chomp_number_base(Base::Binary, is_negated, &bytes[2..], state),
Some(b"0o") => chomp_number_base(Base::Octal, is_negated, &bytes[2..], state), Some(b"0o") => chomp_number_base(Base::Octal, is_negated, &bytes[2..], state),
@ -61,13 +61,13 @@ fn chomp_number_base<'a>(
is_negative: bool, is_negative: bool,
bytes: &'a [u8], bytes: &'a [u8],
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, NumLiteral<'a>, Number> { ) -> ParseResult<'a, NumLiteral<'a>, ENumber> {
let (_is_float, chomped) = chomp_number(bytes); let (_is_float, chomped) = chomp_number(bytes);
let string = unsafe { std::str::from_utf8_unchecked(&bytes[..chomped]) }; let string = unsafe { std::str::from_utf8_unchecked(&bytes[..chomped]) };
let new = state.advance_without_indenting_ee(chomped + 2 + is_negative as usize, |_, _| { let new = state.advance_without_indenting_ee(chomped + 2 + is_negative as usize, |_, _| {
Number::LineTooLong ENumber::LineTooLong
})?; })?;
Ok(( Ok((
@ -85,24 +85,25 @@ fn chomp_number_dec<'a>(
is_negative: bool, is_negative: bool,
bytes: &'a [u8], bytes: &'a [u8],
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, NumLiteral<'a>, Number> { ) -> ParseResult<'a, NumLiteral<'a>, ENumber> {
let (is_float, chomped) = chomp_number(bytes); let (is_float, chomped) = chomp_number(bytes);
if is_negative && chomped == 0 { if is_negative && chomped == 0 {
// we're probably actually looking at unary negation here // we're probably actually looking at unary negation here
return Err((Progress::NoProgress, Number::End, state)); return Err((Progress::NoProgress, ENumber::End, state));
} }
if !bytes.get(0).copied().unwrap_or_default().is_ascii_digit() { if !bytes.get(0).copied().unwrap_or_default().is_ascii_digit() {
// we're probably actually looking at unary negation here // we're probably actually looking at unary negation here
return Err((Progress::NoProgress, Number::End, state)); return Err((Progress::NoProgress, ENumber::End, state));
} }
let string = let string =
unsafe { std::str::from_utf8_unchecked(&state.bytes[0..chomped + is_negative as usize]) }; unsafe { std::str::from_utf8_unchecked(&state.bytes[0..chomped + is_negative as usize]) };
let new = state let new = state.advance_without_indenting_ee(chomped + is_negative as usize, |_, _| {
.advance_without_indenting_ee(chomped + is_negative as usize, |_, _| Number::LineTooLong)?; ENumber::LineTooLong
})?;
Ok(( Ok((
Progress::MadeProgress, Progress::MadeProgress,

View file

@ -186,7 +186,7 @@ pub enum SyntaxError<'a> {
ArgumentsBeforeEquals(Region), ArgumentsBeforeEquals(Region),
NotYetImplemented(String), NotYetImplemented(String),
Todo, Todo,
Type(Type<'a>), Type(EType<'a>),
Pattern(EPattern<'a>), Pattern(EPattern<'a>),
Expr(EExpr<'a>), Expr(EExpr<'a>),
Header(EHeader<'a>), Header(EHeader<'a>),
@ -214,6 +214,7 @@ pub enum EHeader<'a> {
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum EProvides<'a> { pub enum EProvides<'a> {
Provides(Row, Col), Provides(Row, Col),
Open(Row, Col),
To(Row, Col), To(Row, Col),
IndentProvides(Row, Col), IndentProvides(Row, Col),
IndentTo(Row, Col), IndentTo(Row, Col),
@ -230,6 +231,7 @@ pub enum EProvides<'a> {
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EExposes { pub enum EExposes {
Exposes(Row, Col), Exposes(Row, Col),
Open(Row, Col),
IndentExposes(Row, Col), IndentExposes(Row, Col),
IndentListStart(Row, Col), IndentListStart(Row, Col),
IndentListEnd(Row, Col), IndentListEnd(Row, Col),
@ -242,6 +244,7 @@ pub enum EExposes {
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum ERequires<'a> { pub enum ERequires<'a> {
Requires(Row, Col), Requires(Row, Col),
Open(Row, Col),
IndentRequires(Row, Col), IndentRequires(Row, Col),
IndentListStart(Row, Col), IndentListStart(Row, Col),
IndentListEnd(Row, Col), IndentListEnd(Row, Col),
@ -258,7 +261,7 @@ pub enum ETypedIdent<'a> {
HasType(Row, Col), HasType(Row, Col),
IndentHasType(Row, Col), IndentHasType(Row, Col),
Name(Row, Col), Name(Row, Col),
Type(Type<'a>, Row, Col), Type(EType<'a>, Row, Col),
IndentType(Row, Col), IndentType(Row, Col),
Identifier(Row, Col), Identifier(Row, Col),
} }
@ -302,6 +305,7 @@ pub enum EPackageEntry<'a> {
pub enum EEffects<'a> { pub enum EEffects<'a> {
Space(BadInputError, Row, Col), Space(BadInputError, Row, Col),
Effects(Row, Col), Effects(Row, Col),
Open(Row, Col),
IndentEffects(Row, Col), IndentEffects(Row, Col),
ListStart(Row, Col), ListStart(Row, Col),
ListEnd(Row, Col), ListEnd(Row, Col),
@ -315,6 +319,7 @@ pub enum EEffects<'a> {
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EImports { pub enum EImports {
Open(Row, Col),
Imports(Row, Col), Imports(Row, Col),
IndentImports(Row, Col), IndentImports(Row, Col),
IndentListStart(Row, Col), IndentListStart(Row, Col),
@ -394,7 +399,7 @@ pub enum EExpr<'a> {
DefMissingFinalExpr(Row, Col), DefMissingFinalExpr(Row, Col),
DefMissingFinalExpr2(&'a EExpr<'a>, Row, Col), DefMissingFinalExpr2(&'a EExpr<'a>, Row, Col),
Type(Type<'a>, Row, Col), Type(EType<'a>, Row, Col),
Pattern(&'a EPattern<'a>, Row, Col), Pattern(&'a EPattern<'a>, Row, Col),
IndentDefBody(Row, Col), IndentDefBody(Row, Col),
IndentEquals(Row, Col), IndentEquals(Row, Col),
@ -409,10 +414,10 @@ pub enum EExpr<'a> {
BackpassComma(Row, Col), BackpassComma(Row, Col),
BackpassArrow(Row, Col), BackpassArrow(Row, Col),
When(When<'a>, Row, Col), When(EWhen<'a>, Row, Col),
If(If<'a>, Row, Col), If(EIf<'a>, Row, Col),
Expect(Expect<'a>, Row, Col), Expect(EExpect<'a>, Row, Col),
Lambda(ELambda<'a>, Row, Col), Lambda(ELambda<'a>, Row, Col),
Underscore(Row, Col), Underscore(Row, Col),
@ -420,15 +425,15 @@ pub enum EExpr<'a> {
InParens(EInParens<'a>, Row, Col), InParens(EInParens<'a>, Row, Col),
Record(ERecord<'a>, Row, Col), Record(ERecord<'a>, Row, Col),
Str(EString<'a>, Row, Col), Str(EString<'a>, Row, Col),
Number(Number, Row, Col), Number(ENumber, Row, Col),
List(List<'a>, Row, Col), List(EList<'a>, Row, Col),
IndentStart(Row, Col), IndentStart(Row, Col),
IndentEnd(Row, Col), IndentEnd(Row, Col),
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum Number { pub enum ENumber {
End, End,
LineTooLong, LineTooLong,
} }
@ -502,7 +507,7 @@ pub enum ELambda<'a> {
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum List<'a> { pub enum EList<'a> {
Open(Row, Col), Open(Row, Col),
End(Row, Col), End(Row, Col),
Space(BadInputError, Row, Col), Space(BadInputError, Row, Col),
@ -514,7 +519,7 @@ pub enum List<'a> {
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum When<'a> { pub enum EWhen<'a> {
Space(BadInputError, Row, Col), Space(BadInputError, Row, Col),
When(Row, Col), When(Row, Col),
Is(Row, Col), Is(Row, Col),
@ -538,7 +543,7 @@ pub enum When<'a> {
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum If<'a> { pub enum EIf<'a> {
Space(BadInputError, Row, Col), Space(BadInputError, Row, Col),
If(Row, Col), If(Row, Col),
Then(Row, Col), Then(Row, Col),
@ -557,7 +562,7 @@ pub enum If<'a> {
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expect<'a> { pub enum EExpect<'a> {
Space(BadInputError, Row, Col), Space(BadInputError, Row, Col),
Expect(Row, Col), Expect(Row, Col),
Condition(&'a EExpr<'a>, Row, Col), Condition(&'a EExpr<'a>, Row, Col),
@ -575,7 +580,7 @@ pub enum EPattern<'a> {
Space(BadInputError, Row, Col), Space(BadInputError, Row, Col),
PInParens(PInParens<'a>, Row, Col), PInParens(PInParens<'a>, Row, Col),
NumLiteral(Number, Row, Col), NumLiteral(ENumber, Row, Col),
IndentStart(Row, Col), IndentStart(Row, Col),
IndentEnd(Row, Col), IndentEnd(Row, Col),
@ -614,13 +619,14 @@ pub enum PInParens<'a> {
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum Type<'a> { pub enum EType<'a> {
TRecord(TRecord<'a>, Row, Col), TRecord(ETypeRecord<'a>, Row, Col),
TTagUnion(TTagUnion<'a>, Row, Col), TTagUnion(ETypeTagUnion<'a>, Row, Col),
TInParens(TInParens<'a>, Row, Col), TInParens(ETypeInParens<'a>, Row, Col),
TApply(TApply, Row, Col), TApply(ETypeApply, Row, Col),
TBadTypeVariable(Row, Col), TBadTypeVariable(Row, Col),
TWildcard(Row, Col), TWildcard(Row, Col),
TInferred(Row, Col),
/// ///
TStart(Row, Col), TStart(Row, Col),
TEnd(Row, Col), TEnd(Row, Col),
@ -633,14 +639,14 @@ pub enum Type<'a> {
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum TRecord<'a> { pub enum ETypeRecord<'a> {
End(Row, Col), End(Row, Col),
Open(Row, Col), Open(Row, Col),
Field(Row, Col), Field(Row, Col),
Colon(Row, Col), Colon(Row, Col),
Optional(Row, Col), Optional(Row, Col),
Type(&'a Type<'a>, Row, Col), Type(&'a EType<'a>, Row, Col),
Space(BadInputError, Row, Col), Space(BadInputError, Row, Col),
@ -651,11 +657,11 @@ pub enum TRecord<'a> {
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum TTagUnion<'a> { pub enum ETypeTagUnion<'a> {
End(Row, Col), End(Row, Col),
Open(Row, Col), Open(Row, Col),
Type(&'a Type<'a>, Row, Col), Type(&'a EType<'a>, Row, Col),
Space(BadInputError, Row, Col), Space(BadInputError, Row, Col),
@ -664,11 +670,11 @@ pub enum TTagUnion<'a> {
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum TInParens<'a> { pub enum ETypeInParens<'a> {
End(Row, Col), End(Row, Col),
Open(Row, Col), Open(Row, Col),
/// ///
Type(&'a Type<'a>, Row, Col), Type(&'a EType<'a>, Row, Col),
/// ///
Space(BadInputError, Row, Col), Space(BadInputError, Row, Col),
@ -678,7 +684,7 @@ pub enum TInParens<'a> {
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum TApply { pub enum ETypeApply {
/// ///
StartNotUppercase(Row, Col), StartNotUppercase(Row, Col),
End(Row, Col), End(Row, Col),
@ -1183,83 +1189,6 @@ macro_rules! collection {
}; };
} }
#[macro_export]
macro_rules! collection_e {
($opening_brace:expr, $elem:expr, $delimiter:expr, $closing_brace:expr, $min_indent:expr, $space_problem:expr, $indent_problem:expr) => {
skip_first!(
$opening_brace,
skip_first!(
// We specifically allow space characters inside here, so that
// `[ ]` can be successfully parsed as an empty list, and then
// changed by the formatter back into `[]`.
//
// We don't allow newlines or comments in the middle of empty
// roc_collections because those are normally stored in an Expr,
// and there's no Expr in which to store them in an empty collection!
//
// We could change the AST to add extra storage specifically to
// support empty literals containing newlines or comments, but this
// does not seem worth even the tiniest regression in compiler performance.
zero_or_more!($crate::parser::word1(b' ', |row, col| $space_problem(
crate::parser::BadInputError::LineTooLong,
row,
col
))),
skip_second!(
$crate::parser::sep_by0(
$delimiter,
$crate::blankspace::space0_around_ee(
$elem,
$min_indent,
$space_problem,
$indent_problem,
$indent_problem
)
),
$closing_brace
)
)
)
};
}
/// Parse zero or more elements between two braces (e.g. square braces).
/// Elements can be optionally surrounded by spaces, and are separated by a
/// delimiter (e.g comma-separated) with optionally a trailing delimiter.
/// Braces and delimiters get discarded.
#[macro_export]
macro_rules! collection_trailing_sep {
($opening_brace:expr, $elem:expr, $delimiter:expr, $closing_brace:expr, $min_indent:expr) => {
skip_first!(
$opening_brace,
skip_first!(
// We specifically allow space characters inside here, so that
// `[ ]` can be successfully parsed as an empty list, and then
// changed by the formatter back into `[]`.
//
// We don't allow newlines or comments in the middle of empty
// roc_collections because those are normally stored in an Expr,
// and there's no Expr in which to store them in an empty collection!
//
// We could change the AST to add extra storage specifically to
// support empty literals containing newlines or comments, but this
// does not seem worth even the tiniest regression in compiler performance.
zero_or_more!($crate::parser::ascii_char(b' ')),
skip_second!(
and!(
$crate::parser::trailing_sep_by0(
$delimiter,
$crate::blankspace::space0_around($elem, $min_indent)
),
$crate::blankspace::space0($min_indent)
),
$closing_brace
)
)
)
};
}
#[macro_export] #[macro_export]
macro_rules! collection_trailing_sep_e { macro_rules! collection_trailing_sep_e {
($opening_brace:expr, $elem:expr, $delimiter:expr, $closing_brace:expr, $min_indent:expr, $open_problem:expr, $space_problem:expr, $indent_problem:expr, $space_before:expr) => { ($opening_brace:expr, $elem:expr, $delimiter:expr, $closing_brace:expr, $min_indent:expr, $open_problem:expr, $space_problem:expr, $indent_problem:expr, $space_before:expr) => {
@ -1300,10 +1229,10 @@ macro_rules! collection_trailing_sep_e {
} }
} }
let collection = $crate::ast::Collection { let collection = $crate::ast::Collection::with_items_and_comments(
items: parsed_elems.into_bump_slice(), arena,
final_comments, parsed_elems.into_bump_slice(),
}; final_comments);
Ok((MadeProgress, collection, state)) Ok((MadeProgress, collection, state))
} }

View file

@ -331,10 +331,7 @@ fn record_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, PRec
) )
.parse(arena, state)?; .parse(arena, state)?;
// TODO let result = Pattern::RecordDestructure(fields);
let _unused = fields.final_comments;
let result = Pattern::RecordDestructure(fields.items);
Ok((MadeProgress, result, state)) Ok((MadeProgress, result, state))
} }

View file

@ -1,63 +1,64 @@
use crate::ast::{AssignedField, Collection, Tag, TypeAnnotation}; use crate::ast::{AssignedField, Tag, TypeAnnotation};
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e}; use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::keyword; use crate::keyword;
use crate::parser::{ use crate::parser::{
allocated, backtrackable, optional, specialize, specialize_ref, word1, word2, ParseResult, allocated, backtrackable, optional, specialize, specialize_ref, word1, word2, EType,
Parser, ETypeApply, ETypeInParens, ETypeRecord, ETypeTagUnion, ParseResult, Parser,
Progress::{self, *}, Progress::{self, *},
State, TApply, TInParens, TRecord, TTagUnion, Type, State,
}; };
use bumpalo::collections::vec::Vec; use bumpalo::collections::vec::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
pub fn located_help<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> { pub fn located_help<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
expression(min_indent) expression(min_indent)
} }
#[inline(always)] #[inline(always)]
fn tag_union_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, TTagUnion<'a>> { fn tag_union_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, ETypeTagUnion<'a>> {
move |arena, state| { move |arena, state| {
let (_, tags, state) = collection_trailing_sep_e!( let (_, tags, state) = collection_trailing_sep_e!(
word1(b'[', TTagUnion::Open), word1(b'[', ETypeTagUnion::Open),
loc!(tag_type(min_indent)), loc!(tag_type(min_indent)),
word1(b',', TTagUnion::End), word1(b',', ETypeTagUnion::End),
word1(b']', TTagUnion::End), word1(b']', ETypeTagUnion::End),
min_indent, min_indent,
TTagUnion::Open, ETypeTagUnion::Open,
TTagUnion::Space, ETypeTagUnion::Space,
TTagUnion::IndentEnd, ETypeTagUnion::IndentEnd,
Tag::SpaceBefore Tag::SpaceBefore
) )
.parse(arena, state)?; .parse(arena, state)?;
// This could be an open tag union, e.g. `[ Foo, Bar ]a` // This could be an open tag union, e.g. `[ Foo, Bar ]a`
let (_, ext, state) = let (_, ext, state) = optional(allocated(specialize_ref(
optional(allocated(specialize_ref(TTagUnion::Type, term(min_indent)))) ETypeTagUnion::Type,
term(min_indent),
)))
.parse(arena, state)?; .parse(arena, state)?;
let result = TypeAnnotation::TagUnion { let result = TypeAnnotation::TagUnion { tags, ext };
tags: tags.items,
ext,
final_comments: tags.final_comments,
};
Ok((MadeProgress, result, state)) Ok((MadeProgress, result, state))
} }
} }
fn fail_type_start<'a, T: 'a>() -> impl Parser<'a, T, Type<'a>> { fn fail_type_start<'a, T: 'a>() -> impl Parser<'a, T, EType<'a>> {
|_arena, state: State<'a>| Err((NoProgress, Type::TStart(state.line, state.column), state)) |_arena, state: State<'a>| Err((NoProgress, EType::TStart(state.line, state.column), state))
} }
fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> { fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
map_with_arena!( map_with_arena!(
and!( and!(
one_of!( one_of!(
loc_wildcard(), loc_wildcard(),
specialize(Type::TInParens, loc_type_in_parens(min_indent)), loc_inferred(),
loc!(specialize(Type::TRecord, record_type(min_indent))), specialize(EType::TInParens, loc_type_in_parens(min_indent)),
loc!(specialize(Type::TTagUnion, tag_union_type(min_indent))), loc!(specialize(EType::TRecord, record_type(min_indent))),
loc!(specialize(EType::TTagUnion, tag_union_type(min_indent))),
loc!(applied_type(min_indent)), loc!(applied_type(min_indent)),
loc!(parse_type_variable), loc!(parse_type_variable),
fail_type_start(), fail_type_start(),
@ -67,14 +68,14 @@ fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Typ
map!( map!(
and!( and!(
skip_second!( skip_second!(
backtrackable(space0_e(min_indent, Type::TSpace, Type::TIndentEnd)), backtrackable(space0_e(min_indent, EType::TSpace, EType::TIndentEnd)),
crate::parser::keyword_e(keyword::AS, Type::TEnd) crate::parser::keyword_e(keyword::AS, EType::TEnd)
), ),
space0_before_e( space0_before_e(
term(min_indent), term(min_indent),
min_indent, min_indent,
Type::TSpace, EType::TSpace,
Type::TAsIndentStart EType::TAsIndentStart
) )
), ),
Some Some
@ -103,24 +104,36 @@ fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Typ
} }
/// The `*` type variable, e.g. in (List *) Wildcard, /// The `*` type variable, e.g. in (List *) Wildcard,
fn loc_wildcard<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> { fn loc_wildcard<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
map!(loc!(word1(b'*', Type::TWildcard)), |loc_val: Located<()>| { map!(loc!(word1(b'*', EType::TWildcard)), |loc_val: Located<
(),
>| {
loc_val.map(|_| TypeAnnotation::Wildcard) loc_val.map(|_| TypeAnnotation::Wildcard)
}) })
} }
fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> { /// The `_` indicating an inferred type, e.g. in (List _)
fn loc_inferred<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
map!(loc!(word1(b'_', EType::TInferred)), |loc_val: Located<
(),
>| {
loc_val.map(|_| TypeAnnotation::Inferred)
})
}
fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
use crate::ast::Spaceable; use crate::ast::Spaceable;
map_with_arena!( map_with_arena!(
and!( and!(
backtrackable(space0_e(min_indent, Type::TSpace, Type::TIndentStart)), backtrackable(space0_e(min_indent, EType::TSpace, EType::TIndentStart)),
one_of!( one_of!(
loc_wildcard(), loc_wildcard(),
specialize(Type::TInParens, loc_type_in_parens(min_indent)), loc_inferred(),
loc!(specialize(Type::TRecord, record_type(min_indent))), specialize(EType::TInParens, loc_type_in_parens(min_indent)),
loc!(specialize(Type::TTagUnion, tag_union_type(min_indent))), loc!(specialize(EType::TRecord, record_type(min_indent))),
loc!(specialize(Type::TApply, parse_concrete_type)), loc!(specialize(EType::TTagUnion, tag_union_type(min_indent))),
loc!(specialize(EType::TApply, parse_concrete_type)),
loc!(parse_type_variable) loc!(parse_type_variable)
) )
), ),
@ -137,28 +150,28 @@ fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotatio
fn loc_type_in_parens<'a>( fn loc_type_in_parens<'a>(
min_indent: u16, min_indent: u16,
) -> impl Parser<'a, Located<TypeAnnotation<'a>>, TInParens<'a>> { ) -> impl Parser<'a, Located<TypeAnnotation<'a>>, ETypeInParens<'a>> {
between!( between!(
word1(b'(', TInParens::Open), word1(b'(', ETypeInParens::Open),
space0_around_ee( space0_around_ee(
move |arena, state| specialize_ref(TInParens::Type, expression(min_indent)) move |arena, state| specialize_ref(ETypeInParens::Type, expression(min_indent))
.parse(arena, state), .parse(arena, state),
min_indent, min_indent,
TInParens::Space, ETypeInParens::Space,
TInParens::IndentOpen, ETypeInParens::IndentOpen,
TInParens::IndentEnd, ETypeInParens::IndentEnd,
), ),
word1(b')', TInParens::IndentEnd) word1(b')', ETypeInParens::IndentEnd)
) )
} }
#[inline(always)] #[inline(always)]
fn tag_type<'a>(min_indent: u16) -> impl Parser<'a, Tag<'a>, TTagUnion<'a>> { fn tag_type<'a>(min_indent: u16) -> impl Parser<'a, Tag<'a>, ETypeTagUnion<'a>> {
move |arena, state: State<'a>| { move |arena, state: State<'a>| {
let (_, name, state) = loc!(parse_tag_name(TTagUnion::End)).parse(arena, state)?; let (_, name, state) = loc!(parse_tag_name(ETypeTagUnion::End)).parse(arena, state)?;
let (_, args, state) = let (_, args, state) = specialize_ref(ETypeTagUnion::Type, loc_applied_args_e(min_indent))
specialize_ref(TTagUnion::Type, loc_applied_args_e(min_indent)).parse(arena, state)?; .parse(arena, state)?;
let result = if name.value.starts_with('@') { let result = if name.value.starts_with('@') {
Tag::Private { Tag::Private {
@ -190,7 +203,7 @@ where
fn record_type_field<'a>( fn record_type_field<'a>(
min_indent: u16, min_indent: u16,
) -> impl Parser<'a, AssignedField<'a, TypeAnnotation<'a>>, TRecord<'a>> { ) -> impl Parser<'a, AssignedField<'a, TypeAnnotation<'a>>, ETypeRecord<'a>> {
use crate::ident::lowercase_ident; use crate::ident::lowercase_ident;
use crate::parser::Either::*; use crate::parser::Either::*;
use AssignedField::*; use AssignedField::*;
@ -201,29 +214,33 @@ fn record_type_field<'a>(
let row = state.line; let row = state.line;
let col = state.column; let col = state.column;
let (progress, loc_label, state) = loc!(specialize( let (progress, loc_label, state) = loc!(specialize(
move |_, _, _| TRecord::Field(row, col), move |_, _, _| ETypeRecord::Field(row, col),
lowercase_ident() lowercase_ident()
)) ))
.parse(arena, state)?; .parse(arena, state)?;
debug_assert_eq!(progress, MadeProgress); debug_assert_eq!(progress, MadeProgress);
let (_, spaces, state) = let (_, spaces, state) =
space0_e(min_indent, TRecord::Space, TRecord::IndentEnd).parse(arena, state)?; space0_e(min_indent, ETypeRecord::Space, ETypeRecord::IndentEnd).parse(arena, state)?;
// Having a value is optional; both `{ email }` and `{ email: blah }` work. // Having a value is optional; both `{ email }` and `{ email: blah }` work.
// (This is true in both literals and types.) // (This is true in both literals and types.)
let (_, opt_loc_val, state) = optional(either!( let (_, opt_loc_val, state) = optional(either!(
word1(b':', TRecord::Colon), word1(b':', ETypeRecord::Colon),
word1(b'?', TRecord::Optional) word1(b'?', ETypeRecord::Optional)
)) ))
.parse(arena, state)?; .parse(arena, state)?;
let val_parser = specialize_ref(TRecord::Type, term(min_indent)); let val_parser = specialize_ref(ETypeRecord::Type, term(min_indent));
match opt_loc_val { match opt_loc_val {
Some(First(_)) => { Some(First(_)) => {
let (_, loc_val, state) = let (_, loc_val, state) = space0_before_e(
space0_before_e(val_parser, min_indent, TRecord::Space, TRecord::IndentColon) val_parser,
min_indent,
ETypeRecord::Space,
ETypeRecord::IndentColon,
)
.parse(arena, state)?; .parse(arena, state)?;
Ok(( Ok((
@ -236,8 +253,8 @@ fn record_type_field<'a>(
let (_, loc_val, state) = space0_before_e( let (_, loc_val, state) = space0_before_e(
val_parser, val_parser,
min_indent, min_indent,
TRecord::Space, ETypeRecord::Space,
TRecord::IndentOptional, ETypeRecord::IndentOptional,
) )
.parse(arena, state)?; .parse(arena, state)?;
@ -263,44 +280,38 @@ fn record_type_field<'a>(
} }
#[inline(always)] #[inline(always)]
fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, TRecord<'a>> { fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, ETypeRecord<'a>> {
use crate::type_annotation::TypeAnnotation::*; use crate::type_annotation::TypeAnnotation::*;
move |arena, state| { move |arena, state| {
let (_, fields, state) = collection_trailing_sep_e!( let (_, fields, state) = collection_trailing_sep_e!(
// word1_check_indent!(b'{', TRecord::Open, min_indent, TRecord::IndentOpen), // word1_check_indent!(b'{', TRecord::Open, min_indent, TRecord::IndentOpen),
word1(b'{', TRecord::Open), word1(b'{', ETypeRecord::Open),
loc!(record_type_field(min_indent)), loc!(record_type_field(min_indent)),
word1(b',', TRecord::End), word1(b',', ETypeRecord::End),
// word1_check_indent!(b'}', TRecord::End, min_indent, TRecord::IndentEnd), // word1_check_indent!(b'}', TRecord::End, min_indent, TRecord::IndentEnd),
word1(b'}', TRecord::End), word1(b'}', ETypeRecord::End),
min_indent, min_indent,
TRecord::Open, ETypeRecord::Open,
TRecord::Space, ETypeRecord::Space,
TRecord::IndentEnd, ETypeRecord::IndentEnd,
AssignedField::SpaceBefore AssignedField::SpaceBefore
) )
.parse(arena, state)?; .parse(arena, state)?;
let field_term = specialize_ref(TRecord::Type, term(min_indent)); let field_term = specialize_ref(ETypeRecord::Type, term(min_indent));
let (_, ext, state) = optional(allocated(field_term)).parse(arena, state)?; let (_, ext, state) = optional(allocated(field_term)).parse(arena, state)?;
let result = Record { let result = Record { fields, ext };
fields: Collection {
items: fields.items,
final_comments: fields.final_comments,
},
ext,
};
Ok((MadeProgress, result, state)) Ok((MadeProgress, result, state))
} }
} }
fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, Type<'a>> { fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, EType<'a>> {
map!( map!(
and!( and!(
specialize(Type::TApply, parse_concrete_type), specialize(EType::TApply, parse_concrete_type),
// Optionally parse space-separated arguments for the constructor, // Optionally parse space-separated arguments for the constructor,
// e.g. `Str Float` in `Map Str Float` // e.g. `Str Float` in `Map Str Float`
loc_applied_args_e(min_indent) loc_applied_args_e(min_indent)
@ -324,33 +335,33 @@ fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, Type
fn loc_applied_args_e<'a>( fn loc_applied_args_e<'a>(
min_indent: u16, min_indent: u16,
) -> impl Parser<'a, Vec<'a, Located<TypeAnnotation<'a>>>, Type<'a>> { ) -> impl Parser<'a, Vec<'a, Located<TypeAnnotation<'a>>>, EType<'a>> {
zero_or_more!(loc_applied_arg(min_indent)) zero_or_more!(loc_applied_arg(min_indent))
} }
fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> { fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
move |arena, state: State<'a>| { move |arena, state: State<'a>| {
let (p1, first, state) = space0_before_e( let (p1, first, state) = space0_before_e(
term(min_indent), term(min_indent),
min_indent, min_indent,
Type::TSpace, EType::TSpace,
Type::TIndentStart, EType::TIndentStart,
) )
.parse(arena, state)?; .parse(arena, state)?;
let (p2, rest, state) = zero_or_more!(skip_first!( let (p2, rest, state) = zero_or_more!(skip_first!(
word1(b',', Type::TFunctionArgument), word1(b',', EType::TFunctionArgument),
one_of![ one_of![
space0_around_ee( space0_around_ee(
term(min_indent), term(min_indent),
min_indent, min_indent,
Type::TSpace, EType::TSpace,
Type::TIndentStart, EType::TIndentStart,
Type::TIndentEnd EType::TIndentEnd
), ),
|_, state: State<'a>| Err(( |_, state: State<'a>| Err((
NoProgress, NoProgress,
Type::TFunctionArgument(state.line, state.column), EType::TFunctionArgument(state.line, state.column),
state state
)) ))
] ]
@ -360,8 +371,8 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
// TODO this space0 is dropped, so newlines just before the function arrow when there // TODO this space0 is dropped, so newlines just before the function arrow when there
// is only one argument are not seen by the formatter. Can we do better? // is only one argument are not seen by the formatter. Can we do better?
let (p3, is_function, state) = optional(skip_first!( let (p3, is_function, state) = optional(skip_first!(
space0_e(min_indent, Type::TSpace, Type::TIndentStart), space0_e(min_indent, EType::TSpace, EType::TIndentStart),
word2(b'-', b'>', Type::TStart) word2(b'-', b'>', EType::TStart)
)) ))
.parse(arena, state)?; .parse(arena, state)?;
@ -369,8 +380,8 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
let (p4, return_type, state) = space0_before_e( let (p4, return_type, state) = space0_before_e(
term(min_indent), term(min_indent),
min_indent, min_indent,
Type::TSpace, EType::TSpace,
Type::TIndentStart, EType::TIndentStart,
) )
.parse(arena, state)?; .parse(arena, state)?;
@ -418,7 +429,7 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
fn parse_concrete_type<'a>( fn parse_concrete_type<'a>(
arena: &'a Bump, arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, TypeAnnotation<'a>, TApply> { ) -> ParseResult<'a, TypeAnnotation<'a>, ETypeApply> {
let initial_bytes = state.bytes; let initial_bytes = state.bytes;
match crate::ident::concrete_type().parse(arena, state) { match crate::ident::concrete_type().parse(arena, state) {
@ -428,7 +439,7 @@ fn parse_concrete_type<'a>(
Ok((MadeProgress, answer, state)) Ok((MadeProgress, answer, state))
} }
Err((NoProgress, _, state)) => { Err((NoProgress, _, state)) => {
Err((NoProgress, TApply::End(state.line, state.column), state)) Err((NoProgress, ETypeApply::End(state.line, state.column), state))
} }
Err((MadeProgress, _, mut state)) => { Err((MadeProgress, _, mut state)) => {
// we made some progress, but ultimately failed. // we made some progress, but ultimately failed.
@ -439,7 +450,7 @@ fn parse_concrete_type<'a>(
unsafe { std::str::from_utf8_unchecked(&initial_bytes[..chomped + delta]) }; unsafe { std::str::from_utf8_unchecked(&initial_bytes[..chomped + delta]) };
state = state.advance_without_indenting_ee(chomped, |r, c| { state = state.advance_without_indenting_ee(chomped, |r, c| {
TApply::Space(crate::parser::BadInputError::LineTooLong, r, c) ETypeApply::Space(crate::parser::BadInputError::LineTooLong, r, c)
})?; })?;
Ok((MadeProgress, TypeAnnotation::Malformed(parsed_str), state)) Ok((MadeProgress, TypeAnnotation::Malformed(parsed_str), state))
@ -450,7 +461,7 @@ fn parse_concrete_type<'a>(
fn parse_type_variable<'a>( fn parse_type_variable<'a>(
arena: &'a Bump, arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, TypeAnnotation<'a>, Type<'a>> { ) -> ParseResult<'a, TypeAnnotation<'a>, EType<'a>> {
match crate::ident::lowercase_ident().parse(arena, state) { match crate::ident::lowercase_ident().parse(arena, state) {
Ok((_, name, state)) => { Ok((_, name, state)) => {
let answer = TypeAnnotation::BoundVariable(name); let answer = TypeAnnotation::BoundVariable(name);
@ -459,7 +470,7 @@ fn parse_type_variable<'a>(
} }
Err((progress, _, state)) => Err(( Err((progress, _, state)) => Err((
progress, progress,
Type::TBadTypeVariable(state.line, state.column), EType::TBadTypeVariable(state.line, state.column),
state, state,
)), )),
} }

View file

@ -0,0 +1,14 @@
BinOps(
[
(
|L 0-0, C 0-1| Var {
module_name: "",
ident: "x",
},
|L 0-0, C 2-3| Plus,
),
],
|L 0-0, C 4-5| Num(
"2",
),
)

View file

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

View file

@ -0,0 +1,13 @@
BinOps(
[
(
|L 0-0, C 0-1| Num(
"1",
),
|L 0-0, C 3-4| Plus,
),
],
|L 0-0, C 7-8| Num(
"2",
),
)

View file

@ -0,0 +1 @@
1 + 2

View file

@ -0,0 +1,14 @@
Apply(
|L 0-0, C 0-4| GlobalTag(
"Whee",
),
[
|L 0-0, C 5-7| Num(
"12",
),
|L 0-0, C 8-10| Num(
"34",
),
],
Space,
)

View file

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

View file

@ -0,0 +1,18 @@
Apply(
|L 0-0, C 0-4| GlobalTag(
"Whee",
),
[
|L 0-0, C 6-8| ParensAround(
Num(
"12",
),
),
|L 0-0, C 11-13| ParensAround(
Num(
"34",
),
),
],
Space,
)

View file

@ -0,0 +1 @@
Whee (12) (34)

View file

@ -0,0 +1,14 @@
Apply(
|L 0-0, C 0-5| PrivateTag(
"@Whee",
),
[
|L 0-0, C 6-8| Num(
"12",
),
|L 0-0, C 9-11| Num(
"34",
),
],
Space,
)

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