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:
spell-check:
name: spell check
runs-on: [self-hosted]
runs-on: [self-hosted, linux]
timeout-minutes: 10
env:
FORCE_COLOR: 1

View File

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

View File

@ -51,3 +51,8 @@ Eric Newbury <enewbury@users.noreply.github.com>
Ayaz Hafiz <ayaz.hafiz.1@gmail.com>
Johannes Maas <github@j-maas.de>
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`
If you're on MacOS and using a OS version >= 10.15:
`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
You will need to start a fresh terminal session to use nix.
### Usage

4
Cargo.lock generated
View File

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

View File

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

View File

@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir cli cli_utils compiler docs editor ast code_markup 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:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
@ -79,17 +79,17 @@ test-rust:
# not pre-compiling the host can cause race conditions
RUN echo "4" | cargo run --release examples/benchmarks/NQueens.roc
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.
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
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 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 \
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:
FROM +test-rust

View File

@ -20,6 +20,7 @@ use crate::{
expr2::{ClosureExtra, Expr2, ExprId, WhenBranch},
record_field::RecordField,
},
fun_def::FunctionDef,
pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct},
types::{Type2, TypeId},
val_def::ValueDef,
@ -276,7 +277,7 @@ pub fn constrain_expr<'a>(
expr_id: expr_node_id,
closure_var,
fn_var,
..
called_via,
} => {
// The expression that evaluates to the function being called, e.g. `foo` in
// (foo) bar baz
@ -348,7 +349,7 @@ pub fn constrain_expr<'a>(
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);
@ -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 {
symbol,
updates,
@ -1031,7 +1152,6 @@ pub fn constrain_expr<'a>(
exists(arena, vars, And(and_constraints))
}
Expr2::LetRec { .. } => todo!(),
Expr2::LetFunction { .. } => todo!(),
}
}
@ -1765,7 +1885,7 @@ pub mod test_constrain {
use roc_parse::parser::SyntaxError;
use roc_region::all::Region;
use roc_types::{
pretty_print::content_to_string,
pretty_print::{content_to_string, name_all_type_vars},
solved_types::Solved,
subs::{Subs, VarStore, Variable},
};
@ -1799,7 +1919,7 @@ pub mod test_constrain {
aliases,
};
let mut subs = Subs::new(var_store);
let mut subs = Subs::new_from_varstore(var_store);
for (var, name) in rigid_variables {
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);
match expr2_result {
Ok((expr, _)) => {
Ok((expr, output)) => {
let constraint = constrain_expr(
&code_arena,
&mut env,
@ -1865,17 +1985,22 @@ pub mod test_constrain {
let mut var_store = VarStore::default();
std::mem::swap(ref_var_store, &mut var_store);
let rigids = output.introduced_variables.name_by_var;
let (mut solved, _, _) = run_solve(
&code_arena,
pool,
Default::default(),
Default::default(),
rigids,
constraint,
var_store,
);
let subs = solved.inner_mut();
// name type vars
name_all_type_vars(var, subs);
let content = subs.get_content_without_compacting(var);
// 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]
fn constrain_closure() {
infer_eq(
@ -2145,4 +2366,130 @@ pub mod test_constrain {
"{}* -> 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,
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)]
@ -316,7 +320,7 @@ fn from_pending_alias<'a>(
}
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 {
alias: symbol,
variable_region: loc_lowercase.region,
@ -454,6 +458,10 @@ fn canonicalize_pending_def<'a>(
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);
// 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!
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);
let return_type: TypeId;
@ -558,7 +566,8 @@ fn canonicalize_pending_def<'a>(
for (node_id, ((_, pattern_id), typ)) in
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;
@ -689,14 +698,14 @@ fn canonicalize_pending_def<'a>(
// parent commit for the bug this fixed!
let refs = References::new();
let arguments: PoolVec<(PatternId, Variable)> =
let arguments: PoolVec<(Variable, PatternId)> =
PoolVec::with_capacity(closure_args.len() as u32, env.pool);
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())
{
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 {

View File

@ -6,8 +6,8 @@ use crate::{
mem_pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec},
};
use roc_can::expr::Recursive;
use roc_module::called_via::CalledVia;
use roc_module::low_level::LowLevel;
use roc_module::operator::CalledVia;
use roc_module::symbol::Symbol;
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),
List { items, .. } => {
List(items) => {
let mut output = Output::default();
let output_ref = &mut output;
@ -185,13 +185,12 @@ pub fn expr_to_expr2<'a>(
RecordUpdate {
fields,
update: loc_update,
final_comments: _,
} => {
let (can_update, update_out) =
expr_to_expr2(env, scope, &loc_update.value, loc_update.region);
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)) => {
output.references.union_mut(update_out.references);
@ -236,14 +235,11 @@ pub fn expr_to_expr2<'a>(
}
}
Record {
fields,
final_comments: _,
} => {
Record(fields) => {
if fields.is_empty() {
(Expr2::EmptyRecord, Output::default())
} else {
match canonicalize_fields(env, scope, fields) {
match canonicalize_fields(env, scope, fields.items) {
Ok((can_fields, output)) => (
Expr2::Record {
record_var: env.var_store.fresh(),

View File

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

View File

@ -2,7 +2,7 @@ pub mod ast;
mod declaration;
pub mod def;
pub mod expr;
mod fun_def;
pub mod fun_def;
pub mod header;
pub mod pattern;
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 crate::{

View File

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

View File

@ -1,7 +1,7 @@
use crate::mem_pool::pool::{NodeId, Pool};
use bumpalo::{collections::Vec as BumpVec, Bump};
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_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region};
@ -134,23 +134,37 @@ impl<'a> Env<'a> {
)),
}
} else {
match self
.dep_idents
.get(&module_id)
.and_then(|exposed_ids| exposed_ids.get_id(&ident))
{
Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id);
match self.dep_idents.get(&module_id) {
Some(exposed_ids) => match exposed_ids.get_id(&ident) {
Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id);
self.qualified_lookups.insert(symbol);
self.qualified_lookups.insert(symbol);
Ok(symbol)
Ok(symbol)
}
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,
ident,
region,
exposed_values,
})
}
},
None => {
panic!(
"Module {} exists, but is not recorded in dep_idents",
module_name
)
}
None => Err(RuntimeError::ValueNotExposed {
module_name,
ident,
region,
}),
}
}
}

View File

@ -7,6 +7,7 @@ use crate::mem_pool::{
pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone,
};
use roc_collections::all::WyHash;
use roc_module::ident::Lowercase;
use roc_types::subs::Variable;
#[derive(Debug)]
@ -18,7 +19,7 @@ pub struct Rigids {
#[allow(clippy::needless_collect)]
impl Rigids {
pub fn new(
named: HashMap<&str, Variable, BuildHasherDefault<WyHash>>,
named: HashMap<Lowercase, Variable, BuildHasherDefault<WyHash>>,
unnamed: HashSet<Variable, BuildHasherDefault<WyHash>>,
pool: &mut Pool,
) -> Self {
@ -26,7 +27,7 @@ impl Rigids {
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)));

View File

@ -61,7 +61,7 @@ roc_load = { path = "../compiler/load" }
roc_gen_llvm = { path = "../compiler/gen_llvm", optional = true }
roc_build = { path = "../compiler/build", default-features = false }
roc_fmt = { path = "../compiler/fmt" }
roc_reporting = { path = "../compiler/reporting" }
roc_reporting = { path = "../reporting" }
roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" }
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,
surgically_link: bool,
precompiled: bool,
target_valgrind: bool,
) -> Result<BuiltFile, LoadingProblem<'a>> {
let compilation_start = SystemTime::now();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
@ -116,6 +117,7 @@ pub fn build_file<'a>(
.keys()
.map(|x| x.as_str(&loaded.interns).to_string())
.collect(),
target_valgrind,
);
// TODO try to move as much of this linking as possible to the precompiled
@ -247,7 +249,7 @@ pub fn build_file<'a>(
link(
target,
binary_path.clone(),
&inputs,
&inputs,
link_type
)
.map_err(|_| {
@ -280,6 +282,7 @@ pub fn build_file<'a>(
})
}
#[allow(clippy::too_many_arguments)]
fn spawn_rebuild_thread(
opt_level: OptLevel,
surgically_link: bool,
@ -288,9 +291,12 @@ fn spawn_rebuild_thread(
binary_path: PathBuf,
target: &Triple,
exported_symbols: Vec<String>,
target_valgrind: bool,
) -> std::thread::JoinHandle<u128> {
let thread_local_target = target.clone();
std::thread::spawn(move || {
print!("🔨 Rebuilding host... ");
let rebuild_host_start = SystemTime::now();
if !precompiled {
if surgically_link {
@ -299,6 +305,7 @@ fn spawn_rebuild_thread(
&thread_local_target,
host_input_path.as_path(),
exported_symbols,
target_valgrind,
)
.unwrap();
} else {
@ -307,6 +314,7 @@ fn spawn_rebuild_thread(
&thread_local_target,
host_input_path.as_path(),
None,
target_valgrind,
);
}
}
@ -316,6 +324,9 @@ fn spawn_rebuild_thread(
std::fs::copy(prehost, binary_path.as_path()).unwrap();
}
let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
println!("Done!");
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_LINK: &str = "roc-linker";
pub const FLAG_PRECOMPILED: &str = "precompiled-host";
pub const FLAG_VALGRIND: &str = "valgrind";
pub const ROC_FILE: &str = "ROC_FILE";
pub const BACKEND: &str = "BACKEND";
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.")
.required(false),
)
.arg(
Arg::new(FLAG_VALGRIND)
.long(FLAG_VALGRIND)
.about("Some assembly instructions are not supported by valgrind, this flag prevents those from being output when building the host.")
.required(false),
)
)
.subcommand(App::new(CMD_REPL)
.about("Launch the interactive Read Eval Print Loop (REPL)")
@ -258,6 +265,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
};
let surgically_link = matches.is_present(FLAG_LINK);
let precompiled = matches.is_present(FLAG_PRECOMPILED);
if surgically_link && !roc_linker::supported(&link_type, &target) {
panic!(
"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 target_valgrind = matches.is_present(FLAG_VALGRIND);
let res_binary_path = build_file(
&arena,
&target,
@ -298,6 +307,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
link_type,
surgically_link,
precompiled,
target_valgrind,
);
match res_binary_path {

View File

@ -2,12 +2,12 @@ use bumpalo::collections::Vec;
use bumpalo::Bump;
use libloading::Library;
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::operator::CalledVia;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::ProcLayout;
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_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable};
@ -133,10 +133,7 @@ fn jit_to_ast_help<'a>(
),
Layout::Builtin(Builtin::EmptyList) => {
Ok(run_jit_function!(lib, main_fn_name, &'static str, |_| {
Expr::List {
items: &[],
final_comments: &[],
}
Expr::List(Collection::empty())
}))
}
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)
}
Layout::Builtin(Builtin::EmptyList) => Expr::List {
items: &[],
final_comments: &[],
},
Layout::Builtin(Builtin::EmptyList) => Expr::List(Collection::empty()),
Layout::Builtin(Builtin::List(elem_layout)) => {
// 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) };
@ -522,10 +516,7 @@ fn list_to_ast<'a>(
let output = output.into_bump_slice();
Expr::List {
items: output,
final_comments: &[],
}
Expr::List(Collection::with_items(output))
}
fn single_tag_union_to_ast<'a>(
@ -621,10 +612,7 @@ fn struct_to_ast<'a>(
let output = env.arena.alloc([loc_field]);
Expr::Record {
fields: output,
final_comments: &[],
}
Expr::Record(Collection::with_items(output))
} else {
debug_assert_eq!(sorted_fields.len(), field_layouts.len());
@ -658,10 +646,7 @@ fn struct_to_ast<'a>(
let output = output.into_bump_slice();
Expr::Record {
fields: output,
final_comments: &[],
}
Expr::Record(Collection::with_items(output))
}
}
@ -735,10 +720,7 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
region: Region::zero(),
};
Expr::Record {
fields: arena.alloc([loc_assigned_field]),
final_comments: arena.alloc([]),
}
Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field])))
}
FlatType::TagUnion(tags, _) if tags.len() == 1 => {
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(),
};
Expr::Record {
fields: arena.alloc([loc_assigned_field]),
final_comments: &[],
}
Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field])))
}
FlatType::TagUnion(tags, _) if tags.len() == 1 => {
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(),
};
Expr::Record {
fields: arena.alloc([loc_assigned_field]),
final_comments: arena.alloc([]),
}
Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field])))
}
FlatType::TagUnion(tags, _) => {
// 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
if let Err(errors) = env.module.verify() {
panic!(
"Errors defining module: {}\n\nUncomment things nearby to see more details.",
errors
"Errors defining module:\n{}\n\nUncomment things nearby to see more details.",
errors.to_string()
);
}

View File

@ -53,7 +53,14 @@ mod cli_run {
expected_ending: &str,
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() {
panic!("{}", compile_out.stderr);
}
@ -111,20 +118,18 @@ mod cli_run {
}
valgrind_out
} else if let Some(input_file) = input_file {
run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(),
stdin,
&[input_file.to_str().unwrap()],
)
} else {
if let Some(input_file) = input_file {
run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(),
stdin,
&[input_file.to_str().unwrap()],
)
} else {
run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(),
stdin,
&[],
)
}
run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(),
stdin,
&[],
)
};
if !&out.stdout.ends_with(expected_ending) {
panic!(
@ -796,7 +801,7 @@ fn read_wasi_stdout(wasi_env: wasmer_wasi::WasiEnv) -> String {
let mut buf = String::new();
stdout.read_to_string(&mut buf).unwrap();
return buf;
buf
}
_ => todo!(),
}

61
cli_utils/Cargo.lock generated
View File

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

View File

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

View File

@ -86,6 +86,7 @@ pub fn build_zig_host_native(
target: &str,
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
_target_valgrind: bool,
) -> Output {
let mut command = Command::new("zig");
command
@ -118,6 +119,15 @@ pub fn build_zig_host_native(
"-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) {
command.args(&["-O", "ReleaseSafe"]);
}
@ -135,6 +145,8 @@ pub fn build_zig_host_native(
_target: &str,
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
// For compatibility with the non-macOS def above. Keep these in sync.
_target_valgrind: bool,
) -> Output {
use serde_json::Value;
@ -339,6 +351,7 @@ pub fn rebuild_host(
target: &Triple,
host_input_path: &Path,
shared_lib_path: Option<&Path>,
target_valgrind: bool,
) {
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");
@ -394,6 +407,7 @@ pub fn rebuild_host(
"native",
opt_level,
shared_lib_path,
target_valgrind,
)
}
Architecture::X86_32(_) => {
@ -407,6 +421,7 @@ pub fn rebuild_host(
"i386-linux-musl",
opt_level,
shared_lib_path,
target_valgrind,
)
}
@ -421,6 +436,7 @@ pub fn rebuild_host(
target_triple_str(target),
opt_level,
shared_lib_path,
target_valgrind,
)
}
_ => 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");
full_version_string
.trim_end()
.split('.')
.take(2)
.collect::<Vec<&str>>()

View File

@ -862,94 +862,44 @@ pub fn listSwap(
return newList;
}
pub fn listTakeFirst(
pub fn listSublist(
list: RocList,
alignment: u32,
element_width: usize,
take_count: usize,
start: usize,
len: usize,
dec: Dec,
) callconv(.C) RocList {
if (len == 0) {
return RocList.empty();
}
if (list.bytes) |source_ptr| {
if (take_count == 0) {
const size = list.len();
if (start >= size) {
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,
) callconv(.C) RocList {
if (take_count == 0) {
return RocList.empty();
}
if (list.bytes) |source_ptr| {
const size = list.len();
if (size <= take_count) {
return list;
}
const drop_count = size - take_count;
return listDrop(
list,
alignment,
element_width,
drop_count,
dec,
);
} else {
return RocList.empty();
}
}
pub fn listDrop(
list: RocList,
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;
const keep_len = std.math.min(len, size - start);
const drop_len = std.math.max(start, 0);
var i: usize = 0;
const iterations = std.math.min(drop_count, size);
while (i < iterations) : (i += 1) {
while (i < drop_len) : (i += 1) {
const element = source_ptr + i * element_width;
dec(element);
}
if (drop_count >= size) {
return RocList.empty();
}
const output = RocList.allocate(alignment, keep_count, element_width);
const output = RocList.allocate(alignment, keep_len, element_width);
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);
return output;
} else {
return RocList.empty();
}
return RocList.empty();
}
pub fn listDropAt(
@ -1162,6 +1112,36 @@ pub fn listAny(
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
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.listSortWith, "sort_with");
exportListFn(list.listConcat, "concat");
exportListFn(list.listTakeFirst, "take_first");
exportListFn(list.listTakeLast, "take_last");
exportListFn(list.listDrop, "drop");
exportListFn(list.listSublist, "sublist");
exportListFn(list.listDropAt, "drop_at");
exportListFn(list.listSet, "set");
exportListFn(list.listSetInPlace, "set_in_place");
exportListFn(list.listSwap, "swap");
exportListFn(list.listAny, "any");
exportListFn(list.listAll, "all");
exportListFn(list.listFindUnsafe, "find_unsafe");
}
@ -128,6 +127,7 @@ comptime {
exportStrFn(str.repeat, "repeat");
exportStrFn(str.strTrim, "trim");
exportStrFn(str.strTrimLeft, "trim_left");
exportStrFn(str.strTrimRight, "trim_right");
inline for (INTEGERS) |T| {
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();
}
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 {
var byte_count: usize = 0;
@ -1820,6 +1855,77 @@ test "strTrimLeft: small to small" {
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" {
const original_bytes = "hello world";
const expected_bytes = "dlrow olleh";

View File

@ -691,6 +691,10 @@ all : List elem, (elem -> Bool) -> Bool
## any of the elements satisfy it.
any : List elem, (elem -> Bool) -> Bool
## Run the given predicate on each element of the list, returning `True` if
## all of the elements satisfy it.
all : List elem, (elem -> Bool) -> Bool
## Returns the first element of the list satisfying a predicate function.
## If no satisfying element is found, an `Err NotFound` is returned.
find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*

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_TRIM: &str = "roc_builtins.str.trim";
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: &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_APPEND: &str = "roc_builtins.list.append";
pub const LIST_PREPEND: &str = "roc_builtins.list.prepend";
pub const LIST_TAKE_FIRST: &str = "roc_builtins.list.take_first";
pub const LIST_TAKE_LAST: &str = "roc_builtins.list.take_last";
pub const LIST_DROP: &str = "roc_builtins.list.drop";
pub const LIST_SUBLIST: &str = "roc_builtins.list.sublist";
pub const LIST_DROP_AT: &str = "roc_builtins.list.drop_at";
pub const LIST_SWAP: &str = "roc_builtins.list.swap";
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_IN_PLACE: &str = "roc_builtins.list.set_in_place";
pub const LIST_ANY: &str = "roc_builtins.list.any";
pub const LIST_ALL: &str = "roc_builtins.list.all";
pub const LIST_FIND_UNSAFE: &str = "roc_builtins.list.find_unsafe";
pub const 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())
);
// trimRight : Str -> Str
add_top_level_function_type!(
Symbol::STR_TRIM_RIGHT,
vec![str_type()],
Box::new(str_type())
);
// trim : Str -> Str
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))),
);
// 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
add_top_level_function_type!(
Symbol::LIST_DROP,
@ -1102,6 +1128,16 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
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
add_top_level_function_type!(
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
// len : Dict * * -> Nat
@ -1381,6 +1424,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
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
}

View File

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

View File

@ -1,11 +1,11 @@
use crate::def::Def;
use crate::expr::{ClosureData, Expr::*};
use crate::expr::{Expr, Recursive, WhenBranch};
use crate::expr::{Expr, Field, Recursive, WhenBranch};
use crate::pattern::Pattern;
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::operator::CalledVia;
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
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_TRIM => str_trim,
STR_TRIM_LEFT => str_trim_left,
STR_TRIM_RIGHT => str_trim_right,
LIST_LEN => list_len,
LIST_GET => list_get,
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_LAST => list_take_last,
LIST_SUBLIST => list_sublist,
LIST_SPLIT => list_split,
LIST_INTERSPERSE => list_intersperse,
LIST_DROP => list_drop,
LIST_DROP_AT => list_drop_at,
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_SORT_WITH => list_sort_with,
LIST_ANY => list_any,
LIST_ALL => list_all,
LIST_FIND => list_find,
DICT_LEN => dict_len,
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_WITH_DEFAULT => result_with_default,
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)
}
/// 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
fn str_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
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 {
let list_var = var_store.fresh();
let len_var = var_store.fresh();
let zero = int(len_var, Variable::NATURAL, 0);
let body = RunLowLevel {
op: LowLevel::ListTakeFirst,
op: LowLevel::ListSublist,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(len_var, zero),
(len_var, Var(Symbol::ARG_2)),
],
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 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 {
op: LowLevel::ListTakeLast,
op: LowLevel::ListSublist,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(len_var, get_start),
(len_var, Var(Symbol::ARG_2)),
],
ret_var: list_var,
@ -2089,15 +2131,13 @@ fn list_sublist(symbol: Symbol, var_store: &mut VarStore) -> Def {
field: "len".into(),
};
let body_drop = RunLowLevel {
op: LowLevel::ListDrop,
args: vec![(list_var, Var(sym_list)), (start_var, get_start)],
ret_var: list_var,
};
let body_take = RunLowLevel {
op: LowLevel::ListTakeFirst,
args: vec![(list_var, body_drop), (len_var, get_len)],
let body = RunLowLevel {
op: LowLevel::ListSublist,
args: vec![
(list_var, Var(sym_list)),
(start_var, get_start),
(len_var, get_len),
],
ret_var: list_var,
};
@ -2105,28 +2145,229 @@ fn list_sublist(symbol: Symbol, var_store: &mut VarStore) -> Def {
symbol,
vec![(list_var, sym_list), (rec_var, sym_rec)],
var_store,
body_take,
body,
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
fn list_drop(symbol: Symbol, var_store: &mut VarStore) -> Def {
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 {
op: LowLevel::ListDrop,
op: LowLevel::ListSublist,
args: vec![
(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,
};
defn(
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,
body,
list_var,
@ -2805,6 +3046,11 @@ fn list_any(symbol: Symbol, var_store: &mut VarStore) -> Def {
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 ]*
fn list_find(symbol: Symbol, var_store: &mut VarStore) -> Def {
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 {
let ret_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)]
// fn record(fields: Vec<(Lowercase, Field)>, var_store: &mut VarStore) -> Expr {
// let mut send_map = SendMap::default();
// for (k, v) in fields {
// send_map.insert(k, v);
// }
// Expr::Record {
// record_var: var_store.fresh(),
// fields: send_map,
// }
// }
#[inline(always)]
fn record(fields: Vec<(Lowercase, Field)>, var_store: &mut VarStore) -> Expr {
let mut send_map = SendMap::default();
for (k, v) in fields {
send_map.insert(k, v);
}
Expr::Record {
record_var: var_store.fresh(),
fields: send_map,
}
}
#[inline(always)]
fn defn(

View File

@ -1,6 +1,6 @@
use crate::procedure::References;
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_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region};
@ -99,23 +99,37 @@ impl<'a> Env<'a> {
)),
}
} else {
match self
.dep_idents
.get(&module_id)
.and_then(|exposed_ids| exposed_ids.get_id(&ident))
{
Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id);
match self.dep_idents.get(&module_id) {
Some(exposed_ids) => match exposed_ids.get_id(&ident) {
Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id);
self.qualified_lookups.insert(symbol);
self.qualified_lookups.insert(symbol);
Ok(symbol)
Ok(symbol)
}
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,
ident,
region,
exposed_values,
})
}
},
None => {
panic!(
"Module {} exists, but is not recorded in dep_idents",
module_name
)
}
None => Err(RuntimeError::ValueNotExposed {
module_name,
ident,
region,
}),
}
}
}

View File

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

View File

@ -2,9 +2,9 @@
use bumpalo::collections::Vec;
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::operator::BinOp::Pizza;
use roc_module::operator::{BinOp, CalledVia};
use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::{AssignedField, Def, WhenBranch};
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 })
}
List {
items,
final_comments,
} => {
List(items) => {
let mut new_items = Vec::with_capacity_in(items.len(), arena);
for item in items.iter() {
new_items.push(desugar_expr(arena, item));
}
let new_items = new_items.into_bump_slice();
let value: Expr<'a> = List {
items: new_items,
final_comments,
};
let value: Expr<'a> = List(items.replace_items(new_items));
arena.alloc(Located {
region: loc_expr.region,
value,
})
}
Record {
fields,
final_comments,
} => {
let mut new_fields = Vec::with_capacity_in(fields.len(), arena);
for field in fields.iter() {
Record(fields) => arena.alloc(Located {
region: loc_expr.region,
value: Record(fields.map_items(arena, |field| {
let value = desugar_field(arena, &field.value);
new_fields.push(Located {
Located {
value,
region: field.region,
});
}
}
})),
}),
let new_fields = new_fields.into_bump_slice();
arena.alloc(Located {
region: loc_expr.region,
value: Record {
fields: new_fields,
final_comments,
},
})
}
RecordUpdate {
fields,
update,
final_comments,
} => {
RecordUpdate { fields, update } => {
// 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);
for field in fields.iter() {
let new_fields = fields.map_items(arena, |field| {
let value = desugar_field(arena, &field.value);
new_fields.push(Located {
Located {
value,
region: field.region,
});
}
let new_fields = new_fields.into_bump_slice();
}
});
arena.alloc(Located {
region: loc_expr.region,
value: RecordUpdate {
update: *update,
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) => {
use roc_module::operator::UnaryOp::*;
use roc_module::called_via::UnaryOp::*;
let region = loc_op.region;
let op = loc_op.value;
@ -506,7 +475,7 @@ fn binop_step<'a>(
op_stack: &mut Vec<Located<BinOp>>,
next_op: Located<BinOp>,
) -> Step<'a> {
use roc_module::operator::Associativity::*;
use roc_module::called_via::Associativity::*;
use std::cmp::Ordering;
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 opt_erroneous = None;
for loc_pattern in *patterns {
for loc_pattern in patterns.iter() {
match loc_pattern.value {
Identifier(label) => {
match scope.introduce(

View File

@ -254,7 +254,7 @@ pub fn constrain_expr(
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;
// The expression that evaluates to the function being called, e.g. `foo` in
// (foo) bar baz
@ -317,7 +317,7 @@ pub fn constrain_expr(
region,
);
let category = Category::CallResult(opt_symbol);
let category = Category::CallResult(opt_symbol, *called_via);
exists(
vars,

View File

@ -1,6 +1,6 @@
use crate::spaces::{fmt_comments_only, fmt_spaces, newline, NewlineAt, INDENT};
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;
/// Does an AST node need parens around it?
@ -81,9 +81,9 @@ where
}
macro_rules! format_sequence {
($buf: expr, $indent:expr, $start:expr, $end:expr, $items:expr, $final_comments:expr, $newline:expr, $t:ident) => {
let is_multiline =
$items.iter().any(|item| item.value.is_multiline()) || !$final_comments.is_empty();
($buf: expr, $indent:expr, $start:expr, $end:expr, $items:expr, $newline:expr, $t:ident) => {
let is_multiline = $items.iter().any(|item| item.value.is_multiline())
|| !$items.final_comments().is_empty();
if is_multiline {
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);
$buf.push($end);
} else {
@ -175,7 +180,7 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
true
}
Wildcard | BoundVariable(_) | Malformed(_) => false,
Wildcard | Inferred | BoundVariable(_) | Malformed(_) => false,
Function(args, result) => {
(&result.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())
}
TagUnion {
tags,
ext,
final_comments: _,
} => {
TagUnion { tags, ext } => {
match ext {
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),
Wildcard => buf.push('*'),
Inferred => buf.push('_'),
TagUnion {
tags,
ext,
final_comments,
} => {
format_sequence!(buf, indent, '[', ']', tags, final_comments, newlines, Tag);
TagUnion { tags, ext } => {
format_sequence!(buf, indent, '[', ']', tags, newlines, Tag);
if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent);
}
}
Record {
fields:
Collection {
items,
final_comments,
},
ext,
} => {
format_sequence!(
buf,
indent,
'{',
'}',
items,
final_comments,
newlines,
AssignedField
);
Record { fields, ext } => {
format_sequence!(buf, indent, '{', '}', fields, newlines, AssignedField);
if let Some(loc_ext_ann) = *ext {
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::spaces::{add_spaces, fmt_comments_only, fmt_spaces, newline, NewlineAt, INDENT};
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::{AssignedField, Base, CommentOrNewline, Expr, Pattern, WhenBranch};
use roc_parse::ast::{
AssignedField, Base, Collection, CommentOrNewline, Expr, Pattern, WhenBranch,
};
use roc_region::all::Located;
impl<'a> Formattable<'a> for Expr<'a> {
@ -39,7 +41,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
// These expressions always have newlines
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) => {
use roc_parse::ast::StrLiteral::*;
@ -98,7 +100,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
.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()),
}
}
@ -250,18 +252,11 @@ impl<'a> Formattable<'a> for Expr<'a> {
buf.push_str(string);
}
Record {
fields,
final_comments,
} => {
fmt_record(buf, None, fields, final_comments, indent);
Record(fields) => {
fmt_record(buf, None, *fields, indent);
}
RecordUpdate {
fields,
update,
final_comments,
} => {
fmt_record(buf, Some(*update), fields, final_comments, indent);
RecordUpdate { update, fields } => {
fmt_record(buf, Some(*update), *fields, indent);
}
Closure(loc_patterns, loc_ret) => {
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);
}
When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent),
List {
items,
final_comments,
} => {
fmt_list(buf, items, final_comments, indent);
List(items) => {
fmt_list(buf, *items, indent);
}
BinOps(lefts, right) => fmt_bin_ops(buf, lefts, right, false, parens, indent),
UnaryOp(sub_expr, unary_op) => {
match &unary_op.value {
operator::UnaryOp::Negate => {
called_via::UnaryOp::Negate => {
buf.push('-');
}
operator::UnaryOp::Not => {
called_via::UnaryOp::Not => {
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) {
match op {
operator::BinOp::Caret => buf.push('^'),
operator::BinOp::Star => buf.push('*'),
operator::BinOp::Slash => buf.push('/'),
operator::BinOp::DoubleSlash => buf.push_str("//"),
operator::BinOp::Percent => buf.push('%'),
operator::BinOp::DoublePercent => buf.push_str("%%"),
operator::BinOp::Plus => buf.push('+'),
operator::BinOp::Minus => buf.push('-'),
operator::BinOp::Equals => buf.push_str("=="),
operator::BinOp::NotEquals => buf.push_str("!="),
operator::BinOp::LessThan => buf.push('<'),
operator::BinOp::GreaterThan => buf.push('>'),
operator::BinOp::LessThanOrEq => buf.push_str("<="),
operator::BinOp::GreaterThanOrEq => buf.push_str(">="),
operator::BinOp::And => buf.push_str("&&"),
operator::BinOp::Or => buf.push_str("||"),
operator::BinOp::Pizza => buf.push_str("|>"),
operator::BinOp::Assignment => unreachable!(),
operator::BinOp::HasType => unreachable!(),
operator::BinOp::Backpassing => unreachable!(),
called_via::BinOp::Caret => buf.push('^'),
called_via::BinOp::Star => buf.push('*'),
called_via::BinOp::Slash => buf.push('/'),
called_via::BinOp::DoubleSlash => buf.push_str("//"),
called_via::BinOp::Percent => buf.push('%'),
called_via::BinOp::DoublePercent => buf.push_str("%%"),
called_via::BinOp::Plus => buf.push('+'),
called_via::BinOp::Minus => buf.push('-'),
called_via::BinOp::Equals => buf.push_str("=="),
called_via::BinOp::NotEquals => buf.push_str("!="),
called_via::BinOp::LessThan => buf.push('<'),
called_via::BinOp::GreaterThan => buf.push('>'),
called_via::BinOp::LessThanOrEq => buf.push_str("<="),
called_via::BinOp::GreaterThanOrEq => buf.push_str(">="),
called_via::BinOp::And => buf.push_str("&&"),
called_via::BinOp::Or => buf.push_str("||"),
called_via::BinOp::Pizza => buf.push_str("|>"),
called_via::BinOp::Assignment => unreachable!(),
called_via::BinOp::HasType => 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);
}
fn fmt_list<'a>(
buf: &mut String<'a>,
loc_items: &[&Located<Expr<'a>>],
final_comments: &'a [CommentOrNewline<'a>],
indent: u16,
) {
fn fmt_list<'a>(buf: &mut String<'a>, items: Collection<'a, &'a Located<Expr<'a>>>, indent: u16) {
let loc_items = items.items;
let final_comments = items.final_comments();
if loc_items.is_empty() && final_comments.iter().all(|c| c.is_newline()) {
buf.push_str("[]");
} else {
@ -917,10 +906,11 @@ fn fmt_backpassing<'a>(
fn fmt_record<'a>(
buf: &mut String<'a>,
update: Option<&'a Located<Expr<'a>>>,
loc_fields: &[Located<AssignedField<'a, Expr<'a>>>],
final_comments: &'a [CommentOrNewline<'a>],
fields: Collection<'a, Located<AssignedField<'a, Expr<'a>>>>,
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()) {
buf.push_str("{}");
} else {

View File

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

View File

@ -446,6 +446,10 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
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)]
fn sub_reg64_reg64_imm32(
@ -486,6 +490,26 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
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)]
fn ret(buf: &mut Vec<'_, u8>) {
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_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(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
@ -171,6 +172,20 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
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>);
}
@ -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(
&mut self,
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(
&mut self,
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)
}
#[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)]
fn sub_reg64_reg64_imm32(
buf: &mut Vec<'_, u8>,
@ -1091,6 +1096,28 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
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)]
fn ret(buf: &mut Vec<'_, u8>) {
ret(buf);
@ -1448,9 +1475,9 @@ fn neg_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
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)]
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
buf.reserve(7);
@ -1458,10 +1485,10 @@ fn sete_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
let reg_mod = reg as u8 % 8;
use X86_64GeneralReg::*;
match reg {
RAX | RCX | RDX | RBX => buf.extend(&[0x0F, 0x94, 0xC0 + reg_mod]),
RSP | RBP | RSI | RDI => buf.extend(&[REX, 0x0F, 0x94, 0xC0 + reg_mod]),
RAX | RCX | RDX | RBX => buf.extend(&[0x0F, value, 0xC0 + reg_mod]),
RSP | RBP | RSI | RDI => buf.extend(&[REX, 0x0F, value, 0xC0 + reg_mod]),
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);
}
/// `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.
#[inline(always)]
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]
fn test_addsd_freg64_freg64() {
let arena = bumpalo::Bump::new();
@ -2026,7 +2099,7 @@ mod tests {
}
#[test]
fn test_sete_reg64() {
fn test_set_reg64_help() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
@ -2039,7 +2112,7 @@ mod tests {
],
);
buf.clear();
sete_reg64(&mut buf, reg);
set_reg64_help(&mut buf, reg, 0x94); // sete_reg64
assert_eq!(expected, &buf[..]);
// tests for 8 bytes in the output buffer
@ -2064,7 +2137,7 @@ mod tests {
),
] {
buf.clear();
sete_reg64(&mut buf, *reg);
set_reg64_help(&mut buf, *reg, 0x94); // sete_reg64
assert_eq!(expected, &buf[..]);
}
}

View File

@ -227,97 +227,30 @@ where
ret_layout,
..
} => {
// For most builtins instead of calling a function, we can just inline the low level.
match *func_sym {
Symbol::NUM_ABS => self.build_run_low_level(
// If this function is just a lowlevel wrapper, then inline it
if let Some(lowlevel) = LowLevel::from_inlined_wrapper(*func_sym) {
self.build_run_low_level(
sym,
&LowLevel::NumAbs,
&lowlevel,
arguments,
arg_layouts,
ret_layout,
),
Symbol::NUM_ADD => self.build_run_low_level(
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)
.starts_with(ModuleName::APP) =>
{
let fn_name = LayoutIds::default()
.get(*func_sym, layout)
.to_symbol_string(*func_sym, &self.env().interns);
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(arguments)?;
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
}
x => Err(format!("the function, {:?}, is not yet implemented", x)),
)
} else if func_sym
.module_string(&self.env().interns)
.starts_with(ModuleName::APP)
{
let fn_name = LayoutIds::default()
.get(*func_sym, layout)
.to_symbol_string(*func_sym, &self.env().interns);
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(arguments)?;
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
} else {
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)
}
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(
sym,
bitcode::NUM_POW_INT[IntWidth::I64].to_string(),
@ -471,6 +416,40 @@ where
);
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(
sym,
bitcode::NUM_ROUND[FloatWidth::F64].to_string(),
@ -526,6 +505,14 @@ where
layout: &Layout<'a>,
) -> 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.
fn build_num_sub(
&mut self,
@ -544,6 +531,24 @@ where
arg_layout: &Layout<'a>,
) -> 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.
fn literal_map(&mut self) -> &mut MutMap<Symbol, Literal<'a>>;
@ -785,7 +790,7 @@ where
match call_type {
CallType::ByName { .. } => {}
CallType::LowLevel { .. } => {}
CallType::HigherOrderLowLevel { .. } => {}
CallType::HigherOrder { .. } => {}
CallType::Foreign { .. } => {}
}
}

View File

@ -128,20 +128,19 @@ fn build_has_tag_id_help<'a, 'ctx, 'env>(
[tag_id, tag_value_ptr] => {
let tag_type = basic_type_from_layout(env, &Layout::Union(union_layout));
let argument_cast = env
.builder
.build_bitcast(
*tag_value_ptr,
tag_type.ptr_type(AddressSpace::Generic),
"load_opaque",
)
.into_pointer_value();
let tag_value = env.builder.build_load(argument_cast, "get_value");
let tag_value = env.builder.build_pointer_cast(
tag_value_ptr.into_pointer_value(),
tag_type.ptr_type(AddressSpace::Generic),
"load_opaque_get_tag_id",
);
let actual_tag_id = {
let tag_id_i64 =
crate::llvm::build::get_tag_id(env, function_value, &union_layout, tag_value);
let tag_id_i64 = crate::llvm::build::get_tag_id(
env,
function_value,
&union_layout,
tag_value.into(),
);
env.builder
.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 data_index = env
.context
.i64_type()
.const_int(TAG_DATA_INDEX as u64, false);
let ptr = env
.builder
.build_struct_gep(tag_value, TAG_DATA_INDEX, "get_data_ptr")
.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")
};
@ -191,6 +183,7 @@ pub fn build_transform_caller<'a, 'ctx, 'env>(
function: FunctionValue<'ctx>,
closure_data_layout: LambdaSet<'a>,
argument_layouts: &[Layout<'a>],
result_layout: Layout<'a>,
) -> FunctionValue<'ctx> {
let fn_name: &str = &format!(
"{}_zig_function_caller",
@ -204,6 +197,7 @@ pub fn build_transform_caller<'a, 'ctx, 'env>(
function,
closure_data_layout,
argument_layouts,
result_layout,
fn_name,
),
}
@ -214,6 +208,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
roc_function: FunctionValue<'ctx>,
closure_data_layout: LambdaSet<'a>,
argument_layouts: &[Layout<'a>],
result_layout: Layout<'a>,
fn_name: &str,
) -> FunctionValue<'ctx> {
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) {
let basic_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic);
let argument_cast = env
.builder
.build_bitcast(*argument_ptr, basic_type, "load_opaque")
.into_pointer_value();
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
.builder
.build_bitcast(*argument_ptr, basic_type, "load_opaque_1")
.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);
}
@ -288,31 +293,19 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
}
}
let call = {
env.builder
.build_call(roc_function, arguments_cast.as_slice(), "tmp")
};
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 = crate::llvm::build::call_roc_function(
env,
roc_function,
&result_layout,
arguments_cast.as_slice(),
);
let result_u8_ptr = function_value
.get_nth_param(argument_layouts.len() as u32 + 1)
.unwrap();
let result_ptr = env
.builder
.build_bitcast(
result_u8_ptr,
result.get_type().ptr_type(AddressSpace::Generic),
"write_result",
)
.unwrap()
.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.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_cast = env
.builder
.build_bitcast(value_ptr, value_type, "load_opaque")
.into_pointer_value();
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
.builder
.build_bitcast(value_ptr, value_type, "load_opaque")
.into_pointer_value();
let value = env.builder.build_load(value_cast, "load_opaque");
env.builder.build_load(value_cast, "load_opaque")
};
match rc_operation {
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::build::tag_pointer_clear_tag_id;
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::convert::basic_type_from_layout;
use crate::llvm::convert::{basic_type_from_layout, basic_type_from_layout_1};
use bumpalo::collections::Vec;
use inkwell::values::{
BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
@ -339,7 +339,8 @@ fn build_hash_tag<'a, 'ctx, 'env>(
None => {
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(
env,
@ -423,14 +424,6 @@ fn hash_tag<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify");
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
let hash_bytes = store_and_use_as_u8_ptr(
env,
@ -440,7 +433,6 @@ fn hash_tag<'a, 'ctx, 'env>(
.into(),
&tag_id_layout,
);
let seed = hash_bitcode_fn(
env,
seed,
@ -449,14 +441,9 @@ fn hash_tag<'a, 'ctx, 'env>(
);
// hash the tag data
let answer = build_hash_struct(
env,
layout_ids,
field_layouts,
WhenRecursive::Unreachable,
seed,
as_struct,
);
let tag = tag.into_pointer_value();
let answer =
hash_ptr_to_struct(env, layout_ids, union_layout, field_layouts, seed, tag);
merge_phi.add_incoming(&[(&answer, block)]);
env.builder.build_unconditional_branch(merge_block);
@ -793,7 +780,15 @@ fn hash_list<'a, 'ctx, 'env>(
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);
@ -822,12 +817,12 @@ fn hash_ptr_to_struct<'a, 'ctx, 'env>(
) -> IntValue<'ctx> {
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
let wrapper_ptr = env
.builder
.build_bitcast(tag, wrapper_type, "opaque_to_correct")
.build_bitcast(tag, wrapper_type, "hash_ptr_to_struct_opaque_to_correct")
.into_pointer_value();
let struct_ptr = env

View File

@ -17,6 +17,8 @@ use morphic_lib::UpdateMode;
use roc_builtins::bitcode;
use roc_mono::layout::{Builtin, Layout, LayoutIds};
use super::build::{load_roc_value, store_roc_value};
pub fn pass_update_mode<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
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>(
env: &Env<'a, 'ctx, 'env>,
element: BasicValueEnum<'ctx>,
layout: Layout<'a>,
) -> BasicValueEnum<'ctx> {
let element_ptr = env.builder.build_alloca(element.get_type(), "element");
env.builder.build_store(element_ptr, element);
let element_type = basic_type_from_layout(env, &layout);
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(
element_ptr,
@ -106,7 +112,7 @@ pub fn list_single<'a, 'ctx, 'env>(
env,
&[
env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element),
pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout),
],
bitcode::LIST_SINGLE,
@ -128,7 +134,7 @@ pub fn list_repeat<'a, 'ctx, 'env>(
&[
list_len.into(),
env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element),
pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout),
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
// (e.g. by List.get or List.first, which wrap List.#getUnsafe)
let elem_ptr =
unsafe { builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "elem") };
let elem_ptr = unsafe {
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);
@ -247,7 +254,7 @@ pub fn list_append<'a, 'ctx, 'env>(
&[
pass_list_cc(env, original_wrapper.into()),
env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element),
pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout),
pass_update_mode(env, update_mode),
],
@ -267,7 +274,7 @@ pub fn list_prepend<'a, 'ctx, 'env>(
&[
pass_list_cc(env, original_wrapper.into()),
env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element),
pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout),
],
bitcode::LIST_PREPEND,
@ -297,31 +304,13 @@ pub fn list_swap<'a, 'ctx, 'env>(
)
}
/// List.takeFirst : List elem, Nat -> List elem
pub fn list_take_first<'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>(
/// List.sublist : List elem, { start : Nat, len : Nat } -> List elem
pub fn list_sublist<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
original_wrapper: StructValue<'ctx>,
count: IntValue<'ctx>,
start: IntValue<'ctx>,
len: IntValue<'ctx>,
element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
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()),
env.alignment_intvalue(element_layout),
layout_width(env, element_layout),
count.into(),
start.into(),
len.into(),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_TAKE_LAST,
)
}
/// 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,
bitcode::LIST_SUBLIST,
)
}
@ -406,7 +374,7 @@ pub fn list_set<'a, 'ctx, 'env>(
&[
bytes.into(),
index.into(),
pass_element_as_opaque(env, element),
pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
@ -419,7 +387,7 @@ pub fn list_set<'a, 'ctx, 'env>(
length.into(),
env.alignment_intvalue(element_layout),
index.into(),
pass_element_as_opaque(env, element),
pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
@ -595,7 +563,7 @@ pub fn list_contains<'a, 'ctx, 'env>(
env,
&[
pass_list_cc(env, list),
pass_element_as_opaque(env, element),
pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout),
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 }
pub fn list_find_unsafe<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -1154,6 +1143,7 @@ where
pub fn incrementing_elem_loop<'a, 'ctx, 'env, LoopFn>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
element_layout: Layout<'a>,
ptr: PointerValue<'ctx>,
len: IntValue<'ctx>,
index_name: &str,
@ -1166,9 +1156,14 @@ where
incrementing_index_loop(env, parent, len, index_name, |index| {
// 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);
})

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)
}
/// 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
pub fn str_from_int<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,

View File

@ -1,10 +1,8 @@
use crate::llvm::bitcode::call_bitcode_fn;
use crate::llvm::build::{
cast_block_of_memory_to_tag, get_tag_id, tag_pointer_clear_tag_id, Env, FAST_CALL_CONV,
};
use crate::llvm::build::{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_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 inkwell::types::BasicType;
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()) {
Some(function_value) => function_value,
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(
env,
@ -844,9 +842,29 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
match union_layout {
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 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 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");
env.builder.position_at_end(block);
// TODO drop tag id?
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(
let answer = eq_ptr_to_struct(
env,
layout_ids,
union_layout,
Some(when_recursive.clone()),
field_layouts,
when_recursive.clone(),
struct1,
struct2,
tag1,
tag2,
);
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");
env.builder.position_at_end(block);
let answer =
eq_ptr_to_struct(env, layout_ids, union_layout, field_layouts, tag1, tag2);
let answer = eq_ptr_to_struct(
env,
layout_ids,
union_layout,
None,
field_layouts,
tag1,
tag2,
);
env.builder.build_return(Some(&answer));
@ -1003,6 +1012,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env,
layout_ids,
union_layout,
None,
other_fields,
tag1.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");
env.builder.position_at_end(block);
let answer =
eq_ptr_to_struct(env, layout_ids, union_layout, field_layouts, tag1, tag2);
let answer = eq_ptr_to_struct(
env,
layout_ids,
union_layout,
None,
field_layouts,
tag1,
tag2,
);
env.builder.build_return(Some(&answer));
@ -1128,6 +1145,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env,
layout_ids,
union_layout,
None,
field_layouts,
tag1.into_pointer_value(),
tag2.into_pointer_value(),
@ -1142,6 +1160,7 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
union_layout: &UnionLayout<'a>,
opt_when_recursive: Option<WhenRecursive<'a>>,
field_layouts: &'a [Layout<'a>],
tag1: PointerValue<'ctx>,
tag2: PointerValue<'ctx>,
@ -1184,7 +1203,7 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
env,
layout_ids,
field_layouts,
WhenRecursive::Loop(*union_layout),
opt_when_recursive.unwrap_or(WhenRecursive::Loop(*union_layout)),
struct1,
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>(
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
builtin: &Builtin<'_>,

View File

@ -1,11 +1,11 @@
use crate::debug_info_init;
use crate::llvm::bitcode::call_void_bitcode_fn;
use crate::llvm::build::{
add_func, cast_basic_basic, cast_block_of_memory_to_tag, get_tag_id, get_tag_id_non_recursive,
tag_pointer_clear_tag_id, Env, FAST_CALL_CONV, TAG_DATA_INDEX,
add_func, cast_basic_basic, get_tag_id, tag_pointer_clear_tag_id, use_roc_value, Env,
FAST_CALL_CONV, TAG_DATA_INDEX, TAG_ID_INDEX,
};
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 inkwell::basic_block::BasicBlock;
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 parent = block.get_parent().unwrap();
let modify_block = env.context.append_basic_block(parent, "inc_str_modify");
let cont_block = env.context.append_basic_block(parent, "inc_str_cont");
let modify_block = env
.context
.append_basic_block(parent, "inc_refcount_modify");
let cont_block = env.context.append_basic_block(parent, "inc_refcount_cont");
env.builder
.build_conditional_branch(is_static_allocation, cont_block, modify_block);
@ -181,7 +183,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
env.module,
fn_name,
fn_type,
Linkage::Private,
Linkage::Internal,
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() {
if field_layout.contains_refcounted() {
let field_ptr = env
let raw_value = env
.builder
.build_extract_value(wrapper_struct, i as u32, "decrement_struct_field")
.unwrap();
let field_value = use_roc_value(
env,
*field_layout,
raw_value,
"load_struct_tag_field_for_decrement",
);
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode.to_call_mode(fn_val),
when_recursive,
field_ptr,
field_value,
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);
@ -1289,7 +1306,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
.build_bitcast(
value_ptr,
wrapper_type.ptr_type(AddressSpace::Generic),
"opaque_to_correct",
"opaque_to_correct_recursive_decrement",
)
.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()) {
Some(function_value) => function_value,
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);
modify_refcount_union_help(
@ -1647,18 +1664,24 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
// Add args to scope
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 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
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
.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));
debug_assert!(wrapper_type.is_struct_type());
let data_bytes = env
let opaque_tag_data_ptr = env
.builder
.build_extract_value(wrapper_struct, TAG_DATA_INDEX, "read_tag_id")
.unwrap()
.into_struct_value();
let wrapper_struct = cast_block_of_memory_to_tag(env.builder, data_bytes, wrapper_type);
.build_struct_gep(arg_ptr, TAG_DATA_INDEX, "field_ptr")
.unwrap();
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() {
if let Layout::RecursivePointer = field_layout {
@ -1699,16 +1726,22 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
} else if field_layout.contains_refcounted() {
let field_ptr = env
.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();
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(
env,
parent,
layout_ids,
mode.to_call_mode(fn_val),
when_recursive,
field_ptr,
field_value,
field_layout,
);
}

Binary file not shown.

View File

@ -2,12 +2,13 @@ use bumpalo::{self, collections::Vec};
use code_builder::Align;
use roc_collections::all::MutMap;
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
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::low_level::{build_call_low_level, LowlevelBuildResult};
use crate::low_level::{decode_low_level, LowlevelBuildResult};
use crate::storage::{Storage, StoredValue, StoredValueKind};
use crate::wasm_module::linking::{
DataSymbol, LinkingSection, RelocationSection, WasmObjectSymbol, WASM_SYM_BINDING_WEAK,
@ -22,8 +23,8 @@ use crate::wasm_module::{
LocalId, Signature, SymInfo, ValueType,
};
use crate::{
copy_memory, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, MEMORY_NAME, PTR_TYPE,
STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME,
copy_memory, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, MEMORY_NAME, PTR_SIZE,
PTR_TYPE, STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME,
};
/// 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.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(())
}
Stmt::Switch {
cond_symbol,
cond_layout: _,
cond_layout,
branches,
default_branch,
ret_layout: _,
@ -321,21 +323,45 @@ impl<'a> WasmBackend<'a> {
cond_storage,
);
// create (number_of_branches - 1) new blocks.
// create a block for each branch except the default
for _ in 0..branches.len() {
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
for (i, (value, _, _)) in branches.iter().enumerate() {
// put the cond_symbol on the top of the stack
self.storage
.load_symbols(&mut self.code_builder, &[*cond_symbol]);
self.code_builder.i32_const(*value as i32);
// compare the 2 topmost values
self.code_builder.i32_eq();
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_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
self.code_builder.br_if(i as u32);
@ -418,6 +444,13 @@ impl<'a> WasmBackend<'a> {
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)),
}
}
@ -444,6 +477,11 @@ impl<'a> WasmBackend<'a> {
arguments,
}) => match call_type {
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 (wasm_args, has_return_val) = match wasm_layout {
WasmLayout::StackMemory { .. } => {
@ -486,39 +524,80 @@ impl<'a> WasmBackend<'a> {
}
CallType::LowLevel { op: lowlevel, .. } => {
let return_layout = WasmLayout::new(layout);
self.storage.load_symbols(&mut self.code_builder, arguments);
let build_result = build_call_low_level(
&mut self.code_builder,
&mut self.storage,
lowlevel,
arguments,
&return_layout,
);
use LowlevelBuildResult::*;
match build_result {
Done => Ok(()),
BuiltinCall(name) => {
self.call_imported_builtin(name, arguments, &return_layout);
Ok(())
}
NotImplemented => Err(format!(
"Low level operation {:?} is not yet implemented",
lowlevel
)),
}
self.build_low_level(*lowlevel, arguments, *sym, wasm_layout)
}
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.storage,
lowlevel,
arguments,
&return_layout,
);
use LowlevelBuildResult::*;
match build_result {
Done => Ok(()),
BuiltinCall(name) => {
self.call_zig_builtin(name, arguments, &return_layout);
Ok(())
}
NotImplemented => Err(format!(
"Low level operation {:?} is not yet implemented",
lowlevel
)),
}
}
fn load_literal(
&mut self,
lit: &Literal<'a>,
@ -578,8 +657,8 @@ impl<'a> WasmBackend<'a> {
self.lookup_string_constant(string, sym, layout);
self.code_builder.get_local(local_id);
self.code_builder.insert_memory_relocation(linker_sym_index);
self.code_builder.i32_const(elements_addr as i32);
self.code_builder
.i32_const_mem_addr(elements_addr, linker_sym_index);
self.code_builder.i32_store(Align::Bytes4, offset);
self.code_builder.get_local(local_id);
@ -701,12 +780,10 @@ impl<'a> WasmBackend<'a> {
Ok(())
}
fn call_imported_builtin(
&mut self,
name: &'a str,
arguments: &[Symbol],
ret_layout: &WasmLayout,
) {
/// Generate a call instruction to a Zig builtin function.
/// And if we haven't seen it before, add an Import and linker data for it.
/// Zig calls use LLVM's "fast" calling convention rather than our usual C ABI.
fn call_zig_builtin(&mut self, name: &'a str, arguments: &[Symbol], ret_layout: &WasmLayout) {
let (fn_index, linker_symbol_index) = match self.builtin_sym_index_map.get(name) {
Some(sym_idx) => match &self.linker_symbols[*sym_idx] {
SymInfo::Function(WasmObjectSymbol::Imported { index, .. }) => {
@ -716,11 +793,29 @@ impl<'a> WasmBackend<'a> {
},
None => {
let mut param_types = Vec::with_capacity_in(arguments.len(), self.env.arena);
param_types.extend(arguments.iter().map(|a| self.storage.get(a).value_type()));
let mut param_types = Vec::with_capacity_in(1 + arguments.len(), self.env.arena);
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 {
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;
@ -731,14 +826,15 @@ impl<'a> WasmBackend<'a> {
};
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 {
flags: WASM_SYM_UNDEFINED,
index: import_index,
});
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(

View File

@ -7,6 +7,7 @@ pub mod wasm_module;
use bumpalo::{self, collections::Vec, Bump};
use roc_collections::all::{MutMap, MutSet};
use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, Symbol};
use roc_mono::ir::{Proc, ProcLayout};
use roc_mono::layout::LayoutIds;
@ -36,7 +37,7 @@ pub fn build_module<'a>(
env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> 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);
wasm_module.serialize_mut(&mut buffer);
Ok(buffer)
@ -45,36 +46,54 @@ pub fn build_module<'a>(
pub fn build_module_help<'a>(
env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<WasmModule<'a>, String> {
) -> Result<(WasmModule<'a>, u32), String> {
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 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
for (i, (sym, layout)) in procedures.keys().enumerate() {
proc_symbols.push(*sym);
// Collect the symbols & names for the procedures,
// and filter out procs we're going to inline
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
.get_toplevel(*sym, layout)
.to_symbol_string(*sym, &env.interns);
.get_toplevel(sym, &layout)
.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 {
name: fn_name.clone(),
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);
fn_index += 1;
}
// Main loop: Build the Wasm module
// Build the Wasm module
let (mut module, linker_symbols) = {
let mut backend = WasmBackend::new(env, layout_ids, proc_symbols, linker_symbols, exports);
for ((sym, _), proc) in procedures.into_iter() {
let mut backend = WasmBackend::new(
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.module, backend.linker_symbols)
@ -83,7 +102,7 @@ pub fn build_module_help<'a>(
let symbol_table = LinkingSubSection::SymbolTable(linker_symbols);
module.linking.subsections.push(symbol_table);
Ok(module)
Ok((module, main_fn_index.unwrap()))
}
pub struct CopyMemoryConfig {

View File

@ -15,10 +15,10 @@ pub enum LowlevelBuildResult {
NotImplemented,
}
pub fn build_call_low_level<'a>(
pub fn decode_low_level<'a>(
code_builder: &mut CodeBuilder<'a>,
storage: &mut Storage<'a>,
lowlevel: &LowLevel,
lowlevel: LowLevel,
args: &'a [Symbol],
ret_layout: &WasmLayout,
) -> LowlevelBuildResult {
@ -27,16 +27,39 @@ pub fn build_call_low_level<'a>(
let panic_ret_type = || panic!("Invalid return layout for {:?}: {:?}", lowlevel, ret_layout);
match lowlevel {
StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt
| StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 | StrTrimLeft
| StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | StrTrim | ListLen
| ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat
StrConcat => return BuiltinCall(bitcode::STR_CONCAT),
StrJoinWith => return NotImplemented, // needs Array
StrIsEmpty => {
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
| ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil
| ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListTakeFirst
| ListTakeLast | ListDrop | ListDropAt | ListSwap | ListAny | ListFindUnsafe | DictSize
| DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys
| DictValues | DictUnion | DictIntersection | DictDifference | DictWalk | SetFromList => {
| ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListSublist
| ListDropAt | ListSwap | ListAny | ListAll | ListFindUnsafe | DictSize | DictEmpty
| DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues
| DictUnion | DictIntersection | DictDifference | DictWalk | SetFromList => {
return NotImplemented;
}
@ -129,6 +152,9 @@ pub fn build_call_low_level<'a>(
NumIsMultipleOf => return NotImplemented,
NumAbs => match ret_layout.value_type() {
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);
storage.load_symbols(code_builder, args);
code_builder.i32_sub();
@ -138,6 +164,9 @@ pub fn build_call_low_level<'a>(
code_builder.select();
}
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);
storage.load_symbols(code_builder, args);
code_builder.i64_sub();

View File

@ -5,7 +5,7 @@ use roc_collections::all::MutMap;
use roc_module::symbol::Symbol;
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};
pub enum StoredValueKind {
@ -33,7 +33,7 @@ impl StackMemoryLocation {
pub enum StoredValue {
/// A value stored implicitly in the VM stack (primitives only)
VirtualMachineStack {
vm_state: VirtualMachineSymbolState,
vm_state: VmSymbolState,
value_type: ValueType,
size: u32,
},
@ -126,7 +126,7 @@ impl<'a> Storage<'a> {
}
}
_ => StoredValue::VirtualMachineStack {
vm_state: VirtualMachineSymbolState::NotYetPushed,
vm_state: VmSymbolState::NotYetPushed,
value_type: *value_type,
size: *size,
},
@ -194,6 +194,63 @@ impl<'a> Storage<'a> {
})
}
/// Load a single symbol using the C Calling Convention
/// *Private* because external code should always load symbols in bulk (see load_symbols)
fn load_symbol_ccc(&mut self, code_builder: &mut CodeBuilder, sym: Symbol) {
let storage = self.get(&sym).to_owned();
match storage {
StoredValue::VirtualMachineStack {
vm_state,
value_type,
size,
} => {
let next_local_id = self.get_next_local_id();
let maybe_next_vm_state = code_builder.load_symbol(sym, vm_state, next_local_id);
match maybe_next_vm_state {
// The act of loading the value changed the VM state, so update it
Some(next_vm_state) => {
self.symbol_storage_map.insert(
sym,
StoredValue::VirtualMachineStack {
vm_state: next_vm_state,
value_type,
size,
},
);
}
None => {
// Loading the value required creating a new local, because
// it was not in a convenient position in the VM stack.
self.local_types.push(value_type);
self.symbol_storage_map.insert(
sym,
StoredValue::Local {
local_id: next_local_id,
value_type,
size,
},
);
}
}
}
StoredValue::Local { local_id, .. } => {
code_builder.get_local(local_id);
code_builder.set_top_symbol(sym);
}
StoredValue::StackMemory { location, .. } => {
let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer);
code_builder.get_local(local_id);
if offset != 0 {
code_builder.i32_const(offset as i32);
code_builder.i32_add();
}
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.
@ -204,61 +261,60 @@ impl<'a> Storage<'a> {
return;
}
for sym in symbols.iter() {
let storage = self.get(sym).to_owned();
match storage {
StoredValue::VirtualMachineStack {
vm_state,
value_type,
size,
} => {
let next_local_id = self.get_next_local_id();
let maybe_next_vm_state =
code_builder.load_symbol(*sym, vm_state, next_local_id);
match maybe_next_vm_state {
// The act of loading the value changed the VM state, so update it
Some(next_vm_state) => {
self.symbol_storage_map.insert(
*sym,
StoredValue::VirtualMachineStack {
vm_state: next_vm_state,
value_type,
size,
},
);
}
None => {
// Loading the value required creating a new local, because
// it was not in a convenient position in the VM stack.
self.local_types.push(value_type);
self.symbol_storage_map.insert(
*sym,
StoredValue::Local {
local_id: next_local_id,
value_type,
size,
},
);
}
}
}
StoredValue::Local { local_id, .. }
| StoredValue::StackMemory {
location: StackMemoryLocation::PointerArg(local_id),
..
} => {
code_builder.get_local(local_id);
code_builder.set_top_symbol(*sym);
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);
}
StoredValue::StackMemory {
location: StackMemoryLocation::FrameOffset(offset),
..
} => {
code_builder.get_local(self.stack_frame_pointer.unwrap());
code_builder.i32_const(offset as i32);
code_builder.i32_add();
code_builder.set_top_symbol(*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
/// Copies the _entire_ value. For struct fields etc., see `copy_value_to_memory`
pub fn clone_value(
@ -422,7 +539,7 @@ impl<'a> Storage<'a> {
} = storage
{
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.set_local(local_id);
}

View File

@ -1,7 +1,6 @@
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use core::panic;
use std::fmt::Debug;
use roc_module::symbol::Symbol;
@ -10,6 +9,13 @@ use super::opcodes::{OpCode, OpCode::*};
use super::serialize::{SerialBuffer, Serialize};
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)]
pub struct LocalId(pub u32);
@ -29,6 +35,7 @@ impl Serialize for ValueType {
}
}
#[derive(PartialEq, Eq, Debug)]
pub enum BlockType {
NoResult,
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)
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
@ -73,7 +105,7 @@ impl From<u32> for Align {
}
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum VirtualMachineSymbolState {
pub enum VmSymbolState {
/// Value doesn't exist yet
NotYetPushed,
@ -113,6 +145,8 @@ macro_rules! instruction_memargs {
#[derive(Debug)]
pub struct CodeBuilder<'a> {
arena: &'a Bump,
/// The main container for the instructions
code: Vec<'a, u8>,
@ -135,8 +169,8 @@ pub struct CodeBuilder<'a> {
inner_length: Vec<'a, u8>,
/// Our simulation model of the Wasm stack machine
/// Keeps track of where Symbol values are in the VM stack
vm_stack: Vec<'a, Symbol>,
/// Nested blocks of instructions. A child block can't "see" the stack of its parent block
vm_block_stack: Vec<'a, VmBlock<'a>>,
/// Linker info to help combine the Roc module with builtin & platform modules,
/// e.g. to modify call instructions when function indices change
@ -146,13 +180,22 @@ pub struct CodeBuilder<'a> {
#[allow(clippy::new_without_default)]
impl<'a> CodeBuilder<'a> {
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 {
arena,
code: Vec::with_capacity_in(1024, arena),
insertions: Vec::with_capacity_in(32, arena),
insert_bytes: Vec::with_capacity_in(64, arena),
preamble: Vec::with_capacity_in(32, 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),
}
}
@ -167,45 +210,49 @@ impl<'a> CodeBuilder<'a> {
***********************************************************/
fn current_stack(&self) -> &Vec<'a, Symbol> {
let block = self.vm_block_stack.last().unwrap();
&block.value_stack
}
fn current_stack_mut(&mut self) -> &mut Vec<'a, Symbol> {
let block = self.vm_block_stack.last_mut().unwrap();
&mut block.value_stack
}
/// 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) -> VirtualMachineSymbolState {
let len = self.vm_stack.len();
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;
if len == 0 {
panic!(
"trying to set symbol with nothing on stack, code = {:?}",
self.code
);
}
self.vm_stack[len - 1] = sym;
VirtualMachineSymbolState::Pushed { pushed_at }
VmSymbolState::Pushed { pushed_at }
}
/// Verify if a sequence of symbols is at the top of the stack
pub fn verify_stack_match(&self, symbols: &[Symbol]) -> bool {
let current_stack = self.current_stack();
let n_symbols = symbols.len();
let stack_depth = self.vm_stack.len();
let stack_depth = current_stack.len();
if n_symbols > stack_depth {
return false;
}
let offset = stack_depth - n_symbols;
for (i, sym) in symbols.iter().enumerate() {
if self.vm_stack[offset + i] != *sym {
if current_stack[offset + i] != *sym {
return false;
}
}
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();
self.insert_bytes.push(opcode);
self.insert_bytes.push(opcode as u8);
self.insert_bytes.encode_u32(immediate);
self.insertions.push(Insertion {
@ -213,6 +260,13 @@ impl<'a> CodeBuilder<'a> {
start,
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
@ -225,41 +279,56 @@ impl<'a> CodeBuilder<'a> {
pub fn load_symbol(
&mut self,
symbol: Symbol,
vm_state: VirtualMachineSymbolState,
vm_state: VmSymbolState,
next_local_id: LocalId,
) -> Option<VirtualMachineSymbolState> {
use VirtualMachineSymbolState::*;
) -> Option<VmSymbolState> {
use VmSymbolState::*;
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 } => {
let &top = self.vm_stack.last().unwrap();
if top == symbol {
// We're lucky, the symbol is already on top of the VM stack
// No code to generate! (This reduces code size by up to 25% in tests.)
// Just let the caller know what happened
Some(Popped { pushed_at })
} else {
// Symbol is not on top of the stack. Find it.
if let Some(found_index) = self.vm_stack.iter().rposition(|&s| s == symbol) {
// Insert a local.set where the value was created
self.add_insertion(pushed_at, SETLOCAL as u8, next_local_id.0);
match self.current_stack().last() {
Some(top_symbol) if *top_symbol == symbol => {
// 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.)
// Just let the caller know what happened
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.
// Take the value out of the stack where local.set was inserted
self.vm_stack.remove(found_index);
// 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;
}
}
// Insert a local.get at the current position
// 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 {
if ENABLE_DEBUG_LOG {
println!(
"{:?} has been popped implicitly. Leaving it on the stack.",
symbol
);
}
self.add_insertion(pushed_at, TEELOCAL, next_local_id.0);
}
// Recover the value again at the current position
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
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 } => {
// 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
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
self.get_local(next_local_id);
self.vm_stack.push(symbol);
self.set_top_symbol(symbol);
// This symbol has been promoted to a Local
// 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);
}
/**********************************************************
SERIALIZE
***********************************************************/
/// Serialize all byte vectors in the right order
/// Also update relocation offsets relative to the base offset (code section body start)
pub fn serialize_with_relocs<T: SerialBuffer>(
@ -433,38 +508,78 @@ impl<'a> CodeBuilder<'a> {
/// Base method for generating instructions
/// Emits the opcode and simulates VM stack push/pop
fn inst(&mut self, opcode: OpCode, pops: usize, push: bool) {
let new_len = self.vm_stack.len() - pops as usize;
self.vm_stack.truncate(new_len);
fn inst_base(&mut self, opcode: OpCode, pops: usize, push: bool) {
let current_stack = self.current_stack_mut();
let new_len = current_stack.len() - pops as usize;
current_stack.truncate(new_len);
if push {
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
current_stack.push(Symbol::WASM_TMP);
}
self.code.push(opcode as u8);
}
fn inst_imm8(&mut self, opcode: OpCode, pops: usize, push: bool, immediate: u8) {
self.inst(opcode, pops, push);
self.code.push(immediate);
/// Plain instruction without any immediates
fn inst(&mut self, opcode: OpCode, pops: usize, push: bool) {
self.inst_base(opcode, pops, push);
log_instruction!(
"{:10}\t\t{:?}",
format!("{:?}", opcode),
self.current_stack()
);
}
// public for use in test code
pub fn inst_imm32(&mut self, opcode: OpCode, pops: usize, push: bool, immediate: u32) {
self.inst(opcode, pops, push);
/// Block instruction
fn inst_block(&mut self, opcode: OpCode, pops: usize, block_type: BlockType) {
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);
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) {
self.inst(opcode, pops, push);
self.inst_base(opcode, pops, push);
self.code.push(align as u8);
self.code.encode_u32(offset);
log_instruction!(
"{:10} {:?} {}\t{:?}",
format!("{:?}", opcode),
align,
offset,
self.current_stack()
);
}
/// Insert a linker relocation for a memory address
pub fn insert_memory_relocation(&mut self, symbol_index: u32) {
/// Insert a const reference to a memory address
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 {
type_id: OffsetRelocType::MemoryAddrLeb,
offset: self.code.len() as u32,
offset,
symbol_index,
addend: 0,
});
@ -484,22 +599,38 @@ impl<'a> CodeBuilder<'a> {
instruction_no_args!(nop, NOP, 0, false);
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) {
self.inst_imm8(LOOP, 0, false, ty.as_byte());
self.inst_block(LOOP, 0, ty);
}
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);
instruction_no_args!(end, END, 0, false);
pub fn end(&mut self) {
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) {
self.inst_imm32(BR, 0, false, levels);
}
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);
}
#[allow(dead_code)]
@ -516,30 +647,26 @@ impl<'a> CodeBuilder<'a> {
n_args: usize,
has_return_val: bool,
) {
let stack_depth = self.vm_stack.len();
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);
self.inst_base(CALL, n_args, has_return_val);
// 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;
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 {
type_id: IndexRelocType::FunctionIndexLeb,
offset,
symbol_index,
});
log_instruction!(
"{:10}\t{}\t{:?}",
format!("{:?}", CALL),
function_index,
self.current_stack()
);
}
#[allow(dead_code)]
@ -591,26 +718,44 @@ impl<'a> CodeBuilder<'a> {
instruction_memargs!(i64_store32, I64STORE32, 2, false);
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) {
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) {
self.inst(I32CONST, 0, true);
self.inst_base(I32CONST, 0, true);
self.code.encode_i32(x);
self.log_const(I32CONST, x);
}
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.log_const(I64CONST, x);
}
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.log_const(F32CONST, x);
}
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.log_const(F64CONST, x);
}
// 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 serialize;
pub use code_builder::{
Align, BlockType, CodeBuilder, LocalId, ValueType, VirtualMachineSymbolState,
};
pub use code_builder::{Align, BlockType, CodeBuilder, LocalId, ValueType, VmSymbolState};
pub use linking::{LinkingSubSection, SymInfo};
pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature, WasmModule};

View File

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

View File

@ -29,6 +29,8 @@ pub enum SectionId {
Element = 9,
Code = 10,
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,
}
@ -239,8 +241,7 @@ impl<'a> Serialize for ImportSection<'a> {
#[derive(Debug)]
pub struct FunctionSection<'a> {
/// Private. See WasmModule::add_function_signature
signature_indices: Vec<'a, u32>,
pub signature_indices: Vec<'a, u32>,
}
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
@ -658,11 +623,6 @@ impl<'a> WasmModule<'a> {
counter.serialize_and_count(buffer, &self.start);
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
let code_section_index = counter.section_index;
let code_section_body_index = self

View File

@ -18,7 +18,7 @@ roc_unify = { path = "../unify" }
roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
roc_reporting = { path = "../reporting" }
roc_reporting = { path = "../../reporting" }
morphic_lib = { path = "../../vendor/morphic_lib" }
ven_pretty = { path = "../../vendor/pretty" }
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 {
match type_annotation {
ast::TypeAnnotation::TagUnion {
tags,
ext,
final_comments: _,
} => {
ast::TypeAnnotation::TagUnion { tags, ext } => {
let mut tags_to_render: Vec<Tag> = Vec::new();
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) {
None => {
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::scope::Scope;
use roc_collections::all::{MutSet, SendMap};
use roc_module::called_via::CalledVia;
use roc_module::ident::TagName;
use roc_module::operator::CalledVia;
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable};

View File

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

View File

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

View File

@ -1,3 +1,5 @@
use crate::symbol::Symbol;
/// 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
/// into an Expr when added directly by can::builtins
@ -19,6 +21,7 @@ pub enum LowLevel {
StrFromFloat,
StrTrim,
StrTrimLeft,
StrTrimRight,
ListLen,
ListGetUnsafe,
ListSet,
@ -43,12 +46,11 @@ pub enum LowLevel {
ListKeepOks,
ListKeepErrs,
ListSortWith,
ListTakeFirst,
ListTakeLast,
ListDrop,
ListSublist,
ListDropAt,
ListSwap,
ListAny,
ListAll,
ListFindUnsafe,
DictSize,
DictEmpty,
@ -115,106 +117,6 @@ pub enum LowLevel {
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 {
() => {
ListMap
@ -230,6 +132,7 @@ macro_rules! higher_order {
| ListKeepErrs
| ListSortWith
| ListAny
| ListAll
| ListFindUnsafe
| DictWalk
};
@ -241,17 +144,13 @@ impl LowLevel {
pub fn is_higher_order(&self) -> bool {
use LowLevel::*;
match self {
first_order!() => false,
higher_order!() => true,
}
matches!(self, higher_order!())
}
pub fn function_argument_position(&self) -> usize {
use LowLevel::*;
match self {
first_order!() => unreachable!(),
ListMap => 1,
ListMap2 => 2,
ListMap3 => 3,
@ -265,8 +164,131 @@ impl LowLevel {
ListKeepErrs => 1,
ListSortWith => 1,
ListAny => 1,
ListAll => 1,
ListFindUnsafe => 1,
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
23 RET_POINTER: "#ret_pointer"
// used in wasm dev backend to mark values in the VM stack that have no other Symbol
24 WASM_ANONYMOUS_STACK_VALUE: "#wasm_anonymous_stack_value"
// used in wasm dev backend to mark temporary values in the VM stack
24 WASM_TMP: "#wasm_tmp"
// the _ used in mono when a specialized symbol is deleted
25 REMOVED_SPECIALIZATION: "#removed_specialization"
}
1 NUM: "Num" => {
0 NUM_NUM: "Num" imported // the Num.Num type alias
@ -989,12 +992,16 @@ define_builtins! {
}
2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
1 BOOL_AND: "and"
2 BOOL_OR: "or"
3 BOOL_NOT: "not"
4 BOOL_XOR: "xor"
5 BOOL_EQ: "isEq"
6 BOOL_NEQ: "isNotEq"
1 BOOL_FALSE: "False" imported // Bool.Bool = [ False, True ]
// NB: not strictly needed; used for finding global tag names in error suggestions
2 BOOL_TRUE: "True" imported // Bool.Bool = [ False, True ]
// NB: not strictly needed; used for finding global tag names in error suggestions
3 BOOL_AND: "and"
4 BOOL_OR: "or"
5 BOOL_NOT: "not"
6 BOOL_XOR: "xor"
7 BOOL_EQ: "isEq"
8 BOOL_NEQ: "isNotEq"
}
3 STR: "Str" => {
0 STR_STR: "Str" imported // the Str.Str type alias
@ -1019,6 +1026,7 @@ define_builtins! {
19 STR_REPEAT: "repeat"
20 STR_TRIM: "trim"
21 STR_TRIM_LEFT: "trimLeft"
22 STR_TRIM_RIGHT: "trimRight"
}
4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias
@ -1071,14 +1079,24 @@ define_builtins! {
47 LIST_FIND: "find"
48 LIST_FIND_RESULT: "#find_result" // symbol used in the definition of List.find
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" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
1 RESULT_MAP: "map"
2 RESULT_MAP_ERR: "mapErr"
3 RESULT_WITH_DEFAULT: "withDefault"
4 RESULT_AFTER: "after"
5 RESULT_IS_OK: "isOk"
1 RESULT_OK: "Ok" imported // Result.Result a e = [ Ok a, Err e ]
// NB: not strictly needed; used for finding global tag names in error suggestions
2 RESULT_ERR: "Err" imported // Result.Result a e = [ Ok a, Err e ]
// NB: not strictly needed; used for finding global tag names in error suggestions
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" => {
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"] }
hashbrown = { version = "0.11.2", features = [ "bumpalo" ] }
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 crate::ir::{
Call, CallType, Expr, HostExposedLayouts, ListLiteralElement, Literal, ModifyRc, OptLevel,
Proc, Stmt,
Call, CallType, Expr, HigherOrderLowLevel, HostExposedLayouts, ListLiteralElement, Literal,
ModifyRc, OptLevel, Proc, Stmt,
};
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";
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;
@ -53,7 +53,7 @@ impl TagUnionId {
pub fn func_name_bytes_help<'a, I>(
symbol: Symbol,
argument_layouts: I,
return_layout: Layout<'a>,
return_layout: &Layout<'a>,
) -> [u8; SIZE]
where
I: Iterator<Item = Layout<'a>>,
@ -162,13 +162,13 @@ where
match layout {
RawFunctionLayout::Function(_, _, _) => {
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));
}
RawFunctionLayout::ZeroArgumentThunk(_) => {
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));
}
@ -196,7 +196,7 @@ where
let roc_main_bytes = func_name_bytes_help(
entry_point.symbol,
entry_point.layout.arguments.iter().copied(),
entry_point.layout.result,
&entry_point.layout.result,
);
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 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 module = MOD_APP;
builder.add_call(block, spec_var, module, name, arg_value_id)
@ -688,7 +688,7 @@ fn call_spec(
*update_mode,
call.arguments,
),
HigherOrderLowLevel {
HigherOrder(HigherOrderLowLevel {
specialization_id,
closure_env_layout,
update_mode,
@ -698,7 +698,7 @@ fn call_spec(
function_name,
function_env,
..
} => {
}) => {
use crate::low_level::HigherOrder::*;
let array = specialization_id.to_bytes();
@ -708,7 +708,7 @@ fn call_spec(
let update_mode_var = UpdateModeVar(&mode);
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 module = MOD_APP;
@ -1093,6 +1093,25 @@ fn call_spec(
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 } => {
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::NullableWrapped { nullable_id, .. } => {
if *tag_id == *nullable_id as u8 {
if *tag_id == *nullable_id as _ {
data_id
} else {
builder.add_make_tuple(block, &[cell_id, data_id])?
}
}
UnionLayout::NullableUnwrapped { nullable_id, .. } => {
if *tag_id == *nullable_id as u8 {
if *tag_id == *nullable_id as _ {
data_id
} else {
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 bumpalo::collections::Vec;
use bumpalo::Bump;
@ -560,7 +560,7 @@ impl<'a> BorrowInfState<'a> {
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
let ps = param_map
@ -593,14 +593,14 @@ impl<'a> BorrowInfState<'a> {
self.own_args_using_bools(arguments, ps);
}
HigherOrderLowLevel {
HigherOrder(HigherOrderLowLevel {
op,
arg_layouts,
ret_layout,
function_name,
function_env,
..
} => {
}) => {
use crate::low_level::HigherOrder::*;
let closure_layout = ProcLayout {
@ -619,6 +619,7 @@ impl<'a> BorrowInfState<'a> {
| ListKeepOks { xs }
| ListKeepErrs { xs }
| ListAny { xs }
| ListAll { xs }
| ListFindUnsafe { xs } => {
// own the list if the function wants to own the element
if !function_ps[0].borrow {
@ -795,7 +796,7 @@ impl<'a> BorrowInfState<'a> {
Stmt::Ret(z),
) = (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 {
// 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]),
StrTrim => arena.alloc_slice_copy(&[owned]),
StrTrimLeft => arena.alloc_slice_copy(&[owned]),
StrTrimRight => arena.alloc_slice_copy(&[owned]),
StrSplit => arena.alloc_slice_copy(&[borrowed, borrowed]),
ListSingle => arena.alloc_slice_copy(&[irrelevant]),
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]),
ListMap3 => arena.alloc_slice_copy(&[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])
}
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)
// List.append should own its first argument
ListAppend => arena.alloc_slice_copy(&[owned, owned]),
ListTakeFirst => arena.alloc_slice_copy(&[owned, irrelevant]),
ListTakeLast => arena.alloc_slice_copy(&[owned, irrelevant]),
ListDrop => arena.alloc_slice_copy(&[owned, irrelevant]),
ListSublist => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
ListDropAt => arena.alloc_slice_copy(&[owned, 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 { .. } => {}
LowLevel { .. } => {}
HigherOrderLowLevel { .. } => {}
HigherOrder(_) => {}
}
}

View File

@ -3,7 +3,7 @@ use crate::ir::{
BranchInfo, DestructType, Env, Expr, FloatPrecision, IntPrecision, JoinPointId, Literal, Param,
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_module::ident::TagName;
use roc_module::low_level::LowLevel;
@ -81,7 +81,7 @@ enum GuardedTest<'a> {
#[allow(clippy::enum_variant_names)]
enum Test<'a> {
IsCtor {
tag_id: u8,
tag_id: TagIdIntType,
tag_name: TagName,
union: crate::exhaustive::Union,
arguments: Vec<(Pattern<'a>, Layout<'a>)>,
@ -92,7 +92,7 @@ enum Test<'a> {
IsStr(Box<str>),
IsBit(bool),
IsByte {
tag_id: u8,
tag_id: TagIdIntType,
num_alts: usize,
},
}
@ -419,10 +419,9 @@ fn gather_edges<'a>(
let check = guarded_tests_are_complete(&relevant_tests);
// TODO remove clone
let all_edges = relevant_tests
.into_iter()
.map(|t| edges_for(path, branches.clone(), t))
.map(|t| edges_for(path, &branches, t))
.collect();
let fallbacks = if check {
@ -562,7 +561,7 @@ fn test_at_path<'a>(
},
BitLiteral { value, .. } => IsBit(*value),
EnumLiteral { tag_id, union, .. } => IsByte {
tag_id: *tag_id,
tag_id: *tag_id as _,
num_alts: union.alternatives.len(),
},
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?
fn edges_for<'a>(
path: &[PathInstruction],
branches: Vec<Branch<'a>>,
branches: &[Branch<'a>],
test: GuardedTest<'a>,
) -> (GuardedTest<'a>, Vec<Branch<'a>>) {
let mut new_branches = Vec::new();
@ -864,7 +863,7 @@ fn to_relevant_branch_help<'a>(
EnumLiteral { tag_id, .. } => match test {
IsByte {
tag_id: test_id, ..
} if tag_id == *test_id => {
} if tag_id == *test_id as _ => {
start.extend(end);
Some(Branch {
goal: branch.goal,
@ -1055,9 +1054,19 @@ fn small_defaults(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)]
@ -1171,7 +1180,7 @@ pub fn optimize_when<'a>(
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct PathInstruction {
index: u64,
tag_id: u8,
tag_id: TagIdIntType,
}
fn path_to_expr_help<'a>(
@ -1198,7 +1207,7 @@ fn path_to_expr_help<'a>(
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();
stores.push((symbol, inner_layout, inner_expr));
@ -1317,7 +1326,9 @@ fn test_to_equality<'a>(
Test::IsByte {
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();
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int8), lhs));
@ -1504,13 +1515,13 @@ enum ConstructorKnown<'a> {
Both {
scrutinee: Symbol,
layout: Layout<'a>,
pass: u8,
fail: u8,
pass: TagIdIntType,
fail: TagIdIntType,
},
OnlyPass {
scrutinee: Symbol,
layout: Layout<'a>,
tag_id: u8,
tag_id: TagIdIntType,
},
Neither,
}
@ -1530,7 +1541,7 @@ impl<'a> ConstructorKnown<'a> {
layout: *cond_layout,
scrutinee: cond_symbol,
pass: *tag_id,
fail: (*tag_id == 0) as u8,
fail: (*tag_id == 0) as _,
}
} else {
ConstructorKnown::OnlyPass {
@ -1774,7 +1785,7 @@ fn decide_to_branching<'a>(
BranchInfo::Constructor {
scrutinee: inner_cond_symbol,
layout: inner_cond_layout,
tag_id: tag_id_sum as u8,
tag_id: tag_id_sum as u8 as _,
}
} else {
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_module::ident::{Lowercase, TagName};
use roc_region::all::{Located, Region};
@ -35,7 +35,7 @@ pub enum RenderAs {
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
pub struct TagId(pub u8);
pub struct TagId(pub TagIdIntType);
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Ctor {
@ -71,8 +71,12 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern {
StrLiteral(v) => Literal(Literal::Str(v.clone())),
// To make sure these are exhaustive, we have to "fake" a union here
BitLiteral { value, union, .. } => Ctor(union.clone(), TagId(*value as u8), vec![]),
EnumLiteral { tag_id, union, .. } => Ctor(union.clone(), TagId(*tag_id), vec![]),
BitLiteral { value, union, .. } => {
Ctor(union.clone(), TagId(*value as TagIdIntType), vec![])
}
EnumLiteral { tag_id, union, .. } => {
Ctor(union.clone(), TagId(*tag_id as TagIdIntType), vec![])
}
Underscore => Anything,
Identifier(_) => Anything,

View File

@ -1,5 +1,7 @@
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 bumpalo::collections::Vec;
use bumpalo::Bump;
@ -463,7 +465,7 @@ impl<'a> Context<'a> {
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
HigherOrderLowLevel {
HigherOrder(HigherOrderLowLevel {
op,
closure_env_layout,
specialization_id,
@ -473,7 +475,7 @@ impl<'a> Context<'a> {
function_name,
function_env,
..
} => {
}) => {
// setup
use crate::low_level::HigherOrder::*;
@ -481,7 +483,7 @@ impl<'a> Context<'a> {
($borrows:expr) => {
Expr::Call(crate::ir::Call {
call_type: if let Some(OWNED) = $borrows.map(|p| p.borrow) {
HigherOrderLowLevel {
let higher_order = HigherOrderLowLevel {
op: *op,
closure_env_layout: *closure_env_layout,
function_owns_closure_data: true,
@ -491,7 +493,9 @@ impl<'a> Context<'a> {
function_env: *function_env,
arg_layouts,
ret_layout: *ret_layout,
}
};
CallType::HigherOrder(self.arena.alloc(higher_order))
} else {
call_type
},
@ -532,6 +536,7 @@ impl<'a> Context<'a> {
| ListKeepOks { xs }
| ListKeepErrs { xs }
| ListAny { xs }
| ListAll { xs }
| ListFindUnsafe { xs } => {
let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA];
@ -663,7 +668,7 @@ impl<'a> Context<'a> {
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
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 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;
/// 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) ]`
/// see also: https://youtu.be/ip92VMpf_-A?t=164
NullableWrapped {
nullable_id: i64,
nullable_id: u16,
other_tags: &'a [&'a [Layout<'a>]],
},
/// 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 {
UnionLayout::NonRecursive(tag_layouts) => {
let field_layouts = tag_layouts[tag_id as usize];
@ -265,9 +274,9 @@ impl<'a> UnionLayout<'a> {
nullable_id,
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
} else {
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 {
UnionLayout::NonRecursive(_)
| UnionLayout::NonNullableUnwrapped(_)
| 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),
}
}
@ -431,7 +440,7 @@ pub enum ClosureRepresentation<'a> {
Union {
alphabetic_order_fields: &'a [Layout<'a>],
tag_name: TagName,
tag_id: u8,
tag_id: TagIdIntType,
union_layout: UnionLayout<'a>,
},
/// The closure is represented as a struct. The layouts are sorted
@ -489,7 +498,7 @@ impl<'a> LambdaSet<'a> {
.unwrap();
ClosureRepresentation::Union {
tag_id: index as u8,
tag_id: index as TagIdIntType,
alphabetic_order_fields: fields,
tag_name: TagName::Closure(function_symbol),
union_layout: *union,
@ -863,6 +872,16 @@ impl<'a> Layout<'a> {
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 {
let width = self.stack_size_without_alignment(pointer_size);
let alignment = self.alignment_bytes(pointer_size);
@ -941,16 +960,16 @@ impl<'a> Layout<'a> {
})
.max();
let tag_id_builtin = variant.tag_id_builtin();
match max_alignment {
Some(align) => {
let tag_id_builtin = variant.tag_id_builtin();
round_up_to_alignment(
align,
tag_id_builtin.alignment_bytes(pointer_size),
)
Some(align) => round_up_to_alignment(
align.max(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(_)
@ -1475,7 +1494,7 @@ fn layout_from_flat_type<'a>(
if GENERATE_NULLABLE {
for (index, (_name, variables)) in tags_vec.iter().enumerate() {
if variables.is_empty() {
nullable = Some(index as i64);
nullable = Some(index as TagIdIntType);
break;
}
}
@ -1483,7 +1502,7 @@ fn layout_from_flat_type<'a>(
env.insert_seen(rec_var);
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
continue;
}
@ -1633,7 +1652,7 @@ pub enum WrappedVariant<'a> {
sorted_tag_layouts: Vec<'a, (TagName, &'a [Layout<'a>])>,
},
NullableWrapped {
nullable_id: i64,
nullable_id: TagIdIntType,
nullable_name: TagName,
sorted_tag_layouts: Vec<'a, (TagName, &'a [Layout<'a>])>,
},
@ -1650,7 +1669,7 @@ pub enum 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::*;
match self {
@ -1662,7 +1681,7 @@ impl<'a> WrappedVariant<'a> {
.expect("tag name is not in its own type");
debug_assert!(tag_id < 256);
(tag_id as u8, *argument_layouts)
(tag_id as TagIdIntType, *argument_layouts)
}
NullableWrapped {
nullable_id,
@ -1672,7 +1691,7 @@ impl<'a> WrappedVariant<'a> {
// assumption: the nullable_name is not included in sorted_tag_layouts
if tag_name == nullable_name {
(*nullable_id as u8, &[] as &[_])
(*nullable_id as TagIdIntType, &[] as &[_])
} else {
let (mut tag_id, (_, argument_layouts)) = sorted_tag_layouts
.iter()
@ -1685,7 +1704,7 @@ impl<'a> WrappedVariant<'a> {
}
debug_assert!(tag_id < 256);
(tag_id as u8, *argument_layouts)
(tag_id as TagIdIntType, *argument_layouts)
}
}
NullableUnwrapped {
@ -1695,11 +1714,11 @@ impl<'a> WrappedVariant<'a> {
other_fields,
} => {
if tag_name == nullable_name {
(*nullable_id as u8, &[] as &[_])
(*nullable_id as TagIdIntType, &[] as &[_])
} else {
debug_assert_eq!(other_name, tag_name);
(!*nullable_id as u8, *other_fields)
(!*nullable_id as TagIdIntType, *other_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 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
let is_recursive = opt_rec_var.is_some();
if is_recursive && GENERATE_NULLABLE {
for (index, (name, variables)) in tags_vec.iter().enumerate() {
if variables.is_empty() {
nullable = Some((index as i64, (*name).clone()));
nullable = Some((index as TagIdIntType, (*name).clone()));
break;
}
}
@ -2073,7 +2092,7 @@ pub fn union_sorted_tags_help<'a>(
if is_recursive && GENERATE_NULLABLE {
for (index, (name, variables)) in tags_vec.iter().enumerate() {
if variables.is_empty() {
nullable = Some((index as i64, name.clone()));
nullable = Some((index as TagIdIntType, name.clone()));
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 {
xs: Symbol,
},
ListAll {
xs: Symbol,
},
ListFindUnsafe {
xs: Symbol,
},
@ -77,6 +80,7 @@ impl HigherOrder {
HigherOrder::ListFindUnsafe { .. } => 1,
HigherOrder::DictWalk { .. } => 2,
HigherOrder::ListAny { .. } => 1,
HigherOrder::ListAll { .. } => 1,
}
}
}
@ -100,9 +104,7 @@ enum FirstOrder {
ListLen,
ListGetUnsafe,
ListSet,
ListTakeFirst,
ListTakeLast,
ListDrop,
ListSublist,
ListDropAt,
ListSingle,
ListRepeat,

View File

@ -1,6 +1,6 @@
use crate::inc_dec::{collect_stmt, occurring_variables_expr, JPLiveVarMap, LiveVarSet};
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::Bump;
use roc_collections::all::MutSet;
@ -27,17 +27,17 @@ pub fn insert_reset_reuse<'a, 'i>(
#[derive(Debug)]
struct CtorInfo<'a> {
id: u8,
id: TagIdIntType,
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 {
return false;
}
// 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
!tag_layout.tag_is_null(tag_id)

View File

@ -17,3 +17,5 @@ pretty_assertions = "1.0.0"
indoc = "1.0.3"
quickcheck = "1.0.3"
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::ident::Ident;
use bumpalo::collections::String;
use bumpalo::collections::{String, Vec};
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};
#[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)]
pub enum Module<'a> {
Interface { header: InterfaceHeader<'a> },
@ -115,21 +95,14 @@ pub enum Expr<'a> {
AccessorFunction(&'a str),
// Collection Literals
List {
items: &'a [&'a Loc<Expr<'a>>],
final_comments: &'a [CommentOrNewline<'a>],
},
List(Collection<'a, &'a Loc<Expr<'a>>>),
RecordUpdate {
update: &'a Loc<Expr<'a>>,
fields: &'a [Loc<AssignedField<'a, Expr<'a>>>],
final_comments: &'a &'a [CommentOrNewline<'a>],
fields: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
},
Record {
fields: &'a [Loc<AssignedField<'a, Expr<'a>>>],
final_comments: &'a [CommentOrNewline<'a>],
},
Record(Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>),
// Lookups
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`.
/// This is None if it's a closed record annotation like `{ name: Str }`.
ext: Option<&'a Loc<TypeAnnotation<'a>>>,
// final_comments: &'a [CommentOrNewline<'a>],
},
/// A tag union, e.g. `[
TagUnion {
tags: &'a [Loc<Tag<'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]`.
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 *)
Wildcard,
@ -357,10 +331,11 @@ pub enum Pattern<'a> {
GlobalTag(&'a str),
PrivateTag(&'a str),
Apply(&'a Loc<Pattern<'a>>, &'a [Loc<Pattern<'a>>]),
/// This is Loc<Pattern> rather than Loc<str> so we can record comments
/// around the destructured names, e.g. { x ### x does stuff ###, y }
/// 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 } -> ...
/// 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> {
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::ident::{lowercase_ident, parse_ident, Ident};
use crate::keyword;
use crate::parser::{
self, backtrackable, optional, sep_by1, sep_by1_e, specialize, specialize_ref, then,
trailing_sep_by0, word1, word2, EExpr, EInParens, ELambda, EPattern, ERecord, EString, Either,
Expect, If, List, Number, ParseResult, Parser, State, Type, When,
trailing_sep_by0, word1, word2, EExpect, EExpr, EIf, EInParens, ELambda, EList, ENumber,
EPattern, ERecord, EString, EType, EWhen, Either, ParseResult, Parser, State,
};
use crate::pattern::loc_closure_param;
use crate::type_annotation;
use bumpalo::collections::Vec;
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 crate::parser::Progress::{self, *};
@ -801,7 +803,7 @@ fn parse_defs_end<'a>(
Err((NoProgress, _, _)) => {
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)
{
Err((_, _, _)) => {
@ -861,8 +863,8 @@ fn parse_defs_end<'a>(
space0_before_e(
type_annotation::located_help(min_indent + 1),
min_indent + 1,
Type::TSpace,
Type::TIndentStart,
EType::TSpace,
EType::TIndentStart,
),
)
.parse(arena, state)?;
@ -1099,8 +1101,8 @@ fn parse_expr_operator<'a>(
space0_before_e(
type_annotation::located_help(indented_more),
min_indent,
Type::TSpace,
Type::TIndentStart,
EType::TSpace,
EType::TIndentStart,
),
)
.parse(arena, state)?;
@ -1126,8 +1128,8 @@ fn parse_expr_operator<'a>(
space0_before_e(
type_annotation::located_help(indented_more),
min_indent,
Type::TSpace,
Type::TIndentStart,
EType::TSpace,
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::Record {
fields,
final_comments: _,
} => {
let mut loc_patterns = Vec::with_capacity_in(fields.len(), arena);
for loc_assigned_field in fields.iter() {
Expr::Record(fields) => {
let patterns = fields.map_items_result(arena, |loc_assigned_field| {
let region = loc_assigned_field.region;
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(loc_patterns.into_bump_slice()))
Ok(Pattern::RecordDestructure(patterns))
}
Expr::Float(string) => Ok(Pattern::FloatLiteral(string)),
@ -1651,21 +1647,21 @@ mod when {
pub fn expr_help<'a>(
min_indent: u16,
options: ExprParseOptions,
) -> impl Parser<'a, Expr<'a>, When<'a>> {
) -> impl Parser<'a, Expr<'a>, EWhen<'a>> {
then(
and!(
when_with_indent(),
skip_second!(
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)
}),
min_indent,
When::Space,
When::IndentCondition,
When::IndentIs,
EWhen::Space,
EWhen::IndentCondition,
EWhen::IndentIs,
),
parser::keyword_e(keyword::IS, When::Is)
parser::keyword_e(keyword::IS, EWhen::Is)
)
),
move |arena, state, progress, (case_indent, loc_condition)| {
@ -1673,7 +1669,7 @@ mod when {
return Err((
progress,
// TODO maybe pass case_indent here?
When::PatternAlignment(5, state.line, state.column),
EWhen::PatternAlignment(5, state.line, state.column),
state,
));
}
@ -1693,9 +1689,9 @@ mod when {
}
/// 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>| {
parser::keyword_e(keyword::WHEN, When::When)
parser::keyword_e(keyword::WHEN, EWhen::When)
.parse(arena, state)
.map(|(progress, (), state)| (progress, state.indent_col, state))
}
@ -1704,7 +1700,7 @@ mod when {
fn branches<'a>(
min_indent: u16,
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>| {
let when_indent = state.indent_col;
@ -1741,7 +1737,7 @@ mod when {
let indent = pattern_indent_level - indent_col;
Err((
MadeProgress,
When::PatternAlignment(indent, state.line, state.column),
EWhen::PatternAlignment(indent, state.line, state.column),
state,
))
}
@ -1799,7 +1795,7 @@ mod when {
(Col, Vec<'a, Located<Pattern<'a>>>),
Option<Located<Expr<'a>>>,
),
When<'a>,
EWhen<'a>,
> {
let options = ExprParseOptions {
check_for_arrow: false,
@ -1810,16 +1806,16 @@ mod when {
one_of![
map!(
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
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)
}),
min_indent,
When::Space,
When::IndentIfGuard,
When::IndentArrow,
EWhen::Space,
EWhen::IndentIfGuard,
EWhen::IndentArrow,
)
),
Some
@ -1831,17 +1827,17 @@ mod when {
fn branch_single_alternative<'a>(
min_indent: u16,
) -> impl Parser<'a, Located<Pattern<'a>>, When<'a>> {
) -> impl Parser<'a, Located<Pattern<'a>>, EWhen<'a>> {
move |arena, 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)?;
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,
When::Space,
When::IndentPattern,
EWhen::Space,
EWhen::IndentPattern,
)
.parse(arena, state)?;
@ -1862,12 +1858,12 @@ mod when {
fn branch_alternatives_help<'a>(
min_indent: 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>| {
let initial = state;
// 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((NoProgress, fail, _)) => Err((NoProgress, fail, initial)),
Ok((_progress, spaces, state)) => {
@ -1876,7 +1872,7 @@ mod when {
// this branch is indented too much
Err((
NoProgress,
When::IndentPattern(state.line, state.column),
EWhen::IndentPattern(state.line, state.column),
initial,
))
}
@ -1884,7 +1880,7 @@ mod when {
let indent = wanted - state.column;
Err((
NoProgress,
When::PatternAlignment(indent, state.line, state.column),
EWhen::PatternAlignment(indent, state.line, state.column),
initial,
))
}
@ -1896,7 +1892,7 @@ mod when {
let pattern_indent_col = state.column;
let parser = sep_by1(
word1(b'|', When::Bar),
word1(b'|', EWhen::Bar),
branch_single_alternative(pattern_indent + 1),
);
@ -1930,16 +1926,16 @@ mod when {
}
/// 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!(
word2(b'-', b'>', When::Arrow),
word2(b'-', b'>', EWhen::Arrow),
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,
When::Space,
When::IndentBranch,
EWhen::Space,
EWhen::IndentBranch,
)
)
}
@ -1947,38 +1943,38 @@ mod when {
fn if_branch<'a>(
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| {
// NOTE: only parse spaces before the expression
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)
}),
min_indent,
If::Space,
If::IndentCondition,
If::IndentThenToken,
EIf::Space,
EIf::IndentCondition,
EIf::IndentThenToken,
)
.parse(arena, state)
.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)
.map_err(|(_, f, s)| (MadeProgress, f, s))?;
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)
}),
min_indent,
If::Space,
If::IndentThenBranch,
If::IndentElseToken,
EIf::Space,
EIf::IndentThenBranch,
EIf::IndentElseToken,
)
.parse(arena, state)
.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)
.map_err(|(_, f, s)| (MadeProgress, f, s))?;
@ -1989,26 +1985,26 @@ fn if_branch<'a>(
fn expect_help<'a>(
min_indent: u16,
options: ExprParseOptions,
) -> impl Parser<'a, Expr<'a>, Expect<'a>> {
) -> impl Parser<'a, Expr<'a>, EExpect<'a>> {
move |arena: &'a Bump, state: State<'a>| {
let start = state.get_position();
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(
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)
}),
start.col + 1,
Expect::Space,
Expect::IndentCondition,
EExpect::Space,
EExpect::IndentCondition,
)
.parse(arena, state)
.map_err(|(_, f, s)| (MadeProgress, f, s))?;
let parse_cont = specialize_ref(
Expect::Continuation,
EExpect::Continuation,
space0_before_e(
move |a, s| parse_loc_expr(min_indent, a, s),
min_indent,
@ -2028,9 +2024,9 @@ fn expect_help<'a>(
fn if_expr_help<'a>(
min_indent: u16,
options: ExprParseOptions,
) -> impl Parser<'a, Expr<'a>, If<'a>> {
) -> impl Parser<'a, Expr<'a>, EIf<'a>> {
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);
@ -2044,8 +2040,8 @@ fn if_expr_help<'a>(
// try to parse another `if`
// NOTE this drops spaces between the `else` and the `if`
let optional_if = and!(
backtrackable(space0_e(min_indent, If::Space, If::IndentIf)),
parser::keyword_e(keyword::IF, If::If)
backtrackable(space0_e(min_indent, EIf::Space, EIf::IndentIf)),
parser::keyword_e(keyword::IF, EIf::If)
);
match optional_if.parse(arena, state) {
@ -2058,12 +2054,12 @@ fn if_expr_help<'a>(
};
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)
}),
min_indent,
If::Space,
If::IndentElseBranch,
EIf::Space,
EIf::IndentElseBranch,
)
.parse(arena, state_final_else)
.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| {
let (_, elements, state) = collection_trailing_sep_e!(
word1(b'[', List::Open),
specialize_ref(List::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::Open),
specialize_ref(
EList::Expr,
move |a, s| parse_loc_expr_no_multi_backpassing(min_indent, a, s)
),
word1(b',', EList::End),
word1(b']', EList::End),
min_indent,
List::Open,
List::Space,
List::IndentEnd,
EList::Open,
EList::Space,
EList::IndentEnd,
Expr::SpaceBefore
)
.parse(arena, state)?;
let mut allocated = Vec::with_capacity_in(elements.items.len(), arena);
for parsed_elem in elements.items {
allocated.push(parsed_elem);
}
let expr = Expr::List {
items: allocated.into_bump_slice(),
final_comments: elements.final_comments,
};
let elements = elements.ptrify_items(arena);
let expr = Expr::List(elements);
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 {
Some(update) => Expr::RecordUpdate {
update: &*arena.alloc(update),
fields: loc_assigned_fields_with_comments.value.0.into_bump_slice(),
final_comments: arena.alloc(loc_assigned_fields_with_comments.value.1),
},
None => Expr::Record {
fields: loc_assigned_fields_with_comments.value.0.into_bump_slice(),
final_comments: loc_assigned_fields_with_comments.value.1,
fields: Collection::with_items_and_comments(
arena,
loc_assigned_fields_with_comments.value.0.into_bump_slice(),
arena.alloc(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`
@ -2341,7 +2334,7 @@ fn string_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EString<'a>> {
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!(
crate::number_literal::positive_number_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| {
use crate::number_literal::NumLiteral::*;

View File

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

View File

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

View File

@ -1,5 +1,5 @@
use crate::ast::Base;
use crate::parser::{Number, ParseResult, Parser, Progress, State};
use crate::parser::{ENumber, ParseResult, Parser, Progress, State};
pub enum NumLiteral<'a> {
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>| {
match state.bytes.get(0) {
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
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>| {
match state.bytes.get(0) {
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
Err((Progress::NoProgress, Number::End, state))
Err((Progress::NoProgress, ENumber::End, state))
}
}
}
@ -47,7 +47,7 @@ fn parse_number_base<'a>(
is_negated: bool,
bytes: &'a [u8],
state: State<'a>,
) -> ParseResult<'a, NumLiteral<'a>, Number> {
) -> ParseResult<'a, NumLiteral<'a>, ENumber> {
match bytes.get(0..2) {
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),
@ -61,13 +61,13 @@ fn chomp_number_base<'a>(
is_negative: bool,
bytes: &'a [u8],
state: State<'a>,
) -> ParseResult<'a, NumLiteral<'a>, Number> {
) -> ParseResult<'a, NumLiteral<'a>, ENumber> {
let (_is_float, chomped) = chomp_number(bytes);
let string = unsafe { std::str::from_utf8_unchecked(&bytes[..chomped]) };
let new = state.advance_without_indenting_ee(chomped + 2 + is_negative as usize, |_, _| {
Number::LineTooLong
ENumber::LineTooLong
})?;
Ok((
@ -85,24 +85,25 @@ fn chomp_number_dec<'a>(
is_negative: bool,
bytes: &'a [u8],
state: State<'a>,
) -> ParseResult<'a, NumLiteral<'a>, Number> {
) -> ParseResult<'a, NumLiteral<'a>, ENumber> {
let (is_float, chomped) = chomp_number(bytes);
if is_negative && chomped == 0 {
// 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() {
// 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 =
unsafe { std::str::from_utf8_unchecked(&state.bytes[0..chomped + is_negative as usize]) };
let new = state
.advance_without_indenting_ee(chomped + is_negative as usize, |_, _| Number::LineTooLong)?;
let new = state.advance_without_indenting_ee(chomped + is_negative as usize, |_, _| {
ENumber::LineTooLong
})?;
Ok((
Progress::MadeProgress,

View File

@ -186,7 +186,7 @@ pub enum SyntaxError<'a> {
ArgumentsBeforeEquals(Region),
NotYetImplemented(String),
Todo,
Type(Type<'a>),
Type(EType<'a>),
Pattern(EPattern<'a>),
Expr(EExpr<'a>),
Header(EHeader<'a>),
@ -214,6 +214,7 @@ pub enum EHeader<'a> {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EProvides<'a> {
Provides(Row, Col),
Open(Row, Col),
To(Row, Col),
IndentProvides(Row, Col),
IndentTo(Row, Col),
@ -230,6 +231,7 @@ pub enum EProvides<'a> {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EExposes {
Exposes(Row, Col),
Open(Row, Col),
IndentExposes(Row, Col),
IndentListStart(Row, Col),
IndentListEnd(Row, Col),
@ -242,6 +244,7 @@ pub enum EExposes {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ERequires<'a> {
Requires(Row, Col),
Open(Row, Col),
IndentRequires(Row, Col),
IndentListStart(Row, Col),
IndentListEnd(Row, Col),
@ -258,7 +261,7 @@ pub enum ETypedIdent<'a> {
HasType(Row, Col),
IndentHasType(Row, Col),
Name(Row, Col),
Type(Type<'a>, Row, Col),
Type(EType<'a>, Row, Col),
IndentType(Row, Col),
Identifier(Row, Col),
}
@ -302,6 +305,7 @@ pub enum EPackageEntry<'a> {
pub enum EEffects<'a> {
Space(BadInputError, Row, Col),
Effects(Row, Col),
Open(Row, Col),
IndentEffects(Row, Col),
ListStart(Row, Col),
ListEnd(Row, Col),
@ -315,6 +319,7 @@ pub enum EEffects<'a> {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EImports {
Open(Row, Col),
Imports(Row, Col),
IndentImports(Row, Col),
IndentListStart(Row, Col),
@ -394,7 +399,7 @@ pub enum EExpr<'a> {
DefMissingFinalExpr(Row, Col),
DefMissingFinalExpr2(&'a EExpr<'a>, Row, Col),
Type(Type<'a>, Row, Col),
Type(EType<'a>, Row, Col),
Pattern(&'a EPattern<'a>, Row, Col),
IndentDefBody(Row, Col),
IndentEquals(Row, Col),
@ -409,10 +414,10 @@ pub enum EExpr<'a> {
BackpassComma(Row, Col),
BackpassArrow(Row, Col),
When(When<'a>, Row, Col),
If(If<'a>, Row, Col),
When(EWhen<'a>, Row, Col),
If(EIf<'a>, Row, Col),
Expect(Expect<'a>, Row, Col),
Expect(EExpect<'a>, Row, Col),
Lambda(ELambda<'a>, Row, Col),
Underscore(Row, Col),
@ -420,15 +425,15 @@ pub enum EExpr<'a> {
InParens(EInParens<'a>, Row, Col),
Record(ERecord<'a>, Row, Col),
Str(EString<'a>, Row, Col),
Number(Number, Row, Col),
List(List<'a>, Row, Col),
Number(ENumber, Row, Col),
List(EList<'a>, Row, Col),
IndentStart(Row, Col),
IndentEnd(Row, Col),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Number {
pub enum ENumber {
End,
LineTooLong,
}
@ -502,7 +507,7 @@ pub enum ELambda<'a> {
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum List<'a> {
pub enum EList<'a> {
Open(Row, Col),
End(Row, Col),
Space(BadInputError, Row, Col),
@ -514,7 +519,7 @@ pub enum List<'a> {
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum When<'a> {
pub enum EWhen<'a> {
Space(BadInputError, Row, Col),
When(Row, Col),
Is(Row, Col),
@ -538,7 +543,7 @@ pub enum When<'a> {
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum If<'a> {
pub enum EIf<'a> {
Space(BadInputError, Row, Col),
If(Row, Col),
Then(Row, Col),
@ -557,7 +562,7 @@ pub enum If<'a> {
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expect<'a> {
pub enum EExpect<'a> {
Space(BadInputError, Row, Col),
Expect(Row, Col),
Condition(&'a EExpr<'a>, Row, Col),
@ -575,7 +580,7 @@ pub enum EPattern<'a> {
Space(BadInputError, Row, Col),
PInParens(PInParens<'a>, Row, Col),
NumLiteral(Number, Row, Col),
NumLiteral(ENumber, Row, Col),
IndentStart(Row, Col),
IndentEnd(Row, Col),
@ -614,13 +619,14 @@ pub enum PInParens<'a> {
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Type<'a> {
TRecord(TRecord<'a>, Row, Col),
TTagUnion(TTagUnion<'a>, Row, Col),
TInParens(TInParens<'a>, Row, Col),
TApply(TApply, Row, Col),
pub enum EType<'a> {
TRecord(ETypeRecord<'a>, Row, Col),
TTagUnion(ETypeTagUnion<'a>, Row, Col),
TInParens(ETypeInParens<'a>, Row, Col),
TApply(ETypeApply, Row, Col),
TBadTypeVariable(Row, Col),
TWildcard(Row, Col),
TInferred(Row, Col),
///
TStart(Row, Col),
TEnd(Row, Col),
@ -633,14 +639,14 @@ pub enum Type<'a> {
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TRecord<'a> {
pub enum ETypeRecord<'a> {
End(Row, Col),
Open(Row, Col),
Field(Row, Col),
Colon(Row, Col),
Optional(Row, Col),
Type(&'a Type<'a>, Row, Col),
Type(&'a EType<'a>, Row, Col),
Space(BadInputError, Row, Col),
@ -651,11 +657,11 @@ pub enum TRecord<'a> {
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TTagUnion<'a> {
pub enum ETypeTagUnion<'a> {
End(Row, Col),
Open(Row, Col),
Type(&'a Type<'a>, Row, Col),
Type(&'a EType<'a>, Row, Col),
Space(BadInputError, Row, Col),
@ -664,11 +670,11 @@ pub enum TTagUnion<'a> {
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TInParens<'a> {
pub enum ETypeInParens<'a> {
End(Row, Col),
Open(Row, Col),
///
Type(&'a Type<'a>, Row, Col),
Type(&'a EType<'a>, Row, Col),
///
Space(BadInputError, Row, Col),
@ -678,7 +684,7 @@ pub enum TInParens<'a> {
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TApply {
pub enum ETypeApply {
///
StartNotUppercase(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_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) => {
@ -1300,10 +1229,10 @@ macro_rules! collection_trailing_sep_e {
}
}
let collection = $crate::ast::Collection {
items: parsed_elems.into_bump_slice(),
final_comments,
};
let collection = $crate::ast::Collection::with_items_and_comments(
arena,
parsed_elems.into_bump_slice(),
final_comments);
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)?;
// TODO
let _unused = fields.final_comments;
let result = Pattern::RecordDestructure(fields.items);
let result = Pattern::RecordDestructure(fields);
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::keyword;
use crate::parser::{
allocated, backtrackable, optional, specialize, specialize_ref, word1, word2, ParseResult,
Parser,
allocated, backtrackable, optional, specialize, specialize_ref, word1, word2, EType,
ETypeApply, ETypeInParens, ETypeRecord, ETypeTagUnion, ParseResult, Parser,
Progress::{self, *},
State, TApply, TInParens, TRecord, TTagUnion, Type,
State,
};
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
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)
}
#[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| {
let (_, tags, state) = collection_trailing_sep_e!(
word1(b'[', TTagUnion::Open),
word1(b'[', ETypeTagUnion::Open),
loc!(tag_type(min_indent)),
word1(b',', TTagUnion::End),
word1(b']', TTagUnion::End),
word1(b',', ETypeTagUnion::End),
word1(b']', ETypeTagUnion::End),
min_indent,
TTagUnion::Open,
TTagUnion::Space,
TTagUnion::IndentEnd,
ETypeTagUnion::Open,
ETypeTagUnion::Space,
ETypeTagUnion::IndentEnd,
Tag::SpaceBefore
)
.parse(arena, state)?;
// This could be an open tag union, e.g. `[ Foo, Bar ]a`
let (_, ext, state) =
optional(allocated(specialize_ref(TTagUnion::Type, term(min_indent))))
.parse(arena, state)?;
let (_, ext, state) = optional(allocated(specialize_ref(
ETypeTagUnion::Type,
term(min_indent),
)))
.parse(arena, state)?;
let result = TypeAnnotation::TagUnion {
tags: tags.items,
ext,
final_comments: tags.final_comments,
};
let result = TypeAnnotation::TagUnion { tags, ext };
Ok((MadeProgress, result, state))
}
}
fn fail_type_start<'a, T: 'a>() -> impl Parser<'a, T, Type<'a>> {
|_arena, state: State<'a>| Err((NoProgress, Type::TStart(state.line, state.column), state))
fn fail_type_start<'a, T: 'a>() -> impl Parser<'a, T, EType<'a>> {
|_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!(
and!(
one_of!(
loc_wildcard(),
specialize(Type::TInParens, loc_type_in_parens(min_indent)),
loc!(specialize(Type::TRecord, record_type(min_indent))),
loc!(specialize(Type::TTagUnion, tag_union_type(min_indent))),
loc_inferred(),
specialize(EType::TInParens, loc_type_in_parens(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!(parse_type_variable),
fail_type_start(),
@ -67,14 +68,14 @@ fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Typ
map!(
and!(
skip_second!(
backtrackable(space0_e(min_indent, Type::TSpace, Type::TIndentEnd)),
crate::parser::keyword_e(keyword::AS, Type::TEnd)
backtrackable(space0_e(min_indent, EType::TSpace, EType::TIndentEnd)),
crate::parser::keyword_e(keyword::AS, EType::TEnd)
),
space0_before_e(
term(min_indent),
min_indent,
Type::TSpace,
Type::TAsIndentStart
EType::TSpace,
EType::TAsIndentStart
)
),
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,
fn loc_wildcard<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> {
map!(loc!(word1(b'*', Type::TWildcard)), |loc_val: Located<()>| {
fn loc_wildcard<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
map!(loc!(word1(b'*', EType::TWildcard)), |loc_val: Located<
(),
>| {
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;
map_with_arena!(
and!(
backtrackable(space0_e(min_indent, Type::TSpace, Type::TIndentStart)),
backtrackable(space0_e(min_indent, EType::TSpace, EType::TIndentStart)),
one_of!(
loc_wildcard(),
specialize(Type::TInParens, loc_type_in_parens(min_indent)),
loc!(specialize(Type::TRecord, record_type(min_indent))),
loc!(specialize(Type::TTagUnion, tag_union_type(min_indent))),
loc!(specialize(Type::TApply, parse_concrete_type)),
loc_inferred(),
specialize(EType::TInParens, loc_type_in_parens(min_indent)),
loc!(specialize(EType::TRecord, record_type(min_indent))),
loc!(specialize(EType::TTagUnion, tag_union_type(min_indent))),
loc!(specialize(EType::TApply, parse_concrete_type)),
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>(
min_indent: u16,
) -> impl Parser<'a, Located<TypeAnnotation<'a>>, TInParens<'a>> {
) -> impl Parser<'a, Located<TypeAnnotation<'a>>, ETypeInParens<'a>> {
between!(
word1(b'(', TInParens::Open),
word1(b'(', ETypeInParens::Open),
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),
min_indent,
TInParens::Space,
TInParens::IndentOpen,
TInParens::IndentEnd,
ETypeInParens::Space,
ETypeInParens::IndentOpen,
ETypeInParens::IndentEnd,
),
word1(b')', TInParens::IndentEnd)
word1(b')', ETypeInParens::IndentEnd)
)
}
#[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>| {
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) =
specialize_ref(TTagUnion::Type, loc_applied_args_e(min_indent)).parse(arena, state)?;
let (_, args, state) = specialize_ref(ETypeTagUnion::Type, loc_applied_args_e(min_indent))
.parse(arena, state)?;
let result = if name.value.starts_with('@') {
Tag::Private {
@ -190,7 +203,7 @@ where
fn record_type_field<'a>(
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::parser::Either::*;
use AssignedField::*;
@ -201,30 +214,34 @@ fn record_type_field<'a>(
let row = state.line;
let col = state.column;
let (progress, loc_label, state) = loc!(specialize(
move |_, _, _| TRecord::Field(row, col),
move |_, _, _| ETypeRecord::Field(row, col),
lowercase_ident()
))
.parse(arena, state)?;
debug_assert_eq!(progress, MadeProgress);
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.
// (This is true in both literals and types.)
let (_, opt_loc_val, state) = optional(either!(
word1(b':', TRecord::Colon),
word1(b'?', TRecord::Optional)
word1(b':', ETypeRecord::Colon),
word1(b'?', ETypeRecord::Optional)
))
.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 {
Some(First(_)) => {
let (_, loc_val, state) =
space0_before_e(val_parser, min_indent, TRecord::Space, TRecord::IndentColon)
.parse(arena, state)?;
let (_, loc_val, state) = space0_before_e(
val_parser,
min_indent,
ETypeRecord::Space,
ETypeRecord::IndentColon,
)
.parse(arena, state)?;
Ok((
MadeProgress,
@ -236,8 +253,8 @@ fn record_type_field<'a>(
let (_, loc_val, state) = space0_before_e(
val_parser,
min_indent,
TRecord::Space,
TRecord::IndentOptional,
ETypeRecord::Space,
ETypeRecord::IndentOptional,
)
.parse(arena, state)?;
@ -263,44 +280,38 @@ fn record_type_field<'a>(
}
#[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::*;
move |arena, state| {
let (_, fields, state) = collection_trailing_sep_e!(
// word1_check_indent!(b'{', TRecord::Open, min_indent, TRecord::IndentOpen),
word1(b'{', TRecord::Open),
word1(b'{', ETypeRecord::Open),
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(b'}', TRecord::End),
word1(b'}', ETypeRecord::End),
min_indent,
TRecord::Open,
TRecord::Space,
TRecord::IndentEnd,
ETypeRecord::Open,
ETypeRecord::Space,
ETypeRecord::IndentEnd,
AssignedField::SpaceBefore
)
.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 result = Record {
fields: Collection {
items: fields.items,
final_comments: fields.final_comments,
},
ext,
};
let result = Record { fields, ext };
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!(
and!(
specialize(Type::TApply, parse_concrete_type),
specialize(EType::TApply, parse_concrete_type),
// Optionally parse space-separated arguments for the constructor,
// e.g. `Str Float` in `Map Str Float`
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>(
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))
}
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>| {
let (p1, first, state) = space0_before_e(
term(min_indent),
min_indent,
Type::TSpace,
Type::TIndentStart,
EType::TSpace,
EType::TIndentStart,
)
.parse(arena, state)?;
let (p2, rest, state) = zero_or_more!(skip_first!(
word1(b',', Type::TFunctionArgument),
word1(b',', EType::TFunctionArgument),
one_of![
space0_around_ee(
term(min_indent),
min_indent,
Type::TSpace,
Type::TIndentStart,
Type::TIndentEnd
EType::TSpace,
EType::TIndentStart,
EType::TIndentEnd
),
|_, state: State<'a>| Err((
NoProgress,
Type::TFunctionArgument(state.line, state.column),
EType::TFunctionArgument(state.line, state.column),
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
// is only one argument are not seen by the formatter. Can we do better?
let (p3, is_function, state) = optional(skip_first!(
space0_e(min_indent, Type::TSpace, Type::TIndentStart),
word2(b'-', b'>', Type::TStart)
space0_e(min_indent, EType::TSpace, EType::TIndentStart),
word2(b'-', b'>', EType::TStart)
))
.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(
term(min_indent),
min_indent,
Type::TSpace,
Type::TIndentStart,
EType::TSpace,
EType::TIndentStart,
)
.parse(arena, state)?;
@ -418,7 +429,7 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
fn parse_concrete_type<'a>(
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, TypeAnnotation<'a>, TApply> {
) -> ParseResult<'a, TypeAnnotation<'a>, ETypeApply> {
let initial_bytes = state.bytes;
match crate::ident::concrete_type().parse(arena, state) {
@ -428,7 +439,7 @@ fn parse_concrete_type<'a>(
Ok((MadeProgress, answer, 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)) => {
// 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]) };
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))
@ -450,7 +461,7 @@ fn parse_concrete_type<'a>(
fn parse_type_variable<'a>(
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, TypeAnnotation<'a>, Type<'a>> {
) -> ParseResult<'a, TypeAnnotation<'a>, EType<'a>> {
match crate::ident::lowercase_ident().parse(arena, state) {
Ok((_, name, state)) => {
let answer = TypeAnnotation::BoundVariable(name);
@ -459,7 +470,7 @@ fn parse_type_variable<'a>(
}
Err((progress, _, state)) => Err((
progress,
Type::TBadTypeVariable(state.line, state.column),
EType::TBadTypeVariable(state.line, state.column),
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