diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml index fd319f4e65..0a44b6576e 100644 --- a/.github/workflows/spellcheck.yml +++ b/.github/workflows/spellcheck.yml @@ -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 diff --git a/.github/workflows/www.yml b/.github/workflows/www.yml index 308cc4dd0e..d02f905723 100644 --- a/.github/workflows/www.yml +++ b/.github/workflows/www.yml @@ -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: diff --git a/AUTHORS b/AUTHORS index aca22d319b..1825ce83ee 100644 --- a/AUTHORS +++ b/AUTHORS @@ -51,3 +51,8 @@ Eric Newbury Ayaz Hafiz Johannes Maas Takeshi Sato +Joost Baas +Callum Dunster +Martin Stewart +James Hegedus +Cristiano Piemontese diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index 761211dadc..945bca4ae5 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 1c987c0452..1260b2fec3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index da159d2754..4839856ae9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/Earthfile b/Earthfile index 407477acf2..4ca4ee56f3 100644 --- a/Earthfile +++ b/Earthfile @@ -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 diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index 013c505705..57d44ca2f0 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -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 = + 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", + ); + } } diff --git a/ast/src/lang/core/def/def.rs b/ast/src/lang/core/def/def.rs index 00e75c296e..ce2a273541 100644 --- a/ast/src/lang/core/def/def.rs +++ b/ast/src/lang/core/def/def.rs @@ -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, 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 { diff --git a/ast/src/lang/core/expr/expr2.rs b/ast/src/lang/core/expr/expr2.rs index 1f8983d8cc..86d204447b 100644 --- a/ast/src/lang/core/expr/expr2.rs +++ b/ast/src/lang/core/expr/expr2.rs @@ -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; diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index d9eaf2cf18..d1589a83b6 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -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(), diff --git a/ast/src/lang/core/fun_def.rs b/ast/src/lang/core/fun_def.rs index 02d3bdbab4..e8aa642a86 100644 --- a/ast/src/lang/core/fun_def.rs +++ b/ast/src/lang/core/fun_def.rs @@ -14,15 +14,15 @@ use super::{ #[derive(Debug)] pub enum FunctionDef { WithAnnotation { - name: Symbol, // 8B - arguments: PoolVec<(PatternId, Type2)>, // 8B - rigids: NodeId, // 4B - return_type: TypeId, // 4B - body_id: ExprId, // 4B + name: Symbol, // 8B + arguments: PoolVec<(NodeId, PatternId)>, // 8B + rigids: NodeId, // 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 }, diff --git a/ast/src/lang/core/mod.rs b/ast/src/lang/core/mod.rs index 74300dab4f..801f6afa18 100644 --- a/ast/src/lang/core/mod.rs +++ b/ast/src/lang/core/mod.rs @@ -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; diff --git a/ast/src/lang/core/str.rs b/ast/src/lang/core/str.rs index 0463f77969..fb28813a02 100644 --- a/ast/src/lang/core/str.rs +++ b/ast/src/lang/core/str.rs @@ -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::{ diff --git a/ast/src/lang/core/types.rs b/ast/src/lang/core/types.rs index 3cb2a03f8f..4b9c52eabd 100644 --- a/ast/src/lang/core/types.rs +++ b/ast/src/lang/core/types.rs @@ -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; #[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)>, TypeId), // 16B = 12B + 4B - RecursiveTagUnion(Variable, PoolVec<(TagName, PoolVec)>, TypeId), // 20B = 4B + 12B + 4B + TagUnion(PoolVec<(TagName, PoolVec)>, TypeId), // 12B = 8B + 4B + RecursiveTagUnion(Variable, PoolVec<(TagName, PoolVec)>, TypeId), // 16B = 4B + 8B + 4B EmptyRec, - Record(PoolVec<(PoolStr, RecordField)>, TypeId), // 16B = 12B + 4B + Record(PoolVec<(PoolStr, RecordField)>, TypeId), // 12B = 8B + 4B - Function(PoolVec, TypeId, TypeId), // 20B = 12B + 4B + 4B - Apply(Symbol, PoolVec), // 20B = 8B + 12B + Function(PoolVec, TypeId, TypeId), // 16B = 8B + 4B + 4B + Apply(Symbol, PoolVec), // 16B = 8B + 8B - Erroneous(Problem2), + Erroneous(Problem2), // 24B +} + +#[test] +fn type2_size() { + assert_eq!(std::mem::size_of::(), 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, unnamed_rigids: MutSet, symbols: MutSet, 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, unnamed: MutSet, hidden: MutSet, symbols: MutSet, @@ -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>>], region: Region, -) -> MutMap<&'a str, RecordField> { +) -> MutMap> { 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>], region: Region, ) -> Vec<(TagName, PoolVec)> { @@ -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>], diff --git a/ast/src/lang/env.rs b/ast/src/lang/env.rs index 03d93f561e..04596cd84c 100644 --- a/ast/src/lang/env.rs +++ b/ast/src/lang/env.rs @@ -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, - }), } } } diff --git a/ast/src/lang/rigids.rs b/ast/src/lang/rigids.rs index a10dce0016..c629904dbd 100644 --- a/ast/src/lang/rigids.rs +++ b/ast/src/lang/rigids.rs @@ -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>, + named: HashMap>, unnamed: HashSet>, 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))); diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 0d8ac9ce0e..e85bb427bb 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -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"] } diff --git a/cli/src/build.rs b/cli/src/build.rs index 82b8242bc7..38da872e87 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -55,6 +55,7 @@ pub fn build_file<'a>( link_type: LinkType, surgically_link: bool, precompiled: bool, + target_valgrind: bool, ) -> Result> { 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, + target_valgrind: bool, ) -> std::thread::JoinHandle { 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() }) } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 543f24981a..63118350c3 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -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 { }; 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 { }); 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 { link_type, surgically_link, precompiled, + target_valgrind, ); match res_binary_path { diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 307b097419..271efc7c9b 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -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. diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index 200941919d..3e789da85b 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -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() ); } diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 3251e3b48b..c765fb8cb3 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -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!(), } diff --git a/cli_utils/Cargo.lock b/cli_utils/Cargo.lock index f41f503d27..af2cff70b5 100644 --- a/cli_utils/Cargo.lock +++ b/cli_utils/Cargo.lock @@ -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" diff --git a/compiler/build/Cargo.toml b/compiler/build/Cargo.toml index 3a9e19187f..80aaa3dd00 100644 --- a/compiler/build/Cargo.toml +++ b/compiler/build/Cargo.toml @@ -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" diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index d5285b3602..efc3385800 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -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::>() diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 906ffa4456..9b0144aca8 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -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 { diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 57b31d14e5..8b081cd1cf 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -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."); diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index 8eb369b308..26972282dc 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -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"; diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index be4f8891c1..e30c0bfe45 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -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 ]* diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index cb45e67ac1..46ee44bf3f 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -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"; diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 5d054ad831..917eed18ce 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -639,6 +639,13 @@ pub fn types() -> MutMap { 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 { 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 { 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 { ) } + // 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 { 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 } diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 17e0441542..cc4ef8b7ad 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -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); diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index ddabf11685..d2ce526f1b 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -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 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 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 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 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, 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( diff --git a/compiler/can/src/env.rs b/compiler/can/src/env.rs index 3054193b65..c91fa67bae 100644 --- a/compiler/can/src/env.rs +++ b/compiler/can/src/env.rs @@ -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, - }), } } } diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 4e96b58e51..faa5b7a283 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -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) -> (var_store.fresh(), loc_new_expr), (var_store.fresh(), loc_expr), ], - CalledVia::Space, + CalledVia::StringInterpolation, ); loc_expr = Located::new(0, 0, 0, 0, expr); diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index 36b790764d..97dce75fb1 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -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>) -> &'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>) -> &'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>, next_op: Located, ) -> Step<'a> { - use roc_module::operator::Associativity::*; + use roc_module::called_via::Associativity::*; use std::cmp::Ordering; match op_stack.pop() { diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index adc853bd69..01f0c55862 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -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( diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 11d8c4cabe..eac7d8f1fc 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -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, diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs index 4e73d91b7e..98d156415d 100644 --- a/compiler/fmt/src/annotation.rs +++ b/compiler/fmt/src/annotation.rs @@ -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); diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index 49a0f5f937..cb356ff53a 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -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>], - final_comments: &'a [CommentOrNewline<'a>], - indent: u16, -) { +fn fmt_list<'a>(buf: &mut String<'a>, items: Collection<'a, &'a Located>>, 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>>, - loc_fields: &[Located>>], - final_comments: &'a [CommentOrNewline<'a>], + fields: Collection<'a, Located>>>, 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 { diff --git a/compiler/fmt/src/module.rs b/compiler/fmt/src/module.rs index 6ceca3d28c..17522326c2 100644 --- a/compiler/fmt/src/module.rs +++ b/compiler/fmt/src/module.rs @@ -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>>, + loc_entries: Collection<'a, Located>>, 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>>, + loc_entries: &'a Collection<'a, Located>>, indent: u16, ) { buf.push('['); diff --git a/compiler/gen_dev/README.md b/compiler/gen_dev/README.md index 12321accf1..6a86279cce 100644 --- a/compiler/gen_dev/README.md +++ b/compiler/gen_dev/README.md @@ -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. diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 32273fbf91..890995c726 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -446,6 +446,10 @@ impl Assembler 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 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) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 212d6572f7..b3936857b9 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -149,6 +149,7 @@ pub trait Assembler { 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 { 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, diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 67f3d82a08..33aa59d2c4 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -1053,6 +1053,11 @@ impl Assembler 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 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[..]); } } diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index e17abf1362..a87e2ecdf7 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -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>; @@ -785,7 +790,7 @@ where match call_type { CallType::ByName { .. } => {} CallType::LowLevel { .. } => {} - CallType::HigherOrderLowLevel { .. } => {} + CallType::HigherOrder { .. } => {} CallType::Foreign { .. } => {} } } diff --git a/compiler/gen_llvm/src/llvm/bitcode.rs b/compiler/gen_llvm/src/llvm/bitcode.rs index 95dfa79c4b..159c688580 100644 --- a/compiler/gen_llvm/src/llvm/bitcode.rs +++ b/compiler/gen_llvm/src/llvm/bitcode.rs @@ -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 => { diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index f8a16ea1c6..3bac18c03b 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -8,21 +8,22 @@ use crate::llvm::build_dict::{ }; use crate::llvm::build_hash::generic_hash; use crate::llvm::build_list::{ - self, allocate_list, empty_list, empty_polymorphic_list, list_any, list_append, list_concat, - list_contains, list_drop, list_drop_at, list_find_trivial_not_found, list_find_unsafe, + self, allocate_list, empty_list, empty_polymorphic_list, list_all, list_any, list_append, + list_concat, list_contains, list_drop_at, list_find_trivial_not_found, list_find_unsafe, list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len, list_map, list_map2, list_map3, list_map4, list_map_with_index, list_prepend, list_range, list_repeat, - list_reverse, list_set, list_single, list_sort_with, list_swap, list_take_first, - list_take_last, + list_reverse, list_set, list_single, list_sort_with, list_sublist, list_swap, }; use crate::llvm::build_str::{ empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8, str_from_utf8_range, str_join_with, str_number_of_bytes, str_repeat, str_split, str_starts_with, str_starts_with_code_point, str_to_utf8, str_trim, str_trim_left, + str_trim_right, }; use crate::llvm::compare::{generic_eq, generic_neq}; use crate::llvm::convert::{ - basic_type_from_builtin, basic_type_from_layout, block_of_memory_slices, ptr_int, + basic_type_from_builtin, basic_type_from_layout, basic_type_from_layout_1, + block_of_memory_slices, ptr_int, }; use crate::llvm::refcounting::{ build_reset, decrement_refcount_layout, increment_refcount_layout, PointerToRefcount, @@ -55,10 +56,10 @@ use roc_collections::all::{ImMap, MutMap, MutSet}; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_mono::ir::{ - BranchInfo, CallType, EntryPoint, JoinPointId, ListLiteralElement, ModifyRc, OptLevel, - ProcLayout, + BranchInfo, CallType, EntryPoint, HigherOrderLowLevel, JoinPointId, ListLiteralElement, + ModifyRc, OptLevel, ProcLayout, }; -use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, UnionLayout}; +use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, TagIdIntType, UnionLayout}; use target_lexicon::{Architecture, OperatingSystem, Triple}; /// This is for Inkwell's FunctionValue::verify - we want to know the verification @@ -672,7 +673,7 @@ fn promote_to_main_function<'a, 'ctx, 'env>( top_level: ProcLayout<'a>, ) -> (&'static str, FunctionValue<'ctx>) { let it = top_level.arguments.iter().copied(); - let bytes = roc_mono::alias_analysis::func_name_bytes_help(symbol, it, top_level.result); + let bytes = roc_mono::alias_analysis::func_name_bytes_help(symbol, it, &top_level.result); let func_name = FuncName(&bytes); let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); @@ -936,33 +937,12 @@ pub fn build_exp_call<'a, 'ctx, 'env>( ) } - CallType::HigherOrderLowLevel { - op, - function_owns_closure_data, - specialization_id, - function_name, - function_env, - arg_layouts, - ret_layout, - .. - } => { - let bytes = specialization_id.to_bytes(); + CallType::HigherOrder(higher_order) => { + let bytes = higher_order.specialization_id.to_bytes(); let callee_var = CalleeSpecVar(&bytes); let func_spec = func_spec_solutions.callee_spec(callee_var).unwrap(); - run_higher_order_low_level( - env, - layout_ids, - scope, - layout, - *op, - func_spec, - arg_layouts, - ret_layout, - *function_owns_closure_data, - *function_name, - function_env, - ) + run_higher_order_low_level(env, layout_ids, scope, layout, func_spec, higher_order) } CallType::Foreign { @@ -972,7 +952,7 @@ pub fn build_exp_call<'a, 'ctx, 'env>( } } -const TAG_ID_INDEX: u32 = 1; +pub const TAG_ID_INDEX: u32 = 1; pub const TAG_DATA_INDEX: u32 = 0; pub fn struct_from_fields<'a, 'ctx, 'env, I>( @@ -1065,7 +1045,16 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( if !field_layout.is_dropped_because_empty() { field_types.push(basic_type_from_layout(env, field_layout)); - field_vals.push(field_expr); + if field_layout.is_passed_by_reference() { + let field_value = env.builder.build_load( + field_expr.into_pointer_value(), + "load_tag_to_put_in_struct", + ); + + field_vals.push(field_value); + } else { + field_vals.push(field_expr); + } } } @@ -1173,14 +1162,19 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( match (value, layout) { (StructValue(argument), Layout::Struct(fields)) => { debug_assert!(!fields.is_empty()); - env.builder + + let field_value = env + .builder .build_extract_value( argument, *index as u32, env.arena .alloc(format!("struct_field_access_record_{}", index)), ) - .unwrap() + .unwrap(); + + let field_layout = fields[*index as usize]; + use_roc_value(env, field_layout, field_value, "struct_field_tag") } ( PointerValue(argument), @@ -1229,30 +1223,26 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( index, union_layout, } => { - let builder = env.builder; - // cast the argument bytes into the desired shape for this tag let (argument, _structure_layout) = load_symbol_and_layout(scope, structure); match union_layout { UnionLayout::NonRecursive(tag_layouts) => { - debug_assert!(argument.is_struct_value()); + debug_assert!(argument.is_pointer_value()); + let field_layouts = tag_layouts[*tag_id as usize]; - let struct_layout = Layout::Struct(field_layouts); - let struct_type = basic_type_from_layout(env, &struct_layout); + let tag_id_type = + basic_type_from_layout(env, &union_layout.tag_id_layout()).into_int_type(); - let struct_value = access_index_struct_value( - builder, - argument.into_struct_value(), - struct_type.into_struct_type(), - ); - - let result = builder - .build_extract_value(struct_value, *index as u32, "") - .expect("desired field did not decode"); - - result + lookup_at_index_ptr2( + env, + union_layout, + tag_id_type, + field_layouts, + *index as usize, + argument.into_pointer_value(), + ) } UnionLayout::Recursive(tag_layouts) => { debug_assert!(argument.is_pointer_value()); @@ -1292,9 +1282,9 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( other_tags, } => { debug_assert!(argument.is_pointer_value()); - debug_assert_ne!(*tag_id as i64, *nullable_id); + debug_assert_ne!(*tag_id, *nullable_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 @@ -1438,11 +1428,41 @@ fn build_wrapped_tag<'a, 'ctx, 'env>( } } +pub fn tag_alloca<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + type_: BasicTypeEnum<'ctx>, + name: &str, +) -> PointerValue<'ctx> { + let result_alloca = env.builder.build_alloca(type_, name); + + // Initialize all memory of the alloca. This _should_ not be required, but currently + // LLVM can access uninitialized memory after applying some optimizations. Hopefully + // we can in the future adjust code gen so this is no longer an issue. + // + // An example is + // + // main : Task.Task {} [] + // main = + // when List.len [ Ok "foo", Err 42, Ok "spam" ] is + // n -> Task.putLine (Str.fromInt n) + // + // Here the decrement function of result must first check it's an Ok tag, + // then defers to string decrement, which must check is the string is small or large + // + // After inlining, those checks are combined. That means that even if the tag is Err, + // a check is done on the "string" to see if it is big or small, which will touch the + // uninitialized memory. + let all_zeros = type_.const_zero(); + env.builder.build_store(result_alloca, all_zeros); + + result_alloca +} + pub fn build_tag<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, scope: &Scope<'a, 'ctx>, union_layout: &UnionLayout<'a>, - tag_id: u8, + tag_id: TagIdIntType, arguments: &[Symbol], reuse_allocation: Option>, parent: FunctionValue<'ctx>, @@ -1460,27 +1480,7 @@ pub fn build_tag<'a, 'ctx, 'env>( let wrapper_type = env .context .struct_type(&[internal_type, tag_id_type.into()], false); - let result_alloca = env.builder.build_alloca(wrapper_type, "tag_opaque"); - - // Initialize all memory of the alloca. This _should_ not be required, but currently - // LLVM can access uninitialized memory after applying some optimizations. Hopefully - // we can in the future adjust code gen so this is no longer an issue. - // - // An example is - // - // main : Task.Task {} [] - // main = - // when List.len [ Ok "foo", Err 42, Ok "spam" ] is - // n -> Task.putLine (Str.fromInt n) - // - // Here the decrement function of result must first check it's an Ok tag, - // then defers to string decrement, which must check is the string is small or large - // - // After inlining, those checks are combined. That means that even if the tag is Err, - // a check is done on the "string" to see if it is big or small, which will touch the - // uninitialized memory. - let all_zeros = wrapper_type.const_zero(); - env.builder.build_store(result_alloca, all_zeros); + let result_alloca = tag_alloca(env, wrapper_type.into(), "opaque_tag"); // Determine types let num_fields = arguments.len() + 1; @@ -1514,7 +1514,7 @@ pub fn build_tag<'a, 'ctx, 'env>( // store the tag id let tag_id_ptr = env .builder - .build_struct_gep(result_alloca, TAG_ID_INDEX, "get_opaque_data") + .build_struct_gep(result_alloca, TAG_ID_INDEX, "tag_id_ptr") .unwrap(); let tag_id_intval = tag_id_type.const_int(tag_id as u64, false); @@ -1527,7 +1527,7 @@ pub fn build_tag<'a, 'ctx, 'env>( let struct_opaque_ptr = env .builder - .build_struct_gep(result_alloca, TAG_DATA_INDEX, "get_opaque_data") + .build_struct_gep(result_alloca, TAG_DATA_INDEX, "opaque_data_ptr") .unwrap(); let struct_ptr = env.builder.build_pointer_cast( struct_opaque_ptr, @@ -1547,10 +1547,13 @@ pub fn build_tag<'a, 'ctx, 'env>( .builder .build_struct_gep(struct_ptr, index, "get_tag_field_ptr") .unwrap(); - env.builder.build_store(ptr, field_val); + + let field_layout = tag_field_layouts[index as usize]; + store_roc_value(env, field_layout, ptr, field_val); } - env.builder.build_load(result_alloca, "load_result") + // env.builder.build_load(result_alloca, "load_result") + result_alloca.into() } UnionLayout::Recursive(tags) => { debug_assert!(union_size > 1); @@ -1561,7 +1564,7 @@ pub fn build_tag<'a, 'ctx, 'env>( env, scope, union_layout, - tag_id, + tag_id as _, arguments, tag_field_layouts, tags, @@ -1575,7 +1578,7 @@ pub fn build_tag<'a, 'ctx, 'env>( } => { let tag_field_layouts = { use std::cmp::Ordering::*; - match tag_id.cmp(&(*nullable_id as u8)) { + match tag_id.cmp(&(*nullable_id as _)) { Equal => { let layout = Layout::Union(*union_layout); @@ -1593,7 +1596,7 @@ pub fn build_tag<'a, 'ctx, 'env>( env, scope, union_layout, - tag_id, + tag_id as _, arguments, tag_field_layouts, tags, @@ -1660,7 +1663,7 @@ pub fn build_tag<'a, 'ctx, 'env>( let tag_struct_type = block_of_memory_slices(env.context, &[other_fields], env.ptr_bytes); - if tag_id == *nullable_id as u8 { + if tag_id == *nullable_id as _ { let output_type = tag_struct_type.ptr_type(AddressSpace::Generic); return output_type.const_null().into(); @@ -1858,9 +1861,10 @@ pub fn get_tag_id<'a, 'ctx, 'env>( match union_layout { UnionLayout::NonRecursive(_) => { - let tag = argument.into_struct_value(); + debug_assert!(argument.is_pointer_value(), "{:?}", argument); - get_tag_id_non_recursive(env, tag) + let argument_ptr = argument.into_pointer_value(); + get_tag_id_wrapped(env, argument_ptr) } UnionLayout::Recursive(_) => { let argument_ptr = argument.into_pointer_value(); @@ -2000,7 +2004,26 @@ fn lookup_at_index_ptr2<'a, 'ctx, 'env>( .build_struct_gep(data_ptr, index as u32, "at_index_struct_gep") .unwrap(); - let result = builder.build_load(elem_ptr, "load_at_index_ptr"); + let field_layout = field_layouts[index]; + let result = if field_layout.is_passed_by_reference() { + let field_type = basic_type_from_layout(env, &field_layout); + + let align_bytes = field_layout.alignment_bytes(env.ptr_bytes); + let alloca = tag_alloca(env, field_type, "copied_tag"); + if align_bytes > 0 { + let size = env + .ptr_int() + .const_int(field_layout.stack_size(env.ptr_bytes) as u64, false); + + env.builder + .build_memcpy(alloca, align_bytes, elem_ptr, align_bytes, size) + .unwrap(); + } + + alloca.into() + } else { + builder.build_load(elem_ptr, "load_at_index_ptr") + }; if let Some(Layout::RecursivePointer) = field_layouts.get(index as usize) { // a recursive field is stored as a `i64*`, to use it we must cast it to @@ -2147,17 +2170,12 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>( let ptr_type = value_type.ptr_type(AddressSpace::Generic); unsafe { - builder - .build_bitcast( - env.builder.build_in_bounds_gep( - as_usize_ptr, - &[index_intvalue], - "get_data_ptr", - ), - ptr_type, - "alloc_cast_to_desired", - ) - .into_pointer_value() + builder.build_pointer_cast( + env.builder + .build_in_bounds_gep(as_usize_ptr, &[index_intvalue], "get_data_ptr"), + ptr_type, + "alloc_cast_to_desired", + ) } }; @@ -2183,13 +2201,13 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>( fn list_literal<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, scope: &Scope<'a, 'ctx>, - elem_layout: &Layout<'a>, + element_layout: &Layout<'a>, elems: &[ListLiteralElement], ) -> BasicValueEnum<'ctx> { let ctx = env.context; let builder = env.builder; - let element_type = basic_type_from_layout(env, elem_layout); + let element_type = basic_type_from_layout(env, element_layout); let list_length = elems.len(); let list_length_intval = env.ptr_int().const_int(list_length as _, false); @@ -2199,9 +2217,9 @@ fn list_literal<'a, 'ctx, 'env>( // if element_type.is_int_type() { if false { let element_type = element_type.into_int_type(); - let element_width = elem_layout.stack_size(env.ptr_bytes); + let element_width = element_layout.stack_size(env.ptr_bytes); let size = list_length * element_width as usize; - let alignment = elem_layout + let alignment = element_layout .alignment_bytes(env.ptr_bytes) .max(env.ptr_bytes); @@ -2233,7 +2251,7 @@ fn list_literal<'a, 'ctx, 'env>( for (index, element) in elems.iter().enumerate() { match element { ListLiteralElement::Literal(literal) => { - let val = build_exp_literal(env, elem_layout, literal); + let val = build_exp_literal(env, element_layout, literal); global_elements.push(val.into_int_value()); } ListLiteralElement::Symbol(symbol) => { @@ -2288,7 +2306,7 @@ fn list_literal<'a, 'ctx, 'env>( super::build_list::store_list(env, ptr, list_length_intval) } else { // some of our elements are non-constant, so we must allocate space on the heap - let ptr = allocate_list(env, elem_layout, list_length_intval); + let ptr = allocate_list(env, element_layout, list_length_intval); // then, copy the relevant segment from the constant section into the heap env.builder @@ -2312,26 +2330,103 @@ fn list_literal<'a, 'ctx, 'env>( super::build_list::store_list(env, ptr, list_length_intval) } } else { - let ptr = allocate_list(env, elem_layout, list_length_intval); + let ptr = allocate_list(env, element_layout, list_length_intval); // Copy the elements from the list literal into the array for (index, element) in elems.iter().enumerate() { let val = match element { ListLiteralElement::Literal(literal) => { - build_exp_literal(env, elem_layout, literal) + build_exp_literal(env, element_layout, literal) } ListLiteralElement::Symbol(symbol) => load_symbol(scope, symbol), }; let index_val = ctx.i64_type().const_int(index as u64, false); let elem_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") }; - builder.build_store(elem_ptr, val); + store_roc_value(env, *element_layout, elem_ptr, val); } super::build_list::store_list(env, ptr, list_length_intval) } } +pub fn load_roc_value<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout: Layout<'a>, + source: PointerValue<'ctx>, + name: &str, +) -> BasicValueEnum<'ctx> { + if layout.is_passed_by_reference() { + let alloca = tag_alloca(env, basic_type_from_layout(env, &layout), name); + + store_roc_value(env, layout, alloca, source.into()); + + alloca.into() + } else { + env.builder.build_load(source, name) + } +} + +pub fn use_roc_value<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout: Layout<'a>, + source: BasicValueEnum<'ctx>, + name: &str, +) -> BasicValueEnum<'ctx> { + if layout.is_passed_by_reference() { + let alloca = tag_alloca(env, basic_type_from_layout(env, &layout), name); + + env.builder.build_store(alloca, source); + + alloca.into() + } else { + source + } +} + +pub fn store_roc_value_opaque<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout: Layout<'a>, + opaque_destination: PointerValue<'ctx>, + value: BasicValueEnum<'ctx>, +) { + let target_type = basic_type_from_layout(env, &layout).ptr_type(AddressSpace::Generic); + let destination = + env.builder + .build_pointer_cast(opaque_destination, target_type, "store_roc_value_opaque"); + + store_roc_value(env, layout, destination, value) +} + +pub fn store_roc_value<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout: Layout<'a>, + destination: PointerValue<'ctx>, + value: BasicValueEnum<'ctx>, +) { + if layout.is_passed_by_reference() { + let align_bytes = layout.alignment_bytes(env.ptr_bytes); + + if align_bytes > 0 { + let size = env + .ptr_int() + .const_int(layout.stack_size(env.ptr_bytes) as u64, false); + + env.builder + .build_memcpy( + destination, + align_bytes, + value.into_pointer_value(), + align_bytes, + size, + ) + .unwrap(); + } + } else { + env.builder.build_store(destination, value); + } +} + pub fn build_exp_stmt<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, @@ -2389,15 +2484,73 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( result } Ret(symbol) => { - let value = load_symbol(scope, symbol); + let (value, layout) = load_symbol_and_layout(scope, symbol); - if let Some(block) = env.builder.get_insert_block() { - if block.get_terminator().is_none() { - env.builder.build_return(Some(&value)); + match RocReturn::from_layout(env, layout) { + RocReturn::Return => { + if let Some(block) = env.builder.get_insert_block() { + if block.get_terminator().is_none() { + env.builder.build_return(Some(&value)); + } + } + + value + } + RocReturn::ByPointer => { + // we need to write our value into the final argument of the current function + let parameters = parent.get_params(); + let out_parameter = parameters.last().unwrap(); + debug_assert!(out_parameter.is_pointer_value()); + + // store_roc_value(env, *layout, out_parameter.into_pointer_value(), value); + + let destination = out_parameter.into_pointer_value(); + if layout.is_passed_by_reference() { + let align_bytes = layout.alignment_bytes(env.ptr_bytes); + + if align_bytes > 0 { + let value_ptr = value.into_pointer_value(); + + // We can only do this if the function itself writes data into this + // pointer. If the pointer is passed as an argument, then we must copy + // from one pointer to our destination pointer + if value_ptr.get_first_use().is_some() { + value_ptr.replace_all_uses_with(destination); + } else { + let size = env + .ptr_int() + .const_int(layout.stack_size(env.ptr_bytes) as u64, false); + + env.builder + .build_memcpy( + destination, + align_bytes, + value.into_pointer_value(), + align_bytes, + size, + ) + .unwrap(); + } + } + } else { + env.builder.build_store(destination, value); + } + + if let Some(block) = env.builder.get_insert_block() { + match block.get_terminator() { + None => { + env.builder.build_return(None); + } + Some(terminator) => { + terminator.remove_from_basic_block(); + env.builder.build_return(None); + } + } + } + + env.context.i8_type().const_zero().into() } } - - value } Switch { @@ -2470,7 +2623,11 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( builder.position_at_end(cont_block); for (ptr, param) in joinpoint_args.iter().zip(parameters.iter()) { - let value = env.builder.build_load(*ptr, "load_jp_argument"); + let value = if param.layout.is_passed_by_reference() { + (*ptr).into() + } else { + env.builder.build_load(*ptr, "load_jp_argument") + }; scope.insert(param.symbol, (param.layout, value)); } @@ -2497,8 +2654,9 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( let (cont_block, argument_pointers) = scope.join_points.get(join_point).unwrap(); for (pointer, argument) in argument_pointers.iter().zip(arguments.iter()) { - let value = load_symbol(scope, argument); - builder.build_store(*pointer, value); + let (value, layout) = load_symbol_and_layout(scope, argument); + + store_roc_value(env, *layout, *pointer, value); } builder.build_unconditional_branch(*cont_block); @@ -2640,20 +2798,6 @@ pub fn load_symbol_and_lambda_set<'a, 'ctx, 'b>( } } -fn access_index_struct_value<'ctx>( - builder: &Builder<'ctx>, - from_value: StructValue<'ctx>, - to_type: StructType<'ctx>, -) -> StructValue<'ctx> { - complex_bitcast( - builder, - from_value.into(), - to_type.into(), - "access_index_struct_value", - ) - .into_struct_value() -} - /// Cast a value to another value of the same (or smaller?) size pub fn cast_basic_basic<'ctx>( builder: &Builder<'ctx>, @@ -3109,8 +3253,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>( return_layout: Layout<'a>, c_function_name: &str, ) -> FunctionValue<'ctx> { - // NOTE we ignore env.is_gen_test here - let wrapper_return_type = roc_function.get_type().get_return_type().unwrap(); + // NOTE we ingore env.is_gen_test here let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena); for layout in arguments { @@ -3120,12 +3263,19 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>( // STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}` // let mut argument_types = roc_function.get_type().get_param_types(); let mut argument_types = cc_argument_types; - let return_type = wrapper_return_type; - let output_type = return_type.ptr_type(AddressSpace::Generic); - argument_types.push(output_type.into()); + let c_function_type = match roc_function.get_type().get_return_type() { + None => { + // this function already returns by-pointer + roc_function.get_type() + } + Some(return_type) => { + let output_type = return_type.ptr_type(AddressSpace::Generic); + argument_types.push(output_type.into()); - let c_function_type = env.context.void_type().fn_type(&argument_types, false); + env.context.void_type().fn_type(&argument_types, false) + } + }; let c_function = add_func( env.module, @@ -3171,11 +3321,11 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>( let arguments_for_call = &arguments_for_call.into_bump_slice(); - debug_assert_eq!(args.len(), roc_function.get_params().len()); - let call_result = { if env.is_gen_test { - let roc_wrapper_function = make_exception_catcher(env, roc_function); + debug_assert_eq!(args.len(), roc_function.get_params().len()); + + let roc_wrapper_function = make_exception_catcher(env, roc_function, return_layout); debug_assert_eq!( arguments_for_call.len(), roc_wrapper_function.get_params().len() @@ -3197,7 +3347,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>( .unwrap() .into_pointer_value(); - builder.build_store(output_arg, call_result); + store_roc_value(env, return_layout, output_arg, call_result); builder.build_return(None); c_function @@ -3214,8 +3364,7 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( // a tagged union to indicate to the test loader that a panic occurred. // especially when running 32-bit binaries on a 64-bit machine, there // does not seem to be a smarter solution - let wrapper_return_type = - roc_result_type(env, roc_function.get_type().get_return_type().unwrap()); + let wrapper_return_type = roc_result_type(env, basic_type_from_layout(env, &return_layout)); let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena); for layout in arguments { @@ -3277,18 +3426,14 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( let arguments_for_call = &arguments_for_call.into_bump_slice(); let call_result = { - let roc_wrapper_function = make_exception_catcher(env, roc_function); - debug_assert_eq!( - arguments_for_call.len(), - roc_wrapper_function.get_params().len() - ); + let roc_wrapper_function = make_exception_catcher(env, roc_function, return_layout); builder.position_at_end(entry); call_roc_function( env, roc_wrapper_function, - &return_layout, + &Layout::Struct(&[Layout::Builtin(Builtin::Int64), return_layout]), arguments_for_call, ) }; @@ -3361,7 +3506,8 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( let wrapper_return_type = if env.is_gen_test { roc_result_type(env, roc_function.get_type().get_return_type().unwrap()).into() } else { - roc_function.get_type().get_return_type().unwrap() + // roc_function.get_type().get_return_type().unwrap() + basic_type_from_layout(env, &return_layout) }; let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena); @@ -3418,10 +3564,15 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( CCReturn::Void => { debug_assert_eq!(args.len(), roc_function.get_params().len()); } - CCReturn::ByPointer => { - args = &args[..args.len() - 1]; - debug_assert_eq!(args.len(), roc_function.get_params().len()); - } + CCReturn::ByPointer => match RocReturn::from_layout(env, &return_layout) { + RocReturn::ByPointer => { + debug_assert_eq!(args.len(), roc_function.get_params().len()); + } + RocReturn::Return => { + args = &args[..args.len() - 1]; + debug_assert_eq!(args.len(), roc_function.get_params().len()); + } + }, } let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena); @@ -3438,7 +3589,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( } } - let arguments_for_call = &arguments_for_call.into_bump_slice(); + let arguments_for_call = arguments_for_call.into_bump_slice(); let call_result = call_roc_function(env, roc_function, &return_layout, arguments_for_call); @@ -3509,21 +3660,17 @@ pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValu .into_pointer_value() } -fn set_jump_and_catch_long_jump<'a, 'ctx, 'env, F, T>( +fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, - function: F, - calling_convention: u32, + roc_function: FunctionValue<'ctx>, arguments: &[BasicValueEnum<'ctx>], - return_type: T, -) -> BasicValueEnum<'ctx> -where - T: inkwell::types::BasicType<'ctx>, - F: Into>, -{ + return_layout: Layout<'a>, +) -> BasicValueEnum<'ctx> { let context = env.context; let builder = env.builder; + let return_type = basic_type_from_layout(env, &return_layout); let call_result_type = roc_result_type(env, return_type.as_basic_type_enum()); let result_alloca = builder.build_alloca(call_result_type, "result"); @@ -3592,13 +3739,9 @@ where { builder.position_at_end(then_block); - let call = env.builder.build_call(function, arguments, "call_function"); + let call_result = call_roc_function(env, roc_function, &return_layout, arguments); - call.set_call_convention(calling_convention); - - let call_result = call.try_as_basic_value().left().unwrap(); - - let return_value = make_good_roc_result(env, call_result); + let return_value = make_good_roc_result(env, return_layout, call_result); builder.build_store(result_alloca, return_value); @@ -3649,16 +3792,18 @@ where env.builder.position_at_end(cont_block); - builder.build_load(result_alloca, "load_result") + builder.build_load(result_alloca, "set_jump_and_catch_long_jump_load_result") } fn make_exception_catcher<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, roc_function: FunctionValue<'ctx>, + return_layout: Layout<'a>, ) -> FunctionValue<'ctx> { let wrapper_function_name = format!("{}_catcher", roc_function.get_name().to_str().unwrap()); - let function_value = make_exception_catching_wrapper(env, roc_function, &wrapper_function_name); + let function_value = + make_exception_catching_wrapper(env, roc_function, return_layout, &wrapper_function_name); function_value.set_linkage(Linkage::Internal); @@ -3691,20 +3836,31 @@ fn roc_result_type<'a, 'ctx, 'env>( fn make_good_roc_result<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, + return_layout: Layout<'a>, return_value: BasicValueEnum<'ctx>, ) -> BasicValueEnum<'ctx> { let context = env.context; let builder = env.builder; - let v1 = roc_result_type(env, return_value.get_type()).const_zero(); + let v1 = roc_result_type(env, basic_type_from_layout(env, &return_layout)).const_zero(); let v2 = builder .build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error") .unwrap(); - let v3 = builder - .build_insert_value(v2, return_value, 2, "set_call_result") - .unwrap(); + let v3 = if return_layout.is_passed_by_reference() { + let loaded = env.builder.build_load( + return_value.into_pointer_value(), + "load_call_result_passed_by_ptr", + ); + builder + .build_insert_value(v2, loaded, 2, "set_call_result") + .unwrap() + } else { + builder + .build_insert_value(v2, return_value, 2, "set_call_result") + .unwrap() + }; v3.into_struct_value().into() } @@ -3712,6 +3868,7 @@ fn make_good_roc_result<'a, 'ctx, 'env>( fn make_exception_catching_wrapper<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, roc_function: FunctionValue<'ctx>, + return_layout: Layout<'a>, wrapper_function_name: &str, ) -> FunctionValue<'ctx> { // build the C calling convention wrapper @@ -3720,10 +3877,17 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>( let builder = env.builder; let roc_function_type = roc_function.get_type(); - let argument_types = roc_function_type.get_param_types(); + let argument_types = match RocReturn::from_layout(env, &return_layout) { + RocReturn::Return => roc_function_type.get_param_types(), + RocReturn::ByPointer => { + let mut types = roc_function_type.get_param_types(); + types.remove(0); - let wrapper_return_type = - roc_result_type(env, roc_function.get_type().get_return_type().unwrap()); + types + } + }; + + let wrapper_return_type = roc_result_type(env, basic_type_from_layout(env, &return_layout)); // argument_types.push(wrapper_return_type.ptr_type(AddressSpace::Generic).into()); @@ -3757,9 +3921,8 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>( env, wrapper_function, roc_function, - roc_function.get_call_conventions(), &arguments, - roc_function_type.get_return_type().unwrap(), + return_layout, ); builder.build_return(Some(&result)); @@ -3904,6 +4067,8 @@ fn build_procedures_help<'a, 'ctx, 'env>( app_ll_file, ); } else { + env.module.print_to_stderr(); + panic!( "The preceding code was from {:?}, which failed LLVM verification in {} build.", fn_val.get_name().to_str().unwrap(), @@ -3953,18 +4118,25 @@ fn build_proc_header<'a, 'ctx, 'env>( let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena); for (layout, _) in args.iter() { - let arg_type = basic_type_from_layout(env, layout); + let arg_type = basic_type_from_layout_1(env, layout); arg_basic_types.push(arg_type); } - let fn_type = ret_type.fn_type(&arg_basic_types, false); + let fn_type = match RocReturn::from_layout(env, &proc.ret_layout) { + RocReturn::Return => ret_type.fn_type(&arg_basic_types, false), + RocReturn::ByPointer => { + // println!( "{:?} will return void instead of {:?}", symbol, proc.ret_layout); + arg_basic_types.push(ret_type.ptr_type(AddressSpace::Generic).into()); + env.context.void_type().fn_type(&arg_basic_types, false) + } + }; let fn_val = add_func( env.module, fn_name.as_str(), fn_type, - Linkage::Private, + Linkage::Internal, FAST_CALL_CONV, ); @@ -4052,26 +4224,49 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( // NOTE this may be incorrect in the long run // here we load any argument that is a pointer - for param in evaluator_arguments.iter_mut() { - if param.is_pointer_value() { + let closure_layout = lambda_set.runtime_representation(); + let layouts_it = arguments.iter().chain(std::iter::once(&closure_layout)); + for (param, layout) in evaluator_arguments.iter_mut().zip(layouts_it) { + if param.is_pointer_value() && !layout.is_passed_by_reference() { *param = builder.build_load(param.into_pointer_value(), "load_param"); } } - let call_result = if env.is_gen_test { - set_jump_and_catch_long_jump( + if env.is_gen_test { + let call_result = set_jump_and_catch_long_jump( env, function_value, evaluator, - evaluator.get_call_conventions(), &evaluator_arguments, - result_type, - ) - } else { - call_roc_function(env, evaluator, return_layout, &evaluator_arguments) - }; + *return_layout, + ); - builder.build_store(output, call_result); + builder.build_store(output, call_result); + } else { + let call_result = call_roc_function(env, evaluator, return_layout, &evaluator_arguments); + + if return_layout.is_passed_by_reference() { + let align_bytes = return_layout.alignment_bytes(env.ptr_bytes); + + if align_bytes > 0 { + let size = env + .ptr_int() + .const_int(return_layout.stack_size(env.ptr_bytes) as u64, false); + + env.builder + .build_memcpy( + output, + align_bytes, + call_result.into_pointer_value(), + align_bytes, + size, + ) + .unwrap(); + } + } else { + builder.build_store(output, call_result); + } + }; builder.build_return(None); @@ -4171,7 +4366,7 @@ pub fn build_proc<'a, 'ctx, 'env>( let bytes = roc_mono::alias_analysis::func_name_bytes_help( symbol, it, - top_level.result, + &top_level.result, ); let func_name = FuncName(&bytes); let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); @@ -4326,24 +4521,81 @@ fn roc_call_with_args<'a, 'ctx, 'env>( call_roc_function(env, fn_val, result_layout, arguments) } -fn call_roc_function<'a, 'ctx, 'env>( +pub fn call_roc_function<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, roc_function: FunctionValue<'ctx>, - _result_layout: &Layout<'a>, + result_layout: &Layout<'a>, arguments: &[BasicValueEnum<'ctx>], ) -> BasicValueEnum<'ctx> { - let call = env.builder.build_call(roc_function, arguments, "call"); + let pass_by_pointer = roc_function.get_type().get_param_types().len() == arguments.len() + 1; - // roc functions should have the fast calling convention - debug_assert_eq!(roc_function.get_call_conventions(), FAST_CALL_CONV); - call.set_call_convention(FAST_CALL_CONV); + match RocReturn::from_layout(env, result_layout) { + RocReturn::ByPointer if !pass_by_pointer => { + // WARNING this is a hack!! + let mut arguments = Vec::from_iter_in(arguments.iter().copied(), env.arena); + arguments.pop(); - call.try_as_basic_value().left().unwrap_or_else(|| { - panic!( - "LLVM error: Invalid call by name for name {:?}", - roc_function.get_name() - ) - }) + let result_type = basic_type_from_layout(env, result_layout); + let result_alloca = env.builder.build_alloca(result_type, "result_value"); + + arguments.push(result_alloca.into()); + + debug_assert_eq!( + roc_function.get_type().get_param_types().len(), + arguments.len() + ); + let call = env.builder.build_call(roc_function, &arguments, "call"); + + // roc functions should have the fast calling convention + debug_assert_eq!(roc_function.get_call_conventions(), FAST_CALL_CONV); + call.set_call_convention(FAST_CALL_CONV); + + env.builder.build_load(result_alloca, "load_result") + } + RocReturn::ByPointer => { + let mut arguments = Vec::from_iter_in(arguments.iter().copied(), env.arena); + + let result_type = basic_type_from_layout(env, result_layout); + let result_alloca = tag_alloca(env, result_type, "result_value"); + + arguments.push(result_alloca.into()); + + debug_assert_eq!( + roc_function.get_type().get_param_types().len(), + arguments.len() + ); + let call = env.builder.build_call(roc_function, &arguments, "call"); + + // roc functions should have the fast calling convention + debug_assert_eq!(roc_function.get_call_conventions(), FAST_CALL_CONV); + call.set_call_convention(FAST_CALL_CONV); + + if result_layout.is_passed_by_reference() { + result_alloca.into() + } else { + env.builder + .build_load(result_alloca, "return_by_pointer_load_result") + } + } + RocReturn::Return => { + debug_assert_eq!( + roc_function.get_type().get_param_types().len(), + arguments.len() + ); + let call = env.builder.build_call(roc_function, arguments, "call"); + + // roc functions should have the fast calling convention + debug_assert_eq!(roc_function.get_call_conventions(), FAST_CALL_CONV); + call.set_call_convention(FAST_CALL_CONV); + + call.try_as_basic_value().left().unwrap_or_else(|| { + panic!( + "LLVM error: Invalid call by name for name {:?}", + roc_function.get_name() + ) + }) + } + } } /// Translates a target_lexicon::Triple to a LLVM calling convention u32 @@ -4374,6 +4626,7 @@ pub struct RocFunctionCall<'ctx> { pub data_is_owned: IntValue<'ctx>, } +#[allow(clippy::too_many_arguments)] fn roc_function_call<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, @@ -4382,6 +4635,7 @@ fn roc_function_call<'a, 'ctx, 'env>( lambda_set: LambdaSet<'a>, closure_data_is_owned: bool, argument_layouts: &[Layout<'a>], + result_layout: Layout<'a>, ) -> RocFunctionCall<'ctx> { use crate::llvm::bitcode::{build_inc_n_wrapper, build_transform_caller}; @@ -4390,9 +4644,10 @@ fn roc_function_call<'a, 'ctx, 'env>( .build_alloca(closure_data.get_type(), "closure_data_ptr"); env.builder.build_store(closure_data_ptr, closure_data); - let stepper_caller = build_transform_caller(env, transform, lambda_set, argument_layouts) - .as_global_value() - .as_pointer_value(); + let stepper_caller = + build_transform_caller(env, transform, lambda_set, argument_layouts, result_layout) + .as_global_value() + .as_pointer_value(); let inc_closure_data = build_inc_n_wrapper(env, layout_ids, &lambda_set.runtime_representation()) @@ -4418,16 +4673,24 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( layout_ids: &mut LayoutIds<'a>, scope: &Scope<'a, 'ctx>, return_layout: &Layout<'a>, - op: roc_mono::low_level::HigherOrder, func_spec: FuncSpec, - argument_layouts: &[Layout<'a>], - result_layout: &Layout<'a>, - function_owns_closure_data: bool, - function_name: Symbol, - function_env: &Symbol, + higher_order: &HigherOrderLowLevel<'a>, ) -> BasicValueEnum<'ctx> { use roc_mono::low_level::HigherOrder::*; + let HigherOrderLowLevel { + op, + arg_layouts: argument_layouts, + ret_layout: result_layout, + function_owns_closure_data, + function_name, + function_env, + .. + } = higher_order; + + let function_owns_closure_data = *function_owns_closure_data; + let function_name = *function_name; + // macros because functions cause lifetime issues related to the `env` or `layout_ids` macro_rules! function_details { () => {{ @@ -4465,6 +4728,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + *result_layout, ); crate::llvm::build_list::list_walk_generic( @@ -4486,7 +4750,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( match op { ListMap { xs } => { // List.map : List before, (before -> after) -> List after - let (list, list_layout) = load_symbol_and_layout(scope, &xs); + let (list, list_layout) = load_symbol_and_layout(scope, xs); let (function, closure, closure_layout) = function_details!(); @@ -4506,6 +4770,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + **result_layout, ); list_map(env, roc_function_call, list, element_layout, result_layout) @@ -4514,8 +4779,8 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( } } ListMap2 { xs, ys } => { - let (list1, list1_layout) = load_symbol_and_layout(scope, &xs); - let (list2, list2_layout) = load_symbol_and_layout(scope, &ys); + let (list1, list1_layout) = load_symbol_and_layout(scope, xs); + let (list2, list2_layout) = load_symbol_and_layout(scope, ys); let (function, closure, closure_layout) = function_details!(); @@ -4535,6 +4800,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + **result_layout, ); list_map2( @@ -4554,9 +4820,9 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( } } ListMap3 { xs, ys, zs } => { - let (list1, list1_layout) = load_symbol_and_layout(scope, &xs); - let (list2, list2_layout) = load_symbol_and_layout(scope, &ys); - let (list3, list3_layout) = load_symbol_and_layout(scope, &zs); + let (list1, list1_layout) = load_symbol_and_layout(scope, xs); + let (list2, list2_layout) = load_symbol_and_layout(scope, ys); + let (list3, list3_layout) = load_symbol_and_layout(scope, zs); let (function, closure, closure_layout) = function_details!(); @@ -4578,6 +4844,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + **result_layout, ); list_map3( @@ -4600,10 +4867,10 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( } } ListMap4 { xs, ys, zs, ws } => { - let (list1, list1_layout) = load_symbol_and_layout(scope, &xs); - let (list2, list2_layout) = load_symbol_and_layout(scope, &ys); - let (list3, list3_layout) = load_symbol_and_layout(scope, &zs); - let (list4, list4_layout) = load_symbol_and_layout(scope, &ws); + let (list1, list1_layout) = load_symbol_and_layout(scope, xs); + let (list2, list2_layout) = load_symbol_and_layout(scope, ys); + let (list3, list3_layout) = load_symbol_and_layout(scope, zs); + let (list4, list4_layout) = load_symbol_and_layout(scope, ws); let (function, closure, closure_layout) = function_details!(); @@ -4636,6 +4903,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + **result_layout, ); list_map4( @@ -4662,7 +4930,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( } ListMapWithIndex { xs } => { // List.mapWithIndex : List before, (Nat, before -> after) -> List after - let (list, list_layout) = load_symbol_and_layout(scope, &xs); + let (list, list_layout) = load_symbol_and_layout(scope, xs); let (function, closure, closure_layout) = function_details!(); @@ -4682,6 +4950,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + **result_layout, ); list_map_with_index(env, roc_function_call, list, element_layout, result_layout) @@ -4691,7 +4960,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( } ListKeepIf { xs } => { // List.keepIf : List elem, (elem -> Bool) -> List elem - let (list, list_layout) = load_symbol_and_layout(scope, &xs); + let (list, list_layout) = load_symbol_and_layout(scope, xs); let (function, closure, closure_layout) = function_details!(); @@ -4708,6 +4977,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + *result_layout, ); list_keep_if(env, layout_ids, roc_function_call, list, element_layout) @@ -4717,7 +4987,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( } ListKeepOks { xs } => { // List.keepOks : List before, (before -> Result after *) -> List after - let (list, list_layout) = load_symbol_and_layout(scope, &xs); + let (list, list_layout) = load_symbol_and_layout(scope, xs); let (function, closure, closure_layout) = function_details!(); @@ -4738,6 +5008,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + *result_layout, ); list_keep_oks( @@ -4757,7 +5028,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( } ListKeepErrs { xs } => { // List.keepErrs : List before, (before -> Result * after) -> List after - let (list, list_layout) = load_symbol_and_layout(scope, &xs); + let (list, list_layout) = load_symbol_and_layout(scope, xs); let (function, closure, closure_layout) = function_details!(); @@ -4778,6 +5049,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + *result_layout, ); list_keep_errs( @@ -4806,7 +5078,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( } ListSortWith { xs } => { // List.sortWith : List a, (a, a -> Ordering) -> List a - let (list, list_layout) = load_symbol_and_layout(scope, &xs); + let (list, list_layout) = load_symbol_and_layout(scope, xs); let (function, closure, closure_layout) = function_details!(); @@ -4830,6 +5102,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + *result_layout, ); list_sort_with( @@ -4844,7 +5117,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( } } ListAny { xs } => { - let (list, list_layout) = load_symbol_and_layout(scope, &xs); + let (list, list_layout) = load_symbol_and_layout(scope, xs); let (function, closure, closure_layout) = function_details!(); match list_layout { @@ -4860,6 +5133,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + Layout::Builtin(Builtin::Int1), ); list_any(env, roc_function_call, list, element_layout) @@ -4867,8 +5141,35 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( _ => unreachable!("invalid list layout"), } } + ListAll { xs } => { + let (list, list_layout) = load_symbol_and_layout(scope, xs); + let (function, closure, closure_layout) = function_details!(); + + match list_layout { + Layout::Builtin(Builtin::EmptyList) => { + env.context.bool_type().const_int(1, false).into() + } + Layout::Builtin(Builtin::List(element_layout)) => { + let argument_layouts = &[**element_layout]; + + let roc_function_call = roc_function_call( + env, + layout_ids, + function, + closure, + closure_layout, + function_owns_closure_data, + argument_layouts, + Layout::Builtin(Builtin::Int1), + ); + + list_all(env, roc_function_call, list, element_layout) + } + _ => unreachable!("invalid list layout"), + } + } ListFindUnsafe { xs } => { - let (list, list_layout) = load_symbol_and_layout(scope, &xs); + let (list, list_layout) = load_symbol_and_layout(scope, xs); let (function, closure, closure_layout) = function_details!(); @@ -4892,6 +5193,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + Layout::Builtin(Builtin::Int1), ); list_find_unsafe(env, layout_ids, roc_function_call, list, element_layout) } @@ -4899,8 +5201,8 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( } } DictWalk { xs, state } => { - let (dict, dict_layout) = load_symbol_and_layout(scope, &xs); - let (default, default_layout) = load_symbol_and_layout(scope, &state); + let (dict, dict_layout) = load_symbol_and_layout(scope, xs); + let (default, default_layout) = load_symbol_and_layout(scope, state); let (function, closure, closure_layout) = function_details!(); @@ -4920,6 +5222,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( closure_layout, function_owns_closure_data, argument_layouts, + *result_layout, ); dict_walk( @@ -5071,6 +5374,12 @@ fn run_low_level<'a, 'ctx, 'env>( str_trim_left(env, scope, args[0]) } + StrTrimRight => { + // Str.trim : Str -> Str + debug_assert_eq!(args.len(), 1); + + str_trim_right(env, scope, args[0]) + } ListLen => { // List.len : List * -> Int debug_assert_eq!(args.len(), 1); @@ -5169,66 +5478,30 @@ fn run_low_level<'a, 'ctx, 'env>( _ => unreachable!("Invalid layout {:?} in List.swap", list_layout), } } - ListTakeFirst => { - // List.takeFirst : List elem, Nat -> List elem - debug_assert_eq!(args.len(), 2); + ListSublist => { + // List.sublist : List elem, { start : Nat, len : Nat } -> List elem + // + // As a low-level, record is destructed + // List.sublist : List elem, start : Nat, len : Nat -> List elem + debug_assert_eq!(args.len(), 3); let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); let original_wrapper = list.into_struct_value(); - let count = load_symbol(scope, &args[1]); + let start = load_symbol(scope, &args[1]); + let len = load_symbol(scope, &args[2]); match list_layout { Layout::Builtin(Builtin::EmptyList) => empty_list(env), - Layout::Builtin(Builtin::List(element_layout)) => list_take_first( - env, - original_wrapper, - count.into_int_value(), - element_layout, - ), - _ => unreachable!("Invalid layout {:?} in List.takeFirst", list_layout), - } - } - ListTakeLast => { - // List.takeLast : List elem, Nat -> List elem - debug_assert_eq!(args.len(), 2); - - let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); - let original_wrapper = list.into_struct_value(); - - let count = load_symbol(scope, &args[1]); - - match list_layout { - Layout::Builtin(Builtin::EmptyList) => empty_list(env), - Layout::Builtin(Builtin::List(element_layout)) => list_take_last( + Layout::Builtin(Builtin::List(element_layout)) => list_sublist( env, layout_ids, original_wrapper, - count.into_int_value(), + start.into_int_value(), + len.into_int_value(), element_layout, ), - _ => unreachable!("Invalid layout {:?} in List.takeLast", list_layout), - } - } - ListDrop => { - // List.drop : List elem, Nat -> List elem - debug_assert_eq!(args.len(), 2); - - let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); - let original_wrapper = list.into_struct_value(); - - let count = load_symbol(scope, &args[1]); - - match list_layout { - Layout::Builtin(Builtin::EmptyList) => empty_list(env), - Layout::Builtin(Builtin::List(element_layout)) => list_drop( - env, - layout_ids, - original_wrapper, - count.into_int_value(), - element_layout, - ), - _ => unreachable!("Invalid layout {:?} in List.drop", list_layout), + _ => unreachable!("Invalid layout {:?} in List.sublist", list_layout), } } ListDropAt => { @@ -5795,7 +6068,7 @@ fn run_low_level<'a, 'ctx, 'env>( ListMap | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith - | ListAny | ListFindUnsafe | DictWalk => { + | ListAny | ListAll | ListFindUnsafe | DictWalk => { unreachable!("these are higher order, and are handled elsewhere") } } @@ -5844,6 +6117,34 @@ fn to_cc_type_builtin<'a, 'ctx, 'env>( } } +enum RocReturn { + /// Return as normal + Return, + /// require an extra argument, a pointer + /// where the result is written into returns void + ByPointer, +} + +impl RocReturn { + fn roc_return_by_pointer(layout: Layout) -> bool { + match layout { + Layout::Union(UnionLayout::NonRecursive(_)) => true, + Layout::LambdaSet(lambda_set) => { + RocReturn::roc_return_by_pointer(lambda_set.runtime_representation()) + } + _ => false, + } + } + + fn from_layout<'a, 'ctx, 'env>(_env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> Self { + if Self::roc_return_by_pointer(*layout) { + RocReturn::ByPointer + } else { + RocReturn::Return + } + } +} + enum CCReturn { /// Return as normal Return, @@ -5939,7 +6240,7 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( env.module, &fastcc_function_name, fastcc_type, - Linkage::Private, + Linkage::Internal, FAST_CALL_CONV, ); diff --git a/compiler/gen_llvm/src/llvm/build_hash.rs b/compiler/gen_llvm/src/llvm/build_hash.rs index d3dc006198..261af4e7d4 100644 --- a/compiler/gen_llvm/src/llvm/build_hash.rs +++ b/compiler/gen_llvm/src/llvm/build_hash.rs @@ -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 diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 7112c4b19a..a62b7a2f47 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -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); }) diff --git a/compiler/gen_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index e9bd164b0a..975d979866 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -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>, diff --git a/compiler/gen_llvm/src/llvm/compare.rs b/compiler/gen_llvm/src/llvm/compare.rs index 1ca6178c0b..c624890616 100644 --- a/compiler/gen_llvm/src/llvm/compare.rs +++ b/compiler/gen_llvm/src/llvm/compare.rs @@ -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>, 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, ) diff --git a/compiler/gen_llvm/src/llvm/convert.rs b/compiler/gen_llvm/src/llvm/convert.rs index d32216c10a..98306210ad 100644 --- a/compiler/gen_llvm/src/llvm/convert.rs +++ b/compiler/gen_llvm/src/llvm/convert.rs @@ -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<'_>, diff --git a/compiler/gen_llvm/src/llvm/refcounting.rs b/compiler/gen_llvm/src/llvm/refcounting.rs index b6cd354349..b7f4e01eeb 100644 --- a/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/compiler/gen_llvm/src/llvm/refcounting.rs @@ -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, ); } diff --git a/compiler/gen_wasm/lib/libc.a b/compiler/gen_wasm/lib/libc.a new file mode 100644 index 0000000000..de6e3c2aa4 Binary files /dev/null and b/compiler/gen_wasm/lib/libc.a differ diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index a43ff579e3..aba1aaa29d 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -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; 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( diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index c1f2eec941..a8b19213dd 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -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, 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, 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 { diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index 48eafa1621..d94c6f8c7c 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -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(); diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index f99beea619..cf4f336c06 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -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); } diff --git a/compiler/gen_wasm/src/wasm_module/code_builder.rs b/compiler/gen_wasm/src/wasm_module/code_builder.rs index 559221bb0b..2109f3e436 100644 --- a/compiler/gen_wasm/src/wasm_module/code_builder.rs +++ b/compiler/gen_wasm/src/wasm_module/code_builder.rs @@ -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 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 { - use VirtualMachineSymbolState::*; + ) -> Option { + 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( @@ -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(&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', diff --git a/compiler/gen_wasm/src/wasm_module/mod.rs b/compiler/gen_wasm/src/wasm_module/mod.rs index b925bfafd7..2a404e9b48 100644 --- a/compiler/gen_wasm/src/wasm_module/mod.rs +++ b/compiler/gen_wasm/src/wasm_module/mod.rs @@ -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}; diff --git a/compiler/gen_wasm/src/wasm_module/opcodes.rs b/compiler/gen_wasm/src/wasm_module/opcodes.rs index 895cf1e5f7..868b77375d 100644 --- a/compiler/gen_wasm/src/wasm_module/opcodes.rs +++ b/compiler/gen_wasm/src/wasm_module/opcodes.rs @@ -1,5 +1,5 @@ #[repr(u8)] -#[derive(Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum OpCode { UNREACHABLE = 0x00, NOP = 0x01, diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 2ff0805604..495a738a6b 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -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(&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 diff --git a/compiler/load/Cargo.toml b/compiler/load/Cargo.toml index 2fef36c603..ac6505b016 100644 --- a/compiler/load/Cargo.toml +++ b/compiler/load/Cargo.toml @@ -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"] } diff --git a/compiler/load/src/docs.rs b/compiler/load/src/docs.rs index 0cbc74ea83..edadfbcd5b 100644 --- a/compiler/load/src/docs.rs +++ b/compiler/load/src/docs.rs @@ -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 = 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; diff --git a/compiler/load/src/effect_module.rs b/compiler/load/src/effect_module.rs index eeea64d7a1..24f46cda44 100644 --- a/compiler/load/src/effect_module.rs +++ b/compiler/load/src/effect_module.rs @@ -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}; diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 0d20be6415..092153bb5f 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -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, typechecked: MutMap>, found_specializations: MutMap>, - external_specializations_requested: MutMap>, + external_specializations_requested: MutMap>, /// Various information imports: MutMap>, @@ -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>, + external_specializations_requested: BumpMap, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, problems: Vec, module_timing: ModuleTiming, @@ -911,9 +910,6 @@ struct State<'a> { /// pending specializations in the same thread. pub needs_specialization: MutSet, - pub all_pending_specializations: - MutMap, PendingSpecialization<'a>>>, - pub specializations_in_flight: u32, pub timings: MutMap, @@ -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, 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>] = - header.provides.clone().into_bump_slice(); + let provides: &'a [Located>] = 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, 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>, module_thunks: &'a [Symbol], /// A host-exposed function must be specialized; it's a seed for subsequent specializations - specializations_for_host: BumpMap, PendingSpecialization<'a>>>, + host_specializations: roc_mono::ir::HostSpecializations, runtime_errors: BumpMap, 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 diff --git a/compiler/module/src/operator.rs b/compiler/module/src/called_via.rs similarity index 95% rename from compiler/module/src/operator.rs rename to compiler/module/src/called_via.rs index 14ba2de17b..a53b20ab08 100644 --- a/compiler/module/src/operator.rs +++ b/compiler/module/src/called_via.rs @@ -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)] diff --git a/compiler/module/src/lib.rs b/compiler/module/src/lib.rs index 3b0c3eea17..044f697a07 100644 --- a/compiler/module/src/lib.rs +++ b/compiler/module/src/lib.rs @@ -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] diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 8ac9bdd2c0..9405956fc0 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -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 { + 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, } } } diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 882e79ef62..d7747a8e21 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -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 diff --git a/compiler/mono/Cargo.toml b/compiler/mono/Cargo.toml index 6075ecb44c..02045363d6 100644 --- a/compiler/mono/Cargo.toml +++ b/compiler/mono/Cargo.toml @@ -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" diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index bd90890392..306b2a52ae 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -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>, @@ -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])? diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 965aa5698a..bc5492c8bf 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -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(_) => {} } } diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index 16b3ea0a84..841866353b 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -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), 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>, + branches: &[Branch<'a>], test: GuardedTest<'a>, ) -> (GuardedTest<'a>, Vec>) { 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 diff --git a/compiler/mono/src/exhaustive.rs b/compiler/mono/src/exhaustive.rs index 77c28d64a1..e1b2847eea 100644 --- a/compiler/mono/src/exhaustive.rs +++ b/compiler/mono/src/exhaustive.rs @@ -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, diff --git a/compiler/mono/src/inc_dec.rs b/compiler/mono/src/inc_dec.rs index d377795253..c6dfd67314 100644 --- a/compiler/mono/src/inc_dec.rs +++ b/compiler/mono/src/inc_dec.rs @@ -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 diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index c0c02f3466..430ac4c786 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -1,14 +1,12 @@ #![allow(clippy::manual_map)] -use self::InProgressProc::*; use crate::exhaustive::{Ctor, Guard, RenderAs, TagId}; use crate::layout::{ Builtin, ClosureRepresentation, LambdaSet, Layout, LayoutCache, LayoutProblem, - RawFunctionLayout, UnionLayout, WrappedVariant, + RawFunctionLayout, TagIdIntType, UnionLayout, WrappedVariant, }; use bumpalo::collections::Vec; use bumpalo::Bump; -use hashbrown::hash_map::Entry; use roc_can::expr::ClosureData; use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; @@ -17,13 +15,30 @@ use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_problem::can::RuntimeError; use roc_region::all::{Located, Region}; use roc_std::RocDec; -use roc_types::solved_types::SolvedType; -use roc_types::subs::{Content, FlatType, Subs, Variable, VariableSubsSlice}; +use roc_types::subs::{Content, FlatType, StorageSubs, Subs, Variable, VariableSubsSlice}; use std::collections::HashMap; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder}; pub const PRETTY_PRINT_IR_SYMBOLS: bool = false; +// 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 + +// i128 alignment is different on arm +#[cfg(target_arch = "aarch64")] +static_assertions::assert_eq_size!([u8; 4 * 8], Literal); +#[cfg(not(target_arch = "aarch64"))] +static_assertions::assert_eq_size!([u8; 3 * 8], Literal); +static_assertions::assert_eq_size!([u8; 10 * 8], Expr); +#[cfg(not(target_arch = "aarch64"))] +static_assertions::assert_eq_size!([u8; 19 * 8], Stmt); +#[cfg(target_arch = "aarch64")] +static_assertions::assert_eq_size!([u8; 20 * 8], Stmt); +static_assertions::assert_eq_size!([u8; 6 * 8], ProcLayout); +static_assertions::assert_eq_size!([u8; 8 * 8], Call); +static_assertions::assert_eq_size!([u8; 6 * 8], CallType); + macro_rules! return_on_layout_error { ($env:expr, $layout_result:expr) => { match $layout_result { @@ -206,65 +221,6 @@ impl<'a> Default for CapturedSymbols<'a> { } } -#[derive(Clone, Debug, PartialEq)] -pub struct PendingSpecialization<'a> { - solved_type: SolvedType, - host_exposed_aliases: BumpMap, - _lifetime: std::marker::PhantomData<&'a u8>, -} - -impl<'a> PendingSpecialization<'a> { - pub fn from_var(arena: &'a Bump, subs: &Subs, var: Variable) -> Self { - let solved_type = SolvedType::from_var(subs, var); - PendingSpecialization { - solved_type, - host_exposed_aliases: BumpMap::new_in(arena), - _lifetime: std::marker::PhantomData, - } - } - - pub fn from_var_host_exposed( - arena: &'a Bump, - subs: &Subs, - var: Variable, - exposed: &MutMap, - ) -> Self { - let solved_type = SolvedType::from_var(subs, var); - - let mut host_exposed_aliases = BumpMap::with_capacity_in(exposed.len(), arena); - - host_exposed_aliases.extend( - exposed - .iter() - .map(|(symbol, variable)| (*symbol, SolvedType::from_var(subs, *variable))), - ); - - PendingSpecialization { - solved_type, - host_exposed_aliases, - _lifetime: std::marker::PhantomData, - } - } - - /// Add a named function that will be publicly exposed to the host - pub fn from_exposed_function( - arena: &'a Bump, - subs: &Subs, - opt_annotation: Option, - fn_var: Variable, - ) -> Self { - match opt_annotation { - None => PendingSpecialization::from_var(arena, subs, fn_var), - Some(annotation) => PendingSpecialization::from_var_host_exposed( - arena, - subs, - fn_var, - &annotation.introduced_variables.host_exposed_aliases, - ), - } - } -} - #[derive(Clone, Debug, PartialEq)] pub struct Proc<'a> { pub name: Symbol, @@ -398,44 +354,302 @@ impl<'a> Proc<'a> { } } +/// A host-exposed function must be specialized; it's a seed for subsequent specializations #[derive(Clone, Debug)] -pub struct ExternalSpecializations<'a> { +pub struct HostSpecializations { /// Not a bumpalo vec because bumpalo is not thread safe - pub specs: BumpMap>, - _lifetime: std::marker::PhantomData<&'a u8>, + /// Separate array so we can search for membership quickly + symbols: std::vec::Vec, + storage_subs: StorageSubs, + /// For each symbol, what types to specialize it for, points into the storage_subs + types_to_specialize: std::vec::Vec, + /// Variables for an exposed alias + exposed_aliases: std::vec::Vec>, } -impl<'a> ExternalSpecializations<'a> { - pub fn new_in(arena: &'a Bump) -> Self { +impl Default for HostSpecializations { + fn default() -> Self { + Self::new() + } +} + +impl HostSpecializations { + pub fn new() -> Self { Self { - specs: BumpMap::new_in(arena), - _lifetime: std::marker::PhantomData, + symbols: std::vec::Vec::new(), + storage_subs: StorageSubs::new(Subs::default()), + types_to_specialize: std::vec::Vec::new(), + exposed_aliases: std::vec::Vec::new(), } } - pub fn insert(&mut self, symbol: Symbol, typ: SolvedType) { - use hashbrown::hash_map::Entry::{Occupied, Vacant}; + pub fn insert_host_exposed( + &mut self, + env_subs: &mut Subs, + symbol: Symbol, + opt_annotation: Option, + variable: Variable, + ) { + let variable = self.storage_subs.extend_with_variable(env_subs, variable); - let existing = match self.specs.entry(symbol) { - Vacant(entry) => entry.insert(std::vec::Vec::new()), - Occupied(entry) => entry.into_mut(), - }; + let mut host_exposed_aliases = std::vec::Vec::new(); - existing.push(typ); + if let Some(annotation) = opt_annotation { + host_exposed_aliases.extend(annotation.introduced_variables.host_exposed_aliases); + } + + match self.symbols.iter().position(|s| *s == symbol) { + None => { + self.symbols.push(symbol); + self.types_to_specialize.push(variable); + self.exposed_aliases.push(host_exposed_aliases); + } + Some(_) => { + // we assume that only one specialization of a function is directly exposed to the + // host. Other host-exposed symbols may (transitively) specialize this symbol, + // but then the existing specialization mechanism will find those specializations + panic!("A host-exposed symbol can only be exposed once"); + } + } + + debug_assert_eq!(self.types_to_specialize.len(), self.exposed_aliases.len()); } - pub fn extend(&mut self, other: Self) { - use hashbrown::hash_map::Entry::{Occupied, Vacant}; + fn decompose( + self, + ) -> ( + StorageSubs, + impl Iterator)>, + ) { + let it1 = self.symbols.into_iter(); - for (symbol, solved_types) in other.specs { - let existing = match self.specs.entry(symbol) { - Vacant(entry) => entry.insert(std::vec::Vec::new()), - Occupied(entry) => entry.into_mut(), - }; + let it2 = self.types_to_specialize.into_iter(); + let it3 = self.exposed_aliases.into_iter(); - existing.extend(solved_types); + ( + self.storage_subs, + it1.zip(it2).zip(it3).map(|((a, b), c)| (a, b, c)), + ) + } +} + +/// Specializations of this module's symbols that other modules need +#[derive(Clone, Debug)] +pub struct ExternalSpecializations { + /// Not a bumpalo vec because bumpalo is not thread safe + /// Separate array so we can search for membership quickly + symbols: std::vec::Vec, + storage_subs: StorageSubs, + /// For each symbol, what types to specialize it for, points into the storage_subs + types_to_specialize: std::vec::Vec>, +} + +impl Default for ExternalSpecializations { + fn default() -> Self { + Self::new() + } +} + +impl ExternalSpecializations { + pub fn new() -> Self { + Self { + symbols: std::vec::Vec::new(), + storage_subs: StorageSubs::new(Subs::default()), + types_to_specialize: std::vec::Vec::new(), } } + + fn insert_external(&mut self, symbol: Symbol, env_subs: &mut Subs, variable: Variable) { + let variable = self.storage_subs.extend_with_variable(env_subs, variable); + + match self.symbols.iter().position(|s| *s == symbol) { + None => { + self.symbols.push(symbol); + self.types_to_specialize.push(vec![variable]); + } + Some(index) => { + let types_to_specialize = &mut self.types_to_specialize[index]; + types_to_specialize.push(variable); + } + } + } + + fn decompose( + self, + ) -> ( + StorageSubs, + impl Iterator)>, + ) { + ( + self.storage_subs, + self.symbols + .into_iter() + .zip(self.types_to_specialize.into_iter()), + ) + } +} + +#[derive(Clone, Debug)] +pub struct Suspended<'a> { + pub store: StorageSubs, + pub symbols: Vec<'a, Symbol>, + pub layouts: Vec<'a, ProcLayout<'a>>, + pub variables: Vec<'a, Variable>, +} + +impl<'a> Suspended<'a> { + fn new_in(arena: &'a Bump) -> Self { + Self { + store: StorageSubs::new(Subs::new_from_varstore(Default::default())), + symbols: Vec::new_in(arena), + layouts: Vec::new_in(arena), + variables: Vec::new_in(arena), + } + } + + fn specialization( + &mut self, + subs: &mut Subs, + symbol: Symbol, + proc_layout: ProcLayout<'a>, + variable: Variable, + ) { + // de-duplicate + for (i, s) in self.symbols.iter().enumerate() { + if *s == symbol { + let existing = &self.layouts[i]; + if &proc_layout == existing { + // symbol + layout combo exists + return; + } + } + } + + self.symbols.push(symbol); + self.layouts.push(proc_layout); + + let variable = self.store.extend_with_variable(subs, variable); + + self.variables.push(variable); + } +} + +#[derive(Clone, Debug)] +enum PendingSpecializations<'a> { + /// We are finding specializations we need. This is a separate step so + /// that we can give specializations we need to modules higher up in the dependency chain, so + /// that they can start making specializations too + Finding(Suspended<'a>), + /// We are making specializations. If any new one comes up, we can just make it immediately + Making, +} + +#[derive(Clone, Debug, Default)] +struct Specialized<'a> { + symbols: std::vec::Vec, + proc_layouts: std::vec::Vec>, + procedures: std::vec::Vec>, +} + +impl<'a> Specialized<'a> { + fn len(&self) -> usize { + self.symbols.len() + } + + #[allow(dead_code)] + fn is_empty(&self) -> bool { + self.symbols.is_empty() + } + + fn into_iter_assert_done(self) -> impl Iterator, Proc<'a>)> { + self.symbols + .into_iter() + .zip(self.proc_layouts.into_iter()) + .zip(self.procedures.into_iter()) + .filter_map(|((s, l), in_progress)| { + if let Symbol::REMOVED_SPECIALIZATION = s { + None + } else { + match in_progress { + InProgressProc::InProgress => panic!("Function is not done specializing"), + InProgressProc::Done(proc) => Some((s, l, proc)), + } + } + }) + } + + fn is_specialized(&mut self, symbol: Symbol, layout: &ProcLayout<'a>) -> bool { + for (i, s) in self.symbols.iter().enumerate() { + if *s == symbol && &self.proc_layouts[i] == layout { + return true; + } + } + + false + } + + fn mark_in_progress(&mut self, symbol: Symbol, layout: ProcLayout<'a>) { + for (i, s) in self.symbols.iter().enumerate() { + if *s == symbol && self.proc_layouts[i] == layout { + match &self.procedures[i] { + InProgressProc::InProgress => { + return; + } + InProgressProc::Done(_) => { + panic!("marking in progress, but this proc is already done!") + } + } + } + } + + // the key/layout combo was not found; insert it + self.symbols.push(symbol); + self.proc_layouts.push(layout); + self.procedures.push(InProgressProc::InProgress); + } + + fn remove_specialized(&mut self, symbol: Symbol, layout: &ProcLayout<'a>) -> bool { + let mut index = None; + + for (i, s) in self.symbols.iter().enumerate() { + if *s == symbol && &self.proc_layouts[i] == layout { + index = Some(i); + } + } + + if let Some(index) = index { + self.symbols[index] = Symbol::REMOVED_SPECIALIZATION; + + true + } else { + false + } + } + + fn insert_specialized(&mut self, symbol: Symbol, layout: ProcLayout<'a>, proc: Proc<'a>) { + for (i, s) in self.symbols.iter().enumerate() { + if *s == symbol && self.proc_layouts[i] == layout { + match &self.procedures[i] { + InProgressProc::InProgress => { + self.procedures[i] = InProgressProc::Done(proc); + return; + } + InProgressProc::Done(_) => { + // overwrite existing! this is important in practice + // TODO investigate why we generate the wrong proc in some cases and then + // correct later + self.procedures[i] = InProgressProc::Done(proc); + return; + } + } + } + } + + // the key/layout combo was not found; insert it + self.symbols.push(symbol); + self.proc_layouts.push(layout); + self.procedures.push(InProgressProc::Done(proc)); + } } #[derive(Clone, Debug)] @@ -443,12 +657,10 @@ pub struct Procs<'a> { pub partial_procs: PartialProcs<'a>, pub imported_module_thunks: &'a [Symbol], pub module_thunks: &'a [Symbol], - pub pending_specializations: - Option, PendingSpecialization<'a>>>>, - pub specialized: BumpMap<(Symbol, ProcLayout<'a>), InProgressProc<'a>>, + pending_specializations: PendingSpecializations<'a>, + specialized: Specialized<'a>, pub runtime_errors: BumpMap, - pub call_by_pointer_wrappers: BumpMap, - pub externals_we_need: BumpMap>, + pub externals_we_need: BumpMap, } impl<'a> Procs<'a> { @@ -457,10 +669,9 @@ impl<'a> Procs<'a> { partial_procs: PartialProcs::new_in(arena), imported_module_thunks: &[], module_thunks: &[], - pending_specializations: Some(BumpMap::new_in(arena)), - specialized: BumpMap::new_in(arena), + pending_specializations: PendingSpecializations::Finding(Suspended::new_in(arena)), + specialized: Specialized::default(), runtime_errors: BumpMap::new_in(arena), - call_by_pointer_wrappers: BumpMap::new_in(arena), externals_we_need: BumpMap::new_in(arena), } } @@ -491,23 +702,11 @@ impl<'a> Procs<'a> { ) -> MutMap<(Symbol, ProcLayout<'a>), Proc<'a>> { let mut result = MutMap::with_capacity_and_hasher(self.specialized.len(), default_hasher()); - for (key, in_prog_proc) in self.specialized.into_iter() { - match in_prog_proc { - InProgress => { - let (symbol, layout) = key; - eprintln!( - "The procedure {:?} should have be done by now:\n\n {:?}", - symbol, layout - ); + for (symbol, layout, mut proc) in self.specialized.into_iter_assert_done() { + proc.make_tail_recursive(env); - panic!(); - } - Done(mut proc) => { - proc.make_tail_recursive(env); - - result.insert(key, proc); - } - } + let key = (symbol, layout); + result.insert(key, proc); } result @@ -541,25 +740,20 @@ impl<'a> Procs<'a> { // by the surrounding context, so we can add pending specializations // for them immediately. - let already_specialized = self - .specialized - .keys() - .any(|(s, t)| *s == symbol && *t == top_level); + let already_specialized = self.specialized.is_specialized(symbol, &top_level); let layout = top_level; // if we've already specialized this one, no further work is needed. if !already_specialized { - let pending = PendingSpecialization::from_var(env.arena, env.subs, annotation); - if self.is_module_thunk(symbol) { debug_assert!(layout.arguments.is_empty()); } match &mut self.pending_specializations { - Some(pending_specializations) => { + PendingSpecializations::Finding(suspended) => { // register the pending specialization, so this gets code genned later - add_pending(pending_specializations, symbol, layout, pending); + suspended.specialization(env.subs, symbol, layout, annotation); match self.partial_procs.symbol_to_id(symbol) { Some(occupied) => { @@ -590,11 +784,11 @@ impl<'a> Procs<'a> { } } } - None => { + PendingSpecializations::Making => { // Mark this proc as in-progress, so if we're dealing with // mutually recursive functions, we don't loop forever. // (We had a bug around this before this system existed!) - self.specialized.insert((symbol, layout), InProgress); + self.specialized.mark_in_progress(symbol, layout); let outside_layout = layout; @@ -626,12 +820,13 @@ impl<'a> Procs<'a> { self.partial_procs.insert(symbol, partial_proc) }; - match specialize( + match specialize_variable( env, self, symbol, layout_cache, - pending, + annotation, + &[], partial_proc_id, ) { Ok((proc, layout)) => { @@ -647,7 +842,7 @@ impl<'a> Procs<'a> { debug_assert!(top_level.arguments.is_empty()); } - self.specialized.insert((symbol, top_level), Done(proc)); + self.specialized.insert_specialized(symbol, top_level, proc); } Err(error) => { panic!("TODO generate a RuntimeError message for {:?}", error); @@ -672,7 +867,7 @@ impl<'a> Procs<'a> { layout_cache: &mut LayoutCache<'a>, ) { // If we've already specialized this one, no further work is needed. - if self.specialized.contains_key(&(name, layout)) { + if self.specialized.is_specialized(name, &layout) { return; } @@ -690,12 +885,10 @@ impl<'a> Procs<'a> { // This should only be called when pending_specializations is Some. // Otherwise, it's being called in the wrong pass! match &mut self.pending_specializations { - Some(pending_specializations) => { - let pending = PendingSpecialization::from_var(env.arena, env.subs, fn_var); - - add_pending(pending_specializations, name, layout, pending) + PendingSpecializations::Finding(suspended) => { + suspended.specialization(env.subs, name, layout, fn_var); } - None => { + PendingSpecializations::Making => { let symbol = name; let partial_proc_id = match self.partial_procs.symbol_to_id(symbol) { @@ -706,7 +899,7 @@ impl<'a> Procs<'a> { // Mark this proc as in-progress, so if we're dealing with // mutually recursive functions, we don't loop forever. // (We had a bug around this before this system existed!) - self.specialized.insert((symbol, layout), InProgress); + self.specialized.mark_in_progress(symbol, layout); // See https://github.com/rtfeldman/roc/issues/1600 // @@ -744,8 +937,9 @@ impl<'a> Procs<'a> { // NOTE: some function are specialized to have a closure, but don't actually // need any closure argument. Here is where we correct this sort of thing, // by trusting the layout of the Proc, not of what we specialize for - self.specialized.remove(&(symbol, layout)); - self.specialized.insert((symbol, proper_layout), Done(proc)); + self.specialized.remove_specialized(symbol, &layout); + self.specialized + .insert_specialized(symbol, proper_layout, proc); } Err(error) => { panic!("TODO generate a RuntimeError message for {:?}", error); @@ -756,22 +950,6 @@ impl<'a> Procs<'a> { } } -fn add_pending<'a>( - pending_specializations: &mut BumpMap< - Symbol, - MutMap, PendingSpecialization<'a>>, - >, - symbol: Symbol, - layout: ProcLayout<'a>, - pending: PendingSpecialization<'a>, -) { - let all_pending = pending_specializations - .entry(symbol) - .or_insert_with(|| HashMap::with_capacity_and_hasher(1, default_hasher())); - - all_pending.insert(layout, pending); -} - #[derive(Default)] pub struct Specializations<'a> { by_symbol: MutMap, Proc<'a>>>, @@ -922,7 +1100,7 @@ pub enum BranchInfo<'a> { Constructor { scrutinee: Symbol, layout: Layout<'a>, - tag_id: u8, + tag_id: TagIdIntType, }, } @@ -961,8 +1139,17 @@ impl<'a> BranchInfo<'a> { #[derive(Clone, Copy, Debug, PartialEq)] pub enum ModifyRc { + /// Increment a reference count Inc(Symbol, u64), + /// Decrement a reference count Dec(Symbol), + /// A DecRef is a non-recursive reference count decrement + /// e.g. If we Dec a list of lists, then if the reference count of the outer list is one, + /// a Dec will recursively decrement all elements, then free the memory of the outer list. + /// A DecRef would just free the outer list. + /// That is dangerous because you may not free the elements, but in our Zig builtins, + /// sometimes we know we already dealt with the elements (e.g. by copying them all over + /// to a new list) and so we can just do a DecRef, which is much cheaper in such a case. DecRef(Symbol), } @@ -1072,11 +1259,11 @@ impl<'a> Call<'a> { .text(format!("lowlevel {:?} ", lowlevel)) .append(alloc.intersperse(it, " ")) } - HigherOrderLowLevel { op: lowlevel, .. } => { + HigherOrder(higher_order) => { let it = arguments.iter().map(|s| symbol_to_doc(alloc, *s)); alloc - .text(format!("lowlevel {:?} ", lowlevel)) + .text(format!("lowlevel {:?} ", higher_order.op)) .append(alloc.intersperse(it, " ")) } Foreign { @@ -1118,43 +1305,46 @@ impl UpdateModeId { pub enum CallType<'a> { ByName { name: Symbol, - ret_layout: Layout<'a>, + ret_layout: &'a Layout<'a>, arg_layouts: &'a [Layout<'a>], specialization_id: CallSpecId, }, Foreign { foreign_symbol: ForeignSymbol, - ret_layout: Layout<'a>, + ret_layout: &'a Layout<'a>, }, LowLevel { op: LowLevel, update_mode: UpdateModeId, }, - HigherOrderLowLevel { - op: crate::low_level::HigherOrder, - /// the layout of the closure argument, if any - closure_env_layout: Option>, + HigherOrder(&'a HigherOrderLowLevel<'a>), +} - /// name of the top-level function that is passed as an argument - /// e.g. in `List.map xs Num.abs` this would be `Num.abs` - function_name: Symbol, +#[derive(Clone, Debug, PartialEq)] +pub struct HigherOrderLowLevel<'a> { + pub op: crate::low_level::HigherOrder, + /// the layout of the closure argument, if any + pub closure_env_layout: Option>, - /// Symbol of the environment captured by the function argument - function_env: Symbol, + /// name of the top-level function that is passed as an argument + /// e.g. in `List.map xs Num.abs` this would be `Num.abs` + pub function_name: Symbol, - /// does the function argument need to own the closure data - function_owns_closure_data: bool, + /// Symbol of the environment captured by the function argument + pub function_env: Symbol, - /// specialization id of the function argument, used for name generation - specialization_id: CallSpecId, + /// does the function argument need to own the closure data + pub function_owns_closure_data: bool, - /// update mode of the higher order lowlevel itself - update_mode: UpdateModeId, + /// specialization id of the function argument, used for name generation + pub specialization_id: CallSpecId, - /// function layout, used for name generation - arg_layouts: &'a [Layout<'a>], - ret_layout: Layout<'a>, - }, + /// update mode of the higher order lowlevel itself + pub update_mode: UpdateModeId, + + /// function layout, used for name generation + pub arg_layouts: &'a [Layout<'a>], + pub ret_layout: Layout<'a>, } #[derive(Clone, Debug, PartialEq)] @@ -1167,7 +1357,7 @@ pub enum Expr<'a> { Tag { tag_layout: UnionLayout<'a>, tag_name: TagName, - tag_id: u8, + tag_id: TagIdIntType, arguments: &'a [Symbol], }, Struct(&'a [Symbol]), @@ -1185,7 +1375,7 @@ pub enum Expr<'a> { UnionAtIndex { structure: Symbol, - tag_id: u8, + tag_id: TagIdIntType, union_layout: UnionLayout<'a>, index: u64, }, @@ -1202,7 +1392,7 @@ pub enum Expr<'a> { // normal Tag fields tag_layout: UnionLayout<'a>, tag_name: TagName, - tag_id: u8, + tag_id: TagIdIntType, arguments: &'a [Symbol], }, Reset(Symbol), @@ -1732,154 +1922,202 @@ fn pattern_to_when<'a>( } } -pub fn specialize_all<'a>( +fn specialize_suspended<'a>( env: &mut Env<'a, '_>, - mut procs: Procs<'a>, - externals_others_need: ExternalSpecializations<'a>, - specializations_for_host: BumpMap, PendingSpecialization<'a>>>, + procs: &mut Procs<'a>, layout_cache: &mut LayoutCache<'a>, -) -> Procs<'a> { - specialize_externals_others_need(env, &mut procs, externals_others_need, layout_cache); + suspended: Suspended<'a>, +) { + let offset_variable = StorageSubs::merge_into(suspended.store, env.subs); - // When calling from_can, pending_specializations should be unavailable. - // This must be a single pass, and we must not add any more entries to it! - let opt_pending_specializations = std::mem::replace(&mut procs.pending_specializations, None); + for (i, (symbol, var)) in suspended + .symbols + .iter() + .zip(suspended.variables.iter()) + .enumerate() + { + let name = *symbol; + let outside_layout = suspended.layouts[i]; - let it = specializations_for_host - .into_iter() - .chain(opt_pending_specializations.into_iter().flatten()); + let var = offset_variable(*var); - for (name, by_layout) in it { - for (outside_layout, pending) in by_layout.into_iter() { - // If we've already seen this (Symbol, Layout) combination before, - // don't try to specialize it again. If we do, we'll loop forever! - let key = (name, outside_layout); + // TODO define our own Entry for Specialized? + let partial_proc = if procs.specialized.is_specialized(name, &outside_layout) { + // already specialized, just continue + continue; + } else { + match procs.partial_procs.symbol_to_id(name) { + Some(v) => { + // Mark this proc as in-progress, so if we're dealing with + // mutually recursive functions, we don't loop forever. + // (We had a bug around this before this system existed!) + procs.specialized.mark_in_progress(name, outside_layout); - let partial_proc = match procs.specialized.entry(key) { - Entry::Occupied(_) => { - // already specialized, just continue + v + } + None => { + // TODO this assumes the specialization is done by another module + // make sure this does not become a problem down the road! continue; } - Entry::Vacant(vacant) => { - match procs.partial_procs.symbol_to_id(name) { - Some(v) => { - // Mark this proc as in-progress, so if we're dealing with - // mutually recursive functions, we don't loop forever. - // (We had a bug around this before this system existed!) - vacant.insert(InProgress); + } + }; - v - } - None => { - // TODO this assumes the specialization is done by another module - // make sure this does not become a problem down the road! - continue; - } - } + match specialize_variable(env, procs, name, layout_cache, var, &[], partial_proc) { + Ok((proc, layout)) => { + // TODO thiscode is duplicated elsewhere + let top_level = ProcLayout::from_raw(env.arena, layout); + + if procs.is_module_thunk(proc.name) { + debug_assert!( + top_level.arguments.is_empty(), + "{:?} from {:?}", + name, + layout + ); } - }; - match specialize(env, &mut procs, name, layout_cache, pending, partial_proc) { - Ok((proc, layout)) => { - // TODO thiscode is duplicated elsewhere - let top_level = ProcLayout::from_raw(env.arena, layout); + debug_assert_eq!(outside_layout, top_level, " in {:?}", name); + procs.specialized.insert_specialized(name, top_level, proc); + } + Err(SpecializeFailure { + attempted_layout, .. + }) => { + let proc = generate_runtime_error_function(env, name, attempted_layout); - if procs.is_module_thunk(proc.name) { - debug_assert!( - top_level.arguments.is_empty(), - "{:?} from {:?}", - name, - layout - ); - } + let top_level = ProcLayout::from_raw(env.arena, attempted_layout); - debug_assert_eq!(outside_layout, top_level, " in {:?}", name); - procs.specialized.insert((name, top_level), Done(proc)); - } - Err(SpecializeFailure { - attempted_layout, .. - }) => { - let proc = generate_runtime_error_function(env, name, attempted_layout); - - let top_level = ProcLayout::from_raw(env.arena, attempted_layout); - - procs.specialized.insert((name, top_level), Done(proc)); - } + procs.specialized.insert_specialized(name, top_level, proc); } } } +} + +pub fn specialize_all<'a>( + env: &mut Env<'a, '_>, + mut procs: Procs<'a>, + externals_others_need: std::vec::Vec, + specializations_for_host: HostSpecializations, + layout_cache: &mut LayoutCache<'a>, +) -> Procs<'a> { + for externals in externals_others_need { + specialize_external_specializations(env, &mut procs, layout_cache, externals); + } + + // When calling from_can, pending_specializations should be unavailable. + // This must be a single pass, and we must not add any more entries to it! + let pending_specializations = std::mem::replace( + &mut procs.pending_specializations, + PendingSpecializations::Making, + ); + + match pending_specializations { + PendingSpecializations::Making => {} + PendingSpecializations::Finding(suspended) => { + specialize_suspended(env, &mut procs, layout_cache, suspended) + } + } + + specialize_host_specializations(env, &mut procs, layout_cache, specializations_for_host); procs } -fn specialize_externals_others_need<'a>( +fn specialize_host_specializations<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, - externals_others_need: ExternalSpecializations<'a>, layout_cache: &mut LayoutCache<'a>, + host_specializations: HostSpecializations, ) { - for (symbol, solved_types) in externals_others_need.specs.iter() { - // de-duplicate by the Hash instance (set only deduplicates by Eq instance) - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; + let (store, it) = host_specializations.decompose(); - let mut seen_hashes = Vec::with_capacity_in(solved_types.len(), env.arena); + let offset_variable = StorageSubs::merge_into(store, env.subs); - let hash_the_thing = |x: &SolvedType| { - let mut hasher = DefaultHasher::new(); - x.hash(&mut hasher); - hasher.finish() - }; + for (symbol, variable, host_exposed_aliases) in it { + specialize_external_help( + env, + procs, + layout_cache, + symbol, + offset_variable(variable), + &host_exposed_aliases, + ) + } +} - for solved_type in solved_types { - let hash = hash_the_thing(solved_type); +fn specialize_external_specializations<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + externals_others_need: ExternalSpecializations, +) { + let (store, it) = externals_others_need.decompose(); - if seen_hashes.iter().any(|h| *h == hash) { - // we've seen this one already - continue; - } + let offset_variable = StorageSubs::merge_into(store, env.subs); - seen_hashes.push(hash); + for (symbol, solved_types) in it { + for store_variable in solved_types { + // historical note: we used to deduplicate with a hash here, + // but the cost of that hash is very high. So for now we make + // duplicate specializations, and the insertion into a hash map + // below will deduplicate them. - let name = *symbol; - - let partial_proc_id = match procs.partial_procs.symbol_to_id(name) { - Some(v) => v, - None => { - panic!("Cannot find a partial proc for {:?}", name); - } - }; - - // TODO I believe this is also duplicated - match specialize_solved_type( + specialize_external_help( env, procs, - name, layout_cache, - solved_type, - BumpMap::new_in(env.arena), - partial_proc_id, - ) { - Ok((proc, layout)) => { - let top_level = ProcLayout::from_raw(env.arena, layout); + symbol, + offset_variable(store_variable), + &[], + ) + } + } +} - if procs.is_module_thunk(name) { - debug_assert!(top_level.arguments.is_empty()); - } +fn specialize_external_help<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + name: Symbol, + variable: Variable, + host_exposed_aliases: &[(Symbol, Variable)], +) { + let partial_proc_id = match procs.partial_procs.symbol_to_id(name) { + Some(v) => v, + None => { + panic!("Cannot find a partial proc for {:?}", name); + } + }; - procs.specialized.insert((name, top_level), Done(proc)); - } - Err(SpecializeFailure { - problem: _, - attempted_layout, - }) => { - let proc = generate_runtime_error_function(env, name, attempted_layout); + let specialization_result = specialize_variable( + env, + procs, + name, + layout_cache, + variable, + host_exposed_aliases, + partial_proc_id, + ); - let top_level = ProcLayout::from_raw(env.arena, attempted_layout); + match specialization_result { + Ok((proc, layout)) => { + let top_level = ProcLayout::from_raw(env.arena, layout); - procs.specialized.insert((name, top_level), Done(proc)); - } + if procs.is_module_thunk(name) { + debug_assert!(top_level.arguments.is_empty()); } + + procs.specialized.insert_specialized(name, top_level, proc); + } + Err(SpecializeFailure { + problem: _, + attempted_layout, + }) => { + let proc = generate_runtime_error_function(env, name, attempted_layout); + + let top_level = ProcLayout::from_raw(env.arena, attempted_layout); + + procs.specialized.insert_specialized(name, top_level, proc); } } } @@ -2014,7 +2252,7 @@ fn specialize_external<'a>( Symbol::ARG_CLOSURE, argument_symbols.into_bump_slice(), argument_layouts, - *return_layout, + return_layout, assigned, hole, ); @@ -2036,9 +2274,7 @@ fn specialize_external<'a>( *return_layout, ); - procs - .specialized - .insert((name, top_level), InProgressProc::Done(proc)); + procs.specialized.insert_specialized(name, top_level, proc); aliases.insert(*symbol, (name, top_level, layout)); } @@ -2451,77 +2687,13 @@ struct SpecializeFailure<'a> { type SpecializeSuccess<'a> = (Proc<'a>, RawFunctionLayout<'a>); -fn specialize<'a, 'b>( - env: &mut Env<'a, '_>, - procs: &'b mut Procs<'a>, - proc_name: Symbol, - layout_cache: &mut LayoutCache<'a>, - pending: PendingSpecialization, - partial_proc_id: PartialProcId, -) -> Result, SpecializeFailure<'a>> { - let PendingSpecialization { - solved_type, - host_exposed_aliases, - .. - } = pending; - - specialize_solved_type( - env, - procs, - proc_name, - layout_cache, - &solved_type, - host_exposed_aliases, - partial_proc_id, - ) -} - -fn introduce_solved_type_to_subs<'a>(env: &mut Env<'a, '_>, solved_type: &SolvedType) -> Variable { - use roc_solve::solve::insert_type_into_subs; - use roc_types::solved_types::{to_type, FreeVars}; - use roc_types::subs::VarStore; - let mut free_vars = FreeVars::default(); - let mut var_store = VarStore::new_from_subs(env.subs); - - let before = var_store.peek(); - - let normal_type = to_type(solved_type, &mut free_vars, &mut var_store); - - let after = var_store.peek(); - let variables_introduced = after - before; - - env.subs.extend_by(variables_introduced as usize); - - insert_type_into_subs(env.subs, &normal_type) -} - -fn specialize_solved_type<'a>( - env: &mut Env<'a, '_>, - procs: &mut Procs<'a>, - proc_name: Symbol, - layout_cache: &mut LayoutCache<'a>, - solved_type: &SolvedType, - host_exposed_aliases: BumpMap, - partial_proc_id: PartialProcId, -) -> Result, SpecializeFailure<'a>> { - specialize_variable_help( - env, - procs, - proc_name, - layout_cache, - |env| introduce_solved_type_to_subs(env, solved_type), - host_exposed_aliases, - partial_proc_id, - ) -} - fn specialize_variable<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, proc_name: Symbol, layout_cache: &mut LayoutCache<'a>, fn_var: Variable, - host_exposed_aliases: BumpMap, + host_exposed_aliases: &[(Symbol, Variable)], partial_proc_id: PartialProcId, ) -> Result, SpecializeFailure<'a>> { specialize_variable_help( @@ -2541,7 +2713,7 @@ fn specialize_variable_help<'a, F>( proc_name: Symbol, layout_cache: &mut LayoutCache<'a>, fn_var_thunk: F, - host_exposed_aliases: BumpMap, + host_exposed_variables: &[(Symbol, Variable)], partial_proc_id: PartialProcId, ) -> Result, SpecializeFailure<'a>> where @@ -2576,21 +2748,13 @@ where let annotation_var = procs.partial_procs.get_id(partial_proc_id).annotation; instantiate_rigids(env.subs, annotation_var); - let mut host_exposed_variables = Vec::with_capacity_in(host_exposed_aliases.len(), env.arena); - - for (symbol, solved_type) in host_exposed_aliases { - let alias_var = introduce_solved_type_to_subs(env, &solved_type); - - host_exposed_variables.push((symbol, alias_var)); - } - let specialized = specialize_external( env, procs, proc_name, layout_cache, fn_var, - &host_exposed_variables, + host_exposed_variables, partial_proc_id, ); @@ -3858,7 +4022,7 @@ pub fn with_hole<'a>( closure_data_symbol, arg_symbols, arg_layouts, - *ret_layout, + ret_layout, assigned, hole, ); @@ -3885,7 +4049,7 @@ pub fn with_hole<'a>( closure_data_symbol, arg_symbols, arg_layouts, - *ret_layout, + ret_layout, assigned, hole, ); @@ -3934,7 +4098,7 @@ pub fn with_hole<'a>( let call = self::Call { call_type: CallType::Foreign { foreign_symbol, - ret_layout: layout, + ret_layout: env.arena.alloc(layout), }, arguments: arg_symbols, }; @@ -3986,8 +4150,8 @@ pub fn with_hole<'a>( lambda_set, op, closure_data_symbol, - |(top_level_function, closure_data, closure_env_layout, specialization_id, update_mode)| self::Call { - call_type: CallType::HigherOrderLowLevel { + |(top_level_function, closure_data, closure_env_layout, specialization_id, update_mode)| { + let higher_order = HigherOrderLowLevel { op: crate::low_level::HigherOrder::$ho { $($x,)* }, closure_env_layout, specialization_id, @@ -3997,8 +4161,12 @@ pub fn with_hole<'a>( function_name: top_level_function, arg_layouts, ret_layout, - }, - arguments: arena.alloc([$($x,)* top_level_function, closure_data]), + }; + + self::Call { + call_type: CallType::HigherOrder(arena.alloc(higher_order)), + arguments: arena.alloc([$($x,)* top_level_function, closure_data]), + } }, layout, assigned, @@ -4084,6 +4252,12 @@ pub fn with_hole<'a>( let xs = arg_symbols[0]; match_on_closure_argument!(ListAny, [xs]) } + ListAll => { + debug_assert_eq!(arg_symbols.len(), 2); + let xs = arg_symbols[0]; + match_on_closure_argument!(ListAll, [xs]) + } + ListKeepOks => { debug_assert_eq!(arg_symbols.len(), 2); let xs = arg_symbols[0]; @@ -4389,7 +4563,7 @@ fn convert_tag_union<'a>( let tag = Expr::Tag { tag_layout: union_layout, tag_name, - tag_id: tag_id as u8, + tag_id: tag_id as _, arguments: field_symbols, }; @@ -4412,7 +4586,7 @@ fn convert_tag_union<'a>( let tag = Expr::Tag { tag_layout: union_layout, tag_name, - tag_id: tag_id as u8, + tag_id: tag_id as _, arguments: field_symbols, }; @@ -4437,7 +4611,7 @@ fn convert_tag_union<'a>( let tag = Expr::Tag { tag_layout: union_layout, tag_name, - tag_id: tag_id as u8, + tag_id: tag_id as _, arguments: field_symbols, }; @@ -4464,7 +4638,7 @@ fn convert_tag_union<'a>( let tag = Expr::Tag { tag_layout: union_layout, tag_name, - tag_id: tag_id as u8, + tag_id: tag_id as _, arguments: field_symbols, }; @@ -4482,7 +4656,7 @@ fn convert_tag_union<'a>( let tag = Expr::Tag { tag_layout: union_layout, tag_name, - tag_id: tag_id as u8, + tag_id: tag_id as _, arguments: field_symbols, }; @@ -5475,7 +5649,7 @@ fn substitute_in_call<'a>( }), CallType::Foreign { .. } => None, CallType::LowLevel { .. } => None, - CallType::HigherOrderLowLevel { .. } => None, + CallType::HigherOrder { .. } => None, }; let mut did_change = false; @@ -5794,7 +5968,7 @@ fn store_tag_pattern<'a>( structure: Symbol, union_layout: UnionLayout<'a>, arguments: &[(Pattern<'a>, Layout<'a>)], - tag_id: u8, + tag_id: TagIdIntType, mut stmt: Stmt<'a>, ) -> StorePattern<'a> { use Pattern::*; @@ -6090,7 +6264,7 @@ fn force_thunk<'a>( let call = self::Call { call_type: CallType::ByName { name: thunk_name, - ret_layout: layout, + ret_layout: env.arena.alloc(layout), arg_layouts: &[], specialization_id: env.next_call_specialization_id(), }, @@ -6334,12 +6508,11 @@ fn add_needed_external<'a>( use hashbrown::hash_map::Entry::{Occupied, Vacant}; let existing = match procs.externals_we_need.entry(name.module_id()) { - Vacant(entry) => entry.insert(ExternalSpecializations::new_in(env.arena)), + Vacant(entry) => entry.insert(ExternalSpecializations::new()), Occupied(entry) => entry.into_mut(), }; - let solved_type = SolvedType::from_var(env.subs, fn_var); - existing.insert(name, solved_type); + existing.insert_external(name, env.subs, fn_var); } fn build_call<'a>( @@ -6455,7 +6628,7 @@ fn call_by_name<'a>( closure_data_symbol, arg_symbols, arg_layouts, - *ret_layout, + ret_layout, assigned, hole, ); @@ -6564,8 +6737,7 @@ fn call_by_name_help<'a>( // If we've already specialized this one, no further work is needed. if procs .specialized - .keys() - .any(|x| x == &(proc_name, top_level_layout)) + .is_specialized(proc_name, &top_level_layout) { debug_assert_eq!( argument_layouts.len(), @@ -6579,7 +6751,7 @@ fn call_by_name_help<'a>( let call = self::Call { call_type: CallType::ByName { name: proc_name, - ret_layout: *ret_layout, + ret_layout, arg_layouts: argument_layouts, specialization_id: env.next_call_specialization_id(), }, @@ -6620,7 +6792,7 @@ fn call_by_name_help<'a>( let call = self::Call { call_type: CallType::ByName { name: proc_name, - ret_layout: *ret_layout, + ret_layout, arg_layouts: argument_layouts, specialization_id: env.next_call_specialization_id(), }, @@ -6633,8 +6805,6 @@ fn call_by_name_help<'a>( assign_to_symbols(env, procs, layout_cache, iter, result) } } else { - let pending = PendingSpecialization::from_var(env.arena, env.subs, fn_var); - // When requested (that is, when procs.pending_specializations is `Some`), // store a pending specialization rather than specializing immediately. // @@ -6651,16 +6821,11 @@ fn call_by_name_help<'a>( } match &mut procs.pending_specializations { - Some(pending_specializations) => { + PendingSpecializations::Finding(suspended) => { debug_assert!(!env.is_imported_symbol(proc_name)); // register the pending specialization, so this gets code genned later - add_pending( - pending_specializations, - proc_name, - top_level_layout, - pending, - ); + suspended.specialization(env.subs, proc_name, top_level_layout, fn_var); debug_assert_eq!( argument_layouts.len(), @@ -6674,7 +6839,7 @@ fn call_by_name_help<'a>( let call = self::Call { call_type: CallType::ByName { name: proc_name, - ret_layout: *ret_layout, + ret_layout, arg_layouts: argument_layouts, specialization_id: env.next_call_specialization_id(), }, @@ -6686,20 +6851,9 @@ fn call_by_name_help<'a>( let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev()); assign_to_symbols(env, procs, layout_cache, iter, result) } - None => { + PendingSpecializations::Making => { let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name); - /* - debug_assert_eq!( - argument_layouts.len(), - field_symbols.len(), - "Function {:?} is called with {} arguments, but the layout expects {}", - proc_name, - field_symbols.len(), - argument_layouts.len(), - ); - */ - let field_symbols = field_symbols.into_bump_slice(); match opt_partial_proc { @@ -6709,10 +6863,17 @@ fn call_by_name_help<'a>( // (We had a bug around this before this system existed!) procs .specialized - .insert((proc_name, top_level_layout), InProgress); + .mark_in_progress(proc_name, top_level_layout); - match specialize(env, procs, proc_name, layout_cache, pending, partial_proc) - { + match specialize_variable( + env, + procs, + proc_name, + layout_cache, + fn_var, + &[], + partial_proc, + ) { Ok((proc, layout)) => { // now we just call our freshly-specialized function call_specialized_proc( @@ -6785,13 +6946,11 @@ fn call_by_name_module_thunk<'a>( // If we've already specialized this one, no further work is needed. let already_specialized = procs .specialized - .contains_key(&(proc_name, top_level_layout)); + .is_specialized(proc_name, &top_level_layout); if already_specialized { force_thunk(env, proc_name, inner_layout, assigned, hole) } else { - let pending = PendingSpecialization::from_var(env.arena, env.subs, fn_var); - // When requested (that is, when procs.pending_specializations is `Some`), // store a pending specialization rather than specializing immediately. // @@ -6808,20 +6967,15 @@ fn call_by_name_module_thunk<'a>( } match &mut procs.pending_specializations { - Some(pending_specializations) => { + PendingSpecializations::Finding(suspended) => { debug_assert!(!env.is_imported_symbol(proc_name)); // register the pending specialization, so this gets code genned later - add_pending( - pending_specializations, - proc_name, - top_level_layout, - pending, - ); + suspended.specialization(env.subs, proc_name, top_level_layout, fn_var); force_thunk(env, proc_name, inner_layout, assigned, hole) } - None => { + PendingSpecializations::Making => { let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name); match opt_partial_proc { @@ -6831,10 +6985,17 @@ fn call_by_name_module_thunk<'a>( // (We had a bug around this before this system existed!) procs .specialized - .insert((proc_name, top_level_layout), InProgress); + .mark_in_progress(proc_name, top_level_layout); - match specialize(env, procs, proc_name, layout_cache, pending, partial_proc) - { + match specialize_variable( + env, + procs, + proc_name, + layout_cache, + fn_var, + &[], + partial_proc, + ) { Ok((proc, raw_layout)) => { debug_assert!( raw_layout.is_zero_argument_thunk(), @@ -6842,13 +7003,16 @@ fn call_by_name_module_thunk<'a>( raw_layout ); - let was_present = - procs.specialized.remove(&(proc_name, top_level_layout)); - debug_assert!(was_present.is_some()); - - procs + let was_present = procs .specialized - .insert((proc_name, top_level_layout), Done(proc)); + .remove_specialized(proc_name, &top_level_layout); + debug_assert!(was_present); + + procs.specialized.insert_specialized( + proc_name, + top_level_layout, + proc, + ); force_thunk(env, proc_name, inner_layout, assigned, hole) } @@ -6862,13 +7026,16 @@ fn call_by_name_module_thunk<'a>( attempted_layout, ); - let was_present = - procs.specialized.remove(&(proc_name, top_level_layout)); - debug_assert!(was_present.is_some()); - - procs + let was_present = procs .specialized - .insert((proc_name, top_level_layout), Done(proc)); + .remove_specialized(proc_name, &top_level_layout); + debug_assert!(was_present); + + procs.specialized.insert_specialized( + proc_name, + top_level_layout, + proc, + ); force_thunk(env, proc_name, inner_layout, assigned, hole) } @@ -6900,11 +7067,9 @@ fn call_specialized_proc<'a>( ) -> Stmt<'a> { let function_layout = ProcLayout::from_raw(env.arena, layout); - procs.specialized.remove(&(proc_name, function_layout)); - procs .specialized - .insert((proc_name, function_layout), Done(proc)); + .insert_specialized(proc_name, function_layout, proc); if field_symbols.is_empty() { debug_assert!(loc_args.is_empty()); @@ -6922,7 +7087,7 @@ fn call_specialized_proc<'a>( let call = self::Call { call_type: CallType::ByName { name: proc_name, - ret_layout: function_layout.result, + ret_layout: env.arena.alloc(function_layout.result), arg_layouts: function_layout.arguments, specialization_id: env.next_call_specialization_id(), }, @@ -6965,7 +7130,7 @@ fn call_specialized_proc<'a>( closure_data_symbol, field_symbols, argument_layouts.into_bump_slice(), - function_layout.result, + env.arena.alloc(function_layout.result), assigned, hole, ); @@ -6995,7 +7160,7 @@ fn call_specialized_proc<'a>( let call = self::Call { call_type: CallType::ByName { name: proc_name, - ret_layout: function_layout.result, + ret_layout: env.arena.alloc(function_layout.result), arg_layouts: function_layout.arguments, specialization_id: env.next_call_specialization_id(), }, @@ -7038,7 +7203,7 @@ pub enum Pattern<'a> { }, AppliedTag { tag_name: TagName, - tag_id: u8, + tag_id: TagIdIntType, arguments: Vec<'a, (Pattern<'a>, Layout<'a>)>, layout: UnionLayout<'a>, union: crate::exhaustive::Union, @@ -7225,7 +7390,7 @@ fn from_can_pattern_help<'a>( let mut ctors = std::vec::Vec::with_capacity(tag_names.len()); for (i, tag_name) in tag_names.into_iter().enumerate() { ctors.push(Ctor { - tag_id: TagId(i as u8), + tag_id: TagId(i as _), name: tag_name, arity: 0, }) @@ -7316,7 +7481,7 @@ fn from_can_pattern_help<'a>( for (i, (tag_name, args)) in tags.iter().enumerate() { ctors.push(Ctor { - tag_id: TagId(i as u8), + tag_id: TagId(i as _), name: tag_name.clone(), arity: args.len(), }) @@ -7353,7 +7518,7 @@ fn from_can_pattern_help<'a>( Pattern::AppliedTag { tag_name: tag_name.clone(), - tag_id: tag_id as u8, + tag_id: tag_id as _, arguments: mono_args, union, layout, @@ -7367,7 +7532,7 @@ fn from_can_pattern_help<'a>( for (i, (tag_name, args)) in tags.iter().enumerate() { ctors.push(Ctor { - tag_id: TagId(i as u8), + tag_id: TagId(i as _), name: tag_name.clone(), // don't include tag discriminant in arity arity: args.len() - 1, @@ -7398,7 +7563,7 @@ fn from_can_pattern_help<'a>( Pattern::AppliedTag { tag_name: tag_name.clone(), - tag_id: tag_id as u8, + tag_id: tag_id as _, arguments: mono_args, union, layout, @@ -7412,7 +7577,7 @@ fn from_can_pattern_help<'a>( debug_assert_eq!(&w_tag_name, tag_name); ctors.push(Ctor { - tag_id: TagId(0_u8), + tag_id: TagId(0), name: tag_name.clone(), arity: fields.len(), }); @@ -7441,7 +7606,7 @@ fn from_can_pattern_help<'a>( Pattern::AppliedTag { tag_name: tag_name.clone(), - tag_id: tag_id as u8, + tag_id: tag_id as _, arguments: mono_args, union, layout, @@ -7459,7 +7624,7 @@ fn from_can_pattern_help<'a>( for (tag_name, args) in tags.iter() { if i == nullable_id as usize { ctors.push(Ctor { - tag_id: TagId(i as u8), + tag_id: TagId(i as _), name: nullable_name.clone(), // don't include tag discriminant in arity arity: 0, @@ -7469,7 +7634,7 @@ fn from_can_pattern_help<'a>( } ctors.push(Ctor { - tag_id: TagId(i as u8), + tag_id: TagId(i as _), name: tag_name.clone(), // don't include tag discriminant in arity arity: args.len() - 1, @@ -7480,7 +7645,7 @@ fn from_can_pattern_help<'a>( if i == nullable_id as usize { ctors.push(Ctor { - tag_id: TagId(i as u8), + tag_id: TagId(i as _), name: nullable_name.clone(), // don't include tag discriminant in arity arity: 0, @@ -7514,7 +7679,7 @@ fn from_can_pattern_help<'a>( Pattern::AppliedTag { tag_name: tag_name.clone(), - tag_id: tag_id as u8, + tag_id: tag_id as _, arguments: mono_args, union, layout, @@ -7530,13 +7695,13 @@ fn from_can_pattern_help<'a>( debug_assert!(!other_fields.is_empty()); ctors.push(Ctor { - tag_id: TagId(nullable_id as u8), + tag_id: TagId(nullable_id as _), name: nullable_name.clone(), arity: 0, }); ctors.push(Ctor { - tag_id: TagId(!nullable_id as u8), + tag_id: TagId(!nullable_id as _), name: nullable_name.clone(), // FIXME drop tag arity: other_fields.len() - 1, @@ -7570,7 +7735,7 @@ fn from_can_pattern_help<'a>( Pattern::AppliedTag { tag_name: tag_name.clone(), - tag_id: tag_id as u8, + tag_id: tag_id as _, arguments: mono_args, union, layout, @@ -8079,7 +8244,7 @@ fn match_on_lambda_set<'a>( closure_data_symbol: Symbol, argument_symbols: &'a [Symbol], argument_layouts: &'a [Layout<'a>], - return_layout: Layout<'a>, + return_layout: &'a Layout<'a>, assigned: Symbol, hole: &'a Stmt<'a>, ) -> Stmt<'a> { @@ -8176,7 +8341,7 @@ fn union_lambda_set_to_switch<'a>( closure_data_symbol: Symbol, argument_symbols: &'a [Symbol], argument_layouts: &'a [Layout<'a>], - return_layout: Layout<'a>, + return_layout: &'a Layout<'a>, assigned: Symbol, hole: &'a Stmt<'a>, ) -> Stmt<'a> { @@ -8220,12 +8385,12 @@ fn union_lambda_set_to_switch<'a>( cond_layout: closure_tag_id_layout, branches: branches.into_bump_slice(), default_branch, - ret_layout: return_layout, + ret_layout: *return_layout, }; let param = Param { symbol: assigned, - layout: return_layout, + layout: *return_layout, borrow: false, }; @@ -8247,7 +8412,7 @@ fn union_lambda_set_branch<'a>( closure_data_layout: Layout<'a>, argument_symbols_slice: &'a [Symbol], argument_layouts_slice: &'a [Layout<'a>], - return_layout: Layout<'a>, + return_layout: &'a Layout<'a>, ) -> Stmt<'a> { let result_symbol = env.unique_symbol(); @@ -8276,7 +8441,7 @@ fn union_lambda_set_branch_help<'a>( closure_data_layout: Layout<'a>, argument_symbols_slice: &'a [Symbol], argument_layouts_slice: &'a [Layout<'a>], - return_layout: Layout<'a>, + return_layout: &'a Layout<'a>, assigned: Symbol, hole: &'a Stmt<'a>, ) -> Stmt<'a> { @@ -8322,7 +8487,7 @@ fn union_lambda_set_branch_help<'a>( arguments: argument_symbols, }; - build_call(env, call, assigned, return_layout, hole) + build_call(env, call, assigned, *return_layout, hole) } #[allow(clippy::too_many_arguments)] @@ -8334,7 +8499,7 @@ fn enum_lambda_set_to_switch<'a>( closure_data_symbol: Symbol, argument_symbols: &'a [Symbol], argument_layouts: &'a [Layout<'a>], - return_layout: Layout<'a>, + return_layout: &'a Layout<'a>, assigned: Symbol, hole: &'a Stmt<'a>, ) -> Stmt<'a> { @@ -8371,12 +8536,12 @@ fn enum_lambda_set_to_switch<'a>( cond_layout: closure_tag_id_layout, branches: branches.into_bump_slice(), default_branch, - ret_layout: return_layout, + ret_layout: *return_layout, }; let param = Param { symbol: assigned, - layout: return_layout, + layout: *return_layout, borrow: false, }; @@ -8397,7 +8562,7 @@ fn enum_lambda_set_branch<'a>( closure_data_layout: Layout<'a>, argument_symbols_slice: &'a [Symbol], argument_layouts_slice: &'a [Layout<'a>], - return_layout: Layout<'a>, + return_layout: &'a Layout<'a>, ) -> Stmt<'a> { let result_symbol = env.unique_symbol(); @@ -8438,7 +8603,7 @@ fn enum_lambda_set_branch<'a>( }, arguments: argument_symbols, }; - build_call(env, call, assigned, return_layout, env.arena.alloc(hole)) + build_call(env, call, assigned, *return_layout, env.arena.alloc(hole)) } #[allow(clippy::too_many_arguments)] diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 673da22e32..7ea9cc4e71 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -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::() * 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::() * 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); + } +} diff --git a/compiler/mono/src/low_level.rs b/compiler/mono/src/low_level.rs index 2a31addd74..7053ef87ae 100644 --- a/compiler/mono/src/low_level.rs +++ b/compiler/mono/src/low_level.rs @@ -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, diff --git a/compiler/mono/src/reset_reuse.rs b/compiler/mono/src/reset_reuse.rs index 64fde943c5..cf8ecff9cf 100644 --- a/compiler/mono/src/reset_reuse.rs +++ b/compiler/mono/src/reset_reuse.rs @@ -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) diff --git a/compiler/parse/Cargo.toml b/compiler/parse/Cargo.toml index 34774f8360..d76b482822 100644 --- a/compiler/parse/Cargo.toml +++ b/compiler/parse/Cargo.toml @@ -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" diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index 6d31884d56..943c5d1058 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -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>], - final_comments: &'a [CommentOrNewline<'a>], - }, + List(Collection<'a, &'a Loc>>), RecordUpdate { update: &'a Loc>, - fields: &'a [Loc>>], - final_comments: &'a &'a [CommentOrNewline<'a>], + fields: Collection<'a, Loc>>>, }, - Record { - fields: &'a [Loc>>], - final_comments: &'a [CommentOrNewline<'a>], - }, + Record(Collection<'a, Loc>>>), // 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>>, - // final_comments: &'a [CommentOrNewline<'a>], }, /// A tag union, e.g. `[ TagUnion { - tags: &'a [Loc>], /// 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>>, - final_comments: &'a [CommentOrNewline<'a>], + tags: Collection<'a, Loc>>, }, + /// '_', 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>, &'a [Loc>]), + /// This is Loc rather than Loc 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>]), + RecordDestructure(Collection<'a, Loc>>), /// 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(&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(&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( + &self, + arena: &'a Bump, + f: impl Fn(&T) -> Result, + ) -> Result, 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 { + 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; diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 612389da92..77553c3ef2 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -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 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>>), Option>>, ), - 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>, When<'a>> { + ) -> impl Parser<'a, Located>, 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, - ) -> impl Parser<'a, (Col, Vec<'a, Located>>), When<'a>> { + ) -> impl Parser<'a, (Col, Vec<'a, Located>>), 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>, When<'a>> { + fn branch_result<'a>(indent: u16) -> impl Parser<'a, Located>, 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>, Located>), If<'a>> { +) -> impl Parser<'a, (Located>, Located>), 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::*; diff --git a/compiler/parse/src/header.rs b/compiler/parse/src/header.rs index 42c35cd0dc..b1fbcc23eb 100644 --- a/compiler/parse/src/header.rs +++ b/compiler/parse/src/header.rs @@ -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> for &'a str { @@ -60,8 +60,8 @@ impl<'a> ModuleName<'a> { #[derive(Clone, Debug, PartialEq)] pub struct InterfaceHeader<'a> { pub name: Loc>, - pub exposes: Vec<'a, Loc>>, - pub imports: Vec<'a, Loc>>, + pub exposes: Collection<'a, Loc>>, + pub imports: Collection<'a, Loc>>, // 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>, pub packages: Collection<'a, Loc>>, - pub imports: Vec<'a, Loc>>, - pub provides: Vec<'a, Loc>>, + pub imports: Collection<'a, Loc>>, + pub provides: Collection<'a, Loc>>, pub to: Loc>, // 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>>, + pub rigids: Collection<'a, Loc>>, pub signature: Loc>, } @@ -145,10 +145,10 @@ pub struct PlatformRequires<'a> { pub struct PlatformHeader<'a> { pub name: Loc>, pub requires: PlatformRequires<'a>, - pub exposes: Vec<'a, Loc>>>, + pub exposes: Collection<'a, Loc>>>, pub packages: Collection<'a, Loc>>, - pub imports: Vec<'a, Loc>>, - pub provides: Vec<'a, Loc>>, + pub imports: Collection<'a, Loc>>, + pub provides: Collection<'a, Loc>>, 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>], } -#[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>>), + Module( + ModuleName<'a>, + Collection<'a, Loc>>, + ), /// e.g. `base.Task` or `base.Task.{ after }` or `base.{ Task.{ Task, after } }` Package( &'a str, ModuleName<'a>, - Vec<'a, Loc>>, + Collection<'a, Loc>>, ), // 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. /// diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index eec34ecbcb..f0d2200e49 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -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>>, + Collection<'a, Located>>, )> = 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>>, + entries: Collection<'a, Located>>, to: Located>, 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>>, + Collection<'a, Located>>, ), 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>>, ERequires<'a>> { - collection_e!( +) -> impl Parser<'a, Collection<'a, Located>>, 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>>, + Collection<'a, Located>>, ), 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>>>, + Collection<'a, Located>>>, ), 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>>, + Collection<'a, Located>>, ), 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>>>, + Option>>>, ); 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), diff --git a/compiler/parse/src/number_literal.rs b/compiler/parse/src/number_literal.rs index 2386f7803a..fc1d11e736 100644 --- a/compiler/parse/src/number_literal.rs +++ b/compiler/parse/src/number_literal.rs @@ -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, diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index 777bb67f76..e9726ffb66 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -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)) } diff --git a/compiler/parse/src/pattern.rs b/compiler/parse/src/pattern.rs index a855537d4f..6f1dfa3351 100644 --- a/compiler/parse/src/pattern.rs +++ b/compiler/parse/src/pattern.rs @@ -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)) } diff --git a/compiler/parse/src/type_annotation.rs b/compiler/parse/src/type_annotation.rs index cc9e362921..2bc21e2921 100644 --- a/compiler/parse/src/type_annotation.rs +++ b/compiler/parse/src/type_annotation.rs @@ -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>, Type<'a>> { +pub fn located_help<'a>( + min_indent: u16, +) -> impl Parser<'a, Located>, 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>, Type<'a>> { +fn term<'a>(min_indent: u16) -> impl Parser<'a, Located>, 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>, 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>, Typ } /// The `*` type variable, e.g. in (List *) Wildcard, -fn loc_wildcard<'a>() -> impl Parser<'a, Located>, Type<'a>> { - map!(loc!(word1(b'*', Type::TWildcard)), |loc_val: Located<()>| { +fn loc_wildcard<'a>() -> impl Parser<'a, Located>, 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>, Type<'a>> { +/// The `_` indicating an inferred type, e.g. in (List _) +fn loc_inferred<'a>() -> impl Parser<'a, Located>, 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>, 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( min_indent: u16, -) -> impl Parser<'a, Located>, TInParens<'a>> { +) -> impl Parser<'a, Located>, 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>>, Type<'a>> { +) -> impl Parser<'a, Vec<'a, Located>>, EType<'a>> { zero_or_more!(loc_applied_arg(min_indent)) } -fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located>, Type<'a>> { +fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located>, 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 // 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 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 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, )), } diff --git a/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast new file mode 100644 index 0000000000..5d07b7897e --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast @@ -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", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.roc b/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.roc new file mode 100644 index 0000000000..f027c466fb --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.roc @@ -0,0 +1 @@ +x + 2 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast new file mode 100644 index 0000000000..b3ba87f0bf --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast @@ -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", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.roc b/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.roc new file mode 100644 index 0000000000..ece34b09b5 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.roc @@ -0,0 +1 @@ +1 + 2 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast new file mode 100644 index 0000000000..8e6245cb9f --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast @@ -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, +) diff --git a/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.roc b/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.roc new file mode 100644 index 0000000000..70563e8174 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.roc @@ -0,0 +1 @@ +Whee 12 34 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast new file mode 100644 index 0000000000..14cf85eed0 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast @@ -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, +) diff --git a/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.roc b/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.roc new file mode 100644 index 0000000000..a3ef4e0108 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.roc @@ -0,0 +1 @@ +Whee (12) (34) \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast new file mode 100644 index 0000000000..47719397a0 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast @@ -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, +) diff --git a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.roc b/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.roc new file mode 100644 index 0000000000..ba23819345 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.roc @@ -0,0 +1 @@ +@Whee 12 34 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/apply_three_args.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_three_args.expr.result-ast new file mode 100644 index 0000000000..45d7902727 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/apply_three_args.expr.result-ast @@ -0,0 +1,21 @@ +Apply( + |L 0-0, C 0-1| Var { + module_name: "", + ident: "a", + }, + [ + |L 0-0, C 2-3| Var { + module_name: "", + ident: "b", + }, + |L 0-0, C 4-5| Var { + module_name: "", + ident: "c", + }, + |L 0-0, C 6-7| Var { + module_name: "", + ident: "d", + }, + ], + Space, +) diff --git a/compiler/parse/tests/snapshots/pass/apply_three_args.expr.roc b/compiler/parse/tests/snapshots/pass/apply_three_args.expr.roc new file mode 100644 index 0000000000..ff85291034 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/apply_three_args.expr.roc @@ -0,0 +1 @@ +a b c d \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast new file mode 100644 index 0000000000..a4e39d6fd5 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast @@ -0,0 +1,15 @@ +Apply( + |L 0-0, C 0-4| Var { + module_name: "", + ident: "whee", + }, + [ + |L 0-0, C 6-8| Num( + "12", + ), + |L 0-0, C 10-12| Num( + "34", + ), + ], + Space, +) diff --git a/compiler/parse/tests/snapshots/pass/apply_two_args.expr.roc b/compiler/parse/tests/snapshots/pass/apply_two_args.expr.roc new file mode 100644 index 0000000000..5a1f135798 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/apply_two_args.expr.roc @@ -0,0 +1 @@ +whee 12 34 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast new file mode 100644 index 0000000000..54e180b592 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast @@ -0,0 +1,19 @@ +Apply( + |L 0-0, C 0-5| UnaryOp( + |L 0-0, C 1-5| Var { + module_name: "", + ident: "whee", + }, + |L 0-0, C 0-1| Negate, + ), + [ + |L 0-0, C 7-9| Num( + "12", + ), + |L 0-0, C 10-13| Var { + module_name: "", + ident: "foo", + }, + ], + Space, +) diff --git a/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.roc b/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.roc new file mode 100644 index 0000000000..f04dfd38d3 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.roc @@ -0,0 +1 @@ +-whee 12 foo \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast new file mode 100644 index 0000000000..86eee8cafa --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast @@ -0,0 +1,19 @@ +Apply( + |L 0-0, C 0-5| UnaryOp( + |L 0-0, C 1-5| Var { + module_name: "", + ident: "whee", + }, + |L 0-0, C 0-1| Not, + ), + [ + |L 0-0, C 7-9| Num( + "12", + ), + |L 0-0, C 10-13| Var { + module_name: "", + ident: "foo", + }, + ], + Space, +) diff --git a/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.roc b/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.roc new file mode 100644 index 0000000000..94e1f25809 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.roc @@ -0,0 +1 @@ +!whee 12 foo \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast new file mode 100644 index 0000000000..6d906548f6 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast @@ -0,0 +1,12 @@ +Apply( + |L 0-0, C 0-4| Var { + module_name: "", + ident: "whee", + }, + [ + |L 0-0, C 5-6| Num( + "1", + ), + ], + Space, +) diff --git a/compiler/parse/tests/snapshots/pass/basic_apply.expr.roc b/compiler/parse/tests/snapshots/pass/basic_apply.expr.roc new file mode 100644 index 0000000000..c9bcab2318 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/basic_apply.expr.roc @@ -0,0 +1 @@ +whee 1 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast new file mode 100644 index 0000000000..3e891ee430 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast @@ -0,0 +1,43 @@ +SpaceBefore( + Defs( + [ + |L 6-6, C 0-5| Body( + |L 6-6, C 0-1| Identifier( + "x", + ), + |L 6-6, C 4-5| Num( + "5", + ), + ), + ], + |L 8-8, C 0-2| SpaceBefore( + Num( + "42", + ), + [ + Newline, + Newline, + ], + ), + ), + [ + DocComment( + "first line of docs", + ), + DocComment( + " second line", + ), + DocComment( + " third line", + ), + DocComment( + "fourth line", + ), + DocComment( + "", + ), + DocComment( + "sixth line after doc new line", + ), + ], +) diff --git a/compiler/parse/tests/snapshots/pass/basic_docs.expr.roc b/compiler/parse/tests/snapshots/pass/basic_docs.expr.roc new file mode 100644 index 0000000000..2f66a22f48 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/basic_docs.expr.roc @@ -0,0 +1,9 @@ +## first line of docs +## second line +## third line +## fourth line +## +## sixth line after doc new line +x = 5 + +42 diff --git a/compiler/parse/tests/snapshots/pass/basic_field.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_field.expr.result-ast new file mode 100644 index 0000000000..89beaecfa5 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/basic_field.expr.result-ast @@ -0,0 +1,7 @@ +Access( + Var { + module_name: "", + ident: "rec", + }, + "field", +) diff --git a/compiler/parse/tests/snapshots/pass/basic_field.expr.roc b/compiler/parse/tests/snapshots/pass/basic_field.expr.roc new file mode 100644 index 0000000000..72832dcc6e --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/basic_field.expr.roc @@ -0,0 +1 @@ +rec.field \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/basic_global_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_global_tag.expr.result-ast new file mode 100644 index 0000000000..8c0a66666f --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/basic_global_tag.expr.result-ast @@ -0,0 +1,3 @@ +GlobalTag( + "Whee", +) diff --git a/compiler/parse/tests/snapshots/pass/basic_global_tag.expr.roc b/compiler/parse/tests/snapshots/pass/basic_global_tag.expr.roc new file mode 100644 index 0000000000..ab03689fd9 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/basic_global_tag.expr.roc @@ -0,0 +1 @@ +Whee \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/basic_private_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_private_tag.expr.result-ast new file mode 100644 index 0000000000..1761f76aa1 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/basic_private_tag.expr.result-ast @@ -0,0 +1,3 @@ +PrivateTag( + "@Whee", +) diff --git a/compiler/parse/tests/snapshots/pass/basic_private_tag.expr.roc b/compiler/parse/tests/snapshots/pass/basic_private_tag.expr.roc new file mode 100644 index 0000000000..476a77dfc2 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/basic_private_tag.expr.roc @@ -0,0 +1 @@ +@Whee \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/basic_var.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_var.expr.result-ast new file mode 100644 index 0000000000..ade68e0ead --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/basic_var.expr.result-ast @@ -0,0 +1,4 @@ +Var { + module_name: "", + ident: "whee", +} diff --git a/compiler/parse/tests/snapshots/pass/basic_var.expr.roc b/compiler/parse/tests/snapshots/pass/basic_var.expr.roc new file mode 100644 index 0000000000..53871cf18c --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/basic_var.expr.roc @@ -0,0 +1 @@ +whee \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast b/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast new file mode 100644 index 0000000000..61ea2b2405 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast @@ -0,0 +1,13 @@ +Closure( + [ + |L 0-0, C 1-2| Underscore( + "", + ), + |L 0-0, C 4-9| Underscore( + "name", + ), + ], + |L 0-0, C 13-15| Num( + "42", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.roc b/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.roc new file mode 100644 index 0000000000..13ea565a79 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.roc @@ -0,0 +1 @@ +\_, _name -> 42 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast new file mode 100644 index 0000000000..34d2472071 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast @@ -0,0 +1,20 @@ +BinOps( + [ + ( + |L 0-0, C 0-2| Num( + "12", + ), + |L 0-0, C 4-5| Star, + ), + ], + |L 1-1, C 1-3| SpaceBefore( + Num( + "92", + ), + [ + LineComment( + " test!", + ), + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/comment_after_op.expr.roc b/compiler/parse/tests/snapshots/pass/comment_after_op.expr.roc new file mode 100644 index 0000000000..8023584069 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/comment_after_op.expr.roc @@ -0,0 +1,2 @@ +12 * # test! + 92 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast new file mode 100644 index 0000000000..af6abd380a --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast @@ -0,0 +1,20 @@ +BinOps( + [ + ( + |L 0-0, C 0-1| SpaceAfter( + Num( + "3", + ), + [ + LineComment( + " test!", + ), + ], + ), + |L 1-1, C 0-1| Plus, + ), + ], + |L 1-1, C 2-3| Num( + "4", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/comment_before_op.expr.roc b/compiler/parse/tests/snapshots/pass/comment_before_op.expr.roc new file mode 100644 index 0000000000..f0f6c3f6a7 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/comment_before_op.expr.roc @@ -0,0 +1,2 @@ +3 # test! ++ 4 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/comment_inside_empty_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_inside_empty_list.expr.result-ast new file mode 100644 index 0000000000..52f4bb6157 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/comment_inside_empty_list.expr.result-ast @@ -0,0 +1,10 @@ +List( + Collection { + items: [], + final_comments: [ + LineComment( + "comment", + ), + ], + }, +) diff --git a/compiler/parse/tests/snapshots/pass/comment_inside_empty_list.expr.roc b/compiler/parse/tests/snapshots/pass/comment_inside_empty_list.expr.roc new file mode 100644 index 0000000000..d1fdba611d --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/comment_inside_empty_list.expr.roc @@ -0,0 +1,2 @@ +[#comment +] \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast new file mode 100644 index 0000000000..ded9ef9ea0 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast @@ -0,0 +1,20 @@ +BinOps( + [ + ( + |L 0-0, C 0-1| SpaceAfter( + Num( + "3", + ), + [ + LineComment( + " 2 × 2", + ), + ], + ), + |L 1-1, C 0-1| Plus, + ), + ], + |L 1-1, C 2-3| Num( + "4", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.roc b/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.roc new file mode 100644 index 0000000000..f522366503 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.roc @@ -0,0 +1,2 @@ +3 # 2 × 2 ++ 4 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast b/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast new file mode 100644 index 0000000000..74bf278e99 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast @@ -0,0 +1,23 @@ +App { + header: AppHeader { + name: |L 0-0, C 4-14| PlainLine( + "test-app", + ), + packages: [], + imports: [], + provides: [], + to: |L 0-0, C 53-57| ExistingPackage( + "blah", + ), + before_header: [], + after_app_keyword: [], + before_packages: [], + after_packages: [], + before_imports: [], + after_imports: [], + before_provides: [], + after_provides: [], + before_to: [], + after_to: [], + }, +} diff --git a/compiler/parse/tests/snapshots/pass/empty_app_header.header.roc b/compiler/parse/tests/snapshots/pass/empty_app_header.header.roc new file mode 100644 index 0000000000..e524c249e9 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/empty_app_header.header.roc @@ -0,0 +1 @@ +app "test-app" packages {} imports [] provides [] to blah diff --git a/compiler/parse/tests/snapshots/pass/empty_interface_header.header.result-ast b/compiler/parse/tests/snapshots/pass/empty_interface_header.header.result-ast new file mode 100644 index 0000000000..0e71559779 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/empty_interface_header.header.result-ast @@ -0,0 +1,15 @@ +Interface { + header: InterfaceHeader { + name: |L 0-0, C 10-13| ModuleName( + "Foo", + ), + exposes: [], + imports: [], + before_header: [], + after_interface_keyword: [], + before_exposes: [], + after_exposes: [], + before_imports: [], + after_imports: [], + }, +} diff --git a/compiler/parse/tests/snapshots/pass/empty_interface_header.header.roc b/compiler/parse/tests/snapshots/pass/empty_interface_header.header.roc new file mode 100644 index 0000000000..5959237bda --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/empty_interface_header.header.roc @@ -0,0 +1 @@ +interface Foo exposes [] imports [] diff --git a/compiler/parse/tests/snapshots/pass/empty_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/empty_list.expr.result-ast new file mode 100644 index 0000000000..36f4a52b6e --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/empty_list.expr.result-ast @@ -0,0 +1,3 @@ +List( + [], +) diff --git a/compiler/parse/tests/snapshots/pass/empty_list.expr.roc b/compiler/parse/tests/snapshots/pass/empty_list.expr.roc new file mode 100644 index 0000000000..0637a088a0 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/empty_list.expr.roc @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast b/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast new file mode 100644 index 0000000000..fa3993d530 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast @@ -0,0 +1,43 @@ +Platform { + header: PlatformHeader { + name: |L 0-0, C 9-23| PackageName { + account: "rtfeldman", + pkg: "blah", + }, + requires: PlatformRequires { + rigids: [], + signature: |L 0-0, C 38-47| Entry { + ident: |L 0-0, C 38-42| "main", + spaces_before_colon: [], + ann: |L 0-0, C 45-47| Record { + fields: [], + ext: None, + }, + }, + }, + exposes: [], + packages: [], + imports: [], + provides: [], + effects: Effects { + spaces_before_effects_keyword: [], + spaces_after_effects_keyword: [], + spaces_after_type_name: [], + effect_shortname: "fx", + effect_type_name: "Blah", + entries: [], + }, + before_header: [], + after_platform_keyword: [], + before_requires: [], + after_requires: [], + before_exposes: [], + after_exposes: [], + before_packages: [], + after_packages: [], + before_imports: [], + after_imports: [], + before_provides: [], + after_provides: [], + }, +} diff --git a/compiler/parse/tests/snapshots/pass/empty_platform_header.header.roc b/compiler/parse/tests/snapshots/pass/empty_platform_header.header.roc new file mode 100644 index 0000000000..86550152a0 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/empty_platform_header.header.roc @@ -0,0 +1 @@ +platform rtfeldman/blah requires {} { main : {} } exposes [] packages {} imports [] provides [] effects fx.Blah {} \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/empty_record.expr.result-ast b/compiler/parse/tests/snapshots/pass/empty_record.expr.result-ast new file mode 100644 index 0000000000..328cb0d3a3 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/empty_record.expr.result-ast @@ -0,0 +1,3 @@ +Record( + [], +) diff --git a/compiler/parse/tests/snapshots/pass/empty_record.expr.roc b/compiler/parse/tests/snapshots/pass/empty_record.expr.roc new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/empty_record.expr.roc @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/empty_string.expr.result-ast b/compiler/parse/tests/snapshots/pass/empty_string.expr.result-ast new file mode 100644 index 0000000000..a00db728af --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/empty_string.expr.result-ast @@ -0,0 +1,5 @@ +Str( + PlainLine( + "", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/empty_string.expr.roc b/compiler/parse/tests/snapshots/pass/empty_string.expr.roc new file mode 100644 index 0000000000..e16c76dff8 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/empty_string.expr.roc @@ -0,0 +1 @@ +"" diff --git a/compiler/parse/tests/snapshots/pass/equals.expr.result-ast b/compiler/parse/tests/snapshots/pass/equals.expr.result-ast new file mode 100644 index 0000000000..e69ba3d3da --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/equals.expr.result-ast @@ -0,0 +1,15 @@ +BinOps( + [ + ( + |L 0-0, C 0-1| Var { + module_name: "", + ident: "x", + }, + |L 0-0, C 1-3| Equals, + ), + ], + |L 0-0, C 3-4| Var { + module_name: "", + ident: "y", + }, +) diff --git a/compiler/parse/tests/snapshots/pass/equals.expr.roc b/compiler/parse/tests/snapshots/pass/equals.expr.roc new file mode 100644 index 0000000000..470efe6934 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/equals.expr.roc @@ -0,0 +1 @@ +x==y \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/equals_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/equals_with_spaces.expr.result-ast new file mode 100644 index 0000000000..10e4d471e7 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/equals_with_spaces.expr.result-ast @@ -0,0 +1,15 @@ +BinOps( + [ + ( + |L 0-0, C 0-1| Var { + module_name: "", + ident: "x", + }, + |L 0-0, C 2-4| Equals, + ), + ], + |L 0-0, C 5-6| Var { + module_name: "", + ident: "y", + }, +) diff --git a/compiler/parse/tests/snapshots/pass/equals_with_spaces.expr.roc b/compiler/parse/tests/snapshots/pass/equals_with_spaces.expr.roc new file mode 100644 index 0000000000..616ee7099e --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/equals_with_spaces.expr.roc @@ -0,0 +1 @@ +x == y \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/expect.expr.result-ast b/compiler/parse/tests/snapshots/pass/expect.expr.result-ast new file mode 100644 index 0000000000..06d5710e1a --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/expect.expr.result-ast @@ -0,0 +1,24 @@ +Expect( + |L 0-0, C 7-13| BinOps( + [ + ( + |L 0-0, C 7-8| Num( + "1", + ), + |L 0-0, C 9-11| Equals, + ), + ], + |L 0-0, C 12-13| Num( + "1", + ), + ), + |L 2-2, C 0-1| SpaceBefore( + Num( + "4", + ), + [ + Newline, + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/expect.expr.roc b/compiler/parse/tests/snapshots/pass/expect.expr.roc new file mode 100644 index 0000000000..d5c03f0843 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/expect.expr.roc @@ -0,0 +1,3 @@ +expect 1 == 1 + +4 diff --git a/compiler/parse/tests/snapshots/pass/float_with_underscores.expr.result-ast b/compiler/parse/tests/snapshots/pass/float_with_underscores.expr.result-ast new file mode 100644 index 0000000000..fc78c66972 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/float_with_underscores.expr.result-ast @@ -0,0 +1,3 @@ +Float( + "-1_23_456.0_1_23_456", +) diff --git a/compiler/parse/tests/snapshots/pass/float_with_underscores.expr.roc b/compiler/parse/tests/snapshots/pass/float_with_underscores.expr.roc new file mode 100644 index 0000000000..b0d09ed2ec --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/float_with_underscores.expr.roc @@ -0,0 +1 @@ +-1_23_456.0_1_23_456 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast b/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast new file mode 100644 index 0000000000..ae59f87b1d --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast @@ -0,0 +1,51 @@ +App { + header: AppHeader { + name: |L 0-0, C 4-15| PlainLine( + "quicksort", + ), + packages: [ + |L 1-1, C 15-33| Entry { + shorthand: "base", + spaces_after_shorthand: [], + package_or_path: |L 1-1, C 21-33| Path( + PlainLine( + "./platform", + ), + ), + }, + ], + imports: [ + |L 2-2, C 14-25| Package( + "foo", + ModuleName( + "Bar.Baz", + ), + [], + ), + ], + provides: [ + |L 3-3, C 15-24| Exposed( + "quicksort", + ), + ], + to: |L 3-3, C 30-34| ExistingPackage( + "base", + ), + before_header: [], + after_app_keyword: [], + before_packages: [ + Newline, + ], + after_packages: [], + before_imports: [ + Newline, + ], + after_imports: [], + before_provides: [ + Newline, + ], + after_provides: [], + before_to: [], + after_to: [], + }, +} diff --git a/compiler/parse/tests/snapshots/pass/full_app_header.header.roc b/compiler/parse/tests/snapshots/pass/full_app_header.header.roc new file mode 100644 index 0000000000..ce81012e75 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/full_app_header.header.roc @@ -0,0 +1,4 @@ +app "quicksort" + packages { base: "./platform" } + imports [ foo.Bar.Baz ] + provides [ quicksort ] to base diff --git a/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast b/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast new file mode 100644 index 0000000000..da4c7707a8 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast @@ -0,0 +1,76 @@ +App { + header: AppHeader { + name: |L 0-0, C 4-15| PlainLine( + "quicksort", + ), + packages: [ + |L 1-1, C 15-33| Entry { + shorthand: "base", + spaces_after_shorthand: [], + package_or_path: |L 1-1, C 21-33| Path( + PlainLine( + "./platform", + ), + ), + }, + ], + imports: [ + |L 2-6, C 14-5| Package( + "foo", + ModuleName( + "Bar", + ), + Collection { + items: [ + |L 3-3, C 8-11| SpaceBefore( + Exposed( + "Baz", + ), + [ + Newline, + ], + ), + |L 4-4, C 8-16| SpaceBefore( + Exposed( + "FortyTwo", + ), + [ + Newline, + ], + ), + ], + final_comments: [ + Newline, + LineComment( + " I'm a happy comment", + ), + ], + }, + ), + ], + provides: [ + |L 7-7, C 15-24| Exposed( + "quicksort", + ), + ], + to: |L 7-7, C 31-35| ExistingPackage( + "base", + ), + before_header: [], + after_app_keyword: [], + before_packages: [ + Newline, + ], + after_packages: [], + before_imports: [ + Newline, + ], + after_imports: [], + before_provides: [ + Newline, + ], + after_provides: [], + before_to: [], + after_to: [], + }, +} diff --git a/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.roc b/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.roc new file mode 100644 index 0000000000..7e5398b845 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.roc @@ -0,0 +1,8 @@ +app "quicksort" + packages { base: "./platform", } + imports [ foo.Bar.{ + Baz, + FortyTwo, + # I'm a happy comment + } ] + provides [ quicksort, ] to base diff --git a/compiler/parse/tests/snapshots/pass/highest_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/highest_float.expr.result-ast new file mode 100644 index 0000000000..9b46a3b26d --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/highest_float.expr.result-ast @@ -0,0 +1,3 @@ +Float( + "179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0", +) diff --git a/compiler/parse/tests/snapshots/pass/highest_float.expr.roc b/compiler/parse/tests/snapshots/pass/highest_float.expr.roc new file mode 100644 index 0000000000..568e886219 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/highest_float.expr.roc @@ -0,0 +1 @@ +179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/highest_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/highest_int.expr.result-ast new file mode 100644 index 0000000000..aefb235b32 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/highest_int.expr.result-ast @@ -0,0 +1,3 @@ +Num( + "9223372036854775807", +) diff --git a/compiler/parse/tests/snapshots/pass/highest_int.expr.roc b/compiler/parse/tests/snapshots/pass/highest_int.expr.roc new file mode 100644 index 0000000000..996d127e59 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/highest_int.expr.roc @@ -0,0 +1 @@ +9223372036854775807 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast new file mode 100644 index 0000000000..08f0ebe98f --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast @@ -0,0 +1,21 @@ +Defs( + [ + |L 0-0, C 0-6| Body( + |L 0-0, C 0-4| Identifier( + "iffy", + ), + |L 0-0, C 5-6| Num( + "5", + ), + ), + ], + |L 2-2, C 0-2| SpaceBefore( + Num( + "42", + ), + [ + Newline, + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/if_def.expr.roc b/compiler/parse/tests/snapshots/pass/if_def.expr.roc new file mode 100644 index 0000000000..868e83bbc8 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/if_def.expr.roc @@ -0,0 +1,3 @@ +iffy=5 + +42 diff --git a/compiler/parse/tests/snapshots/pass/int_with_underscore.expr.result-ast b/compiler/parse/tests/snapshots/pass/int_with_underscore.expr.result-ast new file mode 100644 index 0000000000..3912cc7edd --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/int_with_underscore.expr.result-ast @@ -0,0 +1,3 @@ +Num( + "1__23", +) diff --git a/compiler/parse/tests/snapshots/pass/int_with_underscore.expr.roc b/compiler/parse/tests/snapshots/pass/int_with_underscore.expr.roc new file mode 100644 index 0000000000..18487633d1 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/int_with_underscore.expr.roc @@ -0,0 +1 @@ +1__23 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/lowest_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/lowest_float.expr.result-ast new file mode 100644 index 0000000000..6bce8865e0 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/lowest_float.expr.result-ast @@ -0,0 +1,3 @@ +Float( + "-179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0", +) diff --git a/compiler/parse/tests/snapshots/pass/lowest_float.expr.roc b/compiler/parse/tests/snapshots/pass/lowest_float.expr.roc new file mode 100644 index 0000000000..d0c1a19c24 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/lowest_float.expr.roc @@ -0,0 +1 @@ +-179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/lowest_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/lowest_int.expr.result-ast new file mode 100644 index 0000000000..01111135a9 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/lowest_int.expr.result-ast @@ -0,0 +1,3 @@ +Num( + "-9223372036854775808", +) diff --git a/compiler/parse/tests/snapshots/pass/lowest_int.expr.roc b/compiler/parse/tests/snapshots/pass/lowest_int.expr.roc new file mode 100644 index 0000000000..9af921bf67 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/lowest_int.expr.roc @@ -0,0 +1 @@ +-9223372036854775808 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast b/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast new file mode 100644 index 0000000000..00c40bc292 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast @@ -0,0 +1,14 @@ +Closure( + [ + |L 0-0, C 1-11| MalformedIdent( + "the_answer", + Underscore( + 0, + 5, + ), + ), + ], + |L 0-0, C 15-17| Num( + "42", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.roc b/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.roc new file mode 100644 index 0000000000..2972e6dc77 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.roc @@ -0,0 +1 @@ +\the_answer -> 42 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast b/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast new file mode 100644 index 0000000000..038dab88da --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast @@ -0,0 +1,40 @@ +When( + |L 0-0, C 5-6| Var { + module_name: "", + ident: "x", + }, + [ + WhenBranch { + patterns: [ + |L 1-1, C 4-11| SpaceBefore( + Malformed( + "bar.and", + ), + [ + Newline, + ], + ), + ], + value: |L 1-1, C 15-16| Num( + "1", + ), + guard: None, + }, + WhenBranch { + patterns: [ + |L 2-2, C 4-5| SpaceBefore( + Underscore( + "", + ), + [ + Newline, + ], + ), + ], + value: |L 2-2, C 9-10| Num( + "4", + ), + guard: None, + }, + ], +) diff --git a/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.roc b/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.roc new file mode 100644 index 0000000000..f7a694456b --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.roc @@ -0,0 +1,3 @@ +when x is + bar.and -> 1 + _ -> 4 diff --git a/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast b/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast new file mode 100644 index 0000000000..65b4c3c94f --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast @@ -0,0 +1,40 @@ +When( + |L 0-0, C 5-6| Var { + module_name: "", + ident: "x", + }, + [ + WhenBranch { + patterns: [ + |L 1-1, C 4-11| SpaceBefore( + Malformed( + "Foo.and", + ), + [ + Newline, + ], + ), + ], + value: |L 1-1, C 15-16| Num( + "1", + ), + guard: None, + }, + WhenBranch { + patterns: [ + |L 2-2, C 4-5| SpaceBefore( + Underscore( + "", + ), + [ + Newline, + ], + ), + ], + value: |L 2-2, C 9-10| Num( + "4", + ), + guard: None, + }, + ], +) diff --git a/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.roc b/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.roc new file mode 100644 index 0000000000..137974847c --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.roc @@ -0,0 +1,3 @@ +when x is + Foo.and -> 1 + _ -> 4 diff --git a/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast b/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast new file mode 100644 index 0000000000..cb26ef49e9 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast @@ -0,0 +1,27 @@ +App { + header: AppHeader { + name: |L 0-0, C 4-14| PlainLine( + "test-app", + ), + packages: [], + imports: [], + provides: [], + to: |L 0-0, C 30-38| NewPackage( + Path( + PlainLine( + "./blah", + ), + ), + ), + before_header: [], + after_app_keyword: [], + before_packages: [], + after_packages: [], + before_imports: [], + after_imports: [], + before_provides: [], + after_provides: [], + before_to: [], + after_to: [], + }, +} diff --git a/compiler/parse/tests/snapshots/pass/minimal_app_header.header.roc b/compiler/parse/tests/snapshots/pass/minimal_app_header.header.roc new file mode 100644 index 0000000000..4678035773 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/minimal_app_header.header.roc @@ -0,0 +1 @@ +app "test-app" provides [] to "./blah" diff --git a/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast b/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast new file mode 100644 index 0000000000..2540766a37 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast @@ -0,0 +1,13 @@ +BinOps( + [ + ( + |L 0-0, C 0-3| Num( + "-12", + ), + |L 0-0, C 3-4| Minus, + ), + ], + |L 0-0, C 4-5| Num( + "5", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.roc b/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.roc new file mode 100644 index 0000000000..8d6e38dba4 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.roc @@ -0,0 +1 @@ +-12-5 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast b/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast new file mode 100644 index 0000000000..eb8ed8b3ea --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast @@ -0,0 +1,37 @@ +SpaceBefore( + Defs( + [ + |L 4-4, C 0-5| Body( + |L 4-4, C 0-1| Identifier( + "x", + ), + |L 4-4, C 4-5| Num( + "5", + ), + ), + ], + |L 6-6, C 0-2| SpaceBefore( + Num( + "42", + ), + [ + Newline, + Newline, + ], + ), + ), + [ + LineComment( + "## not docs!", + ), + DocComment( + "docs, but with a problem", + ), + DocComment( + "(namely that this is a mix of docs and regular comments)", + ), + LineComment( + " not docs", + ), + ], +) diff --git a/compiler/parse/tests/snapshots/pass/mixed_docs.expr.roc b/compiler/parse/tests/snapshots/pass/mixed_docs.expr.roc new file mode 100644 index 0000000000..4d9a22c8c3 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/mixed_docs.expr.roc @@ -0,0 +1,7 @@ +### not docs! +## docs, but with a problem +## (namely that this is a mix of docs and regular comments) +# not docs +x = 5 + +42 diff --git a/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast b/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast new file mode 100644 index 0000000000..1a9e243989 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast @@ -0,0 +1,42 @@ +[ + |L 0-3, C 0-5| SpaceAfter( + SpaceBefore( + Body( + |L 0-0, C 0-4| Identifier( + "main", + ), + |L 1-3, C 4-5| SpaceBefore( + Defs( + [ + |L 1-1, C 4-10| Body( + |L 1-1, C 4-5| Identifier( + "i", + ), + |L 1-1, C 8-10| Num( + "64", + ), + ), + ], + |L 3-3, C 4-5| SpaceBefore( + Var { + module_name: "", + ident: "i", + }, + [ + Newline, + Newline, + ], + ), + ), + [ + Newline, + ], + ), + ), + [], + ), + [ + Newline, + ], + ), +] diff --git a/compiler/parse/tests/snapshots/pass/module_def_newline.module.roc b/compiler/parse/tests/snapshots/pass/module_def_newline.module.roc new file mode 100644 index 0000000000..c14cf34253 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/module_def_newline.module.roc @@ -0,0 +1,4 @@ +main = + i = 64 + + i diff --git a/compiler/parse/tests/snapshots/pass/multi_backpassing.expr.result-ast b/compiler/parse/tests/snapshots/pass/multi_backpassing.expr.result-ast new file mode 100644 index 0000000000..d077cef709 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/multi_backpassing.expr.result-ast @@ -0,0 +1,46 @@ +Backpassing( + [ + |L 0-0, C 0-1| Identifier( + "x", + ), + |L 0-0, C 3-4| Identifier( + "y", + ), + ], + |L 0-0, C 8-23| Apply( + |L 0-0, C 8-17| Var { + module_name: "List", + ident: "map2", + }, + [ + |L 0-0, C 18-20| List( + [], + ), + |L 0-0, C 21-23| List( + [], + ), + ], + Space, + ), + |L 2-2, C 0-5| SpaceBefore( + BinOps( + [ + ( + |L 2-2, C 0-1| Var { + module_name: "", + ident: "x", + }, + |L 2-2, C 2-3| Plus, + ), + ], + |L 2-2, C 4-5| Var { + module_name: "", + ident: "y", + }, + ), + [ + Newline, + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/multi_backpassing.expr.roc b/compiler/parse/tests/snapshots/pass/multi_backpassing.expr.roc new file mode 100644 index 0000000000..06a4454183 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/multi_backpassing.expr.roc @@ -0,0 +1,3 @@ +x, y <- List.map2 [] [] + +x + y diff --git a/compiler/parse/tests/snapshots/pass/multi_char_string.expr.result-ast b/compiler/parse/tests/snapshots/pass/multi_char_string.expr.result-ast new file mode 100644 index 0000000000..21f0b7d0a6 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/multi_char_string.expr.result-ast @@ -0,0 +1,5 @@ +Str( + PlainLine( + "foo", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/multi_char_string.expr.roc b/compiler/parse/tests/snapshots/pass/multi_char_string.expr.roc new file mode 100644 index 0000000000..810c96eeeb --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/multi_char_string.expr.roc @@ -0,0 +1 @@ +"foo" diff --git a/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast new file mode 100644 index 0000000000..c6e1c801ac --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast @@ -0,0 +1,27 @@ +Defs( + [ + |L 0-1, C 0-6| Annotation( + |L 0-0, C 0-1| Identifier( + "f", + ), + |L 1-1, C 4-6| SpaceBefore( + Record { + fields: [], + ext: None, + }, + [ + Newline, + ], + ), + ), + ], + |L 3-3, C 0-2| SpaceBefore( + Num( + "42", + ), + [ + Newline, + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.roc b/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.roc new file mode 100644 index 0000000000..0ec71af042 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.roc @@ -0,0 +1,4 @@ +f : + {} + +42 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast new file mode 100644 index 0000000000..8ba2bc2b26 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast @@ -0,0 +1,29 @@ +Defs( + [ + |L 0-1, C 0-6| Annotation( + |L 0-0, C 0-1| Identifier( + "f", + ), + |L 1-1, C 4-6| SpaceBefore( + Record { + fields: [], + ext: None, + }, + [ + LineComment( + " comment", + ), + ], + ), + ), + ], + |L 3-3, C 0-2| SpaceBefore( + Num( + "42", + ), + [ + Newline, + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.roc b/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.roc new file mode 100644 index 0000000000..eee9119dcd --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.roc @@ -0,0 +1,4 @@ +f :# comment + {} + +42 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/multiple_fields.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiple_fields.expr.result-ast new file mode 100644 index 0000000000..bb7b50e47a --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/multiple_fields.expr.result-ast @@ -0,0 +1,13 @@ +Access( + Access( + Access( + Var { + module_name: "", + ident: "rec", + }, + "abc", + ), + "def", + ), + "ghi", +) diff --git a/compiler/parse/tests/snapshots/pass/multiple_fields.expr.roc b/compiler/parse/tests/snapshots/pass/multiple_fields.expr.roc new file mode 100644 index 0000000000..b5cbe3aa75 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/multiple_fields.expr.roc @@ -0,0 +1 @@ +rec.abc.def.ghi \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast new file mode 100644 index 0000000000..f5e1463092 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast @@ -0,0 +1,19 @@ +BinOps( + [ + ( + |L 0-0, C 0-2| Num( + "31", + ), + |L 0-0, C 2-3| Star, + ), + ( + |L 0-0, C 3-5| Num( + "42", + ), + |L 0-0, C 5-6| Plus, + ), + ], + |L 0-0, C 6-9| Num( + "534", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/multiple_operators.expr.roc b/compiler/parse/tests/snapshots/pass/multiple_operators.expr.roc new file mode 100644 index 0000000000..2e789bcecf --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/multiple_operators.expr.roc @@ -0,0 +1 @@ +31*42+534 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/neg_inf_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/neg_inf_float.expr.result-ast new file mode 100644 index 0000000000..617306a026 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/neg_inf_float.expr.result-ast @@ -0,0 +1,7 @@ +UnaryOp( + |L 0-0, C 1-4| Var { + module_name: "", + ident: "inf", + }, + |L 0-0, C 0-1| Negate, +) diff --git a/compiler/parse/tests/snapshots/pass/neg_inf_float.expr.roc b/compiler/parse/tests/snapshots/pass/neg_inf_float.expr.roc new file mode 100644 index 0000000000..f9111418ae --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/neg_inf_float.expr.roc @@ -0,0 +1 @@ +-inf \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/negative_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/negative_float.expr.result-ast new file mode 100644 index 0000000000..b4246aee73 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/negative_float.expr.result-ast @@ -0,0 +1,3 @@ +Float( + "-42.9", +) diff --git a/compiler/parse/tests/snapshots/pass/negative_float.expr.roc b/compiler/parse/tests/snapshots/pass/negative_float.expr.roc new file mode 100644 index 0000000000..1409b58f0f --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/negative_float.expr.roc @@ -0,0 +1 @@ +-42.9 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/negative_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/negative_int.expr.result-ast new file mode 100644 index 0000000000..a41780d138 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/negative_int.expr.result-ast @@ -0,0 +1,3 @@ +Num( + "-42", +) diff --git a/compiler/parse/tests/snapshots/pass/negative_int.expr.roc b/compiler/parse/tests/snapshots/pass/negative_int.expr.roc new file mode 100644 index 0000000000..67f7ad0566 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/negative_int.expr.roc @@ -0,0 +1 @@ +-42 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast b/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast new file mode 100644 index 0000000000..0c597f8d28 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast @@ -0,0 +1,99 @@ +[ + |L 0-5, C 0-20| SpaceAfter( + SpaceBefore( + Body( + |L 0-0, C 0-4| Identifier( + "main", + ), + |L 1-5, C 4-20| SpaceBefore( + Defs( + [ + |L 2-3, C 4-20| AnnotatedBody { + ann_pattern: |L 1-1, C 4-16| Identifier( + "wrappedNotEq", + ), + ann_type: |L 1-1, C 27-31| Function( + [ + |L 1-1, C 19-20| BoundVariable( + "a", + ), + |L 1-1, C 22-23| BoundVariable( + "a", + ), + ], + |L 1-1, C 27-31| Apply( + "", + "Bool", + [], + ), + ), + comment: None, + body_pattern: |L 2-2, C 4-16| Identifier( + "wrappedNotEq", + ), + body_expr: |L 2-3, C 19-20| Closure( + [ + |L 2-2, C 20-24| Identifier( + "num1", + ), + |L 2-2, C 26-30| Identifier( + "num2", + ), + ], + |L 3-3, C 8-20| SpaceBefore( + BinOps( + [ + ( + |L 3-3, C 8-12| Var { + module_name: "", + ident: "num1", + }, + |L 3-3, C 13-15| NotEquals, + ), + ], + |L 3-3, C 16-20| Var { + module_name: "", + ident: "num2", + }, + ), + [ + Newline, + ], + ), + ), + }, + ], + |L 5-5, C 4-20| SpaceBefore( + Apply( + |L 5-5, C 4-16| Var { + module_name: "", + ident: "wrappedNotEq", + }, + [ + |L 5-5, C 17-18| Num( + "2", + ), + |L 5-5, C 19-20| Num( + "3", + ), + ], + Space, + ), + [ + Newline, + Newline, + ], + ), + ), + [ + Newline, + ], + ), + ), + [], + ), + [ + Newline, + ], + ), +] diff --git a/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.roc b/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.roc new file mode 100644 index 0000000000..a7d12703ae --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.roc @@ -0,0 +1,6 @@ +main = + wrappedNotEq : a, a -> Bool + wrappedNotEq = \num1, num2 -> + num1 != num2 + + wrappedNotEq 2 3 diff --git a/compiler/parse/tests/snapshots/pass/nested_module.header.result-ast b/compiler/parse/tests/snapshots/pass/nested_module.header.result-ast new file mode 100644 index 0000000000..532089580b --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/nested_module.header.result-ast @@ -0,0 +1,15 @@ +Interface { + header: InterfaceHeader { + name: |L 0-0, C 10-21| ModuleName( + "Foo.Bar.Baz", + ), + exposes: [], + imports: [], + before_header: [], + after_interface_keyword: [], + before_exposes: [], + after_exposes: [], + before_imports: [], + after_imports: [], + }, +} diff --git a/compiler/parse/tests/snapshots/pass/nested_module.header.roc b/compiler/parse/tests/snapshots/pass/nested_module.header.roc new file mode 100644 index 0000000000..f43501c404 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/nested_module.header.roc @@ -0,0 +1 @@ +interface Foo.Bar.Baz exposes [] imports [] diff --git a/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast new file mode 100644 index 0000000000..70cc7456f6 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast @@ -0,0 +1,26 @@ +Defs( + [ + |L 0-1, C 0-5| Body( + |L 0-0, C 0-1| Identifier( + "x", + ), + |L 1-1, C 4-5| SpaceBefore( + Num( + "5", + ), + [ + Newline, + ], + ), + ), + ], + |L 3-3, C 0-2| SpaceBefore( + Num( + "42", + ), + [ + Newline, + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.roc b/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.roc new file mode 100644 index 0000000000..0662bdb2c1 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.roc @@ -0,0 +1,4 @@ +x = + 5 + +42 diff --git a/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast new file mode 100644 index 0000000000..977121c8ea --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast @@ -0,0 +1,18 @@ +BinOps( + [ + ( + |L 0-0, C 0-1| Num( + "3", + ), + |L 0-0, C 3-4| Star, + ), + ], + |L 1-1, C 2-3| SpaceBefore( + Num( + "4", + ), + [ + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.roc b/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.roc new file mode 100644 index 0000000000..84e2c946bd --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.roc @@ -0,0 +1,2 @@ +3 * + 4 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast new file mode 100644 index 0000000000..f6ac47b32a --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast @@ -0,0 +1,18 @@ +BinOps( + [ + ( + |L 0-0, C 0-1| Num( + "3", + ), + |L 0-0, C 3-4| Minus, + ), + ], + |L 1-1, C 2-3| SpaceBefore( + Num( + "4", + ), + [ + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.roc b/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.roc new file mode 100644 index 0000000000..92bcc0c2df --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.roc @@ -0,0 +1,2 @@ +3 - + 4 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast new file mode 100644 index 0000000000..78bbc9865e --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast @@ -0,0 +1,36 @@ +Defs( + [ + |L 0-1, C 0-7| Body( + |L 0-0, C 0-1| Identifier( + "x", + ), + |L 0-1, C 4-7| BinOps( + [ + ( + |L 0-0, C 4-5| SpaceAfter( + Num( + "1", + ), + [ + Newline, + ], + ), + |L 1-1, C 4-5| LessThan, + ), + ], + |L 1-1, C 6-7| Num( + "2", + ), + ), + ), + ], + |L 3-3, C 0-2| SpaceBefore( + Num( + "42", + ), + [ + Newline, + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.roc b/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.roc new file mode 100644 index 0000000000..1d80197ef1 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.roc @@ -0,0 +1,4 @@ +x = 1 + < 2 + +42 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast new file mode 100644 index 0000000000..73c9b6b40d --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast @@ -0,0 +1,18 @@ +BinOps( + [ + ( + |L 0-0, C 0-1| SpaceAfter( + Num( + "3", + ), + [ + Newline, + ], + ), + |L 1-1, C 0-1| Plus, + ), + ], + |L 1-1, C 2-3| Num( + "4", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/newline_before_add.expr.roc b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.roc new file mode 100644 index 0000000000..2aef041bd9 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.roc @@ -0,0 +1,2 @@ +3 ++ 4 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast new file mode 100644 index 0000000000..4b8d5725c4 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast @@ -0,0 +1,18 @@ +BinOps( + [ + ( + |L 0-0, C 0-1| SpaceAfter( + Num( + "3", + ), + [ + Newline, + ], + ), + |L 1-1, C 0-1| Minus, + ), + ], + |L 1-1, C 2-3| Num( + "4", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.roc b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.roc new file mode 100644 index 0000000000..19ab26bdea --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.roc @@ -0,0 +1,2 @@ +3 +- 4 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/newline_inside_empty_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_inside_empty_list.expr.result-ast new file mode 100644 index 0000000000..c2718d68ad --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/newline_inside_empty_list.expr.result-ast @@ -0,0 +1,8 @@ +List( + Collection { + items: [], + final_comments: [ + Newline, + ], + }, +) diff --git a/compiler/parse/tests/snapshots/pass/newline_inside_empty_list.expr.roc b/compiler/parse/tests/snapshots/pass/newline_inside_empty_list.expr.roc new file mode 100644 index 0000000000..32960f8ced --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/newline_inside_empty_list.expr.roc @@ -0,0 +1,2 @@ +[ +] \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast new file mode 100644 index 0000000000..15f065505b --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast @@ -0,0 +1,17 @@ +List( + [ + |L 1-1, C 0-1| SpaceBefore( + SpaceAfter( + Num( + "1", + ), + [ + Newline, + ], + ), + [ + Newline, + ], + ), + ], +) diff --git a/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.roc b/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.roc new file mode 100644 index 0000000000..730c97e183 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.roc @@ -0,0 +1,3 @@ +[ +1 +] \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast new file mode 100644 index 0000000000..8497dc8470 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast @@ -0,0 +1,74 @@ +Platform { + header: PlatformHeader { + name: |L 0-0, C 9-19| PackageName { + account: "foo", + pkg: "barbaz", + }, + requires: PlatformRequires { + rigids: [ + |L 1-1, C 14-26| Entry { + rigid: "model", + alias: "Model", + }, + ], + signature: |L 1-1, C 30-39| Entry { + ident: |L 1-1, C 30-34| "main", + spaces_before_colon: [], + ann: |L 1-1, C 37-39| Record { + fields: [], + ext: None, + }, + }, + }, + exposes: [], + packages: [ + |L 3-3, C 15-27| Entry { + shorthand: "foo", + spaces_after_shorthand: [], + package_or_path: |L 3-3, C 20-27| Path( + PlainLine( + "./foo", + ), + ), + }, + ], + imports: [], + provides: [ + |L 5-5, C 15-26| Exposed( + "mainForHost", + ), + ], + effects: Effects { + spaces_before_effects_keyword: [ + Newline, + ], + spaces_after_effects_keyword: [], + spaces_after_type_name: [], + effect_shortname: "fx", + effect_type_name: "Effect", + entries: [], + }, + before_header: [], + after_platform_keyword: [], + before_requires: [ + Newline, + ], + after_requires: [], + before_exposes: [ + Newline, + ], + after_exposes: [], + before_packages: [ + Newline, + ], + after_packages: [], + before_imports: [ + Newline, + ], + after_imports: [], + before_provides: [ + Newline, + ], + after_provides: [], + }, +} diff --git a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc new file mode 100644 index 0000000000..d12bc9f00a --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc @@ -0,0 +1,7 @@ +platform foo/barbaz + requires {model=>Model} { main : {} } + exposes [] + packages { foo: "./foo" } + imports [] + provides [ mainForHost ] + effects fx.Effect {} diff --git a/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast b/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast new file mode 100644 index 0000000000..0a5c470bc0 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast @@ -0,0 +1,37 @@ +SpaceBefore( + Defs( + [ + |L 4-4, C 0-5| Body( + |L 4-4, C 0-1| Identifier( + "x", + ), + |L 4-4, C 4-5| Num( + "5", + ), + ), + ], + |L 6-6, C 0-2| SpaceBefore( + Num( + "42", + ), + [ + Newline, + Newline, + ], + ), + ), + [ + LineComment( + "######", + ), + LineComment( + "## not docs!", + ), + LineComment( + "#still not docs", + ), + LineComment( + "#####", + ), + ], +) diff --git a/compiler/parse/tests/snapshots/pass/not_docs.expr.roc b/compiler/parse/tests/snapshots/pass/not_docs.expr.roc new file mode 100644 index 0000000000..28793bd79e --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/not_docs.expr.roc @@ -0,0 +1,7 @@ +####### +### not docs! +##still not docs +###### +x = 5 + +42 diff --git a/compiler/parse/tests/snapshots/pass/one_backpassing.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_backpassing.expr.result-ast new file mode 100644 index 0000000000..257e698479 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/one_backpassing.expr.result-ast @@ -0,0 +1,37 @@ +SpaceBefore( + Backpassing( + [ + |L 1-1, C 0-1| Identifier( + "x", + ), + ], + |L 1-1, C 5-14| ParensAround( + Closure( + [ + |L 1-1, C 7-8| Identifier( + "y", + ), + ], + |L 1-1, C 12-13| Var { + module_name: "", + ident: "y", + }, + ), + ), + |L 3-3, C 0-1| SpaceBefore( + Var { + module_name: "", + ident: "x", + }, + [ + Newline, + Newline, + ], + ), + ), + [ + LineComment( + " leading comment", + ), + ], +) diff --git a/compiler/parse/tests/snapshots/pass/one_backpassing.expr.roc b/compiler/parse/tests/snapshots/pass/one_backpassing.expr.roc new file mode 100644 index 0000000000..a7cfb74204 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/one_backpassing.expr.roc @@ -0,0 +1,4 @@ +# leading comment +x <- (\y -> y) + +x diff --git a/compiler/parse/tests/snapshots/pass/one_char_string.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_char_string.expr.result-ast new file mode 100644 index 0000000000..e15ee75ac8 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/one_char_string.expr.result-ast @@ -0,0 +1,5 @@ +Str( + PlainLine( + "x", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/one_char_string.expr.roc b/compiler/parse/tests/snapshots/pass/one_char_string.expr.roc new file mode 100644 index 0000000000..92232f694a --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/one_char_string.expr.roc @@ -0,0 +1 @@ +"x" diff --git a/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast new file mode 100644 index 0000000000..daa047957c --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast @@ -0,0 +1,28 @@ +SpaceBefore( + Defs( + [ + |L 1-1, C 0-3| Body( + |L 1-1, C 0-1| Identifier( + "x", + ), + |L 1-1, C 2-3| Num( + "5", + ), + ), + ], + |L 3-3, C 0-2| SpaceBefore( + Num( + "42", + ), + [ + Newline, + Newline, + ], + ), + ), + [ + LineComment( + " leading comment", + ), + ], +) diff --git a/compiler/parse/tests/snapshots/pass/one_def.expr.roc b/compiler/parse/tests/snapshots/pass/one_def.expr.roc new file mode 100644 index 0000000000..2f6b7e6b76 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/one_def.expr.roc @@ -0,0 +1,4 @@ +# leading comment +x=5 + +42 diff --git a/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast new file mode 100644 index 0000000000..77fd849f59 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast @@ -0,0 +1,13 @@ +BinOps( + [ + ( + |L 0-0, C 0-1| Num( + "1", + ), + |L 0-0, C 1-2| Minus, + ), + ], + |L 0-0, C 2-3| Num( + "2", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/one_minus_two.expr.roc b/compiler/parse/tests/snapshots/pass/one_minus_two.expr.roc new file mode 100644 index 0000000000..21869de97e --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/one_minus_two.expr.roc @@ -0,0 +1 @@ +1-2 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast new file mode 100644 index 0000000000..7e45139380 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast @@ -0,0 +1,13 @@ +BinOps( + [ + ( + |L 0-0, C 0-1| Num( + "1", + ), + |L 0-0, C 1-2| Plus, + ), + ], + |L 0-0, C 2-3| Num( + "2", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/one_plus_two.expr.roc b/compiler/parse/tests/snapshots/pass/one_plus_two.expr.roc new file mode 100644 index 0000000000..dc462db0d5 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/one_plus_two.expr.roc @@ -0,0 +1 @@ +1+2 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast new file mode 100644 index 0000000000..001962d745 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast @@ -0,0 +1,28 @@ +SpaceBefore( + Defs( + [ + |L 1-1, C 0-5| Body( + |L 1-1, C 0-1| Identifier( + "x", + ), + |L 1-1, C 4-5| Num( + "5", + ), + ), + ], + |L 3-3, C 0-2| SpaceBefore( + Num( + "42", + ), + [ + Newline, + Newline, + ], + ), + ), + [ + LineComment( + " leading comment", + ), + ], +) diff --git a/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.roc b/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.roc new file mode 100644 index 0000000000..b5a1b3af49 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.roc @@ -0,0 +1,4 @@ +# leading comment +x = 5 + +42 diff --git a/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast new file mode 100644 index 0000000000..f35a0b6c44 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast @@ -0,0 +1,24 @@ +BinOps( + [ + ( + |L 0-0, C 0-1| SpaceAfter( + Num( + "3", + ), + [ + Newline, + ], + ), + |L 1-1, C 0-1| Plus, + ), + ], + |L 3-3, C 2-3| SpaceBefore( + Num( + "4", + ), + [ + Newline, + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.roc b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.roc new file mode 100644 index 0000000000..518b92423c --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.roc @@ -0,0 +1,4 @@ +3 ++ + + 4 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast new file mode 100644 index 0000000000..2548a5221e --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast @@ -0,0 +1,7 @@ +List( + [ + |L 0-0, C 1-2| Num( + "1", + ), + ], +) diff --git a/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.roc b/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.roc new file mode 100644 index 0000000000..bace2a0be1 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.roc @@ -0,0 +1 @@ +[1] \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast b/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast new file mode 100644 index 0000000000..ea1031f76d --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast @@ -0,0 +1,14 @@ +Apply( + |L 0-0, C 1-5| ParensAround( + Var { + module_name: "", + ident: "whee", + }, + ), + [ + |L 0-0, C 7-8| Num( + "1", + ), + ], + Space, +) diff --git a/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.roc b/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.roc new file mode 100644 index 0000000000..f39a9ee9af --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.roc @@ -0,0 +1 @@ +(whee) 1 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/parenthetical_basic_field.expr.result-ast b/compiler/parse/tests/snapshots/pass/parenthetical_basic_field.expr.result-ast new file mode 100644 index 0000000000..a2b8bd86ad --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/parenthetical_basic_field.expr.result-ast @@ -0,0 +1,9 @@ +Access( + ParensAround( + Var { + module_name: "", + ident: "rec", + }, + ), + "field", +) diff --git a/compiler/parse/tests/snapshots/pass/parenthetical_basic_field.expr.roc b/compiler/parse/tests/snapshots/pass/parenthetical_basic_field.expr.roc new file mode 100644 index 0000000000..687a92bf5c --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/parenthetical_basic_field.expr.roc @@ -0,0 +1 @@ +(rec).field \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/parenthetical_field_qualified_var.expr.result-ast b/compiler/parse/tests/snapshots/pass/parenthetical_field_qualified_var.expr.result-ast new file mode 100644 index 0000000000..ec387d8d3b --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/parenthetical_field_qualified_var.expr.result-ast @@ -0,0 +1,9 @@ +Access( + ParensAround( + Var { + module_name: "One.Two", + ident: "rec", + }, + ), + "field", +) diff --git a/compiler/parse/tests/snapshots/pass/parenthetical_field_qualified_var.expr.roc b/compiler/parse/tests/snapshots/pass/parenthetical_field_qualified_var.expr.roc new file mode 100644 index 0000000000..5359b0e5bb --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/parenthetical_field_qualified_var.expr.roc @@ -0,0 +1 @@ +(One.Two.rec).field \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/parenthetical_var.expr.result-ast b/compiler/parse/tests/snapshots/pass/parenthetical_var.expr.result-ast new file mode 100644 index 0000000000..dd22ad62c6 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/parenthetical_var.expr.result-ast @@ -0,0 +1,6 @@ +ParensAround( + Var { + module_name: "", + ident: "whee", + }, +) diff --git a/compiler/parse/tests/snapshots/pass/parenthetical_var.expr.roc b/compiler/parse/tests/snapshots/pass/parenthetical_var.expr.roc new file mode 100644 index 0000000000..169a3e0e75 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/parenthetical_var.expr.roc @@ -0,0 +1 @@ +(whee) \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast b/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast new file mode 100644 index 0000000000..f23f7883e8 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast @@ -0,0 +1,36 @@ +Defs( + [ + |L 0-0, C 0-26| Alias { + name: |L 0-0, C 0-4| "Blah", + vars: [ + |L 0-0, C 5-6| Identifier( + "a", + ), + |L 0-0, C 7-8| Identifier( + "b", + ), + ], + ann: |L 0-0, C 11-26| Apply( + "Foo.Bar", + "Baz", + [ + |L 0-0, C 23-24| BoundVariable( + "x", + ), + |L 0-0, C 25-26| BoundVariable( + "y", + ), + ], + ), + }, + ], + |L 2-2, C 0-2| SpaceBefore( + Num( + "42", + ), + [ + Newline, + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/parse_alias.expr.roc b/compiler/parse/tests/snapshots/pass/parse_alias.expr.roc new file mode 100644 index 0000000000..f2a402450b --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/parse_alias.expr.roc @@ -0,0 +1,3 @@ +Blah a b : Foo.Bar.Baz x y + +42 diff --git a/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast b/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast new file mode 100644 index 0000000000..08b1230271 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast @@ -0,0 +1,45 @@ +Defs( + [ + |L 0-0, C 0-33| Annotation( + |L 0-0, C 0-3| Identifier( + "foo", + ), + |L 0-0, C 6-33| As( + |L 0-0, C 6-21| Apply( + "Foo.Bar", + "Baz", + [ + |L 0-0, C 18-19| BoundVariable( + "x", + ), + |L 0-0, C 20-21| BoundVariable( + "y", + ), + ], + ), + [], + |L 0-0, C 25-33| Apply( + "", + "Blah", + [ + |L 0-0, C 30-31| BoundVariable( + "a", + ), + |L 0-0, C 32-33| BoundVariable( + "b", + ), + ], + ), + ), + ), + ], + |L 2-2, C 0-2| SpaceBefore( + Num( + "42", + ), + [ + Newline, + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.roc b/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.roc new file mode 100644 index 0000000000..e5d9c28013 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.roc @@ -0,0 +1,3 @@ +foo : Foo.Bar.Baz x y as Blah a b + +42 diff --git a/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast new file mode 100644 index 0000000000..962f52971a --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast @@ -0,0 +1,80 @@ +When( + |L 0-0, C 5-22| Apply( + |L 0-0, C 5-11| GlobalTag( + "Delmin", + ), + [ + |L 0-0, C 13-19| ParensAround( + Apply( + |L 0-0, C 13-16| GlobalTag( + "Del", + ), + [ + |L 0-0, C 17-19| Var { + module_name: "", + ident: "rx", + }, + ], + Space, + ), + ), + |L 0-0, C 21-22| Num( + "0", + ), + ], + Space, + ), + [ + WhenBranch { + patterns: [ + |L 1-1, C 4-22| SpaceBefore( + Apply( + |L 1-1, C 4-10| GlobalTag( + "Delmin", + ), + [ + |L 1-1, C 12-18| Apply( + |L 1-1, C 12-15| GlobalTag( + "Del", + ), + [ + |L 1-1, C 16-18| Identifier( + "ry", + ), + ], + ), + |L 1-1, C 21-22| Underscore( + "", + ), + ], + ), + [ + Newline, + ], + ), + ], + value: |L 1-1, C 26-47| Apply( + |L 1-1, C 26-30| GlobalTag( + "Node", + ), + [ + |L 1-1, C 31-36| GlobalTag( + "Black", + ), + |L 1-1, C 37-38| Num( + "0", + ), + |L 1-1, C 39-44| GlobalTag( + "False", + ), + |L 1-1, C 45-47| Var { + module_name: "", + ident: "ry", + }, + ], + Space, + ), + guard: None, + }, + ], +) diff --git a/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.roc b/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.roc new file mode 100644 index 0000000000..c397c8ce35 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.roc @@ -0,0 +1,2 @@ +when Delmin (Del rx) 0 is + Delmin (Del ry ) _ -> Node Black 0 False ry diff --git a/compiler/parse/tests/snapshots/pass/pos_inf_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/pos_inf_float.expr.result-ast new file mode 100644 index 0000000000..58a09d0639 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/pos_inf_float.expr.result-ast @@ -0,0 +1,4 @@ +Var { + module_name: "", + ident: "inf", +} diff --git a/compiler/parse/tests/snapshots/pass/pos_inf_float.expr.roc b/compiler/parse/tests/snapshots/pass/pos_inf_float.expr.roc new file mode 100644 index 0000000000..a28aa9a0ff --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/pos_inf_float.expr.roc @@ -0,0 +1 @@ +inf \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/positive_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/positive_float.expr.result-ast new file mode 100644 index 0000000000..366609e578 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/positive_float.expr.result-ast @@ -0,0 +1,3 @@ +Float( + "42.9", +) diff --git a/compiler/parse/tests/snapshots/pass/positive_float.expr.roc b/compiler/parse/tests/snapshots/pass/positive_float.expr.roc new file mode 100644 index 0000000000..9f125123d5 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/positive_float.expr.roc @@ -0,0 +1 @@ +42.9 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/positive_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/positive_int.expr.result-ast new file mode 100644 index 0000000000..23ee1510a7 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/positive_int.expr.result-ast @@ -0,0 +1,3 @@ +Num( + "42", +) diff --git a/compiler/parse/tests/snapshots/pass/positive_int.expr.roc b/compiler/parse/tests/snapshots/pass/positive_int.expr.roc new file mode 100644 index 0000000000..f70d7bba4a --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/positive_int.expr.roc @@ -0,0 +1 @@ +42 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.result-ast new file mode 100644 index 0000000000..951f350742 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.result-ast @@ -0,0 +1,7 @@ +MalformedIdent( + "@One.Two.Whee", + BadPrivateTag( + 0, + 4, + ), +) diff --git a/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.roc b/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.roc new file mode 100644 index 0000000000..e5c825a346 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.roc @@ -0,0 +1 @@ +@One.Two.Whee \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/qualified_field.expr.result-ast b/compiler/parse/tests/snapshots/pass/qualified_field.expr.result-ast new file mode 100644 index 0000000000..d629481dbe --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/qualified_field.expr.result-ast @@ -0,0 +1,13 @@ +Access( + Access( + Access( + Var { + module_name: "One.Two", + ident: "rec", + }, + "abc", + ), + "def", + ), + "ghi", +) diff --git a/compiler/parse/tests/snapshots/pass/qualified_field.expr.roc b/compiler/parse/tests/snapshots/pass/qualified_field.expr.roc new file mode 100644 index 0000000000..f5b18894d8 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/qualified_field.expr.roc @@ -0,0 +1 @@ +One.Two.rec.abc.def.ghi \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.result-ast new file mode 100644 index 0000000000..2ae1a05ac2 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.result-ast @@ -0,0 +1,7 @@ +MalformedIdent( + "One.Two.Whee", + QualifiedTag( + 0, + 12, + ), +) diff --git a/compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.roc b/compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.roc new file mode 100644 index 0000000000..448358be9e --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.roc @@ -0,0 +1 @@ +One.Two.Whee \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/qualified_var.expr.result-ast b/compiler/parse/tests/snapshots/pass/qualified_var.expr.result-ast new file mode 100644 index 0000000000..14a1b7ab96 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/qualified_var.expr.result-ast @@ -0,0 +1,4 @@ +Var { + module_name: "One.Two", + ident: "whee", +} diff --git a/compiler/parse/tests/snapshots/pass/qualified_var.expr.roc b/compiler/parse/tests/snapshots/pass/qualified_var.expr.roc new file mode 100644 index 0000000000..28a06dc970 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/qualified_var.expr.roc @@ -0,0 +1 @@ +One.Two.whee \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast new file mode 100644 index 0000000000..db061e68f7 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast @@ -0,0 +1,48 @@ +SpaceBefore( + Defs( + [ + |L 1-1, C 0-12| Body( + |L 1-1, C 0-8| RecordDestructure( + [ + |L 1-1, C 2-3| Identifier( + "x", + ), + |L 1-1, C 5-7| Identifier( + "y", + ), + ], + ), + |L 1-1, C 11-12| Num( + "5", + ), + ), + |L 2-2, C 0-5| SpaceBefore( + Body( + |L 2-2, C 0-1| Identifier( + "y", + ), + |L 2-2, C 4-5| Num( + "6", + ), + ), + [ + Newline, + ], + ), + ], + |L 4-4, C 0-2| SpaceBefore( + Num( + "42", + ), + [ + Newline, + Newline, + ], + ), + ), + [ + LineComment( + " leading comment", + ), + ], +) diff --git a/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.roc b/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.roc new file mode 100644 index 0000000000..2f565b852a --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.roc @@ -0,0 +1,5 @@ +# leading comment +{ x, y } = 5 +y = 6 + +42 diff --git a/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast new file mode 100644 index 0000000000..7ee1e6b7b0 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast @@ -0,0 +1,22 @@ +RecordUpdate { + update: |L 0-0, C 2-13| Var { + module_name: "Foo.Bar", + ident: "baz", + }, + fields: [ + |L 0-0, C 16-20| RequiredValue( + |L 0-0, C 16-17| "x", + [], + |L 0-0, C 19-20| Num( + "5", + ), + ), + |L 0-0, C 22-26| RequiredValue( + |L 0-0, C 22-23| "y", + [], + |L 0-0, C 25-26| Num( + "0", + ), + ), + ], +} diff --git a/compiler/parse/tests/snapshots/pass/record_update.expr.roc b/compiler/parse/tests/snapshots/pass/record_update.expr.roc new file mode 100644 index 0000000000..ac38be8956 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/record_update.expr.roc @@ -0,0 +1 @@ +{ Foo.Bar.baz & x: 5, y: 0 } \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast new file mode 100644 index 0000000000..b4f4133f3b --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast @@ -0,0 +1,30 @@ +Record( + [ + |L 0-0, C 1-26| RequiredValue( + |L 0-0, C 1-2| "x", + [], + |L 0-0, C 5-26| If( + [ + ( + |L 0-0, C 8-12| GlobalTag( + "True", + ), + |L 0-0, C 18-19| Num( + "1", + ), + ), + ], + |L 0-0, C 25-26| Num( + "2", + ), + ), + ), + |L 0-0, C 28-32| RequiredValue( + |L 0-0, C 28-29| "y", + [], + |L 0-0, C 31-32| Num( + "3", + ), + ), + ], +) diff --git a/compiler/parse/tests/snapshots/pass/record_with_if.expr.roc b/compiler/parse/tests/snapshots/pass/record_with_if.expr.roc new file mode 100644 index 0000000000..7c186e5d31 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/record_with_if.expr.roc @@ -0,0 +1 @@ +{x : if True then 1 else 2, y: 3 } \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast new file mode 100644 index 0000000000..ca8f338bbd --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast @@ -0,0 +1,10 @@ +Closure( + [ + |L 0-0, C 1-2| Identifier( + "a", + ), + ], + |L 0-0, C 6-8| Num( + "42", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.roc b/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.roc new file mode 100644 index 0000000000..188d4d607e --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.roc @@ -0,0 +1 @@ +\a -> 42 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast new file mode 100644 index 0000000000..c3657bfca5 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast @@ -0,0 +1,10 @@ +Closure( + [ + |L 0-0, C 1-2| Underscore( + "", + ), + ], + |L 0-0, C 6-8| Num( + "42", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.roc b/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.roc new file mode 100644 index 0000000000..28c65bdeba --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.roc @@ -0,0 +1 @@ +\_ -> 42 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/space_only_after_minus.expr.result-ast b/compiler/parse/tests/snapshots/pass/space_only_after_minus.expr.result-ast new file mode 100644 index 0000000000..7ba8b91f84 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/space_only_after_minus.expr.result-ast @@ -0,0 +1,15 @@ +BinOps( + [ + ( + |L 0-0, C 0-1| Var { + module_name: "", + ident: "x", + }, + |L 0-0, C 1-2| Minus, + ), + ], + |L 0-0, C 3-4| Var { + module_name: "", + ident: "y", + }, +) diff --git a/compiler/parse/tests/snapshots/pass/space_only_after_minus.expr.roc b/compiler/parse/tests/snapshots/pass/space_only_after_minus.expr.roc new file mode 100644 index 0000000000..d7d545ec40 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/space_only_after_minus.expr.roc @@ -0,0 +1 @@ +x- y \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast new file mode 100644 index 0000000000..38acc0b739 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast @@ -0,0 +1,7 @@ +List( + [ + |L 0-0, C 2-3| Num( + "1", + ), + ], +) diff --git a/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.roc b/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.roc new file mode 100644 index 0000000000..404b10d49d --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.roc @@ -0,0 +1 @@ +[ 1 ] \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/spaces_inside_empty_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/spaces_inside_empty_list.expr.result-ast new file mode 100644 index 0000000000..36f4a52b6e --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/spaces_inside_empty_list.expr.result-ast @@ -0,0 +1,3 @@ +List( + [], +) diff --git a/compiler/parse/tests/snapshots/pass/spaces_inside_empty_list.expr.roc b/compiler/parse/tests/snapshots/pass/spaces_inside_empty_list.expr.roc new file mode 100644 index 0000000000..f6ffad5cb3 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/spaces_inside_empty_list.expr.roc @@ -0,0 +1 @@ +[ ] \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast b/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast new file mode 100644 index 0000000000..df2876e5a3 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast @@ -0,0 +1,59 @@ +[ + |L 1-1, C 0-7| SpaceBefore( + Body( + |L 1-1, C 0-3| Identifier( + "foo", + ), + |L 1-1, C 6-7| Num( + "1", + ), + ), + [ + LineComment( + " comment 1", + ), + ], + ), + |L 4-4, C 0-10| SpaceBefore( + Body( + |L 4-4, C 0-3| Identifier( + "bar", + ), + |L 4-4, C 6-10| Str( + PlainLine( + "hi", + ), + ), + ), + [ + Newline, + Newline, + LineComment( + " comment 2", + ), + ], + ), + |L 5-5, C 0-13| SpaceAfter( + SpaceBefore( + Body( + |L 5-5, C 0-3| Identifier( + "baz", + ), + |L 5-5, C 6-13| Str( + PlainLine( + "stuff", + ), + ), + ), + [ + Newline, + ], + ), + [ + Newline, + LineComment( + " comment n", + ), + ], + ), +] diff --git a/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.roc b/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.roc new file mode 100644 index 0000000000..2c6e02bbfd --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.roc @@ -0,0 +1,7 @@ +# comment 1 +foo = 1 + +# comment 2 +bar = "hi" +baz = "stuff" +# comment n diff --git a/compiler/parse/tests/snapshots/pass/string_without_escape.expr.result-ast b/compiler/parse/tests/snapshots/pass/string_without_escape.expr.result-ast new file mode 100644 index 0000000000..07cfaac49f --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/string_without_escape.expr.result-ast @@ -0,0 +1,5 @@ +Str( + PlainLine( + "123 abc 456 def", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/string_without_escape.expr.roc b/compiler/parse/tests/snapshots/pass/string_without_escape.expr.roc new file mode 100644 index 0000000000..3c069479a0 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/string_without_escape.expr.roc @@ -0,0 +1 @@ +"123 abc 456 def" \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast new file mode 100644 index 0000000000..a0930ee9cf --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast @@ -0,0 +1,14 @@ +BinOps( + [ + ( + |L 0-0, C 0-1| Var { + module_name: "", + ident: "x", + }, + |L 0-0, C 2-3| Minus, + ), + ], + |L 0-0, C 4-5| Num( + "2", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.roc b/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.roc new file mode 100644 index 0000000000..9b0ac03e68 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.roc @@ -0,0 +1 @@ +x - 2 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast new file mode 100644 index 0000000000..f68168b626 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast @@ -0,0 +1,13 @@ +BinOps( + [ + ( + |L 0-0, C 0-1| Num( + "1", + ), + |L 0-0, C 3-4| Minus, + ), + ], + |L 0-0, C 7-8| Num( + "2", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.roc b/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.roc new file mode 100644 index 0000000000..e479d53f85 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.roc @@ -0,0 +1 @@ +1 - 2 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast b/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast new file mode 100644 index 0000000000..921fe3c6ad --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast @@ -0,0 +1,10 @@ +Closure( + [ + |L 0-0, C 1-6| GlobalTag( + "Thing", + ), + ], + |L 0-0, C 10-12| Num( + "42", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/tag_pattern.expr.roc b/compiler/parse/tests/snapshots/pass/tag_pattern.expr.roc new file mode 100644 index 0000000000..62b6fc5abc --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/tag_pattern.expr.roc @@ -0,0 +1 @@ +\Thing -> 42 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast b/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast new file mode 100644 index 0000000000..431a12166e --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast @@ -0,0 +1,13 @@ +BinOps( + [ + ( + |L 0-0, C 0-2| Num( + "10", + ), + |L 0-0, C 2-3| Star, + ), + ], + |L 0-0, C 3-5| Num( + "11", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.roc b/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.roc new file mode 100644 index 0000000000..db8e7f0e5e --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.roc @@ -0,0 +1 @@ +10*11 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast new file mode 100644 index 0000000000..e40979f575 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast @@ -0,0 +1,16 @@ +Closure( + [ + |L 0-0, C 1-2| Identifier( + "a", + ), + |L 0-0, C 4-5| Identifier( + "b", + ), + |L 0-0, C 7-8| Identifier( + "c", + ), + ], + |L 0-0, C 12-14| Num( + "42", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.roc b/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.roc new file mode 100644 index 0000000000..fe1e9f0cad --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.roc @@ -0,0 +1 @@ +\a, b, c -> 42 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast new file mode 100644 index 0000000000..cba1cb7d83 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast @@ -0,0 +1,13 @@ +Closure( + [ + |L 0-0, C 1-2| Identifier( + "a", + ), + |L 0-0, C 4-5| Identifier( + "b", + ), + ], + |L 0-0, C 9-11| Num( + "42", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.roc b/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.roc new file mode 100644 index 0000000000..83db21cf1f --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.roc @@ -0,0 +1 @@ +\a, b -> 42 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/two_backpassing.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_backpassing.expr.result-ast new file mode 100644 index 0000000000..d1fefd06d4 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/two_backpassing.expr.result-ast @@ -0,0 +1,52 @@ +SpaceBefore( + Backpassing( + [ + |L 1-1, C 0-1| Identifier( + "x", + ), + ], + |L 1-1, C 5-14| ParensAround( + Closure( + [ + |L 1-1, C 7-8| Identifier( + "y", + ), + ], + |L 1-1, C 12-13| Var { + module_name: "", + ident: "y", + }, + ), + ), + |L 2-4, C 0-1| SpaceBefore( + Backpassing( + [ + |L 2-2, C 0-1| Identifier( + "z", + ), + ], + |L 2-2, C 5-7| Record( + [], + ), + |L 4-4, C 0-1| SpaceBefore( + Var { + module_name: "", + ident: "x", + }, + [ + Newline, + Newline, + ], + ), + ), + [ + Newline, + ], + ), + ), + [ + LineComment( + " leading comment", + ), + ], +) diff --git a/compiler/parse/tests/snapshots/pass/two_backpassing.expr.roc b/compiler/parse/tests/snapshots/pass/two_backpassing.expr.roc new file mode 100644 index 0000000000..f36a289c6c --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/two_backpassing.expr.roc @@ -0,0 +1,5 @@ +# leading comment +x <- (\y -> y) +z <- {} + +x diff --git a/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast new file mode 100644 index 0000000000..bfda25dc05 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast @@ -0,0 +1,44 @@ +When( + |L 0-0, C 5-6| Var { + module_name: "", + ident: "x", + }, + [ + WhenBranch { + patterns: [ + |L 1-1, C 1-3| SpaceBefore( + StrLiteral( + PlainLine( + "", + ), + ), + [ + Newline, + ], + ), + ], + value: |L 1-1, C 7-8| Num( + "1", + ), + guard: None, + }, + WhenBranch { + patterns: [ + |L 2-2, C 1-7| SpaceBefore( + StrLiteral( + PlainLine( + "mise", + ), + ), + [ + Newline, + ], + ), + ], + value: |L 2-2, C 11-12| Num( + "2", + ), + guard: None, + }, + ], +) diff --git a/compiler/parse/tests/snapshots/pass/two_branch_when.expr.roc b/compiler/parse/tests/snapshots/pass/two_branch_when.expr.roc new file mode 100644 index 0000000000..783ecbc023 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/two_branch_when.expr.roc @@ -0,0 +1,3 @@ +when x is + "" -> 1 + "mise" -> 2 diff --git a/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast new file mode 100644 index 0000000000..4b7817ea4c --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast @@ -0,0 +1,41 @@ +SpaceBefore( + Defs( + [ + |L 1-1, C 0-5| Body( + |L 1-1, C 0-1| Identifier( + "x", + ), + |L 1-1, C 4-5| Num( + "5", + ), + ), + |L 2-2, C 0-5| SpaceBefore( + Body( + |L 2-2, C 0-1| Identifier( + "y", + ), + |L 2-2, C 4-5| Num( + "6", + ), + ), + [ + Newline, + ], + ), + ], + |L 4-4, C 0-2| SpaceBefore( + Num( + "42", + ), + [ + Newline, + Newline, + ], + ), + ), + [ + LineComment( + " leading comment", + ), + ], +) diff --git a/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.roc b/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.roc new file mode 100644 index 0000000000..3616875083 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.roc @@ -0,0 +1,5 @@ +# leading comment +x = 5 +y = 6 + +42 diff --git a/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast b/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast new file mode 100644 index 0000000000..3408a90669 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast @@ -0,0 +1,38 @@ +Defs( + [ + |L 0-0, C 0-30| Annotation( + |L 0-0, C 0-7| Identifier( + "doStuff", + ), + |L 0-0, C 20-30| Function( + [ + |L 0-0, C 10-16| Apply( + "", + "UserId", + [], + ), + ], + |L 0-0, C 20-30| Apply( + "", + "Task", + [ + |L 0-0, C 25-28| Apply( + "", + "Str", + [], + ), + |L 0-0, C 29-30| Inferred, + ], + ), + ), + ), + ], + |L 1-1, C 0-2| SpaceBefore( + Num( + "42", + ), + [ + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.roc b/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.roc new file mode 100644 index 0000000000..49d06baf92 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.roc @@ -0,0 +1,2 @@ +doStuff : UserId -> Task Str _ +42 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/unary_negation.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_negation.expr.result-ast new file mode 100644 index 0000000000..0b69481d27 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/unary_negation.expr.result-ast @@ -0,0 +1,7 @@ +UnaryOp( + |L 0-0, C 1-4| Var { + module_name: "", + ident: "foo", + }, + |L 0-0, C 0-1| Negate, +) diff --git a/compiler/parse/tests/snapshots/pass/unary_negation.expr.roc b/compiler/parse/tests/snapshots/pass/unary_negation.expr.roc new file mode 100644 index 0000000000..46409ea605 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/unary_negation.expr.roc @@ -0,0 +1 @@ +-foo \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/unary_negation_access.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_negation_access.expr.result-ast new file mode 100644 index 0000000000..bac391b5bb --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/unary_negation_access.expr.result-ast @@ -0,0 +1,10 @@ +UnaryOp( + |L 0-0, C 1-11| Access( + Var { + module_name: "", + ident: "rec1", + }, + "field", + ), + |L 0-0, C 0-1| Negate, +) diff --git a/compiler/parse/tests/snapshots/pass/unary_negation_access.expr.roc b/compiler/parse/tests/snapshots/pass/unary_negation_access.expr.roc new file mode 100644 index 0000000000..c942e53928 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/unary_negation_access.expr.roc @@ -0,0 +1 @@ +-rec1.field \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast new file mode 100644 index 0000000000..b584acfb8b --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast @@ -0,0 +1,19 @@ +Apply( + |L 0-0, C 0-4| Var { + module_name: "", + ident: "whee", + }, + [ + |L 0-0, C 6-8| Num( + "12", + ), + |L 0-0, C 9-13| UnaryOp( + |L 0-0, C 10-13| Var { + module_name: "", + ident: "foo", + }, + |L 0-0, C 9-10| Negate, + ), + ], + Space, +) diff --git a/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.roc b/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.roc new file mode 100644 index 0000000000..8484ae0e95 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.roc @@ -0,0 +1 @@ +whee 12 -foo \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast new file mode 100644 index 0000000000..995295897e --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast @@ -0,0 +1,21 @@ +UnaryOp( + |L 0-0, C 2-14| ParensAround( + Apply( + |L 0-0, C 2-6| Var { + module_name: "", + ident: "whee", + }, + [ + |L 0-0, C 8-10| Num( + "12", + ), + |L 0-0, C 11-14| Var { + module_name: "", + ident: "foo", + }, + ], + Space, + ), + ), + |L 0-0, C 0-1| Negate, +) diff --git a/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.roc b/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.roc new file mode 100644 index 0000000000..1c89e5dc15 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.roc @@ -0,0 +1 @@ +-(whee 12 foo) \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/unary_not.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_not.expr.result-ast new file mode 100644 index 0000000000..1264450798 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/unary_not.expr.result-ast @@ -0,0 +1,7 @@ +UnaryOp( + |L 0-0, C 1-5| Var { + module_name: "", + ident: "blah", + }, + |L 0-0, C 0-1| Not, +) diff --git a/compiler/parse/tests/snapshots/pass/unary_not.expr.roc b/compiler/parse/tests/snapshots/pass/unary_not.expr.roc new file mode 100644 index 0000000000..9641cc0354 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/unary_not.expr.roc @@ -0,0 +1 @@ +!blah \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast new file mode 100644 index 0000000000..4d744df18b --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast @@ -0,0 +1,21 @@ +UnaryOp( + |L 0-0, C 2-14| ParensAround( + Apply( + |L 0-0, C 2-6| Var { + module_name: "", + ident: "whee", + }, + [ + |L 0-0, C 8-10| Num( + "12", + ), + |L 0-0, C 11-14| Var { + module_name: "", + ident: "foo", + }, + ], + Space, + ), + ), + |L 0-0, C 0-1| Not, +) diff --git a/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.roc b/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.roc new file mode 100644 index 0000000000..2065d557d3 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.roc @@ -0,0 +1 @@ +!(whee 12 foo) \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast b/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast new file mode 100644 index 0000000000..afb4402f55 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast @@ -0,0 +1,36 @@ +SpaceBefore( + Backpassing( + [ + |L 1-1, C 0-1| Underscore( + "", + ), + ], + |L 1-1, C 5-14| ParensAround( + Closure( + [ + |L 1-1, C 7-8| Identifier( + "y", + ), + ], + |L 1-1, C 12-13| Var { + module_name: "", + ident: "y", + }, + ), + ), + |L 3-3, C 0-1| SpaceBefore( + Num( + "4", + ), + [ + Newline, + Newline, + ], + ), + ), + [ + LineComment( + " leading comment", + ), + ], +) diff --git a/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.roc b/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.roc new file mode 100644 index 0000000000..3e0d8ca803 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.roc @@ -0,0 +1,4 @@ +# leading comment +_ <- (\y -> y) + +4 diff --git a/compiler/parse/tests/snapshots/pass/var_else.expr.result-ast b/compiler/parse/tests/snapshots/pass/var_else.expr.result-ast new file mode 100644 index 0000000000..7c569cf669 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/var_else.expr.result-ast @@ -0,0 +1,4 @@ +Var { + module_name: "", + ident: "elsewhere", +} diff --git a/compiler/parse/tests/snapshots/pass/var_else.expr.roc b/compiler/parse/tests/snapshots/pass/var_else.expr.roc new file mode 100644 index 0000000000..f98eb10ae8 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/var_else.expr.roc @@ -0,0 +1 @@ +elsewhere \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/var_if.expr.result-ast b/compiler/parse/tests/snapshots/pass/var_if.expr.result-ast new file mode 100644 index 0000000000..2b95a15c2e --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/var_if.expr.result-ast @@ -0,0 +1,4 @@ +Var { + module_name: "", + ident: "iffy", +} diff --git a/compiler/parse/tests/snapshots/pass/var_if.expr.roc b/compiler/parse/tests/snapshots/pass/var_if.expr.roc new file mode 100644 index 0000000000..65ad4cb8b4 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/var_if.expr.roc @@ -0,0 +1 @@ +iffy \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/var_is.expr.result-ast b/compiler/parse/tests/snapshots/pass/var_is.expr.result-ast new file mode 100644 index 0000000000..2e84e98557 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/var_is.expr.result-ast @@ -0,0 +1,4 @@ +Var { + module_name: "", + ident: "isnt", +} diff --git a/compiler/parse/tests/snapshots/pass/var_is.expr.roc b/compiler/parse/tests/snapshots/pass/var_is.expr.roc new file mode 100644 index 0000000000..ccdb611527 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/var_is.expr.roc @@ -0,0 +1 @@ +isnt \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast b/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast new file mode 100644 index 0000000000..f3eedcea65 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast @@ -0,0 +1,14 @@ +BinOps( + [ + ( + |L 0-0, C 0-1| Var { + module_name: "", + ident: "x", + }, + |L 0-0, C 1-2| Minus, + ), + ], + |L 0-0, C 2-3| Num( + "2", + ), +) diff --git a/compiler/parse/tests/snapshots/pass/var_minus_two.expr.roc b/compiler/parse/tests/snapshots/pass/var_minus_two.expr.roc new file mode 100644 index 0000000000..9c90f0b33f --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/var_minus_two.expr.roc @@ -0,0 +1 @@ +x-2 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/var_then.expr.result-ast b/compiler/parse/tests/snapshots/pass/var_then.expr.result-ast new file mode 100644 index 0000000000..b989386f63 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/var_then.expr.result-ast @@ -0,0 +1,4 @@ +Var { + module_name: "", + ident: "thenever", +} diff --git a/compiler/parse/tests/snapshots/pass/var_then.expr.roc b/compiler/parse/tests/snapshots/pass/var_then.expr.roc new file mode 100644 index 0000000000..5129806177 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/var_then.expr.roc @@ -0,0 +1 @@ +thenever \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/var_when.expr.result-ast b/compiler/parse/tests/snapshots/pass/var_when.expr.result-ast new file mode 100644 index 0000000000..d7d0d773f3 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/var_when.expr.result-ast @@ -0,0 +1,4 @@ +Var { + module_name: "", + ident: "whenever", +} diff --git a/compiler/parse/tests/snapshots/pass/var_when.expr.roc b/compiler/parse/tests/snapshots/pass/var_when.expr.roc new file mode 100644 index 0000000000..3e93c51e69 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/var_when.expr.roc @@ -0,0 +1 @@ +whenever \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast new file mode 100644 index 0000000000..83f3424244 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast @@ -0,0 +1,73 @@ +When( + |L 0-0, C 5-6| Var { + module_name: "", + ident: "x", + }, + [ + WhenBranch { + patterns: [ + |L 1-1, C 4-5| SpaceBefore( + Underscore( + "", + ), + [ + Newline, + ], + ), + ], + value: |L 2-2, C 8-9| SpaceBefore( + Num( + "1", + ), + [ + Newline, + ], + ), + guard: None, + }, + WhenBranch { + patterns: [ + |L 4-4, C 4-5| SpaceBefore( + Underscore( + "", + ), + [ + Newline, + Newline, + ], + ), + ], + value: |L 5-5, C 8-9| SpaceBefore( + Num( + "2", + ), + [ + Newline, + ], + ), + guard: None, + }, + WhenBranch { + patterns: [ + |L 7-7, C 4-6| SpaceBefore( + GlobalTag( + "Ok", + ), + [ + Newline, + Newline, + ], + ), + ], + value: |L 8-8, C 8-9| SpaceBefore( + Num( + "3", + ), + [ + Newline, + ], + ), + guard: None, + }, + ], +) diff --git a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.roc b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.roc new file mode 100644 index 0000000000..392b1fe99f --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.roc @@ -0,0 +1,9 @@ +when x is + _ -> + 1 + + _ -> + 2 + + Ok -> + 3 diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast new file mode 100644 index 0000000000..324e03f7ce --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast @@ -0,0 +1,31 @@ +ParensAround( + When( + |L 0-0, C 6-7| Var { + module_name: "", + ident: "x", + }, + [ + WhenBranch { + patterns: [ + |L 1-1, C 4-6| SpaceBefore( + GlobalTag( + "Ok", + ), + [ + Newline, + ], + ), + ], + value: |L 2-2, C 8-9| SpaceBefore( + Num( + "3", + ), + [ + Newline, + ], + ), + guard: None, + }, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens.expr.roc b/compiler/parse/tests/snapshots/pass/when_in_parens.expr.roc new file mode 100644 index 0000000000..e65900b295 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/when_in_parens.expr.roc @@ -0,0 +1,3 @@ +(when x is + Ok -> + 3) diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast new file mode 100644 index 0000000000..b7fcacfb2d --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast @@ -0,0 +1,31 @@ +ParensAround( + SpaceAfter( + When( + |L 0-0, C 6-7| Var { + module_name: "", + ident: "x", + }, + [ + WhenBranch { + patterns: [ + |L 1-1, C 4-6| SpaceBefore( + GlobalTag( + "Ok", + ), + [ + Newline, + ], + ), + ], + value: |L 1-1, C 10-11| Num( + "3", + ), + guard: None, + }, + ], + ), + [ + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.roc b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.roc new file mode 100644 index 0000000000..21bd43313f --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.roc @@ -0,0 +1,3 @@ +(when x is + Ok -> 3 + ) diff --git a/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast new file mode 100644 index 0000000000..46a12593ae --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast @@ -0,0 +1,59 @@ +When( + |L 0-0, C 5-6| Var { + module_name: "", + ident: "x", + }, + [ + WhenBranch { + patterns: [ + |L 1-1, C 1-7| SpaceBefore( + StrLiteral( + PlainLine( + "blah", + ), + ), + [ + Newline, + ], + ), + |L 1-1, C 10-16| StrLiteral( + PlainLine( + "blop", + ), + ), + ], + value: |L 1-1, C 20-21| Num( + "1", + ), + guard: None, + }, + WhenBranch { + patterns: [ + |L 2-2, C 1-6| SpaceBefore( + StrLiteral( + PlainLine( + "foo", + ), + ), + [ + Newline, + ], + ), + |L 3-3, C 2-7| SpaceBefore( + StrLiteral( + PlainLine( + "bar", + ), + ), + [ + Newline, + ], + ), + ], + value: |L 3-3, C 11-12| Num( + "2", + ), + guard: None, + }, + ], +) diff --git a/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.roc b/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.roc new file mode 100644 index 0000000000..2e4d050fe4 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.roc @@ -0,0 +1,4 @@ +when x is + "blah" | "blop" -> 1 + "foo" | + "bar" -> 2 diff --git a/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast new file mode 100644 index 0000000000..6438516e8d --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast @@ -0,0 +1,54 @@ +When( + |L 0-0, C 5-6| Var { + module_name: "", + ident: "x", + }, + [ + WhenBranch { + patterns: [ + |L 1-1, C 4-5| SpaceBefore( + NumLiteral( + "1", + ), + [ + Newline, + ], + ), + ], + value: |L 1-2, C 9-6| Apply( + |L 1-1, C 9-16| Var { + module_name: "Num", + ident: "neg", + }, + [ + |L 2-2, C 5-6| SpaceBefore( + Num( + "2", + ), + [ + Newline, + ], + ), + ], + Space, + ), + guard: None, + }, + WhenBranch { + patterns: [ + |L 3-3, C 4-5| SpaceBefore( + Underscore( + "", + ), + [ + Newline, + ], + ), + ], + value: |L 3-3, C 9-10| Num( + "4", + ), + guard: None, + }, + ], +) diff --git a/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.roc b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.roc new file mode 100644 index 0000000000..fd5da76dec --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.roc @@ -0,0 +1,4 @@ +when x is + 1 -> Num.neg + 2 + _ -> 4 diff --git a/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast new file mode 100644 index 0000000000..b1c09c7e0c --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast @@ -0,0 +1,40 @@ +When( + |L 0-0, C 5-6| Var { + module_name: "", + ident: "x", + }, + [ + WhenBranch { + patterns: [ + |L 1-1, C 1-2| SpaceBefore( + NumLiteral( + "1", + ), + [ + Newline, + ], + ), + ], + value: |L 1-1, C 6-7| Num( + "2", + ), + guard: None, + }, + WhenBranch { + patterns: [ + |L 2-2, C 1-3| SpaceBefore( + NumLiteral( + "-3", + ), + [ + Newline, + ], + ), + ], + value: |L 2-2, C 7-8| Num( + "4", + ), + guard: None, + }, + ], +) diff --git a/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.roc b/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.roc new file mode 100644 index 0000000000..90eed06a25 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.roc @@ -0,0 +1,3 @@ +when x is + 1 -> 2 + -3 -> 4 diff --git a/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast new file mode 100644 index 0000000000..e1ed86ded5 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast @@ -0,0 +1,40 @@ +When( + |L 0-0, C 5-6| Var { + module_name: "", + ident: "x", + }, + [ + WhenBranch { + patterns: [ + |L 1-1, C 1-2| SpaceBefore( + NumLiteral( + "1", + ), + [ + Newline, + ], + ), + ], + value: |L 1-1, C 6-7| Num( + "2", + ), + guard: None, + }, + WhenBranch { + patterns: [ + |L 2-2, C 1-2| SpaceBefore( + NumLiteral( + "3", + ), + [ + Newline, + ], + ), + ], + value: |L 2-2, C 6-7| Num( + "4", + ), + guard: None, + }, + ], +) diff --git a/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.roc b/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.roc new file mode 100644 index 0000000000..c10dcafcf0 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.roc @@ -0,0 +1,3 @@ +when x is + 1 -> 2 + 3 -> 4 diff --git a/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast new file mode 100644 index 0000000000..174cc8abb2 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast @@ -0,0 +1,51 @@ +When( + |L 0-0, C 5-6| Var { + module_name: "", + ident: "x", + }, + [ + WhenBranch { + patterns: [ + |L 1-1, C 1-6| SpaceBefore( + RecordDestructure( + [ + |L 1-1, C 3-4| Identifier( + "y", + ), + ], + ), + [ + Newline, + ], + ), + ], + value: |L 1-1, C 10-11| Num( + "2", + ), + guard: None, + }, + WhenBranch { + patterns: [ + |L 2-2, C 1-9| SpaceBefore( + RecordDestructure( + [ + |L 2-2, C 3-4| Identifier( + "z", + ), + |L 2-2, C 6-7| Identifier( + "w", + ), + ], + ), + [ + Newline, + ], + ), + ], + value: |L 2-2, C 13-14| Num( + "4", + ), + guard: None, + }, + ], +) diff --git a/compiler/parse/tests/snapshots/pass/when_with_records.expr.roc b/compiler/parse/tests/snapshots/pass/when_with_records.expr.roc new file mode 100644 index 0000000000..3de2e8289b --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/when_with_records.expr.roc @@ -0,0 +1,3 @@ +when x is + { y } -> 2 + { z, w } -> 4 diff --git a/compiler/parse/tests/snapshots/pass/zero_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/zero_float.expr.result-ast new file mode 100644 index 0000000000..b164e7a7fe --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/zero_float.expr.result-ast @@ -0,0 +1,3 @@ +Float( + "0.0", +) diff --git a/compiler/parse/tests/snapshots/pass/zero_float.expr.roc b/compiler/parse/tests/snapshots/pass/zero_float.expr.roc new file mode 100644 index 0000000000..171538eb0b --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/zero_float.expr.roc @@ -0,0 +1 @@ +0.0 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/zero_int.expr.result-ast b/compiler/parse/tests/snapshots/pass/zero_int.expr.result-ast new file mode 100644 index 0000000000..0a7ff7c8ce --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/zero_int.expr.result-ast @@ -0,0 +1,3 @@ +Num( + "0", +) diff --git a/compiler/parse/tests/snapshots/pass/zero_int.expr.roc b/compiler/parse/tests/snapshots/pass/zero_int.expr.roc new file mode 100644 index 0000000000..c227083464 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/zero_int.expr.roc @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index cf699dd62b..b9dae1d201 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -15,32 +15,255 @@ extern crate roc_parse; mod test_parse { use bumpalo::collections::vec::Vec; use bumpalo::{self, Bump}; - use roc_module::operator::BinOp::{self, *}; - use roc_module::operator::{CalledVia, UnaryOp}; - use roc_parse::ast::AssignedField::*; - use roc_parse::ast::CommentOrNewline::*; use roc_parse::ast::Expr::{self, *}; - use roc_parse::ast::Pattern::{self, *}; - use roc_parse::ast::StrLiteral::{self, *}; + use roc_parse::ast::StrLiteral::*; use roc_parse::ast::StrSegment::*; - use roc_parse::ast::{ - self, Collection, Def, EscapedChar, Spaceable, TypeAnnotation, WhenBranch, - }; - use roc_parse::header::{ - AppHeader, Effects, ExposesEntry, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, - PackageName, PackageOrPath, PlatformHeader, PlatformRequires, PlatformRigid, To, - TypedIdent, - }; + use roc_parse::ast::{self, EscapedChar}; use roc_parse::module::module_defs; use roc_parse::parser::{Parser, State, SyntaxError}; use roc_parse::test_helpers::parse_expr_with; use roc_region::all::{Located, Region}; use std::{f64, i64}; + macro_rules! snapshot_tests { + ( + expr => { + $($expr_test_name:ident),* + } + header => { + $($header_test_name:ident),* + } + module => { + $($module_test_name:ident),* + } + ) => { + #[test] + fn no_extra_snapshot_test_files() { + let tests = &[ + $(concat!(stringify!($expr_test_name), ".expr")),*, + $(concat!(stringify!($header_test_name), ".header")),*, + $(concat!(stringify!($module_test_name), ".module")),*, + ].iter().map(|t| *t).collect::>(); + + let mut base = std::path::PathBuf::from("tests"); + base.push("snapshots"); + base.push("pass"); + let files = std::fs::read_dir(&base).unwrap().map(|f| f.unwrap().file_name().to_str().unwrap().to_string()).collect::>(); + for file in files { + if let Some(file) = file.strip_suffix(".roc") { + assert!(tests.contains(file), "{}", file); + } else if let Some(file) = file.strip_suffix(".result-ast") { + assert!(tests.contains(file), "{}", file); + } else { + panic!("unexpected test file found: {}", file); + } + } + } + + $( + #[test] + fn $expr_test_name() { + snapshot_test(stringify!($expr_test_name), "expr", |input| { + let arena = Bump::new(); + let actual_ast = parse_expr_with(&arena, input.trim()).unwrap(); + format!("{:#?}\n", actual_ast) + }); + } + )* + + $( + #[test] + fn $header_test_name() { + snapshot_test(stringify!($header_test_name), "header", |input| { + let arena = Bump::new(); + let actual_ast = roc_parse::module::parse_header(&arena, State::new(input.as_bytes())) + .map(|tuple| tuple.0).unwrap(); + format!("{:#?}\n", actual_ast) + }); + } + )* + + $( + #[test] + fn $module_test_name() { + snapshot_test(stringify!($module_test_name), "module", |input| { + let arena = Bump::new(); + let actual_ast = module_defs() + .parse(&arena, State::new(input.as_bytes())) + .map(|tuple| tuple.1).unwrap(); + format!("{:#?}\n", actual_ast) + }); + } + )* + }; + } + + snapshot_tests! { + expr => { + add_var_with_spaces, + add_with_spaces, + apply_global_tag, + apply_parenthetical_global_tag_args, + apply_private_tag, + apply_three_args, + apply_two_args, + apply_unary_negation, + apply_unary_not, + basic_apply, + basic_docs, + basic_field, + basic_global_tag, + basic_private_tag, + basic_var, + closure_with_underscores, + comment_after_op, + comment_before_op, + comment_inside_empty_list, + comment_with_non_ascii, + empty_list, + empty_record, + empty_string, + equals, + equals_with_spaces, + expect, + float_with_underscores, + highest_float, + highest_int, + if_def, + int_with_underscore, + lowest_float, + lowest_int, + malformed_ident_due_to_underscore, + malformed_pattern_field_access, // See https://github.com/rtfeldman/roc/issues/399 + malformed_pattern_module_name, // See https://github.com/rtfeldman/roc/issues/399 + minus_twelve_minus_five, + mixed_docs, + multi_backpassing, + multi_char_string, + multiline_type_signature, + multiline_type_signature_with_comment, + multiple_fields, + multiple_operators, + neg_inf_float, + negative_float, + negative_int, + newline_after_equals, // Regression test for https://github.com/rtfeldman/roc/issues/51 + newline_after_mul, + newline_after_sub, + newline_and_spaces_before_less_than, + newline_before_add, + newline_before_sub, + newline_inside_empty_list, + newline_singleton_list, + not_docs, + one_backpassing, + one_char_string, + one_def, + one_minus_two, + one_plus_two, + one_spaced_def, + ops_with_newlines, + packed_singleton_list, + parenthetical_apply, + parenthetical_basic_field, + parenthetical_field_qualified_var, + parenthetical_var, + parse_alias, + parse_as_ann, + pattern_with_space_in_parens, // https://github.com/rtfeldman/roc/issues/929 + pos_inf_float, + positive_float, + positive_int, + private_qualified_tag, + qualified_field, + qualified_global_tag, + qualified_var, + record_destructure_def, + record_update, + record_with_if, + single_arg_closure, + single_underscore_closure, + space_only_after_minus, + spaced_singleton_list, + spaces_inside_empty_list, + string_without_escape, + sub_var_with_spaces, + sub_with_spaces, + tag_pattern, + ten_times_eleven, + three_arg_closure, + two_arg_closure, + two_backpassing, + two_branch_when, + two_spaced_def, + type_decl_with_underscore, + unary_negation, + unary_negation_access, // Regression test for https://github.com/rtfeldman/roc/issues/509 + unary_negation_arg, + unary_negation_with_parens, + unary_not, + unary_not_with_parens, + underscore_backpassing, + var_else, + var_if, + var_is, + var_minus_two, + var_then, + var_when, + when_if_guard, + when_in_parens, + when_in_parens_indented, + when_with_alternative_patterns, + when_with_function_application, + when_with_negative_numbers, + when_with_numbers, + when_with_records, + zero_float, + zero_int + } + header => { + empty_app_header, + empty_interface_header, + empty_platform_header, + full_app_header, + full_app_header_trailing_commas, + minimal_app_header, + nested_module, + nonempty_platform_header + } + module => { + standalone_module_defs, + module_def_newline, + nested_def_annotation + } + } + + fn snapshot_test(name: &str, ty: &str, func: impl Fn(&str) -> String) { + let mut parent = std::path::PathBuf::from("tests"); + parent.push("snapshots"); + parent.push("pass"); + let input_path = parent.join(&format!("{}.{}.roc", name, ty)); + let result_path = parent.join(&format!("{}.{}.result-ast", name, ty)); + + let input = std::fs::read_to_string(&input_path).unwrap(); + + let actual_result = func(&input); + + if std::env::var("ROC_PARSER_SNAPSHOT_TEST_OVERWRITE").is_ok() { + std::fs::write(&result_path, actual_result).unwrap(); + } else { + let expected_result = std::fs::read_to_string(&result_path).unwrap(); + + // TODO: do a diff over the "real" content of these strings, rather than + // the debug-formatted content. As is, we get an ugly single-line diff + // from pretty_assertions + assert_eq!(expected_result, actual_result); + } + } + fn assert_parses_to<'a>(input: &'a str, expected_expr: Expr<'a>) { let arena = Bump::new(); let actual = parse_expr_with(&arena, input.trim()); - assert_eq!(Ok(expected_expr), actual); } @@ -85,72 +308,6 @@ mod test_parse { } } - fn single_binop<'a>( - arena: &'a Bump, - args: ( - Located>, - Located, - Located>, - ), - ) -> Expr<'a> { - let (left, op, right) = args; - - Expr::BinOps(arena.alloc([(left, op)]), arena.alloc(right)) - } - - // STRING LITERALS - - fn expect_parsed_str(input: &str, expected: &str) { - assert_parses_to(expected, Expr::Str(PlainLine(input))); - } - - #[test] - fn empty_string() { - assert_parses_to( - indoc!( - r#" - "" - "# - ), - Str(PlainLine("")), - ); - } - - #[test] - fn one_char_string() { - assert_parses_to( - indoc!( - r#" - "x" - "# - ), - Expr::Str(PlainLine("x")), - ); - } - - #[test] - fn multi_char_string() { - assert_parses_to( - indoc!( - r#" - "foo" - "# - ), - Expr::Str(PlainLine("foo")), - ); - } - - #[test] - fn string_without_escape() { - expect_parsed_str("a", r#""a""#); - expect_parsed_str("ab", r#""ab""#); - expect_parsed_str("abc", r#""abc""#); - expect_parsed_str("123", r#""123""#); - expect_parsed_str("abc123", r#""abc123""#); - expect_parsed_str("123abc", r#""123abc""#); - expect_parsed_str("123 abc 456 def", r#""123 abc 456 def""#); - } - // BACKSLASH ESCAPES #[test] @@ -324,127 +481,15 @@ mod test_parse { assert_parsing_fails(&too_long_str, SyntaxError::LineTooLong(0)); } - // INT LITERALS - - #[test] - fn zero_int() { - assert_parses_to("0", Num("0")); - } - - #[test] - fn positive_int() { - assert_parses_to("1", Num("1")); - assert_parses_to("42", Num("42")); - } - - #[test] - fn negative_int() { - assert_parses_to("-1", Num("-1")); - assert_parses_to("-42", Num("-42")); - } - - #[test] - fn highest_int() { - assert_parses_to( - i64::MAX.to_string().as_str(), - Num(i64::MAX.to_string().as_str()), - ); - } - - #[test] - fn lowest_int() { - assert_parses_to( - i64::MIN.to_string().as_str(), - Num(i64::MIN.to_string().as_str()), - ); - } - - #[test] - fn int_with_underscore() { - assert_parses_to("1_2_34_567", Num("1_2_34_567")); - assert_parses_to("-1_2_34_567", Num("-1_2_34_567")); - // The following cases are silly. They aren't supported on purpose, - // but there would be a performance cost to explicitly disallowing them, - // which doesn't seem like it would benefit anyone. - assert_parses_to("1_", Num("1_")); - assert_parses_to("1__23", Num("1__23")); - } - #[quickcheck] fn all_i64_values_parse(num: i64) { assert_parses_to(num.to_string().as_str(), Num(num.to_string().as_str())); } - // FLOAT LITERALS - - #[test] - fn zero_float() { - assert_parses_to("0.0", Float("0.0")); - } - - #[test] - fn positive_float() { - assert_parses_to("1.0", Float("1.0")); - assert_parses_to("1.1", Float("1.1")); - assert_parses_to("42.0", Float("42.0")); - assert_parses_to("42.9", Float("42.9")); - } - - #[test] - fn negative_float() { - assert_parses_to("-1.0", Float("-1.0")); - assert_parses_to("-1.1", Float("-1.1")); - assert_parses_to("-42.0", Float("-42.0")); - assert_parses_to("-42.9", Float("-42.9")); - } - - #[test] - fn float_with_underscores() { - assert_parses_to("1_23_456.0_1_23_456", Float("1_23_456.0_1_23_456")); - assert_parses_to("-1_23_456.0_1_23_456", Float("-1_23_456.0_1_23_456")); - } - - #[test] - fn highest_float() { - let string = format!("{}.0", f64::MAX); - - assert_parses_to(&string, Float(&string)); - } - - #[test] - fn lowest_float() { - let string = format!("{}.0", f64::MIN); - - assert_parses_to(&string, Float(&string)); - } - - #[test] - fn pos_inf_float() { - assert_parses_to( - "inf", - Var { - module_name: "", - ident: "inf", - }, - ); - } - - #[test] - fn neg_inf_float() { - let arena = Bump::new(); - let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Negate); - let inf_expr = Var { - module_name: "", - ident: "inf", - }; - let loc_inf_expr = Located::new(0, 0, 1, 4, inf_expr); - assert_parses_to("-inf", UnaryOp(arena.alloc(loc_inf_expr), loc_op)); - } - #[quickcheck] fn all_f64_values_parse(num: f64) { let string = num.to_string(); - if string.contains(".") { + if string.contains('.') { assert_parses_to(&string, Float(&string)); } else if num.is_nan() { assert_parses_to(&string, Expr::GlobalTag(&string)); @@ -455,1704 +500,6 @@ mod test_parse { } } - // RECORD LITERALS - - #[test] - fn empty_record() { - let arena = Bump::new(); - let expected = Record { - fields: &[], - final_comments: &[], - }; - let actual = parse_expr_with(&arena, "{}"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn record_update() { - let arena = Bump::new(); - let label1 = RequiredValue( - Located::new(0, 0, 16, 17, "x"), - &[], - arena.alloc(Located::new(0, 0, 19, 20, Num("5"))), - ); - let label2 = RequiredValue( - Located::new(0, 0, 22, 23, "y"), - &[], - arena.alloc(Located::new(0, 0, 25, 26, Num("0"))), - ); - let fields: &[_] = &[ - Located::new(0, 0, 16, 20, label1), - Located::new(0, 0, 22, 26, label2), - ]; - let var = Var { - module_name: "Foo.Bar", - ident: "baz", - }; - let update_target = Located::new(0, 0, 2, 13, var); - let expected = RecordUpdate { - update: &*arena.alloc(update_target), - fields, - final_comments: &(&[] as &[_]), - }; - - let actual = parse_expr_with(&arena, "{ Foo.Bar.baz & x: 5, y: 0 }"); - assert_eq!(Ok(expected), actual); - } - - #[test] - fn record_with_if() { - let arena = Bump::new(); - let if_expr = Expr::If( - arena.alloc([( - Located::new(0, 0, 8, 12, Expr::GlobalTag("True")), - Located::new(0, 0, 18, 19, Expr::Num("1")), - )]), - arena.alloc(Located::new(0, 0, 25, 26, Num("2"))), - ); - - let label1 = RequiredValue( - Located::new(0, 0, 1, 2, "x"), - &[], - arena.alloc(Located::new(0, 0, 5, 26, if_expr)), - ); - let label2 = RequiredValue( - Located::new(0, 0, 28, 29, "y"), - &[], - arena.alloc(Located::new(0, 0, 31, 32, Num("3"))), - ); - let fields = &[ - Located::new(0, 0, 1, 26, label1), - Located::new(0, 0, 28, 32, label2), - ]; - let expected = Record { - fields, - final_comments: &[], - }; - - let actual = parse_expr_with(&arena, "{x : if True then 1 else 2, y: 3 }"); - assert_eq!(Ok(expected), actual); - } - - // EXPECT - - #[test] - fn expect() { - let arena = Bump::new(); - - let equals = ( - Located::new(0, 0, 7, 8, Num("1")), - Located::new(0, 0, 9, 11, BinOp::Equals), - ); - - let condition = BinOps( - arena.alloc([equals]), - arena.alloc(Located::new(0, 0, 12, 13, Num("1"))), - ); - let loc_condition = Located::new(0, 0, 7, 13, condition); - - let continuation = Expr::SpaceBefore(arena.alloc(Num("4")), &[Newline, Newline]); - let loc_continuation = Located::new(2, 2, 0, 1, continuation); - - let expected = Expect(arena.alloc(loc_condition), arena.alloc(loc_continuation)); - - let actual = parse_expr_with( - &arena, - indoc!( - r#" - expect 1 == 1 - - 4 - "# - ), - ); - assert_eq!(Ok(expected), actual); - } - - // OPERATORS - - #[test] - fn one_plus_two() { - let arena = Bump::new(); - let tuple = ( - Located::new(0, 0, 0, 1, Num("1")), - Located::new(0, 0, 1, 2, Plus), - Located::new(0, 0, 2, 3, Num("2")), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "1+2"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn one_minus_two() { - let arena = Bump::new(); - let tuple = ( - Located::new(0, 0, 0, 1, Num("1")), - Located::new(0, 0, 1, 2, Minus), - Located::new(0, 0, 2, 3, Num("2")), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "1-2"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn var_minus_two() { - let arena = Bump::new(); - let tuple = ( - Located::new( - 0, - 0, - 0, - 1, - Var { - module_name: "", - ident: "x", - }, - ), - Located::new(0, 0, 1, 2, Minus), - Located::new(0, 0, 2, 3, Num("2")), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "x-2"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn add_with_spaces() { - let arena = Bump::new(); - let tuple = ( - Located::new(0, 0, 0, 1, Num("1")), - Located::new(0, 0, 3, 4, Plus), - Located::new(0, 0, 7, 8, Num("2")), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "1 + 2"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn sub_with_spaces() { - let arena = Bump::new(); - let tuple = ( - Located::new(0, 0, 0, 1, Num("1")), - Located::new(0, 0, 3, 4, Minus), - Located::new(0, 0, 7, 8, Num("2")), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "1 - 2"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn add_var_with_spaces() { - // This is a regression test! It used to break with subtraction and work - // with other arithmetic operatos. - // - // Subtraction is special when it comes to parsing, because of unary negation. - - let arena = Bump::new(); - let var = Var { - module_name: "", - ident: "x", - }; - let tuple = ( - Located::new(0, 0, 0, 1, var), - Located::new(0, 0, 2, 3, Plus), - Located::new(0, 0, 4, 5, Num("2")), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "x + 2"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn sub_var_with_spaces() { - // This is a regression test! It used to break with subtraction and work - // with other arithmetic operatos. - // - // Subtraction is special when it comes to parsing, because of unary negation. - let arena = Bump::new(); - let var = Var { - module_name: "", - ident: "x", - }; - let tuple = ( - Located::new(0, 0, 0, 1, var), - Located::new(0, 0, 2, 3, Minus), - Located::new(0, 0, 4, 5, Num("2")), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "x - 2"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn newline_before_add() { - let arena = Bump::new(); - let spaced_int = Expr::SpaceAfter(arena.alloc(Num("3")), &[Newline]); - let tuple = ( - Located::new(0, 0, 0, 1, spaced_int), - Located::new(1, 1, 0, 1, Plus), - Located::new(1, 1, 2, 3, Num("4")), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "3 \n+ 4"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn newline_before_sub() { - let arena = Bump::new(); - let spaced_int = Expr::SpaceAfter(arena.alloc(Num("3")), &[Newline]); - let tuple = ( - Located::new(0, 0, 0, 1, spaced_int), - Located::new(1, 1, 0, 1, Minus), - Located::new(1, 1, 2, 3, Num("4")), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "3 \n- 4"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn newline_after_mul() { - let arena = Bump::new(); - let spaced_int = arena.alloc(Num("4")).before(&[Newline]); - let tuple = ( - Located::new(0, 0, 0, 1, Num("3")), - Located::new(0, 0, 3, 4, Star), - Located::new(1, 1, 2, 3, spaced_int), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "3 *\n 4"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn newline_after_sub() { - let arena = Bump::new(); - let spaced_int = arena.alloc(Num("4")).before(&[Newline]); - let tuple = ( - Located::new(0, 0, 0, 1, Num("3")), - Located::new(0, 0, 3, 4, Minus), - Located::new(1, 1, 2, 3, spaced_int), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "3 -\n 4"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn newline_and_spaces_before_less_than() { - let arena = Bump::new(); - let spaced_int = arena.alloc(Num("1")).after(&[Newline]); - let tuple = ( - Located::new(0, 0, 4, 5, spaced_int), - Located::new(1, 1, 4, 5, LessThan), - Located::new(1, 1, 6, 7, Num("2")), - ); - - let newlines = bumpalo::vec![in &arena; Newline, Newline]; - let def = Def::Body( - arena.alloc(Located::new(0, 0, 0, 1, Identifier("x"))), - arena.alloc(Located::new(0, 1, 4, 7, single_binop(&arena, tuple))), - ); - let loc_def = &*arena.alloc(Located::new(0, 1, 0, 7, def)); - let defs = &[loc_def]; - let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); - let loc_ret = Located::new(3, 3, 0, 2, ret); - let expected = Defs(defs, arena.alloc(loc_ret)); - - // let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "x = 1\n < 2\n\n42"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn comment_with_non_ascii() { - let arena = Bump::new(); - let spaced_int = arena.alloc(Num("3")).after(&[LineComment(" 2 × 2")]); - let tuple = ( - Located::new(0, 0, 0, 1, spaced_int), - Located::new(1, 1, 0, 1, Plus), - Located::new(1, 1, 2, 3, Num("4")), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "3 # 2 × 2\n+ 4"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn comment_before_op() { - let arena = Bump::new(); - let spaced_int = arena.alloc(Num("3")).after(&[LineComment(" test!")]); - let tuple = ( - Located::new(0, 0, 0, 1, spaced_int), - Located::new(1, 1, 0, 1, Plus), - Located::new(1, 1, 2, 3, Num("4")), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "3 # test!\n+ 4"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn comment_after_op() { - let arena = Bump::new(); - let spaced_int = arena.alloc(Num("92")).before(&[LineComment(" test!")]); - let tuple = ( - Located::new(0, 0, 0, 2, Num("12")), - Located::new(0, 0, 4, 5, Star), - Located::new(1, 1, 1, 3, spaced_int), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "12 * # test!\n 92"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn ops_with_newlines() { - let arena = Bump::new(); - let spaced_int1 = arena.alloc(Num("3")).after(&[Newline]); - let spaced_int2 = arena.alloc(Num("4")).before(&[Newline, Newline]); - let tuple = ( - Located::new(0, 0, 0, 1, spaced_int1), - Located::new(1, 1, 0, 1, Plus), - Located::new(3, 3, 2, 3, spaced_int2), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "3 \n+ \n\n 4"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn space_only_after_minus() { - // This is an edge case with minus because of unary negation. - // (x- y) should parse like subtraction (x - (y)), not function application (x (-y)) - let arena = Bump::new(); - let var1 = Var { - module_name: "", - ident: "x", - }; - let var2 = Var { - module_name: "", - ident: "y", - }; - let tuple = ( - Located::new(0, 0, 0, 1, var1), - Located::new(0, 0, 1, 2, Minus), - Located::new(0, 0, 3, 4, var2), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "x- y"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn minus_twelve_minus_five() { - let arena = Bump::new(); - let tuple = ( - Located::new(0, 0, 0, 3, Num("-12")), - Located::new(0, 0, 3, 4, Minus), - Located::new(0, 0, 4, 5, Num("5")), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "-12-5"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn ten_times_eleven() { - let arena = Bump::new(); - let tuple = ( - Located::new(0, 0, 0, 2, Num("10")), - Located::new(0, 0, 2, 3, Star), - Located::new(0, 0, 3, 5, Num("11")), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "10*11"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn multiple_operators() { - let arena = Bump::new(); - - let lefts = [ - ( - Located::new(0, 0, 0, 2, Num("31")), - Located::new(0, 0, 2, 3, Star), - ), - ( - Located::new(0, 0, 3, 5, Num("42")), - Located::new(0, 0, 5, 6, Plus), - ), - ]; - let right = Located::new(0, 0, 6, 9, Num("534")); - - let expected = BinOps(&lefts, &right); - let actual = parse_expr_with(&arena, "31*42+534"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn equals() { - let arena = Bump::new(); - let var1 = Var { - module_name: "", - ident: "x", - }; - let var2 = Var { - module_name: "", - ident: "y", - }; - let tuple = ( - Located::new(0, 0, 0, 1, var1), - Located::new(0, 0, 1, 3, Equals), - Located::new(0, 0, 3, 4, var2), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "x==y"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn equals_with_spaces() { - let arena = Bump::new(); - let var1 = Var { - module_name: "", - ident: "x", - }; - let var2 = Var { - module_name: "", - ident: "y", - }; - let tuple = ( - Located::new(0, 0, 0, 1, var1), - Located::new(0, 0, 2, 4, Equals), - Located::new(0, 0, 5, 6, var2), - ); - let expected = single_binop(&arena, tuple); - let actual = parse_expr_with(&arena, "x == y"); - - assert_eq!(Ok(expected), actual); - } - - // VAR - - #[test] - fn basic_var() { - let arena = Bump::new(); - let expected = Var { - module_name: "", - ident: "whee", - }; - let actual = parse_expr_with(&arena, "whee"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn var_when() { - // Regression test for identifiers beginning with keywords (if/then/else/when/is) - let arena = Bump::new(); - let expected = Var { - module_name: "", - ident: "whenever", - }; - let actual = parse_expr_with(&arena, "whenever"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn var_is() { - // Regression test for identifiers beginning with keywords (if/then/else/when/is) - let arena = Bump::new(); - let expected = Var { - module_name: "", - ident: "isnt", - }; - let actual = parse_expr_with(&arena, "isnt"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn var_if() { - // Regression test for identifiers beginning with keywords (if/then/else/when/is) - let arena = Bump::new(); - let expected = Var { - module_name: "", - ident: "iffy", - }; - let actual = parse_expr_with(&arena, "iffy"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn var_then() { - // Regression test for identifiers beginning with keywords (if/then/else/when/is) - let arena = Bump::new(); - let expected = Var { - module_name: "", - ident: "thenever", - }; - let actual = parse_expr_with(&arena, "thenever"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn var_else() { - // Regression test for identifiers beginning with keywords (if/then/else/when/is) - let arena = Bump::new(); - let expected = Var { - module_name: "", - ident: "elsewhere", - }; - let actual = parse_expr_with(&arena, "elsewhere"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn parenthetical_var() { - let arena = Bump::new(); - let expected = ParensAround(arena.alloc(Var { - module_name: "", - ident: "whee", - })); - let actual = parse_expr_with(&arena, "(whee)"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn qualified_var() { - let arena = Bump::new(); - let expected = Var { - module_name: "One.Two", - ident: "whee", - }; - let actual = parse_expr_with(&arena, "One.Two.whee"); - - assert_eq!(Ok(expected), actual); - } - - // TAG - - #[test] - fn basic_global_tag() { - let arena = Bump::new(); - let expected = Expr::GlobalTag("Whee"); - let actual = parse_expr_with(&arena, "Whee"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn basic_private_tag() { - let arena = Bump::new(); - let expected = Expr::PrivateTag("@Whee"); - let actual = parse_expr_with(&arena, "@Whee"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn apply_private_tag() { - let arena = Bump::new(); - let arg1 = arena.alloc(Located::new(0, 0, 6, 8, Num("12"))); - let arg2 = arena.alloc(Located::new(0, 0, 9, 11, Num("34"))); - let args = &[&*arg1, &*arg2]; - let expected = Expr::Apply( - arena.alloc(Located::new(0, 0, 0, 5, Expr::PrivateTag("@Whee"))), - args, - CalledVia::Space, - ); - let actual = parse_expr_with(&arena, "@Whee 12 34"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn apply_global_tag() { - let arena = Bump::new(); - let arg1 = arena.alloc(Located::new(0, 0, 5, 7, Num("12"))); - let arg2 = arena.alloc(Located::new(0, 0, 8, 10, Num("34"))); - let args = &[&*arg1, &*arg2]; - let expected = Expr::Apply( - arena.alloc(Located::new(0, 0, 0, 4, Expr::GlobalTag("Whee"))), - args, - CalledVia::Space, - ); - let actual = parse_expr_with(&arena, "Whee 12 34"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn apply_parenthetical_global_tag_args() { - let arena = Bump::new(); - let int1 = ParensAround(arena.alloc(Num("12"))); - let int2 = ParensAround(arena.alloc(Num("34"))); - let arg1 = arena.alloc(Located::new(0, 0, 6, 8, int1)); - let arg2 = arena.alloc(Located::new(0, 0, 11, 13, int2)); - let args = &[&*arg1, &*arg2]; - let expected = Expr::Apply( - arena.alloc(Located::new(0, 0, 0, 4, Expr::GlobalTag("Whee"))), - args, - CalledVia::Space, - ); - let actual = parse_expr_with(&arena, "Whee (12) (34)"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn qualified_global_tag() { - use roc_parse::ident::BadIdent; - - let arena = Bump::new(); - let expected = Expr::MalformedIdent("One.Two.Whee", BadIdent::QualifiedTag(0, 12)); - let actual = parse_expr_with(&arena, "One.Two.Whee"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn private_qualified_tag() { - use roc_parse::ident::BadIdent; - - let arena = Bump::new(); - let expected = Expr::MalformedIdent("@One.Two.Whee", BadIdent::BadPrivateTag(0, 4)); - let actual = parse_expr_with(&arena, "@One.Two.Whee"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn tag_pattern() { - let arena = Bump::new(); - let pattern = Located::new(0, 0, 1, 6, Pattern::GlobalTag("Thing")); - let patterns = &[pattern]; - let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 10, 12, Num("42")))); - let actual = parse_expr_with(&arena, "\\Thing -> 42"); - - assert_eq!(Ok(expected), actual); - } - - // LISTS - - #[test] - fn empty_list() { - let arena = Bump::new(); - let expected = List { - items: &[], - final_comments: &[], - }; - let actual = parse_expr_with(&arena, "[]"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn spaces_inside_empty_list() { - // This is a regression test! - let arena = Bump::new(); - let expected = List { - items: &[], - final_comments: &[], - }; - let actual = parse_expr_with(&arena, "[ ]"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn newline_inside_empty_list() { - let arena = Bump::new(); - let expected = List { - items: &[], - final_comments: &[Newline], - }; - let actual = parse_expr_with(&arena, "[\n]"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn comment_inside_empty_list() { - let arena = Bump::new(); - let expected = List { - items: &[], - final_comments: &[LineComment("comment")], - }; - let actual = parse_expr_with(&arena, "[#comment\n]"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn packed_singleton_list() { - let arena = Bump::new(); - let items = &[&*arena.alloc(Located::new(0, 0, 1, 2, Num("1")))]; - let expected = List { - items, - final_comments: &[], - }; - let actual = parse_expr_with(&arena, "[1]"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn spaced_singleton_list() { - let arena = Bump::new(); - let items = &[&*arena.alloc(Located::new(0, 0, 2, 3, Num("1")))]; - let expected = List { - items, - final_comments: &[], - }; - let actual = parse_expr_with(&arena, "[ 1 ]"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn newline_singleton_list() { - let arena = Bump::new(); - let item = &*arena.alloc(Expr::SpaceAfter( - arena.alloc(Num("1")), - arena.alloc([Newline]), - )); - let item = Expr::SpaceBefore(item, arena.alloc([Newline])); - let items = [&*arena.alloc(Located::new(1, 1, 0, 1, item))]; - let expected = List { - items: &items, - final_comments: &[], - }; - let actual = parse_expr_with(&arena, "[\n1\n]"); - - assert_eq!(Ok(expected), actual); - } - - // FIELD ACCESS - - #[test] - fn basic_field() { - let arena = Bump::new(); - let var = Var { - module_name: "", - ident: "rec", - }; - let expected = Access(arena.alloc(var), "field"); - let actual = parse_expr_with(&arena, "rec.field"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn parenthetical_basic_field() { - let arena = Bump::new(); - let paren_var = ParensAround(arena.alloc(Var { - module_name: "", - ident: "rec", - })); - let expected = Access(arena.alloc(paren_var), "field"); - let actual = parse_expr_with(&arena, "(rec).field"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn parenthetical_field_qualified_var() { - let arena = Bump::new(); - let paren_var = ParensAround(arena.alloc(Var { - module_name: "One.Two", - ident: "rec", - })); - let expected = Access(arena.alloc(paren_var), "field"); - let actual = parse_expr_with(&arena, "(One.Two.rec).field"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn multiple_fields() { - let arena = Bump::new(); - let var = Var { - module_name: "", - ident: "rec", - }; - let expected = Access( - arena.alloc(Access(arena.alloc(Access(arena.alloc(var), "abc")), "def")), - "ghi", - ); - let actual = parse_expr_with(&arena, "rec.abc.def.ghi"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn qualified_field() { - let arena = Bump::new(); - let var = Var { - module_name: "One.Two", - ident: "rec", - }; - let expected = Access( - arena.alloc(Access(arena.alloc(Access(arena.alloc(var), "abc")), "def")), - "ghi", - ); - let actual = parse_expr_with(&arena, "One.Two.rec.abc.def.ghi"); - - assert_eq!(Ok(expected), actual); - } - - // APPLY - - #[test] - fn basic_apply() { - let arena = Bump::new(); - let arg = arena.alloc(Located::new(0, 0, 5, 6, Num("1"))); - let args = &[&*arg]; - let expr = Var { - module_name: "", - ident: "whee", - }; - let expected = Expr::Apply( - arena.alloc(Located::new(0, 0, 0, 4, expr)), - args, - CalledVia::Space, - ); - let actual = parse_expr_with(&arena, "whee 1"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn apply_two_args() { - let arena = Bump::new(); - let arg1 = arena.alloc(Located::new(0, 0, 6, 8, Num("12"))); - let arg2 = arena.alloc(Located::new(0, 0, 10, 12, Num("34"))); - let args = &[&*arg1, &*arg2]; - let expected = Expr::Apply( - arena.alloc(Located::new( - 0, - 0, - 0, - 4, - Var { - module_name: "", - ident: "whee", - }, - )), - args, - CalledVia::Space, - ); - let actual = parse_expr_with(&arena, "whee 12 34"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn apply_three_args() { - let arena = Bump::new(); - let arg1 = arena.alloc(Located::new( - 0, - 0, - 2, - 3, - Var { - module_name: "", - ident: "b", - }, - )); - let arg2 = arena.alloc(Located::new( - 0, - 0, - 4, - 5, - Var { - module_name: "", - ident: "c", - }, - )); - let arg3 = arena.alloc(Located::new( - 0, - 0, - 6, - 7, - Var { - module_name: "", - ident: "d", - }, - )); - let args = &[&*arg1, &*arg2, &*arg3]; - let expected = Expr::Apply( - arena.alloc(Located::new( - 0, - 0, - 0, - 1, - Var { - module_name: "", - ident: "a", - }, - )), - args, - CalledVia::Space, - ); - let actual = parse_expr_with(&arena, "a b c d"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn parenthetical_apply() { - let arena = Bump::new(); - let arg = arena.alloc(Located::new(0, 0, 7, 8, Num("1"))); - let args = &[&*arg]; - let parens_var = Expr::ParensAround(arena.alloc(Var { - module_name: "", - ident: "whee", - })); - let expected = Expr::Apply( - arena.alloc(Located::new(0, 0, 1, 5, parens_var)), - args, - CalledVia::Space, - ); - let actual = parse_expr_with(&arena, "(whee) 1"); - - assert_eq!(Ok(expected), actual); - } - - // UNARY OPERATORS - - #[test] - fn unary_negation() { - let arena = Bump::new(); - let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Negate); - let arg1_expr = Var { - module_name: "", - ident: "foo", - }; - let loc_arg1_expr = Located::new(0, 0, 1, 4, arg1_expr); - let expected = UnaryOp(arena.alloc(loc_arg1_expr), loc_op); - let actual = parse_expr_with(&arena, "-foo"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn unary_not() { - let arena = Bump::new(); - let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Not); - let arg1_expr = Var { - module_name: "", - ident: "blah", - }; - let loc_arg1_expr = Located::new(0, 0, 1, 5, arg1_expr); - let expected = UnaryOp(arena.alloc(loc_arg1_expr), loc_op); - let actual = parse_expr_with(&arena, "!blah"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn apply_unary_negation() { - let arena = Bump::new(); - let arg1 = arena.alloc(Located::new(0, 0, 7, 9, Num("12"))); - let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Negate); - let arg2 = arena.alloc(Located::new( - 0, - 0, - 10, - 13, - Var { - module_name: "", - ident: "foo", - }, - )); - let args = &[&*arg1, &*arg2]; - let function = Located::new( - 0, - 0, - 1, - 5, - Var { - module_name: "", - ident: "whee", - }, - ); - let unary = Located::new(0, 0, 0, 5, UnaryOp(arena.alloc(function), loc_op)); - let expected = Expr::Apply(arena.alloc(unary), args, CalledVia::Space); - let actual = parse_expr_with(&arena, "-whee 12 foo"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn apply_unary_not() { - let arena = Bump::new(); - let arg1 = arena.alloc(Located::new(0, 0, 7, 9, Num("12"))); - let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Not); - let arg2 = arena.alloc(Located::new( - 0, - 0, - 10, - 13, - Var { - module_name: "", - ident: "foo", - }, - )); - let args = &[&*arg1, &*arg2]; - - let function = Located::new( - 0, - 0, - 1, - 5, - Var { - module_name: "", - ident: "whee", - }, - ); - let unary = Located::new(0, 0, 0, 5, UnaryOp(arena.alloc(function), loc_op)); - let expected = Expr::Apply(arena.alloc(unary), args, CalledVia::Space); - let actual = parse_expr_with(&arena, "!whee 12 foo"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn unary_negation_with_parens() { - let arena = Bump::new(); - let arg1 = arena.alloc(Located::new(0, 0, 8, 10, Num("12"))); - let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Negate); - let arg2 = arena.alloc(Located::new( - 0, - 0, - 11, - 14, - Var { - module_name: "", - ident: "foo", - }, - )); - let args = &[&*arg1, &*arg2]; - let apply_expr = Expr::ParensAround(arena.alloc(Expr::Apply( - arena.alloc(Located::new( - 0, - 0, - 2, - 6, - Var { - module_name: "", - ident: "whee", - }, - )), - args, - CalledVia::Space, - ))); - let expected = UnaryOp(arena.alloc(Located::new(0, 0, 2, 14, apply_expr)), loc_op); - let actual = parse_expr_with(&arena, "-(whee 12 foo)"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn unary_not_with_parens() { - let arena = Bump::new(); - let arg1 = arena.alloc(Located::new(0, 0, 8, 10, Num("12"))); - let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Not); - let arg2 = arena.alloc(Located::new( - 0, - 0, - 11, - 14, - Var { - module_name: "", - ident: "foo", - }, - )); - let args = &[&*arg1, &*arg2]; - let apply_expr = Expr::ParensAround(arena.alloc(Expr::Apply( - arena.alloc(Located::new( - 0, - 0, - 2, - 6, - Var { - module_name: "", - ident: "whee", - }, - )), - args, - CalledVia::Space, - ))); - let expected = UnaryOp(arena.alloc(Located::new(0, 0, 2, 14, apply_expr)), loc_op); - let actual = parse_expr_with(&arena, "!(whee 12 foo)"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn unary_negation_arg() { - let arena = Bump::new(); - let arg1 = arena.alloc(Located::new(0, 0, 6, 8, Num("12"))); - let loc_op = Located::new(0, 0, 9, 10, UnaryOp::Negate); - let var1 = Var { - module_name: "", - ident: "foo", - }; - let loc_arg1_expr = Located::new(0, 0, 10, 13, var1); - let arg_op = UnaryOp(arena.alloc(loc_arg1_expr), loc_op); - let arg2 = arena.alloc(Located::new(0, 0, 9, 13, arg_op)); - let args = &[&*arg1, &*arg2]; - let var2 = Var { - module_name: "", - ident: "whee", - }; - let expected = Expr::Apply( - arena.alloc(Located::new(0, 0, 0, 4, var2)), - args, - CalledVia::Space, - ); - let actual = parse_expr_with(&arena, "whee 12 -foo"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn unary_negation_access() { - // Regression test for https://github.com/rtfeldman/roc/issues/509 - let arena = Bump::new(); - let var = Var { - module_name: "", - ident: "rec1", - }; - let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Negate); - let access = Access(arena.alloc(var), "field"); - let loc_access = Located::new(0, 0, 1, 11, access); - let expected = UnaryOp(arena.alloc(loc_access), loc_op); - let actual = parse_expr_with(&arena, "-rec1.field"); - - assert_eq!(Ok(expected), actual); - } - - // CLOSURE - - #[test] - fn single_arg_closure() { - let arena = Bump::new(); - let pattern = Located::new(0, 0, 1, 2, Identifier("a")); - let patterns = &[pattern]; - let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 6, 8, Num("42")))); - let actual = parse_expr_with(&arena, "\\a -> 42"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn single_underscore_closure() { - let arena = Bump::new(); - let pattern = Located::new(0, 0, 1, 2, Pattern::Underscore("")); - let patterns = &[pattern]; - let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 6, 8, Num("42")))); - let actual = parse_expr_with(&arena, "\\_ -> 42"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn malformed_ident_due_to_underscore() { - // This is a regression test against a bug where if you included an - // underscore in an argument name, it would parse as three arguments - // (and would ignore the underscore as if it had been blank space). - let arena = Bump::new(); - - let pattern = Located::new( - 0, - 0, - 1, - 11, - Pattern::MalformedIdent("the_answer", roc_parse::ident::BadIdent::Underscore(0, 5)), - ); - let patterns = &[pattern]; - let expr = Located::new(0, 0, 15, 17, Expr::Num("42")); - - let expected = Closure(patterns, &expr); - let actual = parse_expr_with(&arena, "\\the_answer -> 42"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn two_arg_closure() { - let arena = Bump::new(); - let arg1 = Located::new(0, 0, 1, 2, Identifier("a")); - let arg2 = Located::new(0, 0, 4, 5, Identifier("b")); - let patterns = &[arg1, arg2]; - let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 9, 11, Num("42")))); - let actual = parse_expr_with(&arena, "\\a, b -> 42"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn three_arg_closure() { - let arena = Bump::new(); - let arg1 = Located::new(0, 0, 1, 2, Identifier("a")); - let arg2 = Located::new(0, 0, 4, 5, Identifier("b")); - let arg3 = Located::new(0, 0, 7, 8, Identifier("c")); - let patterns = bumpalo::vec![in &arena; arg1, arg2, arg3]; - let expected = Closure( - arena.alloc(patterns), - arena.alloc(Located::new(0, 0, 12, 14, Num("42"))), - ); - let actual = parse_expr_with(&arena, "\\a, b, c -> 42"); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn closure_with_underscores() { - let arena = Bump::new(); - let underscore1 = Located::new(0, 0, 1, 2, Pattern::Underscore("")); - let underscore2 = Located::new(0, 0, 4, 9, Pattern::Underscore("name")); - let patterns = bumpalo::vec![in &arena; underscore1, underscore2]; - let expected = Closure( - arena.alloc(patterns), - arena.alloc(Located::new(0, 0, 13, 15, Num("42"))), - ); - let actual = parse_expr_with(&arena, "\\_, _name -> 42"); - - assert_eq!(Ok(expected), actual); - } - - // DEF - - #[test] - fn one_def() { - let arena = Bump::new(); - let newlines = bumpalo::vec![in &arena; Newline, Newline]; - let def = Def::Body( - arena.alloc(Located::new(1, 1, 0, 1, Identifier("x"))), - arena.alloc(Located::new(1, 1, 2, 3, Num("5"))), - ); - let loc_def = &*arena.alloc(Located::new(1, 1, 0, 3, def)); - let defs = &[loc_def]; - let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); - let loc_ret = Located::new(3, 3, 0, 2, ret); - let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")]; - let expected = Expr::SpaceBefore( - arena.alloc(Defs(defs, arena.alloc(loc_ret))), - reset_indentation.into_bump_slice(), - ); - - assert_parses_to( - indoc!( - r#"# leading comment - x=5 - - 42 - "# - ), - expected, - ); - } - - #[test] - fn if_def() { - let arena = Bump::new(); - let newlines = bumpalo::vec![in &arena; Newline, Newline]; - let def = Def::Body( - arena.alloc(Located::new(0, 0, 0, 4, Identifier("iffy"))), - arena.alloc(Located::new(0, 0, 5, 6, Num("5"))), - ); - let loc_def = &*arena.alloc(Located::new(0, 0, 0, 6, def)); - let defs = &[loc_def]; - let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); - let loc_ret = Located::new(2, 2, 0, 2, ret); - let expected = Defs(defs, arena.alloc(loc_ret)); - - assert_parses_to( - indoc!( - r#" - iffy=5 - - 42 - "# - ), - expected, - ); - } - - #[test] - fn one_spaced_def() { - let arena = Bump::new(); - let newlines = bumpalo::vec![in &arena; Newline, Newline]; - let def = Def::Body( - arena.alloc(Located::new(1, 1, 0, 1, Identifier("x"))), - arena.alloc(Located::new(1, 1, 4, 5, Num("5"))), - ); - let loc_def = &*arena.alloc(Located::new(1, 1, 0, 5, def)); - let defs = &[loc_def]; - let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); - let loc_ret = Located::new(3, 3, 0, 2, ret); - let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")]; - let expected = Expr::SpaceBefore( - arena.alloc(Defs(defs, arena.alloc(loc_ret))), - reset_indentation.into_bump_slice(), - ); - - assert_parses_to( - indoc!( - r#"# leading comment - x = 5 - - 42 - "# - ), - expected, - ); - } - - #[test] - fn two_spaced_def() { - let arena = Bump::new(); - let newlines = bumpalo::vec![in &arena; Newline, Newline]; - let newline = bumpalo::vec![in &arena; Newline]; - let def1 = Def::Body( - arena.alloc(Located::new(1, 1, 0, 1, Identifier("x"))), - arena.alloc(Located::new(1, 1, 4, 5, Num("5"))), - ); - let loc_def1 = &*arena.alloc(Located::new(1, 1, 0, 5, def1)); - let def2 = Def::SpaceBefore( - &*arena.alloc(Def::Body( - arena.alloc(Located::new(2, 2, 0, 1, Identifier("y"))), - arena.alloc(Located::new(2, 2, 4, 5, Num("6"))), - )), - newline.into_bump_slice(), - ); - let loc_def2 = &*arena.alloc(Located::new(2, 2, 0, 5, def2)); - let defs = &[loc_def1, loc_def2]; - let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); - let loc_ret = Located::new(4, 4, 0, 2, ret); - let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")]; - let expected = Expr::SpaceBefore( - arena.alloc(Defs(defs, arena.alloc(loc_ret))), - reset_indentation.into_bump_slice(), - ); - - assert_parses_to( - indoc!( - r#"# leading comment - x = 5 - y = 6 - - 42 - "# - ), - expected, - ); - } - - #[test] - fn record_destructure_def() { - let arena = Bump::new(); - let newlines = bumpalo::vec![in &arena; Newline, Newline]; - let newline = bumpalo::vec![in &arena; Newline]; - let fields = bumpalo::vec![in &arena; - Located::new(1, 1, 2, 3, Identifier("x")), - Located::new(1, 1, 5, 7, Identifier("y")) - ]; - let def1 = Def::Body( - arena.alloc(Located::new(1, 1, 0, 8, RecordDestructure(&fields))), - arena.alloc(Located::new(1, 1, 11, 12, Num("5"))), - ); - let loc_def1 = &*arena.alloc(Located::new(1, 1, 0, 12, def1)); - let def2 = Def::SpaceBefore( - &*arena.alloc(Def::Body( - arena.alloc(Located::new(2, 2, 0, 1, Identifier("y"))), - arena.alloc(Located::new(2, 2, 4, 5, Num("6"))), - )), - &newline, - ); - let loc_def2 = &*arena.alloc(Located::new(2, 2, 0, 5, def2)); - let defs = &[loc_def1, loc_def2]; - let ret = Expr::SpaceBefore(arena.alloc(Num("42")), &newlines); - let loc_ret = Located::new(4, 4, 0, 2, ret); - let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")]; - let expected = Expr::SpaceBefore( - arena.alloc(Defs(defs, arena.alloc(loc_ret))), - &reset_indentation, - ); - - assert_parses_to( - indoc!( - r#" - # leading comment - { x, y } = 5 - y = 6 - - 42 - "# - ), - expected, - ); - } - - #[test] - fn one_backpassing() { - let arena = Bump::new(); - let newlines = bumpalo::vec![in &arena; Newline, Newline]; - let identifier_x = Located::new(1, 1, 0, 1, Identifier("x")); - let identifier_y = Located::new(1, 1, 7, 8, Identifier("y")); - - let var_x = Var { - module_name: "", - ident: "x", - }; - - let var_y = Var { - module_name: "", - ident: "y", - }; - let loc_var_y = arena.alloc(Located::new(1, 1, 12, 13, var_y)); - - let closure = ParensAround(arena.alloc(Closure(arena.alloc([identifier_y]), loc_var_y))); - let loc_closure = Located::new(1, 1, 5, 14, closure); - - let ret = Expr::SpaceBefore(arena.alloc(var_x), newlines.into_bump_slice()); - let loc_ret = Located::new(3, 3, 0, 1, ret); - - let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")]; - let expected = Expr::SpaceBefore( - arena.alloc(Expr::Backpassing( - arena.alloc([identifier_x]), - arena.alloc(loc_closure), - arena.alloc(loc_ret), - )), - reset_indentation.into_bump_slice(), - ); - - assert_parses_to( - indoc!( - r#"# leading comment - x <- (\y -> y) - - x - "# - ), - expected, - ); - } - - #[test] - fn underscore_backpassing() { - let arena = Bump::new(); - let newlines = bumpalo::vec![in &arena; Newline, Newline]; - let underscore = Located::new(1, 1, 0, 1, Pattern::Underscore("")); - let identifier_y = Located::new(1, 1, 7, 8, Identifier("y")); - - let num_4 = Num("4"); - - let var_y = Var { - module_name: "", - ident: "y", - }; - let loc_var_y = arena.alloc(Located::new(1, 1, 12, 13, var_y)); - - let closure = ParensAround(arena.alloc(Closure(arena.alloc([identifier_y]), loc_var_y))); - let loc_closure = Located::new(1, 1, 5, 14, closure); - - let ret = Expr::SpaceBefore(arena.alloc(num_4), newlines.into_bump_slice()); - let loc_ret = Located::new(3, 3, 0, 1, ret); - - let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")]; - let expected = Expr::SpaceBefore( - arena.alloc(Expr::Backpassing( - arena.alloc([underscore]), - arena.alloc(loc_closure), - arena.alloc(loc_ret), - )), - reset_indentation.into_bump_slice(), - ); - - assert_parses_to( - indoc!( - r#"# leading comment - _ <- (\y -> y) - - 4 - "# - ), - expected, - ); - } - - #[test] - fn two_backpassing() { - let arena = Bump::new(); - - let inner_backpassing = { - let identifier_z = Located::new(2, 2, 0, 1, Identifier("z")); - - let empty_record = Record { - fields: &[], - final_comments: &[], - }; - let loc_empty_record = Located::new(2, 2, 5, 7, empty_record); - - let var_x = Var { - module_name: "", - ident: "x", - }; - - let newlines = bumpalo::vec![in &arena; Newline, Newline]; - let ret = Expr::SpaceBefore(arena.alloc(var_x), newlines.into_bump_slice()); - let loc_ret = Located::new(4, 4, 0, 1, ret); - - Expr::SpaceBefore( - arena.alloc(Expr::Backpassing( - arena.alloc([identifier_z]), - arena.alloc(loc_empty_record), - arena.alloc(loc_ret), - )), - arena.alloc([Newline]), - ) - }; - - let identifier_x = Located::new(1, 1, 0, 1, Identifier("x")); - let identifier_y = Located::new(1, 1, 7, 8, Identifier("y")); - - let var_y = Var { - module_name: "", - ident: "y", - }; - let loc_var_y = arena.alloc(Located::new(1, 1, 12, 13, var_y)); - - let closure = ParensAround(arena.alloc(Closure(arena.alloc([identifier_y]), loc_var_y))); - let loc_closure = Located::new(1, 1, 5, 14, closure); - - let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")]; - let expected = Expr::SpaceBefore( - arena.alloc(Expr::Backpassing( - arena.alloc([identifier_x]), - arena.alloc(loc_closure), - arena.alloc(Located::new(2, 4, 0, 1, inner_backpassing)), - )), - reset_indentation.into_bump_slice(), - ); - - assert_parses_to( - indoc!( - r#"# leading comment - x <- (\y -> y) - z <- {} - - x - "# - ), - expected, - ); - } - - #[test] - fn multi_backpassing() { - let arena = Bump::new(); - - let identifier_x = Located::new(0, 0, 0, 1, Identifier("x")); - let identifier_y = Located::new(0, 0, 3, 4, Identifier("y")); - - let var_x = Var { - module_name: "", - ident: "x", - }; - let loc_var_x = Located::new(2, 2, 0, 1, var_x); - - let var_y = Var { - module_name: "", - ident: "y", - }; - let loc_var_y = Located::new(2, 2, 4, 5, var_y); - - let var_list_map2 = Var { - module_name: "List", - ident: "map2", - }; - - let apply = Expr::Apply( - arena.alloc(Located::new(0, 0, 8, 17, var_list_map2)), - bumpalo::vec![ in &arena; - &*arena.alloc(Located::new(0, 0, 18, 20, Expr::List{ items: &[], final_comments: &[] })), - &*arena.alloc(Located::new(0, 0, 21, 23, Expr::List{ items: &[], final_comments: &[] })), - ] - .into_bump_slice(), - CalledVia::Space, - ); - - let loc_closure = Located::new(0, 0, 8, 23, apply); - - let binop = single_binop( - &arena, - ( - loc_var_x, - Located::new(2, 2, 2, 3, roc_module::operator::BinOp::Plus), - loc_var_y, - ), - ); - - let spaced_binop = Expr::SpaceBefore(arena.alloc(binop), &[Newline, Newline]); - - let expected = Expr::Backpassing( - arena.alloc([identifier_x, identifier_y]), - arena.alloc(loc_closure), - arena.alloc(Located::new(2, 2, 0, 5, spaced_binop)), - ); - - assert_parses_to( - indoc!( - r#" - x, y <- List.map2 [] [] - - x + y - "# - ), - expected, - ); - } - // #[test] // fn type_signature_def() { // let arena = Bump::new(); @@ -2189,156 +536,6 @@ mod test_parse { // ); // } - #[test] - fn parse_as_ann() { - let arena = Bump::new(); - let newlines = bumpalo::vec![in &arena; Newline, Newline]; - let loc_x = Located::new(0, 0, 18, 19, TypeAnnotation::BoundVariable("x")); - let loc_y = Located::new(0, 0, 20, 21, TypeAnnotation::BoundVariable("y")); - let loc_a = Located::new(0, 0, 30, 31, TypeAnnotation::BoundVariable("a")); - let loc_b = Located::new(0, 0, 32, 33, TypeAnnotation::BoundVariable("b")); - let applied_ann_args = bumpalo::vec![in &arena; loc_x, loc_y]; - let applied_ann = - TypeAnnotation::Apply("Foo.Bar", "Baz", applied_ann_args.into_bump_slice()); - let loc_applied_ann = &*arena.alloc(Located::new(0, 0, 6, 21, applied_ann)); - let applied_as_args = bumpalo::vec![in &arena; loc_a, loc_b]; - let applied_as = TypeAnnotation::Apply("", "Blah", applied_as_args.into_bump_slice()); - let loc_applied_as = &*arena.alloc(Located::new(0, 0, 25, 33, applied_as)); - let as_ann = TypeAnnotation::As(loc_applied_ann, &[], loc_applied_as); - let signature = Def::Annotation( - Located::new(0, 0, 0, 3, Identifier("foo")), - Located::new(0, 0, 6, 33, as_ann), - ); - - let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 33, signature)); - let defs = &[loc_ann]; - let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); - let loc_ret = Located::new(2, 2, 0, 2, ret); - let expected = Defs(defs, arena.alloc(loc_ret)); - - assert_parses_to( - indoc!( - r#" - foo : Foo.Bar.Baz x y as Blah a b - - 42 - "# - ), - expected, - ); - } - - #[test] - fn parse_alias() { - let arena = Bump::new(); - let newlines = bumpalo::vec![in &arena; Newline, Newline]; - let loc_x = Located::new(0, 0, 23, 24, TypeAnnotation::BoundVariable("x")); - let loc_y = Located::new(0, 0, 25, 26, TypeAnnotation::BoundVariable("y")); - let loc_a = Located::new(0, 0, 5, 6, Pattern::Identifier("a")); - let loc_b = Located::new(0, 0, 7, 8, Pattern::Identifier("b")); - let applied_ann_args = bumpalo::vec![in &arena; loc_a, loc_b]; - let applied_alias_args = bumpalo::vec![in &arena; loc_x, loc_y]; - let applied_alias = - TypeAnnotation::Apply("Foo.Bar", "Baz", applied_alias_args.into_bump_slice()); - let signature = Def::Alias { - name: Located::new(0, 0, 0, 4, "Blah"), - vars: applied_ann_args.into_bump_slice(), - ann: Located::new(0, 0, 11, 26, applied_alias), - }; - - let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 26, signature)); - let defs = &[loc_ann]; - let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); - let loc_ret = Located::new(2, 2, 0, 2, ret); - let expected = Defs(defs, arena.alloc(loc_ret)); - - assert_parses_to( - indoc!( - r#" - Blah a b : Foo.Bar.Baz x y - - 42 - "# - ), - expected, - ); - } - - #[test] - fn multiline_type_signature() { - assert_parses_to( - "f :\n {}\n\n42", - Defs( - &[&Located::new( - 0, - 1, - 0, - 6, - Def::Annotation( - Located::new(0, 0, 0, 1, Pattern::Identifier("f")), - Located::new( - 1, - 1, - 4, - 6, - TypeAnnotation::SpaceBefore( - &TypeAnnotation::Record { - fields: Collection::empty(), - ext: None, - }, - &[Newline], - ), - ), - ), - )], - &Located::new( - 3, - 3, - 0, - 2, - Expr::SpaceBefore(&Expr::Num("42"), &[Newline, Newline]), - ), - ), - ); - } - - #[test] - fn multiline_type_signature_with_comment() { - assert_parses_to( - "f :# comment\n {}\n\n42", - Defs( - &[&Located::new( - 0, - 1, - 0, - 6, - Def::Annotation( - Located::new(0, 0, 0, 1, Pattern::Identifier("f")), - Located::new( - 1, - 1, - 4, - 6, - TypeAnnotation::SpaceBefore( - &TypeAnnotation::Record { - fields: Collection::empty(), - ext: None, - }, - &[LineComment(" comment")], - ), - ), - ), - )], - &Located::new( - 3, - 3, - 0, - 2, - Expr::SpaceBefore(&Expr::Num("42"), &[Newline, Newline]), - ), - ), - ); - } // #[test] // fn type_signature_function_def() { // use TypeAnnotation; @@ -2610,904 +807,6 @@ mod test_parse { // ); // } - // WHEN - - #[test] - fn two_branch_when() { - let arena = Bump::new(); - let newlines = bumpalo::vec![in &arena; Newline]; - let pattern1 = Pattern::SpaceBefore( - arena.alloc(StrLiteral(PlainLine(""))), - newlines.into_bump_slice(), - ); - let loc_pattern1 = Located::new(1, 1, 1, 3, pattern1); - let expr1 = Num("1"); - let loc_expr1 = Located::new(1, 1, 7, 8, expr1); - let branch1 = &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern1]), - value: loc_expr1, - guard: None, - }); - let newlines = &[Newline]; - let pattern2 = Pattern::SpaceBefore(arena.alloc(StrLiteral(PlainLine("mise"))), newlines); - let loc_pattern2 = Located::new(2, 2, 1, 7, pattern2); - let expr2 = Num("2"); - let loc_expr2 = Located::new(2, 2, 11, 12, expr2); - let branch2 = &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern2]), - value: loc_expr2, - guard: None, - }); - let branches = &[branch1, branch2]; - let var = Var { - module_name: "", - ident: "x", - }; - let loc_cond = Located::new(0, 0, 5, 6, var); - let expected = Expr::When(arena.alloc(loc_cond), branches); - let actual = parse_expr_with( - &arena, - indoc!( - r#" - when x is - "" -> 1 - "mise" -> 2 - "# - ), - ); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn when_with_numbers() { - let arena = Bump::new(); - let newlines = &[Newline]; - let pattern1 = Pattern::SpaceBefore(arena.alloc(NumLiteral("1")), newlines); - let loc_pattern1 = Located::new(1, 1, 1, 2, pattern1); - let expr1 = Num("2"); - let loc_expr1 = Located::new(1, 1, 6, 7, expr1); - let branch1 = &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern1]), - value: loc_expr1, - guard: None, - }); - let newlines = &[Newline]; - let pattern2 = Pattern::SpaceBefore(arena.alloc(NumLiteral("3")), newlines); - let loc_pattern2 = Located::new(2, 2, 1, 2, pattern2); - let expr2 = Num("4"); - let loc_expr2 = Located::new(2, 2, 6, 7, expr2); - let branch2 = &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern2]), - value: loc_expr2, - guard: None, - }); - let branches = &[branch1, branch2]; - let var = Var { - module_name: "", - ident: "x", - }; - let loc_cond = Located::new(0, 0, 5, 6, var); - let expected = Expr::When(arena.alloc(loc_cond), branches); - let actual = parse_expr_with( - &arena, - indoc!( - r#" - when x is - 1 -> 2 - 3 -> 4 - "# - ), - ); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn when_with_negative_numbers() { - let arena = Bump::new(); - let newlines = &[Newline]; - let pattern1 = Pattern::SpaceBefore(arena.alloc(NumLiteral("1")), newlines); - let loc_pattern1 = Located::new(1, 1, 1, 2, pattern1); - let expr1 = Num("2"); - let loc_expr1 = Located::new(1, 1, 6, 7, expr1); - let branch1 = &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern1]), - value: loc_expr1, - guard: None, - }); - let newlines = &[Newline]; - let pattern2 = Pattern::SpaceBefore(arena.alloc(NumLiteral("-3")), newlines); - let loc_pattern2 = Located::new(2, 2, 1, 3, pattern2); - let expr2 = Num("4"); - let loc_expr2 = Located::new(2, 2, 7, 8, expr2); - let branch2 = &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern2]), - value: loc_expr2, - guard: None, - }); - let branches = &[branch1, branch2]; - let var = Var { - module_name: "", - ident: "x", - }; - let loc_cond = Located::new(0, 0, 5, 6, var); - let expected = Expr::When(arena.alloc(loc_cond), branches); - let actual = parse_expr_with( - &arena, - indoc!( - r#" - when x is - 1 -> 2 - -3 -> 4 - "# - ), - ); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn when_with_function_application() { - let arena = Bump::new(); - let newlines = &[Newline]; - let pattern1 = Pattern::SpaceBefore(arena.alloc(NumLiteral("1")), newlines); - let loc_pattern1 = Located::new(1, 1, 4, 5, pattern1); - let num_2 = Num("2"); - let num_neg = Located::new( - 1, - 1, - 9, - 16, - Expr::Var { - module_name: "Num", - ident: "neg", - }, - ); - let expr0 = Located::new(2, 2, 5, 6, Expr::SpaceBefore(&num_2, &[Newline])); - let expr1 = Expr::Apply( - &num_neg, - &*arena.alloc([&*arena.alloc(expr0)]), - CalledVia::Space, - ); - let loc_expr1 = Located::new(1, 2, 9, 6, expr1); - let branch1 = &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern1]), - value: loc_expr1, - guard: None, - }); - let newlines = &[Newline]; - let pattern2 = Pattern::SpaceBefore(arena.alloc(Pattern::Underscore("")), newlines); - let loc_pattern2 = Located::new(3, 3, 4, 5, pattern2); - let expr2 = Num("4"); - let loc_expr2 = Located::new(3, 3, 9, 10, expr2); - let branch2 = &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern2]), - value: loc_expr2, - guard: None, - }); - let branches = &[branch1, branch2]; - let var = Var { - module_name: "", - ident: "x", - }; - let loc_cond = Located::new(0, 0, 5, 6, var); - let expected = Expr::When(arena.alloc(loc_cond), branches); - let actual = parse_expr_with( - &arena, - indoc!( - r#" - when x is - 1 -> Num.neg - 2 - _ -> 4 - "# - ), - ); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn when_if_guard() { - let arena = Bump::new(); - - let branch1 = { - let newlines = &[Newline]; - let pattern1 = Pattern::SpaceBefore(arena.alloc(Pattern::Underscore("")), newlines); - let loc_pattern1 = Located::new(1, 1, 4, 5, pattern1); - let num_1 = Num("1"); - let expr1 = Located::new( - 2, - 2, - 8, - 9, - Expr::SpaceBefore(arena.alloc(num_1), &[Newline]), - ); - let loc_expr1 = expr1; // Located::new(1, 2, 9, 6, expr1); - &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern1]), - value: loc_expr1, - guard: None, - }) - }; - - let branch2 = { - let pattern1 = - Pattern::SpaceBefore(arena.alloc(Pattern::Underscore("")), &[Newline, Newline]); - let loc_pattern1 = Located::new(4, 4, 4, 5, pattern1); - let num_1 = Num("2"); - let expr1 = Located::new( - 5, - 5, - 8, - 9, - Expr::SpaceBefore(arena.alloc(num_1), &[Newline]), - ); - let loc_expr1 = expr1; // Located::new(1, 2, 9, 6, expr1); - &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern1]), - value: loc_expr1, - guard: None, - }) - }; - - let branch3 = { - let pattern1 = - Pattern::SpaceBefore(arena.alloc(Pattern::GlobalTag("Ok")), &[Newline, Newline]); - let loc_pattern1 = Located::new(7, 7, 4, 6, pattern1); - let num_1 = Num("3"); - let expr1 = Located::new( - 8, - 8, - 8, - 9, - Expr::SpaceBefore(arena.alloc(num_1), &[Newline]), - ); - let loc_expr1 = expr1; // Located::new(1, 2, 9, 6, expr1); - &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern1]), - value: loc_expr1, - guard: None, - }) - }; - - let branches = &[branch1, branch2, branch3]; - let var = Var { - module_name: "", - ident: "x", - }; - let loc_cond = Located::new(0, 0, 5, 6, var); - let expected = Expr::When(arena.alloc(loc_cond), branches); - let actual = parse_expr_with( - &arena, - indoc!( - r#" - when x is - _ -> - 1 - - _ -> - 2 - - Ok -> - 3 - "# - ), - ); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn when_in_parens() { - let arena = Bump::new(); - - let branch1 = { - let newlines = &[Newline]; - let pattern1 = Pattern::SpaceBefore(arena.alloc(Pattern::GlobalTag("Ok")), newlines); - let loc_pattern1 = Located::new(1, 1, 4, 6, pattern1); - let num_1 = Num("3"); - let expr1 = Located::new( - 2, - 2, - 8, - 9, - Expr::SpaceBefore(arena.alloc(num_1), &[Newline]), - ); - let loc_expr1 = expr1; // Located::new(1, 2, 9, 6, expr1); - &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern1]), - value: loc_expr1, - guard: None, - }) - }; - - let branches = &[branch1]; - let var = Var { - module_name: "", - ident: "x", - }; - let loc_cond = Located::new(0, 0, 6, 7, var); - let when = Expr::When(arena.alloc(loc_cond), branches); - let expected = Expr::ParensAround(&when); - let actual = parse_expr_with( - &arena, - indoc!( - r#" - (when x is - Ok -> - 3) - "# - ), - ); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn when_with_records() { - let arena = Bump::new(); - let newlines = &[Newline]; - let identifiers1 = &[Located::new(1, 1, 3, 4, Identifier("y"))]; - let pattern1 = Pattern::SpaceBefore(arena.alloc(RecordDestructure(identifiers1)), newlines); - let loc_pattern1 = Located::new(1, 1, 1, 6, pattern1); - let expr1 = Num("2"); - let loc_expr1 = Located::new(1, 1, 10, 11, expr1); - let branch1 = &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern1]), - value: loc_expr1, - guard: None, - }); - let newlines = &[Newline]; - let identifiers2 = &[ - Located::new(2, 2, 3, 4, Identifier("z")), - Located::new(2, 2, 6, 7, Identifier("w")), - ]; - let pattern2 = Pattern::SpaceBefore(arena.alloc(RecordDestructure(identifiers2)), newlines); - let loc_pattern2 = Located::new(2, 2, 1, 9, pattern2); - let expr2 = Num("4"); - let loc_expr2 = Located::new(2, 2, 13, 14, expr2); - let branch2 = &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern2]), - value: loc_expr2, - guard: None, - }); - let branches = &[branch1, branch2]; - let var = Var { - module_name: "", - ident: "x", - }; - let loc_cond = Located::new(0, 0, 5, 6, var); - let expected = Expr::When(arena.alloc(loc_cond), branches); - let actual = parse_expr_with( - &arena, - indoc!( - r#" - when x is - { y } -> 2 - { z, w } -> 4 - "# - ), - ); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn when_in_parens_indented() { - let arena = Bump::new(); - - let branch1 = { - let newlines = &[Newline]; - let pattern1 = Pattern::SpaceBefore(arena.alloc(Pattern::GlobalTag("Ok")), newlines); - let loc_pattern1 = Located::new(1, 1, 4, 6, pattern1); - let num_1 = Num("3"); - let expr1 = Located::new(1, 1, 10, 11, num_1); - let loc_expr1 = expr1; // Located::new(1, 2, 9, 6, expr1); - &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern1]), - value: loc_expr1, - guard: None, - }) - }; - - let branches = &[branch1]; - let var = Var { - module_name: "", - ident: "x", - }; - let loc_cond = Located::new(0, 0, 6, 7, var); - let when = Expr::When(arena.alloc(loc_cond), branches); - let spaced = Expr::SpaceAfter(&when, &[Newline]); - let expected = Expr::ParensAround(&spaced); - let actual = parse_expr_with( - &arena, - indoc!( - r#" - (when x is - Ok -> 3 - ) - "# - ), - ); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn when_with_alternative_patterns() { - let arena = Bump::new(); - let newlines = &[Newline]; - let pattern1 = Pattern::SpaceBefore(arena.alloc(StrLiteral(PlainLine("blah"))), newlines); - let pattern1_alt = StrLiteral(PlainLine("blop")); - let loc_pattern1 = Located::new(1, 1, 1, 7, pattern1); - let loc_pattern1_alt = Located::new(1, 1, 10, 16, pattern1_alt); - let expr1 = Num("1"); - let loc_expr1 = Located::new(1, 1, 20, 21, expr1); - let branch1 = &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern1, loc_pattern1_alt]), - value: loc_expr1, - guard: None, - }); - let newlines = &[Newline]; - let pattern2 = Pattern::SpaceBefore(arena.alloc(StrLiteral(PlainLine("foo"))), newlines); - let newlines = &[Newline]; - let pattern2_alt = - Pattern::SpaceBefore(arena.alloc(StrLiteral(PlainLine("bar"))), newlines); - let loc_pattern2 = Located::new(2, 2, 1, 6, pattern2); - let loc_pattern2_alt = Located::new(3, 3, 2, 7, pattern2_alt); - let expr2 = Num("2"); - let loc_expr2 = Located::new(3, 3, 11, 12, expr2); - let branch2 = &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern2, loc_pattern2_alt]), - value: loc_expr2, - guard: None, - }); - let branches = &[branch1, branch2]; - let var = Var { - module_name: "", - ident: "x", - }; - let loc_cond = Located::new(0, 0, 5, 6, var); - let expected = Expr::When(arena.alloc(loc_cond), branches); - let actual = parse_expr_with( - &arena, - indoc!( - r#" - when x is - "blah" | "blop" -> 1 - "foo" | - "bar" -> 2 - "# - ), - ); - - assert_eq!(Ok(expected), actual); - } - - // MODULE - - #[test] - fn empty_app_header() { - let arena = Bump::new(); - let packages = Collection::empty(); - let imports = Vec::new_in(&arena); - let provides = Vec::new_in(&arena); - let module_name = StrLiteral::PlainLine("test-app"); - let header = AppHeader { - name: Located::new(0, 0, 4, 14, module_name), - packages, - imports, - provides, - to: Located::new(0, 0, 53, 57, To::ExistingPackage("blah")), - before_header: &[], - after_app_keyword: &[], - before_packages: &[], - after_packages: &[], - before_imports: &[], - after_imports: &[], - before_provides: &[], - after_provides: &[], - before_to: &[], - after_to: &[], - }; - - let expected = roc_parse::ast::Module::App { header }; - - let src = indoc!( - r#" - app "test-app" packages {} imports [] provides [] to blah - "# - ); - let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes())) - .map(|tuple| tuple.0); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn minimal_app_header() { - use PackageOrPath::Path; - - let arena = Bump::new(); - let packages = Collection::empty(); - let imports = Vec::new_in(&arena); - let provides = Vec::new_in(&arena); - let module_name = StrLiteral::PlainLine("test-app"); - let header = AppHeader { - before_header: &[], - name: Located::new(0, 0, 4, 14, module_name), - packages, - imports, - provides, - to: Located::new(0, 0, 30, 38, To::NewPackage(Path(PlainLine("./blah")))), - after_app_keyword: &[], - before_packages: &[], - after_packages: &[], - before_imports: &[], - after_imports: &[], - before_provides: &[], - after_provides: &[], - before_to: &[], - after_to: &[], - }; - - let expected = roc_parse::ast::Module::App { header }; - - let src = indoc!( - r#" - app "test-app" provides [] to "./blah" - "# - ); - - let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes())) - .map(|tuple| tuple.0); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn full_app_header() { - use ExposesEntry::Exposed; - use PackageOrPath::Path; - - let newlines = &[Newline]; - let pkg_entry = PackageEntry::Entry { - shorthand: "base", - spaces_after_shorthand: &[], - package_or_path: Located::new(1, 1, 21, 33, Path(PlainLine("./platform"))), - }; - let loc_pkg_entry = Located::new(1, 1, 15, 33, pkg_entry); - let arena = Bump::new(); - let packages = Collection::with_items(arena.alloc([loc_pkg_entry])); - let import = ImportsEntry::Package("foo", ModuleName::new("Bar.Baz"), Vec::new_in(&arena)); - let loc_import = Located::new(2, 2, 14, 25, import); - let imports = bumpalo::vec![in &arena; loc_import]; - let provide_entry = Located::new(3, 3, 15, 24, Exposed("quicksort")); - let provides = bumpalo::vec![in &arena; provide_entry]; - let module_name = StrLiteral::PlainLine("quicksort"); - - let header = AppHeader { - before_header: &[], - name: Located::new(0, 0, 4, 15, module_name), - packages, - imports, - provides, - to: Located::new(3, 3, 30, 34, To::ExistingPackage("base")), - after_app_keyword: &[], - before_packages: newlines, - after_packages: &[], - before_imports: newlines, - after_imports: &[], - before_provides: newlines, - after_provides: &[], - before_to: &[], - after_to: &[], - }; - - let expected = roc_parse::ast::Module::App { header }; - - let src = indoc!( - r#" - app "quicksort" - packages { base: "./platform" } - imports [ foo.Bar.Baz ] - provides [ quicksort ] to base - "# - ); - - let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes())) - .map(|tuple| tuple.0); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn full_app_header_trailing_commas() { - use ExposesEntry::Exposed; - use PackageOrPath::Path; - - let newlines = &[Newline]; - let pkg_entry = PackageEntry::Entry { - shorthand: "base", - spaces_after_shorthand: &[], - package_or_path: Located::new(1, 1, 21, 33, Path(PlainLine("./platform"))), - }; - let loc_pkg_entry = Located::new(1, 1, 15, 33, pkg_entry); - let arena = Bump::new(); - let packages = Collection::with_items(arena.alloc([loc_pkg_entry])); - let import = ImportsEntry::Package("foo", ModuleName::new("Bar.Baz"), Vec::new_in(&arena)); - let loc_import = Located::new(2, 2, 14, 25, import); - let imports = bumpalo::vec![in &arena; loc_import]; - let provide_entry = Located::new(3, 3, 15, 24, Exposed("quicksort")); - let provides = bumpalo::vec![in &arena; provide_entry]; - let module_name = StrLiteral::PlainLine("quicksort"); - - let header = AppHeader { - before_header: &[], - name: Located::new(0, 0, 4, 15, module_name), - packages, - imports, - provides, - to: Located::new(3, 3, 30, 34, To::ExistingPackage("base")), - after_app_keyword: &[], - before_packages: newlines, - after_packages: &[], - before_imports: newlines, - after_imports: &[], - before_provides: newlines, - after_provides: &[], - before_to: &[], - after_to: &[], - }; - - let expected = roc_parse::ast::Module::App { header }; - - let src = indoc!( - r#" - app "quicksort" - packages { base: "./platform", } - imports [ foo.Bar.Baz ] - provides [ quicksort ] to base - "# - ); - - let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes())) - .map(|tuple| tuple.0); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn empty_platform_header() { - let pkg_name = PackageName { - account: "rtfeldman", - pkg: "blah", - }; - let arena = Bump::new(); - let effects = Effects { - effect_type_name: "Blah", - effect_shortname: "fx", - entries: &[], - spaces_before_effects_keyword: &[], - spaces_after_effects_keyword: &[], - spaces_after_type_name: &[], - }; - - let requires = { - let region1 = Region::new(0, 0, 38, 47); - let region2 = Region::new(0, 0, 45, 47); - - PlatformRequires { - rigids: Vec::new_in(&arena), - signature: Located::at( - region1, - TypedIdent::Entry { - ident: Located::new(0, 0, 38, 42, "main"), - spaces_before_colon: &[], - ann: Located::at( - region2, - TypeAnnotation::Record { - fields: Collection::empty(), - ext: None, - }, - ), - }, - ), - } - }; - - let header = PlatformHeader { - before_header: &[], - name: Located::new(0, 0, 9, 23, pkg_name), - requires, - exposes: Vec::new_in(&arena), - packages: Collection::empty(), - imports: Vec::new_in(&arena), - provides: Vec::new_in(&arena), - effects, - after_platform_keyword: &[], - before_requires: &[], - after_requires: &[], - before_exposes: &[], - after_exposes: &[], - before_packages: &[], - after_packages: &[], - before_imports: &[], - after_imports: &[], - before_provides: &[], - after_provides: &[], - }; - - let expected = roc_parse::ast::Module::Platform { header }; - - let src = "platform rtfeldman/blah requires {} { main : {} } exposes [] packages {} imports [] provides [] effects fx.Blah {}"; - let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes())) - .map(|tuple| tuple.0); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn nonempty_platform_header() { - use ExposesEntry::Exposed; - use PackageOrPath::Path; - - let newlines = &[Newline]; - let pkg_name = PackageName { - account: "foo", - pkg: "barbaz", - }; - let pkg_entry = PackageEntry::Entry { - shorthand: "foo", - spaces_after_shorthand: &[], - package_or_path: Located::new(3, 3, 20, 27, Path(PlainLine("./foo"))), - }; - let loc_pkg_entry = Located::new(3, 3, 15, 27, pkg_entry); - let arena = Bump::new(); - let packages = Collection::with_items(arena.alloc([loc_pkg_entry])); - let imports = Vec::new_in(&arena); - let provide_entry = Located::new(5, 5, 15, 26, Exposed("mainForHost")); - let provides = bumpalo::vec![in &arena; provide_entry]; - let effects = Effects { - effect_type_name: "Effect", - effect_shortname: "fx", - entries: &[], - spaces_before_effects_keyword: newlines, - spaces_after_effects_keyword: &[], - spaces_after_type_name: &[], - }; - - let requires = { - let region1 = Region::new(1, 1, 30, 39); - let region2 = Region::new(1, 1, 37, 39); - let region3 = Region::new(1, 1, 14, 26); - - PlatformRequires { - rigids: bumpalo::vec![ in &arena; Located::at(region3, PlatformRigid::Entry { alias: "Model", rigid: "model" }) ], - signature: Located::at( - region1, - TypedIdent::Entry { - ident: Located::new(1, 1, 30, 34, "main"), - spaces_before_colon: &[], - ann: Located::at( - region2, - TypeAnnotation::Record { - fields: Collection::empty(), - ext: None, - }, - ), - }, - ), - } - }; - - let header = PlatformHeader { - before_header: &[], - name: Located::new(0, 0, 9, 19, pkg_name), - requires, - exposes: Vec::new_in(&arena), - packages, - imports, - provides, - effects, - after_platform_keyword: &[], - before_requires: newlines, - after_requires: &[], - before_exposes: newlines, - after_exposes: &[], - before_packages: newlines, - after_packages: &[], - before_imports: newlines, - after_imports: &[], - before_provides: newlines, - after_provides: &[], - }; - - let expected = roc_parse::ast::Module::Platform { header }; - - let src = indoc!( - r#" - platform foo/barbaz - requires {model=>Model} { main : {} } - exposes [] - packages { foo: "./foo" } - imports [] - provides [ mainForHost ] - effects fx.Effect {} - "# - ); - let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes())) - .map(|tuple| tuple.0); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn empty_interface_header() { - let arena = Bump::new(); - let exposes = Vec::new_in(&arena); - let imports = Vec::new_in(&arena); - let module_name = ModuleName::new("Foo"); - let header = InterfaceHeader { - before_header: &[], - name: Located::new(0, 0, 10, 13, module_name), - exposes, - imports, - - after_interface_keyword: &[], - before_exposes: &[], - after_exposes: &[], - before_imports: &[], - after_imports: &[], - }; - - let expected = roc_parse::ast::Module::Interface { header }; - - let src = indoc!( - r#" - interface Foo exposes [] imports [] - "# - ); - let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes())) - .map(|tuple| tuple.0); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn nested_module() { - let arena = Bump::new(); - let exposes = Vec::new_in(&arena); - let imports = Vec::new_in(&arena); - let module_name = ModuleName::new("Foo.Bar.Baz"); - let header = InterfaceHeader { - before_header: &[], - name: Located::new(0, 0, 10, 21, module_name), - exposes, - imports, - - after_interface_keyword: &[], - before_exposes: &[], - after_exposes: &[], - before_imports: &[], - after_imports: &[], - }; - - let expected = roc_parse::ast::Module::Interface { header }; - - let src = indoc!( - r#" - interface Foo.Bar.Baz exposes [] imports [] - "# - ); - let actual = roc_parse::module::parse_header(&arena, State::new(src.as_bytes())) - .map(|tuple| tuple.0); - - assert_eq!(Ok(expected), actual); - } - #[test] fn repro_keyword_bug() { // Reproducing this bug requires a bizarre set of things to all be true: @@ -3539,106 +838,6 @@ mod test_parse { assert_eq!(occurrences, 2); } - #[test] - fn standalone_module_defs() { - use Def::*; - - let arena = Bump::new(); - let pattern1 = Identifier("foo"); - let pattern2 = Identifier("bar"); - let pattern3 = Identifier("baz"); - let def1 = SpaceBefore( - arena.alloc(Body( - arena.alloc(Located::new(1, 1, 0, 3, pattern1)), - arena.alloc(Located::new(1, 1, 6, 7, Num("1"))), - )), - &[LineComment(" comment 1")], - ); - let def2 = SpaceBefore( - arena.alloc(Body( - arena.alloc(Located::new(4, 4, 0, 3, pattern2)), - arena.alloc(Located::new(4, 4, 6, 10, Str(PlainLine("hi")))), - )), - &[Newline, Newline, LineComment(" comment 2")], - ); - let def3 = SpaceAfter( - arena.alloc(SpaceBefore( - arena.alloc(Body( - arena.alloc(Located::new(5, 5, 0, 3, pattern3)), - arena.alloc(Located::new(5, 5, 6, 13, Str(PlainLine("stuff")))), - )), - &[Newline], - )), - &[Newline, LineComment(" comment n")], - ); - - let expected = bumpalo::vec![in &arena; - Located::new(1,1, 0, 7, def1), - Located::new(4,4, 0, 10, def2), - Located::new(5,5, 0, 13, def3), - ]; - - let src = indoc!( - r#" - # comment 1 - foo = 1 - - # comment 2 - bar = "hi" - baz = "stuff" - # comment n - "# - ); - - let actual = module_defs() - .parse(&arena, State::new(src.as_bytes())) - .map(|tuple| tuple.1); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn module_def_newline() { - let arena = Bump::new(); - - let src = indoc!( - r#" - main = - i = 64 - - i - "# - ); - - let actual = module_defs() - .parse(&arena, State::new(src.as_bytes())) - .map(|tuple| tuple.0); - - assert!(actual.is_ok()); - } - - #[test] - fn nested_def_annotation() { - let arena = Bump::new(); - - let src = indoc!( - r#" - main = - wrappedNotEq : a, a -> Bool - wrappedNotEq = \num1, num2 -> - num1 != num2 - - wrappedNotEq 2 3 - "# - ); - - let actual = module_defs() - .parse(&arena, State::new(src.as_bytes())) - .map(|tuple| tuple.0); - - assert!(actual.is_ok()); - } - #[test] fn outdenting_newline_after_else() { let arena = &Bump::new(); @@ -3667,265 +866,6 @@ mod test_parse { } } - #[test] - fn newline_after_equals() { - // Regression test for https://github.com/rtfeldman/roc/issues/51 - let arena = Bump::new(); - let newlines = &[Newline, Newline]; - let num = arena.alloc(Num("5")); - let def = Def::Body( - arena.alloc(Located::new(0, 0, 0, 1, Identifier("x"))), - arena.alloc(Located::new(1, 1, 4, 5, Expr::SpaceBefore(num, &[Newline]))), - ); - let loc_def = &*arena.alloc(Located::new(0, 1, 0, 5, def)); - let defs = &[loc_def]; - let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines); - let loc_ret = Located::new(3, 3, 0, 2, ret); - let expected = Defs(defs, arena.alloc(loc_ret)); - - assert_parses_to( - indoc!( - r#" - x = - 5 - - 42 - "# - ), - expected, - ); - } - - // DOCS - - #[test] - fn basic_docs() { - let arena = Bump::new(); - let newlines = &[Newline, Newline]; - let def = Def::Body( - arena.alloc(Located::new(6, 6, 0, 1, Identifier("x"))), - arena.alloc(Located::new(6, 6, 4, 5, Num("5"))), - ); - let loc_def = &*arena.alloc(Located::new(6, 6, 0, 5, def)); - let defs = &[loc_def]; - let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines); - let loc_ret = Located::new(8, 8, 0, 2, ret); - let reset_indentation = &[ - DocComment("first line of docs"), - DocComment(" second line"), - DocComment(" third line"), - DocComment("fourth line"), - DocComment(""), - DocComment("sixth line after doc new line"), - ]; - let expected = Expr::SpaceBefore( - arena.alloc(Defs(defs, arena.alloc(loc_ret))), - reset_indentation, - ); - - assert_parses_to( - indoc!( - r#" - ## first line of docs - ## second line - ## third line - ## fourth line - ## - ## sixth line after doc new line - x = 5 - - 42 - "# - ), - expected, - ); - } - - #[test] - fn not_docs() { - let arena = Bump::new(); - let newlines = &[Newline, Newline]; - let def = Def::Body( - arena.alloc(Located::new(4, 4, 0, 1, Identifier("x"))), - arena.alloc(Located::new(4, 4, 4, 5, Num("5"))), - ); - let loc_def = &*arena.alloc(Located::new(4, 4, 0, 5, def)); - let defs = &[loc_def]; - let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines); - let loc_ret = Located::new(6, 6, 0, 2, ret); - let reset_indentation = &[ - LineComment("######"), - LineComment("## not docs!"), - LineComment("#still not docs"), - LineComment("#####"), - ]; - let expected = Expr::SpaceBefore( - arena.alloc(Defs(defs, arena.alloc(loc_ret))), - reset_indentation, - ); - - assert_parses_to( - indoc!( - r#" - ####### - ### not docs! - ##still not docs - ###### - x = 5 - - 42 - "# - ), - expected, - ); - } - - #[test] - fn mixed_docs() { - let arena = Bump::new(); - let newlines = &[Newline, Newline]; - let def = Def::Body( - arena.alloc(Located::new(4, 4, 0, 1, Identifier("x"))), - arena.alloc(Located::new(4, 4, 4, 5, Num("5"))), - ); - let loc_def = &*arena.alloc(Located::new(4, 4, 0, 5, def)); - let defs = &[loc_def]; - let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines); - let loc_ret = Located::new(6, 6, 0, 2, ret); - let reset_indentation = &[ - LineComment("## not docs!"), - DocComment("docs, but with a problem"), - DocComment("(namely that this is a mix of docs and regular comments)"), - LineComment(" not docs"), - ]; - let expected = Expr::SpaceBefore( - arena.alloc(Defs(defs, arena.alloc(loc_ret))), - reset_indentation, - ); - - assert_parses_to( - indoc!( - r#" - ### not docs! - ## docs, but with a problem - ## (namely that this is a mix of docs and regular comments) - # not docs - x = 5 - - 42 - "# - ), - expected, - ); - } - - #[test] - fn malformed_pattern_field_access() { - // See https://github.com/rtfeldman/roc/issues/399 - let arena = Bump::new(); - let newlines = &[Newline]; - let pattern1 = Pattern::SpaceBefore(arena.alloc(Pattern::Malformed("bar.and")), newlines); - let loc_pattern1 = Located::new(1, 1, 4, 11, pattern1); - let expr1 = Num("1"); - let loc_expr1 = Located::new(1, 1, 15, 16, expr1); - let branch1 = &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern1]), - value: loc_expr1, - guard: None, - }); - let newlines = &[Newline]; - let pattern2 = Pattern::SpaceBefore(arena.alloc(Pattern::Underscore("")), newlines); - let loc_pattern2 = Located::new(2, 2, 4, 5, pattern2); - let expr2 = Num("4"); - let loc_expr2 = Located::new(2, 2, 9, 10, expr2); - let branch2 = &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern2]), - value: loc_expr2, - guard: None, - }); - let branches = &[branch1, branch2]; - let var = Var { - module_name: "", - ident: "x", - }; - let loc_cond = Located::new(0, 0, 5, 6, var); - let expected = Expr::When(arena.alloc(loc_cond), branches); - let actual = parse_expr_with( - &arena, - indoc!( - r#" - when x is - bar.and -> 1 - _ -> 4 - "# - ), - ); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn malformed_pattern_module_name() { - // See https://github.com/rtfeldman/roc/issues/399 - let arena = Bump::new(); - let newlines = &[Newline]; - let pattern1 = Pattern::SpaceBefore(arena.alloc(Pattern::Malformed("Foo.and")), newlines); - let loc_pattern1 = Located::new(1, 1, 4, 11, pattern1); - let expr1 = Num("1"); - let loc_expr1 = Located::new(1, 1, 15, 16, expr1); - let branch1 = &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern1]), - value: loc_expr1, - guard: None, - }); - let newlines = &[Newline]; - let pattern2 = Pattern::SpaceBefore(arena.alloc(Pattern::Underscore("")), newlines); - let loc_pattern2 = Located::new(2, 2, 4, 5, pattern2); - let expr2 = Num("4"); - let loc_expr2 = Located::new(2, 2, 9, 10, expr2); - let branch2 = &*arena.alloc(WhenBranch { - patterns: arena.alloc([loc_pattern2]), - value: loc_expr2, - guard: None, - }); - let branches = &[branch1, branch2]; - let var = Var { - module_name: "", - ident: "x", - }; - let loc_cond = Located::new(0, 0, 5, 6, var); - let expected = Expr::When(arena.alloc(loc_cond), branches); - let actual = parse_expr_with( - &arena, - indoc!( - r#" - when x is - Foo.and -> 1 - _ -> 4 - "# - ), - ); - - assert_eq!(Ok(expected), actual); - } - - #[test] - fn pattern_with_space_in_parens() { - // https://github.com/rtfeldman/roc/issues/929 - let arena = Bump::new(); - let actual = parse_expr_with( - &arena, - indoc!( - r#" - when Delmin (Del rx) 0 is - Delmin (Del ry ) _ -> Node Black 0 False ry - "# - ), - ); - - assert!(actual.is_ok()); - } - #[test] fn parse_expr_size() { assert_eq!(std::mem::size_of::(), 40); diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index c5bfa42f53..f70e110c54 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -1,6 +1,6 @@ use roc_collections::all::MutSet; +use roc_module::called_via::BinOp; use roc_module::ident::{Ident, Lowercase, ModuleName, TagName}; -use roc_module::operator::BinOp; use roc_module::symbol::{ModuleId, Symbol}; use roc_parse::ast::Base; use roc_parse::pattern::PatternType; @@ -139,6 +139,7 @@ pub enum RuntimeError { module_name: ModuleName, ident: Ident, region: Region, + exposed_values: Vec, }, ModuleNotImported { module_name: ModuleName, diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index 4c7d605b58..90c05be8b1 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -229,6 +229,8 @@ where // Also in tests, we don't want to bother printing the locations // because it makes failed assertions much harder to read. self.value.fmt(f) + } else if f.alternate() { + write!(f, "{:?} {:#?}", region, self.value) } else { write!(f, "{:?} {:?}", region, self.value) } diff --git a/compiler/reporting/Cargo.toml b/compiler/reporting/Cargo.toml deleted file mode 100644 index 35df3f33ae..0000000000 --- a/compiler/reporting/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "roc_reporting" -version = "0.1.0" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2018" - -[dependencies] -roc_collections = { path = "../collections" } -roc_region = { path = "../region" } -roc_module = { path = "../module" } -roc_parse = { path = "../parse" } -roc_problem = { path = "../problem" } -roc_types = { path = "../types" } -roc_can = { path = "../can" } -roc_solve = { path = "../solve" } -roc_mono = { path = "../mono" } -ven_pretty = { path = "../../vendor/pretty" } -distance = "0.4.0" -bumpalo = { version = "3.8.0", features = ["collections"] } - -[dev-dependencies] -roc_constrain = { path = "../constrain" } -roc_builtins = { path = "../builtins" } -roc_problem = { path = "../problem" } -roc_parse = { path = "../parse" } -pretty_assertions = "1.0.0" -indoc = "1.0.3" diff --git a/compiler/reporting/src/lib.rs b/compiler/reporting/src/lib.rs deleted file mode 100644 index ff2791a5f8..0000000000 --- a/compiler/reporting/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -#![warn(clippy::dbg_macro)] -// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. -#![allow(clippy::large_enum_variant)] - -pub mod error; -pub mod report; diff --git a/compiler/solve/Cargo.toml b/compiler/solve/Cargo.toml index 4b24c7b470..de9d320d28 100644 --- a/compiler/solve/Cargo.toml +++ b/compiler/solve/Cargo.toml @@ -12,6 +12,7 @@ roc_module = { path = "../module" } roc_types = { path = "../types" } roc_can = { path = "../can" } roc_unify = { path = "../unify" } +bumpalo = { version = "3.8.0", features = ["collections"] } [dev-dependencies] roc_load = { path = "../load" } diff --git a/compiler/solve/src/module.rs b/compiler/solve/src/module.rs index ebf781e9d4..920cbbdd58 100644 --- a/compiler/solve/src/module.rs +++ b/compiler/solve/src/module.rs @@ -27,7 +27,7 @@ pub fn run_solve( 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); diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index efa09d4dde..854b853bae 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -619,10 +619,13 @@ fn type_to_var( subs: &mut Subs, rank: Rank, pools: &mut Pools, - cached: &mut MutMap, + _: &mut MutMap, typ: &Type, ) -> Variable { - type_to_variable(subs, rank, pools, cached, typ) + // capacity based on the false hello world program + let arena = bumpalo::Bump::with_capacity(4 * 1024); + + type_to_variable(subs, rank, pools, &arena, typ) } /// Abusing existing functions for our purposes @@ -630,25 +633,29 @@ fn type_to_var( pub fn insert_type_into_subs(subs: &mut Subs, typ: &Type) -> Variable { let rank = Rank::NONE; let mut pools = Pools::default(); - let mut cached = MutMap::default(); - type_to_variable(subs, rank, &mut pools, &mut cached, typ) + // capacity based on the false hello world program + let arena = bumpalo::Bump::with_capacity(4 * 1024); + + type_to_variable(subs, rank, &mut pools, &arena, typ) } -fn type_to_variable( +fn type_to_variable<'a>( subs: &mut Subs, rank: Rank, pools: &mut Pools, - cached: &mut MutMap, + arena: &'a bumpalo::Bump, typ: &Type, ) -> Variable { + use bumpalo::collections::Vec; + match typ { Variable(var) => *var, Apply(symbol, args) => { - let mut new_arg_vars = Vec::with_capacity(args.len()); + let mut new_arg_vars = Vec::with_capacity_in(args.len(), arena); for arg in args { - let var = type_to_variable(subs, rank, pools, cached, arg); + let var = type_to_variable(subs, rank, pools, arena, arg); new_arg_vars.push(var); } @@ -663,32 +670,32 @@ fn type_to_variable( // This case is important for the rank of boolean variables Function(arg_vars, closure_type, ret_type) => { - let mut new_arg_vars = Vec::with_capacity(arg_vars.len()); + let mut new_arg_vars = Vec::with_capacity_in(arg_vars.len(), arena); for arg in arg_vars { - let var = type_to_variable(subs, rank, pools, cached, arg); + let var = type_to_variable(subs, rank, pools, arena, arg); new_arg_vars.push(var); } let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); - let ret_var = type_to_variable(subs, rank, pools, cached, ret_type); - let closure_var = type_to_variable(subs, rank, pools, cached, closure_type); + let ret_var = type_to_variable(subs, rank, pools, arena, ret_type); + let closure_var = type_to_variable(subs, rank, pools, arena, closure_type); let content = Content::Structure(FlatType::Func(arg_vars, closure_var, ret_var)); register(subs, rank, pools, content) } Record(fields, ext) => { - let mut field_vars = Vec::with_capacity(fields.len()); + let mut field_vars = Vec::with_capacity_in(fields.len(), arena); for (field, field_type) in fields { let field_var = - field_type.map(|typ| type_to_variable(subs, rank, pools, cached, typ)); + field_type.map(|typ| type_to_variable(subs, rank, pools, arena, typ)); field_vars.push((field.clone(), field_var)); } - let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); + let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext); let (it, new_ext_var) = gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var); @@ -707,14 +714,14 @@ fn type_to_variable( register(subs, rank, pools, content) } TagUnion(tags, ext) => { - let (union_tags, ext) = type_to_union_tags(subs, rank, pools, cached, tags, ext); + let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); register(subs, rank, pools, content) } FunctionOrTagUnion(tag_name, symbol, ext) => { - let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); - let mut ext_tag_vec = Vec::new(); + let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext); + let mut ext_tag_vec = std::vec::Vec::new(); let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union( subs, temp_ext_var, @@ -735,7 +742,7 @@ fn type_to_variable( register(subs, rank, pools, content) } RecursiveTagUnion(rec_var, tags, ext) => { - let (union_tags, ext) = type_to_union_tags(subs, rank, pools, cached, tags, ext); + let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); let content = Content::Structure(FlatType::RecursiveTagUnion(*rec_var, union_tags, ext)); @@ -780,22 +787,22 @@ fn type_to_variable( } } - let mut arg_vars = Vec::with_capacity(args.len()); + let mut arg_vars = Vec::with_capacity_in(args.len(), arena); for (arg, arg_type) in args { - let arg_var = type_to_variable(subs, rank, pools, cached, arg_type); + let arg_var = type_to_variable(subs, rank, pools, arena, arg_type); arg_vars.push((arg.clone(), arg_var)); } - let lambda_set_variables: Vec<_> = lambda_set_variables + let lambda_set_variables_it = lambda_set_variables .iter() - .map(|ls| type_to_variable(subs, rank, pools, cached, &ls.0)) - .collect(); + .map(|ls| type_to_variable(subs, rank, pools, arena, &ls.0)); + let lambda_set_variables = Vec::from_iter_in(lambda_set_variables_it, arena); let arg_vars = AliasVariables::insert_into_subs(subs, arg_vars, lambda_set_variables); - let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); + let alias_var = type_to_variable(subs, rank, pools, arena, alias_type); let content = Content::Alias(*symbol, arg_vars, alias_var); register(subs, rank, pools, content) @@ -808,22 +815,22 @@ fn type_to_variable( lambda_set_variables, .. } => { - let mut arg_vars = Vec::with_capacity(args.len()); + let mut arg_vars = Vec::with_capacity_in(args.len(), arena); for (arg, arg_type) in args { - let arg_var = type_to_variable(subs, rank, pools, cached, arg_type); + let arg_var = type_to_variable(subs, rank, pools, arena, arg_type); arg_vars.push((arg.clone(), arg_var)); } - let lambda_set_variables: Vec<_> = lambda_set_variables + let lambda_set_variables_it = lambda_set_variables .iter() - .map(|ls| type_to_variable(subs, rank, pools, cached, &ls.0)) - .collect(); + .map(|ls| type_to_variable(subs, rank, pools, arena, &ls.0)); + let lambda_set_variables = Vec::from_iter_in(lambda_set_variables_it, arena); let arg_vars = AliasVariables::insert_into_subs(subs, arg_vars, lambda_set_variables); - let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); + let alias_var = type_to_variable(subs, rank, pools, arena, alias_type); // unify the actual_var with the result var // this can be used to access the type of the actual_var @@ -853,20 +860,22 @@ fn type_to_variable( } } -fn type_to_union_tags( +fn type_to_union_tags<'a>( subs: &mut Subs, rank: Rank, pools: &mut Pools, - cached: &mut MutMap, + arena: &'a bumpalo::Bump, tags: &[(TagName, Vec)], ext: &Type, ) -> (UnionTags, Variable) { - let mut tag_vars = Vec::with_capacity(tags.len()); + use bumpalo::collections::Vec; - let mut tag_argument_vars = Vec::new(); + let mut tag_vars = Vec::with_capacity_in(tags.len(), arena); + + let mut tag_argument_vars = Vec::with_capacity_in(tags.len(), arena); for (tag, tag_argument_types) in tags { for arg_type in tag_argument_types { - let new_var = type_to_variable(subs, rank, pools, cached, arg_type); + let new_var = type_to_variable(subs, rank, pools, arena, arg_type); tag_argument_vars.push(new_var); } @@ -875,7 +884,7 @@ fn type_to_union_tags( tag_vars.push((tag.clone(), new_slice)); } - let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); + let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext); let ext = { let (it, ext) = @@ -1031,8 +1040,7 @@ fn pool_to_rank_table( // Sort the variables into buckets by rank. for &var in young_vars.iter() { - let rank = subs.get_rank(var); - subs.set_mark(var, young_mark); + let rank = subs.get_rank_set_mark(var, young_mark); debug_assert!(rank.into_usize() < young_rank.into_usize() + 1); pools.get_mut(rank).push(var); @@ -1245,140 +1253,136 @@ fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable]) /// this is used during the monomorphization process pub fn instantiate_rigids(subs: &mut Subs, var: Variable) { let rank = Rank::NONE; - let mut pools = Pools::default(); - instantiate_rigids_help(subs, rank, &mut pools, var); + instantiate_rigids_help(subs, rank, var); + + // NOTE subs.restore(var) is done at the end of instantiate_rigids_help } -fn instantiate_rigids_help( - subs: &mut Subs, - max_rank: Rank, - pools: &mut Pools, - var: Variable, -) -> Variable { - use roc_types::subs::Content::*; - use roc_types::subs::FlatType::*; +fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) { + let mut visited = vec![]; + let mut stack = vec![initial]; - let desc = subs.get_without_compacting(var); - - if let Some(copy) = desc.copy.into_variable() { - return copy; + macro_rules! var_slice { + ($variable_subs_slice:expr) => {{ + let slice = $variable_subs_slice; + &subs.variables[slice.slice.start as usize..][..slice.slice.length as usize] + }}; } - let make_descriptor = |content| Descriptor { - content, - rank: max_rank, - mark: Mark::NONE, - copy: OptVariable::NONE, - }; + while let Some(var) = stack.pop() { + visited.push(var); - let content = desc.content; - let copy = var; + let desc = subs.get_ref_mut(var); + if desc.copy.is_some() { + continue; + } - pools.get_mut(max_rank).push(copy); + desc.rank = Rank::NONE; + desc.mark = Mark::NONE; + desc.copy = OptVariable::from(var); - // Link the original variable to the new variable. This lets us - // avoid making multiple copies of the variable we are instantiating. - // - // Need to do this before recursively copying to avoid looping. - subs.set( - var, - Descriptor { - content: content.clone(), - rank: desc.rank, - mark: Mark::NONE, - copy: copy.into(), - }, - ); + use Content::*; + use FlatType::*; - // Now we recursively copy the content of the variable. - // We have already marked the variable as copied, so we - // will not repeat this work or crawl this variable again. - match content { - Structure(flat_type) => { - match flat_type { + match &desc.content { + RigidVar(name) => { + // what it's all about: convert the rigid var into a flex var + let name = name.clone(); + + // NOTE: we must write to the mutually borrowed `desc` value here + // using `subs.set` does not work (unclear why, really) + // but get_ref_mut approach saves a lookup, so the weirdness is worth it + desc.content = FlexVar(Some(name)); + desc.rank = max_rank; + desc.mark = Mark::NONE; + desc.copy = OptVariable::NONE; + } + FlexVar(_) | Error => (), + + RecursionVar { structure, .. } => { + stack.push(*structure); + } + + Structure(flat_type) => match flat_type { Apply(_, args) => { - for var_index in args.into_iter() { - let var = subs[var_index]; - instantiate_rigids_help(subs, max_rank, pools, var); - } + stack.extend(var_slice!(*args)); } Func(arg_vars, closure_var, ret_var) => { - instantiate_rigids_help(subs, max_rank, pools, ret_var); - instantiate_rigids_help(subs, max_rank, pools, closure_var); + let arg_vars = *arg_vars; + let ret_var = *ret_var; + let closure_var = *closure_var; - for index in arg_vars.into_iter() { - let var = subs[index]; - instantiate_rigids_help(subs, max_rank, pools, var); - } + stack.extend(var_slice!(arg_vars)); + + stack.push(ret_var); + stack.push(closure_var); } - EmptyRecord | EmptyTagUnion | Erroneous(_) => {} + EmptyRecord => (), + EmptyTagUnion => (), Record(fields, ext_var) => { - for index in fields.iter_variables() { - let var = subs[index]; - instantiate_rigids_help(subs, max_rank, pools, var); - } + let fields = *fields; + let ext_var = *ext_var; + stack.extend(var_slice!(fields.variables())); - instantiate_rigids_help(subs, max_rank, pools, ext_var); + stack.push(ext_var); } - TagUnion(tags, ext_var) => { - for (_, index) in tags.iter_all() { - let slice = subs[index]; - for var_index in slice { - let var = subs[var_index]; - instantiate_rigids_help(subs, max_rank, pools, var); - } + let tags = *tags; + let ext_var = *ext_var; + + for slice_index in tags.variables() { + let slice = subs.variable_slices[slice_index.start as usize]; + stack.extend(var_slice!(slice)); } - instantiate_rigids_help(subs, max_rank, pools, ext_var); + stack.push(ext_var); } - - FunctionOrTagUnion(_tag_name, _symbol, ext_var) => { - instantiate_rigids_help(subs, max_rank, pools, ext_var); + FunctionOrTagUnion(_, _, ext_var) => { + stack.push(*ext_var); } RecursiveTagUnion(rec_var, tags, ext_var) => { - instantiate_rigids_help(subs, max_rank, pools, rec_var); + let tags = *tags; + let ext_var = *ext_var; + let rec_var = *rec_var; - for (_, index) in tags.iter_all() { - let slice = subs[index]; - for var_index in slice { - let var = subs[var_index]; - instantiate_rigids_help(subs, max_rank, pools, var); - } + for slice_index in tags.variables() { + let slice = subs.variable_slices[slice_index.start as usize]; + stack.extend(var_slice!(slice)); } - instantiate_rigids_help(subs, max_rank, pools, ext_var); + stack.push(ext_var); + stack.push(rec_var); } - }; - } - FlexVar(_) | Error => {} + Erroneous(_) => (), + }, + Alias(_, args, var) => { + let var = *var; + let args = *args; - RecursionVar { structure, .. } => { - instantiate_rigids_help(subs, max_rank, pools, structure); - } + stack.extend(var_slice!(args.variables())); - RigidVar(name) => { - // what it's all about: convert the rigid var into a flex var - subs.set(copy, make_descriptor(FlexVar(Some(name)))); - } - - Alias(_symbol, args, real_type_var) => { - for var_index in args.variables().into_iter() { - let var = subs[var_index]; - instantiate_rigids_help(subs, max_rank, pools, var); + stack.push(var); } - - instantiate_rigids_help(subs, max_rank, pools, real_type_var); } } - var + // we have tracked all visited variables, and can now traverse them + // in one go (without looking at the UnificationTable) and clear the copy field + for var in visited { + let descriptor = subs.get_ref_mut(var); + + if descriptor.copy.is_some() { + descriptor.rank = Rank::NONE; + descriptor.mark = Mark::NONE; + descriptor.copy = OptVariable::NONE; + } + } } fn deep_copy_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, var: Variable) -> Variable { diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index dcf74d79bb..90cf426dc1 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -3793,6 +3793,14 @@ mod solve_expr { ); } + #[test] + fn list_split() { + infer_eq_without_problem( + indoc!("List.split"), + "List a, Nat -> { before : List a, others : List a }", + ); + } + #[test] fn list_drop_last() { infer_eq_without_problem( @@ -3805,6 +3813,17 @@ mod solve_expr { ); } + #[test] + fn list_intersperse() { + infer_eq_without_problem( + indoc!( + r#" + List.intersperse + "# + ), + "List a, a -> List a", + ); + } #[test] fn function_that_captures_nothing_is_not_captured() { // we should make sure that a function that doesn't capture anything it not itself captured diff --git a/compiler/test_gen/Cargo.toml b/compiler/test_gen/Cargo.toml index 9e1a5d5aba..bd6ddfd5d1 100644 --- a/compiler/test_gen/Cargo.toml +++ b/compiler/test_gen/Cargo.toml @@ -23,7 +23,7 @@ roc_constrain = { path = "../constrain" } roc_unify = { path = "../unify" } roc_solve = { path = "../solve" } roc_mono = { path = "../mono" } -roc_reporting = { path = "../reporting" } +roc_reporting = { path = "../../reporting" } roc_load = { path = "../load" } roc_can = { path = "../can" } roc_parse = { path = "../parse" } diff --git a/compiler/test_gen/src/gen_compare.rs b/compiler/test_gen/src/gen_compare.rs index e3f6845727..3dcc557e2d 100644 --- a/compiler/test_gen/src/gen_compare.rs +++ b/compiler/test_gen/src/gen_compare.rs @@ -1,19 +1,16 @@ -#![cfg(not(feature = "gen-wasm"))] - #[cfg(feature = "gen-llvm")] use crate::helpers::llvm::assert_evals_to; #[cfg(feature = "gen-dev")] use crate::helpers::dev::assert_evals_to; -// #[cfg(feature = "gen-wasm")] -// use crate::helpers::wasm::assert_evals_to; +#[cfg(feature = "gen-wasm")] +use crate::helpers::wasm::assert_evals_to; -// use crate::assert_wasm_evals_to as assert_evals_to; use indoc::indoc; #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn eq_i64() { assert_evals_to!( indoc!( @@ -30,7 +27,7 @@ fn eq_i64() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn neq_i64() { assert_evals_to!( indoc!( @@ -47,7 +44,7 @@ fn neq_i64() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn eq_u64() { assert_evals_to!( indoc!( @@ -64,7 +61,7 @@ fn eq_u64() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn neq_u64() { assert_evals_to!( indoc!( @@ -81,7 +78,7 @@ fn neq_u64() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn eq_f64() { assert_evals_to!( indoc!( @@ -98,7 +95,7 @@ fn eq_f64() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn neq_f64() { assert_evals_to!( indoc!( @@ -115,7 +112,7 @@ fn neq_f64() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn eq_bool_tag() { assert_evals_to!( indoc!( @@ -132,7 +129,7 @@ fn eq_bool_tag() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn neq_bool_tag() { assert_evals_to!( indoc!( @@ -163,7 +160,7 @@ fn unit() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn newtype() { assert_evals_to!("Identity 42 == Identity 42", true, bool); assert_evals_to!("Identity 42 != Identity 42", false, bool); diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 5e0f737e72..1091eb01e9 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -248,6 +248,47 @@ fn list_sublist() { ); } +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn list_split() { + assert_evals_to!( + r#" + list = List.split [1, 2, 3] 0 + list.before + "#, + RocList::from_slice(&[]), + RocList + ); + assert_evals_to!( + r#" + list = List.split [1, 2, 3] 0 + list.others + "#, + RocList::from_slice(&[1, 2, 3]), + RocList + ); + + assert_evals_to!( + "List.split [1, 2, 3] 1", + (RocList::from_slice(&[1]), RocList::from_slice(&[2, 3]),), + (RocList, RocList,) + ); + assert_evals_to!( + "List.split [1, 2, 3] 3", + (RocList::from_slice(&[1, 2, 3]), RocList::from_slice(&[]),), + (RocList, RocList,) + ); + assert_evals_to!( + "List.split [1, 2, 3] 4", + (RocList::from_slice(&[1, 2, 3]), RocList::from_slice(&[]),), + (RocList, RocList,) + ); + assert_evals_to!( + "List.split [] 1", + (RocList::from_slice(&[]), RocList::from_slice(&[]),), + (RocList, RocList,) + ); +} #[test] #[cfg(any(feature = "gen-llvm"))] fn list_drop() { @@ -277,6 +318,29 @@ fn list_drop_at() { assert_evals_to!("List.dropAt [0] 0", RocList::from_slice(&[]), RocList); } +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn list_intersperse() { + assert_evals_to!( + indoc!( + r#" + List.intersperse [0, 0, 0] 1 + "# + ), + RocList::from_slice(&[0, 1, 0, 1, 0]), + RocList + ); + assert_evals_to!( + indoc!( + r#" + List.intersperse [] 1 + "# + ), + RocList::from_slice(&[]), + RocList + ); +} + #[test] #[cfg(any(feature = "gen-llvm"))] fn list_drop_at_shared() { @@ -2352,6 +2416,28 @@ fn list_any_empty_with_unknown_element_type() { assert_evals_to!("List.any [] (\\_ -> True)", false, bool); } +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn list_all() { + assert_evals_to!("List.all [] (\\e -> e > 3)", true, bool); + assert_evals_to!("List.all [ 1, 2, 3 ] (\\e -> e > 3)", false, bool); + assert_evals_to!("List.all [ 1, 2, 4 ] (\\e -> e > 3)", false, bool); + assert_evals_to!("List.all [ 1, 2, 3 ] (\\e -> e >= 1)", true, bool); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +#[should_panic(expected = r#"Roc failed with message: "UnresolvedTypeVar"#)] +fn list_all_empty_with_unknown_element_type() { + // Segfaults with invalid memory reference. Running this as a stand-alone + // Roc program, generates the following error message: + // + // Application crashed with message + // UnresolvedTypeVar compiler/mono/src/ir.rs line 3775 + // Shutting down + assert_evals_to!("List.all [] (\\_ -> True)", false, bool); +} + #[test] #[cfg(any(feature = "gen-llvm"))] #[should_panic(expected = r#"Roc failed with message: "invalid ret_layout""#)] diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index d3332ffc0d..0adbf3e867 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -571,7 +571,7 @@ fn abs_min_int_overflow() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn gen_if_fn() { assert_evals_to!( indoc!( @@ -713,7 +713,7 @@ fn gen_int_eq() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn gen_int_neq() { assert_evals_to!( indoc!( @@ -726,6 +726,20 @@ fn gen_int_neq() { ); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn gen_int_less_than() { + assert_evals_to!( + indoc!( + r#" + 4 < 5 + "# + ), + true, + bool + ); +} + #[test] #[cfg(any(feature = "gen-llvm"))] fn gen_dec_eq() { @@ -1214,7 +1228,18 @@ fn tail_call_elimination() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-dev"))] +fn int_negate_dev() { + // TODO + // dev backend yet to have `Num.maxInt` or `Num.minInt`. + // add the "gen-dev" feature to the test below after implementing them both. + assert_evals_to!("Num.neg 123", -123, i64); + assert_evals_to!("Num.neg -123", 123, i64); + assert_evals_to!("Num.neg 0", 0, i64); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn int_negate() { assert_evals_to!("Num.neg 123", -123, i64); assert_evals_to!("Num.neg Num.maxInt", -i64::MAX, i64); diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index 7dbde0b8ea..7928fc109e 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -85,7 +85,7 @@ fn branch_third_float() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn branch_first_int() { assert_evals_to!( indoc!( @@ -101,7 +101,7 @@ fn branch_first_int() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn branch_second_int() { assert_evals_to!( indoc!( @@ -134,7 +134,7 @@ fn branch_third_int() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn branch_store_variable() { assert_evals_to!( indoc!( @@ -221,7 +221,7 @@ fn gen_when_one_branch() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn gen_large_when_int() { assert_evals_to!( indoc!( @@ -243,31 +243,31 @@ fn gen_large_when_int() { ); } -// #[test] -// #[cfg(any(feature = "gen-llvm"))] -// fn gen_large_when_float() { -// assert_evals_to!( -// indoc!( -// r#" -// foo = \num -> -// when num is -// 0.5 -> 200.1 -// -3.6 -> 111.2 # TODO adding more negative numbers reproduces parsing bugs here -// 3.6 -> 789.5 -// 1.7 -> 123.3 -// 2.8 -> 456.4 -// _ -> 1000.6 +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn gen_large_when_float() { + assert_evals_to!( + indoc!( + r#" + foo = \num -> + when num is + 0.5 -> 200.1 + -3.6 -> 111.2 # TODO adding more negative numbers reproduces parsing bugs here + 3.6 -> 789.5 + 1.7 -> 123.3 + 2.8 -> 456.4 + _ -> 1000.6 -// foo -3.6 -// "# -// ), -// 111.2, -// f64 -// ); -// } + foo -3.6 + "# + ), + 111.2, + f64 + ); +} #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn or_pattern() { assert_evals_to!( indoc!( @@ -337,7 +337,7 @@ fn return_unnamed_fn() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn gen_when_fn() { assert_evals_to!( indoc!( @@ -385,7 +385,7 @@ fn gen_basic_def() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn gen_multiple_defs() { assert_evals_to!( indoc!( @@ -504,7 +504,7 @@ fn gen_multiple_defs() { // } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn factorial() { assert_evals_to!( indoc!( diff --git a/compiler/test_gen/src/gen_records.rs b/compiler/test_gen/src/gen_records.rs index 927daf0e18..41babc0e07 100644 --- a/compiler/test_gen/src/gen_records.rs +++ b/compiler/test_gen/src/gen_records.rs @@ -11,7 +11,7 @@ use crate::helpers::wasm::assert_evals_to; use indoc::indoc; #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn basic_record() { assert_evals_to!( indoc!( @@ -45,7 +45,7 @@ fn basic_record() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn f64_record() { assert_evals_to!( indoc!( @@ -137,7 +137,7 @@ fn fn_record() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn def_record() { assert_evals_to!( indoc!( @@ -192,7 +192,7 @@ fn when_on_record() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn when_record_with_guard_pattern() { assert_evals_to!( indoc!( @@ -207,7 +207,7 @@ fn when_record_with_guard_pattern() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn let_with_record_pattern() { assert_evals_to!( indoc!( @@ -223,7 +223,7 @@ fn let_with_record_pattern() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn record_guard_pattern() { assert_evals_to!( indoc!( @@ -239,7 +239,7 @@ fn record_guard_pattern() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn twice_record_access() { assert_evals_to!( indoc!( @@ -254,7 +254,7 @@ fn twice_record_access() { ); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn empty_record() { assert_evals_to!( indoc!( @@ -873,7 +873,7 @@ fn update_single_element_record() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn booleans_in_record() { assert_evals_to!( indoc!("{ x: 1 == 1, y: 1 == 1 }"), @@ -908,7 +908,7 @@ fn alignment_in_record() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn blue_and_present() { assert_evals_to!( indoc!( @@ -927,7 +927,7 @@ fn blue_and_present() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn blue_and_absent() { assert_evals_to!( indoc!( @@ -968,3 +968,24 @@ fn update_the_only_field() { i64 ); } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +// https://github.com/rtfeldman/roc/issues/1513 +fn both_have_unique_fields() { + assert_evals_to!( + indoc!( + r#" + a = { x: 42, y: 43 } + b = { x: 42, z: 44 } + + f : { x : I64 }a, { x : I64 }b -> I64 + f = \{ x: x1}, { x: x2 } -> x1 + x2 + + f a b + "# + ), + 84, + i64 + ); +} diff --git a/compiler/test_gen/src/gen_result.rs b/compiler/test_gen/src/gen_result.rs index bfb0634326..4b86af9e83 100644 --- a/compiler/test_gen/src/gen_result.rs +++ b/compiler/test_gen/src/gen_result.rs @@ -189,3 +189,33 @@ fn is_ok() { bool ); } + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn is_err() { + assert_evals_to!( + indoc!( + r#" + result : Result I64 {} + result = Ok 2 + + Result.isErr result + "# + ), + false, + bool + ); + + assert_evals_to!( + indoc!( + r#" + result : Result I64 {} + result = Err {} + + Result.isErr result + "# + ), + true, + bool + ); +} diff --git a/compiler/test_gen/src/gen_str.rs b/compiler/test_gen/src/gen_str.rs index d5949996ee..691ae32971 100644 --- a/compiler/test_gen/src/gen_str.rs +++ b/compiler/test_gen/src/gen_str.rs @@ -1244,3 +1244,96 @@ fn str_trim_left_small_to_small_shared() { (RocStr, RocStr) ); } + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn str_trim_right_small_blank_string() { + assert_evals_to!(indoc!(r#"Str.trimRight " ""#), RocStr::from(""), RocStr); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn str_trim_right_small_to_small() { + assert_evals_to!( + indoc!(r#"Str.trimRight " hello world ""#), + RocStr::from(" hello world"), + RocStr + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn str_trim_right_large_to_large_unique() { + assert_evals_to!( + indoc!(r#"Str.trimRight (Str.concat " hello world from a large string" " ")"#), + RocStr::from(" hello world from a large string"), + RocStr + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn str_trim_right_large_to_small_unique() { + assert_evals_to!( + indoc!(r#"Str.trimRight (Str.concat " hello world" " ")"#), + RocStr::from(" hello world"), + RocStr + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn str_trim_right_large_to_large_shared() { + assert_evals_to!( + indoc!( + r#" + original : Str + original = " hello world world " + + { trimmed: Str.trimRight original, original: original } + "# + ), + ( + RocStr::from(" hello world world "), + RocStr::from(" hello world world"), + ), + (RocStr, RocStr) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn str_trim_right_large_to_small_shared() { + assert_evals_to!( + indoc!( + r#" + original : Str + original = " hello world " + + { trimmed: Str.trimRight original, original: original } + "# + ), + ( + RocStr::from(" hello world "), + RocStr::from(" hello world"), + ), + (RocStr, RocStr) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn str_trim_right_small_to_small_shared() { + assert_evals_to!( + indoc!( + r#" + original : Str + original = " hello world " + + { trimmed: Str.trimRight original, original: original } + "# + ), + (RocStr::from(" hello world "), RocStr::from(" hello world"),), + (RocStr, RocStr) + ); +} diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index 7626ef4d03..538272c1ff 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -453,7 +453,7 @@ fn result_with_guard_pattern() { #[test] #[cfg(any(feature = "gen-llvm"))] -fn maybe_is_just() { +fn maybe_is_just_not_nested() { assert_evals_to!( indoc!( r#" diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index d8f209d782..a74f64eaba 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -198,6 +198,10 @@ fn create_llvm_module<'a>( if name.starts_with("roc_builtins.dict") { function.add_attribute(AttributeLoc::Function, attr); } + + if name.starts_with("roc_builtins.list") { + function.add_attribute(AttributeLoc::Function, attr); + } } // Compile and add all the Procs before adding main diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index b9ee9459aa..762fd4fc7a 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -1,16 +1,22 @@ +use bumpalo::collections::vec::Vec; +use bumpalo::Bump; use std::cell::Cell; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; +use tempfile::{tempdir, TempDir}; use crate::helpers::from_wasm32_memory::FromWasm32Memory; use crate::helpers::wasm32_test_result::Wasm32TestResult; use roc_builtins::bitcode; use roc_can::builtins::builtin_defs_map; use roc_collections::all::{MutMap, MutSet}; +use roc_gen_wasm::wasm_module::linking::{WasmObjectSymbol, WASM_SYM_UNDEFINED}; +use roc_gen_wasm::wasm_module::sections::{Import, ImportDesc}; +use roc_gen_wasm::wasm_module::{ + CodeBuilder, Export, ExportType, LocalId, Signature, SymInfo, ValueType, WasmModule, +}; use roc_gen_wasm::MEMORY_NAME; -use tempfile::tempdir; - const TEST_WRAPPER_NAME: &str = "test_wrapper"; std::thread_local! { @@ -92,11 +98,6 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( // } debug_assert_eq!(exposed_to_host.len(), 1); - let main_fn_symbol = loaded.entry_point.symbol; - let main_fn_index = procedures - .keys() - .position(|(s, _)| *s == main_fn_symbol) - .unwrap(); let exposed_to_host = exposed_to_host.keys().copied().collect::>(); @@ -106,63 +107,61 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( exposed_to_host, }; - let mut wasm_module = roc_gen_wasm::build_module_help(&env, procedures).unwrap(); + let (mut wasm_module, main_fn_index) = + roc_gen_wasm::build_module_help(&env, procedures).unwrap(); - T::insert_test_wrapper( - arena, - &mut wasm_module, - TEST_WRAPPER_NAME, - main_fn_index as u32, - ); + T::insert_test_wrapper(arena, &mut wasm_module, TEST_WRAPPER_NAME, main_fn_index); + + // We can either generate the test platform or write an external source file, whatever works + generate_test_platform(&mut wasm_module, arena); let mut module_bytes = std::vec::Vec::with_capacity(4096); wasm_module.serialize_mut(&mut module_bytes); - // for debugging (e.g. with wasm2wat or wasm-objdump) - if false { - use std::io::Write; - - let mut hash_state = DefaultHasher::new(); - src.hash(&mut hash_state); - let src_hash = hash_state.finish(); - - // Filename contains a hash of the Roc test source code. Helpful when comparing across commits. - let dir = "/tmp/roc/compiler/gen_wasm/output"; - std::fs::create_dir_all(dir).unwrap(); - let path = format!("{}/test-{:016x}.wasm", dir, src_hash); - - // Print out filename (appears just after test name) - println!("dumping file {:?}", path); - - match std::fs::File::create(path) { - Err(e) => eprintln!("Problem creating wasm debug file: {:?}", e), - Ok(mut file) => { - file.write_all(&module_bytes).unwrap(); - } - } - } - // now, do wasmer stuff use wasmer::{Instance, Module, Store}; let store = Store::default(); + // Keep the final .wasm file for debugging with wasm-objdump or wasm2wat + const DEBUG_WASM_FILE: bool = true; + let wasmer_module = { - let dir = tempdir().unwrap(); - let dirpath = dir.path(); - let final_wasm_file = dirpath.join("final.wasm"); - let app_o_file = dirpath.join("app.o"); + let tmp_dir: TempDir; // directory for normal test runs, deleted when dropped + let debug_dir: String; // persistent directory for debugging + + let wasm_build_dir: &Path = if DEBUG_WASM_FILE { + // Directory name based on a hash of the Roc source + let mut hash_state = DefaultHasher::new(); + src.hash(&mut hash_state); + let src_hash = hash_state.finish(); + debug_dir = format!("/tmp/roc/gen_wasm/{:016x}", src_hash); + std::fs::create_dir_all(&debug_dir).unwrap(); + println!( + "Debug command:\n\twasm-objdump -sdx {}/final.wasm", + &debug_dir + ); + Path::new(&debug_dir) + } else { + tmp_dir = tempdir().unwrap(); + tmp_dir.path() + }; + + let final_wasm_file = wasm_build_dir.join("final.wasm"); + let app_o_file = wasm_build_dir.join("app.o"); + let libc_a_file = "../gen_wasm/lib/libc.a"; // write the module to a file so the linker can access it std::fs::write(&app_o_file, &module_bytes).unwrap(); - std::process::Command::new("zig") + let _linker_output = std::process::Command::new("zig") .args(&[ "wasm-ld", // input files app_o_file.to_str().unwrap(), bitcode::BUILTINS_WASM32_OBJ_PATH, + libc_a_file, // output "-o", final_wasm_file.to_str().unwrap(), @@ -182,6 +181,8 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( .output() .unwrap(); + // dbg!(_linker_output); + Module::from_file(&store, &final_wasm_file).unwrap() }; @@ -276,3 +277,123 @@ pub fn identity(value: T) -> T { pub(crate) use assert_evals_to; #[allow(unused_imports)] pub(crate) use assert_wasm_evals_to; + +fn wrap_libc_fn<'a>( + module: &mut WasmModule<'a>, + arena: &'a Bump, + roc_name: &'a str, + libc_name: &'a str, + params: &'a [(ValueType, bool)], + ret_type: Option, +) { + let symbol_table = module.linking.symbol_table_mut(); + + // Type signatures + let mut wrapper_signature = Signature { + param_types: Vec::with_capacity_in(params.len(), arena), + ret_type, + }; + let mut libc_signature = Signature { + param_types: Vec::with_capacity_in(params.len(), arena), + ret_type, + }; + for (ty, used) in params.iter() { + wrapper_signature.param_types.push(*ty); + if *used { + libc_signature.param_types.push(*ty); + } + } + + /* + * Import a function from libc + */ + let libc_signature_index = module.types.insert(libc_signature); + + // Import + let import_index = module.import.entries.len() as u32; + module.import.entries.push(Import { + module: "env", + name: libc_name.to_string(), + description: ImportDesc::Func { + signature_index: libc_signature_index, + }, + }); + + // Linker info + let libc_sym_idx = symbol_table.len() as u32; + symbol_table.push(SymInfo::Function(WasmObjectSymbol::Imported { + flags: WASM_SYM_UNDEFINED, + index: import_index, + })); + + /* + * Export a wrapper function + */ + + // Declaration + let wrapper_sig_index = module.types.insert(wrapper_signature); + module.function.signature_indices.push(wrapper_sig_index); + + // Body + let mut code_builder = CodeBuilder::new(arena); + let mut num_libc_args = 0; + for (i, (_, used)) in params.iter().enumerate() { + if *used { + code_builder.get_local(LocalId(i as u32)); + num_libc_args += 1; + } + } + code_builder.call( + import_index, + libc_sym_idx, + num_libc_args, + ret_type.is_some(), + ); + code_builder.build_fn_header(&[], 0, None); + let wrapper_index = module.code.code_builders.len() as u32; + module.code.code_builders.push(code_builder); + + // Export + module.export.entries.push(Export { + name: roc_name.to_string(), + ty: ExportType::Func, + index: wrapper_index, + }); + + // Linker symbol + symbol_table.push(SymInfo::Function(WasmObjectSymbol::Defined { + flags: 0, + index: wrapper_index, + name: roc_name.to_string(), + })); +} + +fn generate_test_platform<'a>(module: &mut WasmModule<'a>, arena: &'a Bump) { + use ValueType::I32; + + wrap_libc_fn( + module, + arena, + "roc_alloc", + "malloc", + // only the first argument of roc_alloc is passed to malloc + &[(I32, true), (I32, false)], + Some(I32), + ); + wrap_libc_fn( + module, + arena, + "roc_dealloc", + "free", + &[(I32, true), (I32, false)], + None, + ); + wrap_libc_fn( + module, + arena, + "roc_realloc", + "realloc", + &[(I32, true), (I32, false), (I32, true), (I32, false)], + Some(I32), + ); +} diff --git a/compiler/test_gen/src/wasm_str.rs b/compiler/test_gen/src/wasm_str.rs index d893f362f1..7dafb39a5f 100644 --- a/compiler/test_gen/src/wasm_str.rs +++ b/compiler/test_gen/src/wasm_str.rs @@ -1,6 +1,6 @@ // Wasm pointers are only 32bit. This effects RocStr. // These are versions of the str tests assuming 32bit pointers. -#![cfg(not(feature = "gen-dev"))] +#![cfg(feature = "gen-wasm")] // TODO: We need to make these tests work with the llvm wasm backend. @@ -12,7 +12,7 @@ use crate::helpers::wasm::assert_evals_to; #[allow(unused_imports)] use indoc::indoc; -// use roc_std::RocStr; +use roc_std::RocStr; // #[test] // fn str_split_bigger_delimiter_small_str() { @@ -287,165 +287,113 @@ fn small_str_zeroed_literal() { ); } -// TODO: fix linking errors for undefined symbols roc_alloc, roc_dealloc -// #[test] -// fn long_str_literal() { -// assert_evals_to!( -// "\"0123456789 123456789 123456789\"", -// RocStr::from_slice(b"0123456789 123456789 123456789"), -// RocStr -// ); -// } +#[test] +fn long_str_literal() { + assert_evals_to!( + "\"0123456789 123456789 123456789\"", + RocStr::from_slice(b"0123456789 123456789 123456789"), + RocStr + ); +} -// #[test] -// fn small_str_concat_empty_first_arg() { -// assert_llvm_evals_to!( -// r#"Str.concat "" "JJJJJJJJJJJJJJJ""#, -// [ -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0b1000_1111 -// ], -// [u8; 16] -// ); -// } +#[test] +fn small_str_concat_empty_first_arg() { + assert_evals_to!( + r#"Str.concat "" "JJJJJJJ""#, + [0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0b1000_0111], + [u8; 8] + ); +} -// #[test] -// fn small_str_concat_empty_second_arg() { -// assert_llvm_evals_to!( -// r#"Str.concat "JJJJJJJJJJJJJJJ" """#, -// [ -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0b1000_1111 -// ], -// [u8; 16] -// ); -// } +#[test] +fn small_str_concat_empty_second_arg() { + assert_evals_to!( + r#"Str.concat "JJJJJJJ" """#, + [0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0b1000_0111], + [u8; 8] + ); +} -// #[test] -// fn small_str_concat_small_to_big() { -// assert_evals_to!( -// r#"Str.concat "abc" " this is longer than 15 chars""#, -// RocStr::from_slice(b"abc this is longer than 15 chars"), -// RocStr -// ); -// } +#[test] +fn small_str_concat_small_to_big() { + assert_evals_to!( + r#"Str.concat "abc" " this is longer than 7 chars""#, + RocStr::from_slice(b"abc this is longer than 7 chars"), + RocStr + ); +} -// #[test] -// fn small_str_concat_small_to_small_staying_small() { -// assert_llvm_evals_to!( -// r#"Str.concat "J" "JJJJJJJJJJJJJJ""#, -// [ -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0x4a, -// 0b1000_1111 -// ], -// [u8; 16] -// ); -// } +#[test] +fn small_str_concat_small_to_small_staying_small() { + assert_evals_to!( + r#"Str.concat "J" "JJJJJJ""#, + [0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0b1000_0111], + [u8; 8] + ); +} -// #[test] -// fn small_str_concat_small_to_small_overflow_to_big() { -// assert_evals_to!( -// r#"Str.concat "abcdefghijklm" "nopqrstuvwxyz""#, -// RocStr::from_slice(b"abcdefghijklmnopqrstuvwxyz"), -// RocStr -// ); -// } +#[test] +fn small_str_concat_small_to_small_overflow_to_big() { + assert_evals_to!( + r#"Str.concat "abcdefg" "hijklmn""#, + RocStr::from_slice(b"abcdefghijklmn"), + RocStr + ); +} -// #[test] -// fn str_concat_empty() { -// assert_evals_to!(r#"Str.concat "" """#, RocStr::default(), RocStr); -// } +#[test] +fn str_concat_empty() { + assert_evals_to!(r#"Str.concat "" """#, RocStr::default(), RocStr); +} -// #[test] -// fn small_str_is_empty() { -// assert_evals_to!(r#"Str.isEmpty "abc""#, false, bool); -// } +#[test] +fn small_str_is_empty() { + assert_evals_to!(r#"Str.isEmpty "abc""#, false, bool); +} -// #[test] -// fn big_str_is_empty() { -// assert_evals_to!( -// r#"Str.isEmpty "this is more than 15 chars long""#, -// false, -// bool -// ); -// } +#[test] +fn big_str_is_empty() { + assert_evals_to!( + r#"Str.isEmpty "this is more than 15 chars long""#, + false, + bool + ); +} -// #[test] -// fn empty_str_is_empty() { -// assert_evals_to!(r#"Str.isEmpty """#, true, bool); -// } +#[test] +fn empty_str_is_empty() { + assert_evals_to!(r#"Str.isEmpty """#, true, bool); +} -// #[test] -// fn str_starts_with() { -// assert_evals_to!(r#"Str.startsWith "hello world" "hell""#, true, bool); -// assert_evals_to!(r#"Str.startsWith "hello world" """#, true, bool); -// assert_evals_to!(r#"Str.startsWith "nope" "hello world""#, false, bool); -// assert_evals_to!(r#"Str.startsWith "hell" "hello world""#, false, bool); -// assert_evals_to!(r#"Str.startsWith "" "hello world""#, false, bool); -// } +#[test] +fn str_starts_with() { + assert_evals_to!(r#"Str.startsWith "hello world" "hell""#, true, bool); + assert_evals_to!(r#"Str.startsWith "hello world" """#, true, bool); + assert_evals_to!(r#"Str.startsWith "nope" "hello world""#, false, bool); + assert_evals_to!(r#"Str.startsWith "hell" "hello world""#, false, bool); + assert_evals_to!(r#"Str.startsWith "" "hello world""#, false, bool); +} -// #[test] -// fn str_starts_with_code_point() { -// assert_evals_to!( -// &format!(r#"Str.startsWithCodePt "foobar" {}"#, 'f' as u32), -// true, -// bool -// ); -// assert_evals_to!( -// &format!(r#"Str.startsWithCodePt "zoobar" {}"#, 'f' as u32), -// false, -// bool -// ); -// } +#[test] +fn str_starts_with_code_point() { + assert_evals_to!( + &format!(r#"Str.startsWithCodePt "foobar" {}"#, 'f' as u32), + true, + bool + ); + assert_evals_to!( + &format!(r#"Str.startsWithCodePt "zoobar" {}"#, 'f' as u32), + false, + bool + ); +} -// #[test] -// fn str_ends_with() { -// assert_evals_to!(r#"Str.endsWith "hello world" "world""#, true, bool); -// assert_evals_to!(r#"Str.endsWith "nope" "hello world""#, false, bool); -// assert_evals_to!(r#"Str.endsWith "" "hello world""#, false, bool); -// } +#[test] +fn str_ends_with() { + assert_evals_to!(r#"Str.endsWith "hello world" "world""#, true, bool); + assert_evals_to!(r#"Str.endsWith "nope" "hello world""#, false, bool); + assert_evals_to!(r#"Str.endsWith "" "hello world""#, false, bool); +} // #[test] // fn str_count_graphemes_small_str() { @@ -466,37 +414,37 @@ fn small_str_zeroed_literal() { // ); // } -// #[test] -// fn str_starts_with_same_big_str() { -// assert_evals_to!( -// r#"Str.startsWith "123456789123456789" "123456789123456789""#, -// true, -// bool -// ); -// } +#[test] +fn str_starts_with_same_big_str() { + assert_evals_to!( + r#"Str.startsWith "123456789123456789" "123456789123456789""#, + true, + bool + ); +} -// #[test] -// fn str_starts_with_different_big_str() { -// assert_evals_to!( -// r#"Str.startsWith "12345678912345678910" "123456789123456789""#, -// true, -// bool -// ); -// } +#[test] +fn str_starts_with_different_big_str() { + assert_evals_to!( + r#"Str.startsWith "12345678912345678910" "123456789123456789""#, + true, + bool + ); +} -// #[test] -// fn str_starts_with_same_small_str() { -// assert_evals_to!(r#"Str.startsWith "1234" "1234""#, true, bool); -// } +#[test] +fn str_starts_with_same_small_str() { + assert_evals_to!(r#"Str.startsWith "1234" "1234""#, true, bool); +} -// #[test] -// fn str_starts_with_different_small_str() { -// assert_evals_to!(r#"Str.startsWith "1234" "12""#, true, bool); -// } -// #[test] -// fn str_starts_with_false_small_str() { -// assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool); -// } +#[test] +fn str_starts_with_different_small_str() { + assert_evals_to!(r#"Str.startsWith "1234" "12""#, true, bool); +} +#[test] +fn str_starts_with_false_small_str() { + assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool); +} // #[test] // fn str_from_int() { @@ -952,121 +900,239 @@ fn small_str_zeroed_literal() { // ); // } -// #[test] -// fn str_repeat_small() { -// assert_evals_to!( -// indoc!(r#"Str.repeat "Roc" 3"#), -// RocStr::from("RocRocRoc"), -// RocStr -// ); -// } +#[test] +fn str_repeat_small() { + assert_evals_to!( + indoc!(r#"Str.repeat "Roc" 3"#), + RocStr::from("RocRocRoc"), + RocStr + ); +} -// #[test] -// fn str_repeat_big() { -// assert_evals_to!( -// indoc!(r#"Str.repeat "more than 16 characters" 2"#), -// RocStr::from("more than 16 charactersmore than 16 characters"), -// RocStr -// ); -// } +#[test] +fn str_repeat_big() { + assert_evals_to!( + indoc!(r#"Str.repeat "more than 16 characters" 2"#), + RocStr::from("more than 16 charactersmore than 16 characters"), + RocStr + ); +} -// #[test] -// fn str_repeat_empty_string() { -// assert_evals_to!(indoc!(r#"Str.repeat "" 3"#), RocStr::from(""), RocStr); -// } +#[test] +fn str_repeat_empty_string() { + assert_evals_to!(indoc!(r#"Str.repeat "" 3"#), RocStr::from(""), RocStr); +} -// #[test] -// fn str_repeat_zero_times() { -// assert_evals_to!(indoc!(r#"Str.repeat "Roc" 0"#), RocStr::from(""), RocStr); -// } +#[test] +fn str_repeat_zero_times() { + assert_evals_to!(indoc!(r#"Str.repeat "Roc" 0"#), RocStr::from(""), RocStr); +} -// #[test] -// fn str_trim_empty_string() { -// assert_evals_to!(indoc!(r#"Str.trim """#), RocStr::from(""), RocStr); -// } +#[test] +fn str_trim_empty_string() { + assert_evals_to!(indoc!(r#"Str.trim """#), RocStr::from(""), RocStr); +} -// #[test] -// fn str_trim_small_blank_string() { -// assert_evals_to!(indoc!(r#"Str.trim " ""#), RocStr::from(""), RocStr); -// } +#[test] +fn str_trim_small_blank_string() { + assert_evals_to!(indoc!(r#"Str.trim " ""#), RocStr::from(""), RocStr); +} -// #[test] -// fn str_trim_small_to_small() { -// assert_evals_to!( -// indoc!(r#"Str.trim " hello world ""#), -// RocStr::from("hello world"), -// RocStr -// ); -// } +#[test] +fn str_trim_small_to_small() { + assert_evals_to!( + indoc!(r#"Str.trim " hello world ""#), + RocStr::from("hello world"), + RocStr + ); +} -// #[test] -// fn str_trim_large_to_large_unique() { -// assert_evals_to!( -// indoc!(r#"Str.trim (Str.concat " " "hello world from a large string ")"#), -// RocStr::from("hello world from a large string"), -// RocStr -// ); -// } +#[test] +fn str_trim_large_to_large_unique() { + assert_evals_to!( + indoc!(r#"Str.trim (Str.concat " " "hello world from a large string ")"#), + RocStr::from("hello world from a large string"), + RocStr + ); +} -// #[test] -// fn str_trim_large_to_small_unique() { -// assert_evals_to!( -// indoc!(r#"Str.trim (Str.concat " " "hello world ")"#), -// RocStr::from("hello world"), -// RocStr -// ); -// } +#[test] +fn str_trim_large_to_small_unique() { + assert_evals_to!( + indoc!(r#"Str.trim (Str.concat " " "hello world ")"#), + RocStr::from("hello world"), + RocStr + ); +} -// #[test] -// fn str_trim_large_to_large_shared() { -// assert_evals_to!( -// indoc!( -// r#" -// original : Str -// original = " hello world world " +#[test] +fn str_trim_large_to_large_shared() { + assert_evals_to!( + indoc!( + r#" + original : Str + original = " hello world world " -// { trimmed: Str.trim original, original: original } -// "# -// ), -// ( -// RocStr::from(" hello world world "), -// RocStr::from("hello world world"), -// ), -// (RocStr, RocStr) -// ); -// } + { trimmed: Str.trim original, original: original } + "# + ), + ( + RocStr::from(" hello world world "), + RocStr::from("hello world world"), + ), + (RocStr, RocStr) + ); +} -// #[test] -// fn str_trim_large_to_small_shared() { -// assert_evals_to!( -// indoc!( -// r#" -// original : Str -// original = " hello world " +#[test] +fn str_trim_large_to_small_shared() { + assert_evals_to!( + indoc!( + r#" + original : Str + original = " hello world " -// { trimmed: Str.trim original, original: original } -// "# -// ), -// ( -// RocStr::from(" hello world "), -// RocStr::from("hello world"), -// ), -// (RocStr, RocStr) -// ); -// } + { trimmed: Str.trim original, original: original } + "# + ), + ( + RocStr::from(" hello world "), + RocStr::from("hello world"), + ), + (RocStr, RocStr) + ); +} -// #[test] -// fn str_trim_small_to_small_shared() { -// assert_evals_to!( -// indoc!( -// r#" -// original : Str -// original = " hello world " +#[test] +fn str_trim_small_to_small_shared() { + assert_evals_to!( + indoc!( + r#" + original : Str + original = " hello world " -// { trimmed: Str.trim original, original: original } -// "# -// ), -// (RocStr::from(" hello world "), RocStr::from("hello world"),), -// (RocStr, RocStr) -// ); -// } + { trimmed: Str.trim original, original: original } + "# + ), + (RocStr::from(" hello world "), RocStr::from("hello world"),), + (RocStr, RocStr) + ); +} + +#[test] +fn str_trim_left_small_blank_string() { + assert_evals_to!(indoc!(r#"Str.trimLeft " ""#), RocStr::from(""), RocStr); +} + +#[test] +fn str_trim_left_small_to_small() { + assert_evals_to!( + indoc!(r#"Str.trimLeft " hello world ""#), + RocStr::from("hello world "), + RocStr + ); +} + +#[test] +fn str_trim_left_large_to_large_unique() { + assert_evals_to!( + indoc!(r#"Str.trimLeft (Str.concat " " "hello world from a large string ")"#), + RocStr::from("hello world from a large string "), + RocStr + ); +} + +#[test] +fn str_trim_left_large_to_small_unique() { + assert_evals_to!( + indoc!(r#"Str.trimLeft (Str.concat " " "hello world ")"#), + RocStr::from("hello world "), + RocStr + ); +} + +#[test] +fn str_trim_right_small_blank_string() { + assert_evals_to!(indoc!(r#"Str.trimRight " ""#), RocStr::from(""), RocStr); +} + +#[test] +fn str_trim_right_small_to_small() { + assert_evals_to!( + indoc!(r#"Str.trimRight " hello world ""#), + RocStr::from(" hello world"), + RocStr + ); +} + +#[test] +fn str_trim_right_large_to_large_unique() { + assert_evals_to!( + indoc!(r#"Str.trimRight (Str.concat " hello world from a large string" " ")"#), + RocStr::from(" hello world from a large string"), + RocStr + ); +} + +#[test] +fn str_trim_right_large_to_small_unique() { + assert_evals_to!( + indoc!(r#"Str.trimRight (Str.concat " hello world" " ")"#), + RocStr::from(" hello world"), + RocStr + ); +} + +#[test] +fn str_trim_right_large_to_large_shared() { + assert_evals_to!( + indoc!( + r#" + original : Str + original = " hello world world " + + { trimmed: Str.trimRight original, original: original } + "# + ), + ( + RocStr::from(" hello world world "), + RocStr::from(" hello world world"), + ), + (RocStr, RocStr) + ); +} + +#[test] +fn str_trim_right_large_to_small_shared() { + assert_evals_to!( + indoc!( + r#" + original : Str + original = " hello world " + + { trimmed: Str.trimRight original, original: original } + "# + ), + ( + RocStr::from(" hello world "), + RocStr::from(" hello world"), + ), + (RocStr, RocStr) + ); +} + +#[test] +fn str_trim_right_small_to_small_shared() { + assert_evals_to!( + indoc!( + r#" + original : Str + original = " hello world " + + { trimmed: Str.trimRight original, original: original } + "# + ), + (RocStr::from(" hello world "), RocStr::from(" hello world"),), + (RocStr, RocStr) + ); +} diff --git a/compiler/test_mono/generated/if_guard_bind_variable_false.txt b/compiler/test_mono/generated/if_guard_bind_variable_false.txt index 36630718ca..1bb0f10960 100644 --- a/compiler/test_mono/generated/if_guard_bind_variable_false.txt +++ b/compiler/test_mono/generated/if_guard_bind_variable_false.txt @@ -1,4 +1,4 @@ -procedure Bool.5 (#Attr.2, #Attr.3): +procedure Bool.7 (#Attr.2, #Attr.3): let Test.11 = lowlevel Eq #Attr.2 #Attr.3; ret Test.11; @@ -13,7 +13,7 @@ procedure Test.1 (Test.3): ret Test.12; in let Test.10 = 5i64; - let Test.9 = CallByName Bool.5 Test.6 Test.10; + let Test.9 = CallByName Bool.7 Test.6 Test.10; jump Test.8 Test.9; procedure Test.0 (): diff --git a/compiler/types/Cargo.toml b/compiler/types/Cargo.toml index a47245bf95..97cd23ac45 100644 --- a/compiler/types/Cargo.toml +++ b/compiler/types/Cargo.toml @@ -10,4 +10,5 @@ roc_collections = { path = "../collections" } roc_region = { path = "../region" } roc_module = { path = "../module" } ven_ena = { path = "../../vendor/ena" } +bumpalo = { version = "3.8.0", features = ["collections"] } static_assertions = "1.1.0" diff --git a/compiler/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index a1768f6e7f..5173dc43af 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -4,7 +4,6 @@ use roc_collections::all::{ImMap, MutSet, SendMap}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; -use std::hash::{Hash, Hasher}; /// A marker that a given Subs has been solved. /// The only way to obtain a Solved is by running the solver on it. @@ -25,151 +24,11 @@ impl Solved { } } -/// A custom hash instance, that treats flex vars specially, so that -/// -/// `Foo 100 200 100` hashes to the same as `Foo 300 100 300` -/// -/// i.e., we can rename the flex variables, so long as it happens consistently. -/// this is important so we don't generate the same PartialProc twice. -impl Hash for SolvedType { - fn hash(&self, state: &mut H) { - hash_solved_type_help(self, &mut Vec::new(), state); - } -} - -impl PartialEq for SolvedType { - fn eq(&self, other: &Self) -> bool { - use std::collections::hash_map::DefaultHasher; - - let mut state = DefaultHasher::new(); - hash_solved_type_help(self, &mut Vec::new(), &mut state); - let hash1 = state.finish(); - - let mut state = DefaultHasher::new(); - hash_solved_type_help(other, &mut Vec::new(), &mut state); - let hash2 = state.finish(); - - hash1 == hash2 - } -} - -fn hash_solved_type_help( - initial: &SolvedType, - flex_vars: &mut Vec, - state: &mut H, -) { - use SolvedType::*; - - let mut stack = Vec::with_capacity(63); - - stack.push(initial); - - while let Some(solved_type) = stack.pop() { - match solved_type { - Flex(var_id) => { - var_id_hash_help(*var_id, flex_vars, state); - } - Wildcard => "wildcard".hash(state), - EmptyRecord => "empty_record".hash(state), - EmptyTagUnion => "empty_tag_union".hash(state), - Error => "error".hash(state), - Func(arguments, closure, result) => { - stack.extend(arguments); - - stack.push(closure); - stack.push(result); - } - Apply(name, arguments) => { - name.hash(state); - stack.extend(arguments); - } - Rigid(name) => name.hash(state), - Erroneous(problem) => problem.hash(state), - - Record { fields, ext } => { - for (name, x) in fields { - name.hash(state); - "record_field".hash(state); - stack.push(x.as_inner()); - } - stack.push(ext); - } - - TagUnion(tags, ext) => { - for (name, arguments) in tags { - name.hash(state); - stack.extend(arguments); - } - stack.push(ext); - } - - FunctionOrTagUnion(_, _, ext) => { - stack.push(ext); - } - - RecursiveTagUnion(rec, tags, ext) => { - var_id_hash_help(*rec, flex_vars, state); - for (name, arguments) in tags { - name.hash(state); - stack.extend(arguments); - } - stack.push(ext); - } - - Alias(name, arguments, solved_lambda_sets, actual) => { - name.hash(state); - for (name, x) in arguments { - name.hash(state); - stack.push(x); - } - - for set in solved_lambda_sets { - stack.push(&set.0); - } - - stack.push(actual); - } - - HostExposedAlias { - name, - arguments, - lambda_set_variables: solved_lambda_sets, - actual, - actual_var, - } => { - name.hash(state); - for (name, x) in arguments { - name.hash(state); - stack.push(x); - } - - for set in solved_lambda_sets { - stack.push(&set.0); - } - - stack.push(actual); - var_id_hash_help(*actual_var, flex_vars, state); - } - } - } -} - -fn var_id_hash_help(var_id: VarId, flex_vars: &mut Vec, state: &mut H) { - let opt_index = flex_vars.iter().position(|x| *x == var_id); - match opt_index { - Some(index) => index.hash(state), - None => { - flex_vars.len().hash(state); - flex_vars.push(var_id); - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct SolvedLambdaSet(pub SolvedType); /// This is a fully solved type, with no Variables remaining in it. -#[derive(Debug, Clone, Eq)] +#[derive(Debug, Clone)] pub enum SolvedType { /// A function. The types of its arguments, then the type of its return value. Func(Vec, Box, Box), @@ -531,7 +390,7 @@ impl SolvedType { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct BuiltinAlias { pub region: Region, pub vars: Vec>, diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 420ac0c322..7d42f424f7 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -9,10 +9,12 @@ use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey}; // 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; 48], Descriptor); -static_assertions::assert_eq_size!([u8; 32], Content); -static_assertions::assert_eq_size!([u8; 24], FlatType); -static_assertions::assert_eq_size!([u8; 48], Problem); +static_assertions::assert_eq_size!([u8; 6 * 8], Descriptor); +static_assertions::assert_eq_size!([u8; 4 * 8], Content); +static_assertions::assert_eq_size!([u8; 3 * 8], FlatType); +static_assertions::assert_eq_size!([u8; 6 * 8], Problem); +static_assertions::assert_eq_size!([u8; 12], UnionTags); +static_assertions::assert_eq_size!([u8; 2 * 8], RecordFields); #[derive(Clone, Copy, Hash, PartialEq, Eq)] pub struct Mark(i32); @@ -61,16 +63,7 @@ pub struct Subs { impl Default for Subs { fn default() -> Self { - Subs { - utable: Default::default(), - variables: Default::default(), - tag_names: Default::default(), - field_names: Default::default(), - record_fields: Default::default(), - // store an empty slice at the first position - // used for "TagOrFunction" - variable_slices: vec![VariableSubsSlice::default()], - } + Subs::new() } } @@ -79,14 +72,14 @@ impl Default for Subs { /// The starting position is a u32 which should be plenty /// We limit slices to u16::MAX = 65535 elements pub struct SubsSlice { - start: u32, - length: u16, + pub start: u32, + pub length: u16, _marker: std::marker::PhantomData, } /// An index into the Vec of subs pub struct SubsIndex { - start: u32, + pub start: u32, _marker: std::marker::PhantomData, } @@ -324,7 +317,8 @@ fn subs_fmt_desc(this: &Descriptor, subs: &Subs, f: &mut fmt::Formatter) -> fmt: subs_fmt_content(&this.content, subs, f)?; write!(f, " r: {:?}", &this.rank)?; - write!(f, " m: {:?}", &this.mark) + write!(f, " m: {:?}", &this.mark)?; + write!(f, " c: {:?}", &this.copy) } pub struct SubsFmtContent<'a>(pub &'a Content, pub &'a Subs); @@ -679,11 +673,11 @@ impl Variable { /// /// This should only ever be called from tests! pub unsafe fn unsafe_test_debug_variable(v: u32) -> Self { - debug_assert!(v <= Self::NUM_RESERVED_VARS as u32); + debug_assert!(v >= Self::NUM_RESERVED_VARS as u32); Variable(v) } - pub fn index(&self) -> u32 { + pub const fn index(&self) -> u32 { self.0 } } @@ -985,21 +979,31 @@ fn define_integer_types(subs: &mut Subs) { } impl Subs { - pub fn new(var_store: VarStore) -> Self { - let entries = var_store.next; + pub fn new() -> Self { + Self::with_capacity(0) + } + + pub fn with_capacity(capacity: usize) -> Self { + let capacity = capacity.max(Variable::NUM_RESERVED_VARS); let mut subs = Subs { utable: UnificationTable::default(), - ..Default::default() + variables: Default::default(), + tag_names: Default::default(), + field_names: Default::default(), + record_fields: Default::default(), + // store an empty slice at the first position + // used for "TagOrFunction" + variable_slices: vec![VariableSubsSlice::default()], }; // NOTE the utable does not (currently) have a with_capacity; using this as the next-best thing - subs.utable.reserve(entries as usize); + subs.utable.reserve(capacity); // TODO There are at least these opportunities for performance optimization here: // * Making the default flex_var_descriptor be all 0s, so no init step is needed. - for _ in 0..entries { + for _ in 0..capacity { subs.utable.new_key(flex_var_descriptor()); } @@ -1040,7 +1044,14 @@ impl Subs { subs } + pub fn new_from_varstore(var_store: VarStore) -> Self { + let entries = var_store.next; + + Self::with_capacity(entries as usize) + } + pub fn extend_by(&mut self, entries: usize) { + self.utable.reserve(entries); for _ in 0..entries { self.utable.new_key(flex_var_descriptor()); } @@ -1080,6 +1091,10 @@ impl Subs { &self.utable.probe_value_ref(key).value } + pub fn get_ref_mut(&mut self, key: Variable) -> &mut Descriptor { + &mut self.utable.probe_value_ref_mut(key).value + } + pub fn get_rank(&self, key: Variable) -> Rank { self.utable.probe_value_ref(key).value.rank } @@ -1149,6 +1164,26 @@ impl Subs { }); } + pub fn modify(&mut self, key: Variable, mapper: F) + where + F: Fn(&mut Descriptor), + { + mapper(self.get_ref_mut(key)); + } + + pub fn get_rank_set_mark(&mut self, key: Variable, mark: Mark) -> Rank { + let l_key = self.utable.get_root_key(key); + + let mut rank = Rank::NONE; + + self.utable.update_value(l_key, |node| { + node.value.mark = mark; + rank = node.value.rank; + }); + + rank + } + pub fn equivalent(&mut self, left: Variable, right: Variable) -> bool { self.utable.unioned(left, right) } @@ -1192,22 +1227,7 @@ impl Subs { } pub fn restore(&mut self, var: Variable) { - let desc = self.get(var); - - if desc.copy.is_some() { - let content = desc.content; - - let desc = Descriptor { - content: content.clone(), - rank: Rank::NONE, - mark: Mark::NONE, - copy: OptVariable::NONE, - }; - - self.set(var, desc); - - restore_content(self, &content); - } + restore_help(self, var) } pub fn len(&self) -> usize { @@ -1218,6 +1238,10 @@ impl Subs { self.utable.is_empty() } + pub fn contains(&self, var: Variable) -> bool { + (var.index() as usize) < self.len() + } + pub fn snapshot(&mut self) -> Snapshot> { self.utable.snapshot() } @@ -1326,6 +1350,12 @@ impl From for Descriptor { } } +static_assertions::assert_eq_size!([u8; 4 * 8], Content); +static_assertions::assert_eq_size!([u8; 4 * 8], (Variable, Option)); +static_assertions::assert_eq_size!([u8; 3 * 8], (Symbol, AliasVariables, Variable)); +static_assertions::assert_eq_size!([u8; 12], AliasVariables); +static_assertions::assert_eq_size!([u8; 3 * 8], FlatType); + #[derive(Clone, Debug)] pub enum Content { /// A type variable which the user did not name in an annotation, @@ -1347,10 +1377,10 @@ pub enum Content { #[derive(Clone, Copy, Debug, Default)] pub struct AliasVariables { - lowercases_start: u32, - variables_start: u32, - lowercases_len: u16, - variables_len: u16, + pub lowercases_start: u32, + pub variables_start: u32, + pub lowercases_len: u16, + pub variables_len: u16, } impl AliasVariables { @@ -1466,6 +1496,8 @@ impl Content { } } +static_assertions::assert_eq_size!([u8; 3 * 8], FlatType); + #[derive(Clone, Debug)] pub enum FlatType { Apply(Symbol, VariableSubsSlice), @@ -1489,7 +1521,7 @@ pub enum Builtin { #[derive(Clone, Copy, Debug, Default)] pub struct VariableSubsSlice { - slice: SubsSlice, + pub slice: SubsSlice, } impl VariableSubsSlice { @@ -1813,6 +1845,12 @@ impl RecordFields { } } + pub const fn variables(&self) -> VariableSubsSlice { + let slice = SubsSlice::new(self.variables_start, self.length); + + VariableSubsSlice { slice } + } + pub fn iter_variables(&self) -> impl Iterator> { let slice = SubsSlice::new(self.variables_start, self.length); slice.into_iter() @@ -2701,79 +2739,643 @@ fn get_fresh_var_name(state: &mut ErrorTypeState) -> Lowercase { name } -fn restore_content(subs: &mut Subs, content: &Content) { - use Content::*; - use FlatType::*; +fn restore_help(subs: &mut Subs, initial: Variable) { + let mut stack = vec![initial]; - match content { - FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => (), + let variable_slices = &subs.variable_slices; - Structure(flat_type) => match flat_type { - Apply(_, args) => { - for index in args.into_iter() { - let var = subs[index]; - subs.restore(var); - } - } + let variables = &subs.variables; + let var_slice = |variable_subs_slice: VariableSubsSlice| { + &variables[variable_subs_slice.slice.start as usize..] + [..variable_subs_slice.slice.length as usize] + }; - Func(arg_vars, closure_var, ret_var) => { - for index in arg_vars.into_iter() { - let var = subs[index]; - subs.restore(var); + while let Some(var) = stack.pop() { + let desc = &mut subs.utable.probe_value_ref_mut(var).value; + + if desc.copy.is_some() { + desc.rank = Rank::NONE; + desc.mark = Mark::NONE; + desc.copy = OptVariable::NONE; + + use Content::*; + use FlatType::*; + + match &desc.content { + FlexVar(_) | RigidVar(_) | Error => (), + + RecursionVar { structure, .. } => { + stack.push(*structure); } - subs.restore(*ret_var); - subs.restore(*closure_var); - } - - EmptyRecord => (), - EmptyTagUnion => (), - - Record(fields, ext_var) => { - for index in fields.iter_variables() { - let var = subs[index]; - subs.restore(var); - } - - subs.restore(*ext_var); - } - TagUnion(tags, ext_var) => { - for slice_index in tags.variables() { - let slice = subs[slice_index]; - for var_index in slice { - let var = subs[var_index]; - subs.restore(var); + Structure(flat_type) => match flat_type { + Apply(_, args) => { + stack.extend(var_slice(*args)); } - } - subs.restore(*ext_var); - } - FunctionOrTagUnion(_, _, ext_var) => { - subs.restore(*ext_var); - } + Func(arg_vars, closure_var, ret_var) => { + stack.extend(var_slice(*arg_vars)); - RecursiveTagUnion(rec_var, tags, ext_var) => { - for slice_index in tags.variables() { - let slice = subs[slice_index]; - for var_index in slice { - let var = subs[var_index]; - subs.restore(var); + stack.push(*ret_var); + stack.push(*closure_var); } + + EmptyRecord => (), + EmptyTagUnion => (), + + Record(fields, ext_var) => { + stack.extend(var_slice(fields.variables())); + + stack.push(*ext_var); + } + TagUnion(tags, ext_var) => { + for slice_index in tags.variables() { + let slice = variable_slices[slice_index.start as usize]; + stack.extend(var_slice(slice)); + } + + stack.push(*ext_var); + } + FunctionOrTagUnion(_, _, ext_var) => { + stack.push(*ext_var); + } + + RecursiveTagUnion(rec_var, tags, ext_var) => { + for slice_index in tags.variables() { + let slice = variable_slices[slice_index.start as usize]; + stack.extend(var_slice(slice)); + } + + stack.push(*ext_var); + stack.push(*rec_var); + } + + Erroneous(_) => (), + }, + Alias(_, args, var) => { + stack.extend(var_slice(args.variables())); + + stack.push(*var); } - - subs.restore(*ext_var); - subs.restore(*rec_var); } - - Erroneous(_) => (), - }, - Alias(_, args, var) => { - for var_index in args.variables().into_iter() { - let var = subs[var_index]; - subs.restore(var); - } - - subs.restore(*var); + } + } +} + +#[derive(Clone, Debug)] +pub struct StorageSubs { + subs: Subs, +} + +#[derive(Copy, Clone, Debug)] +struct StorageSubsOffsets { + utable: u32, + variables: u32, + tag_names: u32, + field_names: u32, + record_fields: u32, + variable_slices: u32, +} + +impl StorageSubs { + pub fn new(subs: Subs) -> Self { + Self { subs } + } + + pub fn extend_with_variable(&mut self, source: &mut Subs, variable: Variable) -> Variable { + deep_copy_var_to(source, &mut self.subs, variable) + } + + pub fn merge_into(self, target: &mut Subs) -> impl Fn(Variable) -> Variable { + let self_offsets = StorageSubsOffsets { + utable: self.subs.utable.len() as u32, + variables: self.subs.variables.len() as u32, + tag_names: self.subs.tag_names.len() as u32, + field_names: self.subs.field_names.len() as u32, + record_fields: self.subs.record_fields.len() as u32, + variable_slices: self.subs.variable_slices.len() as u32, + }; + + let offsets = StorageSubsOffsets { + utable: (target.utable.len() - Variable::NUM_RESERVED_VARS) as u32, + variables: target.variables.len() as u32, + tag_names: target.tag_names.len() as u32, + field_names: target.field_names.len() as u32, + record_fields: target.record_fields.len() as u32, + variable_slices: target.variable_slices.len() as u32, + }; + + // The first Variable::NUM_RESERVED_VARS are the same in every subs, + // so we can skip copying them! + let range = Variable::NUM_RESERVED_VARS..self.subs.utable.len(); + + // fill new slots with empty values + target.extend_by(range.len()); + + for i in range { + let variable = Variable(i as u32); + let descriptor = self.subs.get_ref(variable); + debug_assert!(descriptor.copy.is_none()); + + let new_content = Self::offset_content(&offsets, &descriptor.content); + + let new_descriptor = Descriptor { + rank: descriptor.rank, + mark: descriptor.mark, + copy: OptVariable::NONE, + content: new_content, + }; + + let new_variable = Self::offset_variable(&offsets, variable); + target.set(new_variable, new_descriptor); + } + + target.variables.extend( + self.subs + .variables + .iter() + .map(|v| Self::offset_variable(&offsets, *v)), + ); + + target.variable_slices.extend( + self.subs + .variable_slices + .into_iter() + .map(|v| Self::offset_variable_slice(&offsets, v)), + ); + + target.tag_names.extend(self.subs.tag_names); + target.field_names.extend(self.subs.field_names); + target.record_fields.extend(self.subs.record_fields); + + debug_assert_eq!( + target.utable.len(), + (self_offsets.utable + offsets.utable) as usize + ); + + debug_assert_eq!( + target.tag_names.len(), + (self_offsets.tag_names + offsets.tag_names) as usize + ); + + move |v| { + let offsets = offsets; + Self::offset_variable(&offsets, v) + } + } + fn offset_flat_type(offsets: &StorageSubsOffsets, flat_type: &FlatType) -> FlatType { + match flat_type { + FlatType::Apply(symbol, arguments) => { + FlatType::Apply(*symbol, Self::offset_variable_slice(offsets, *arguments)) + } + FlatType::Func(arguments, lambda_set, result) => FlatType::Func( + Self::offset_variable_slice(offsets, *arguments), + Self::offset_variable(offsets, *lambda_set), + Self::offset_variable(offsets, *result), + ), + FlatType::Record(record_fields, ext) => FlatType::Record( + Self::offset_record_fields(offsets, *record_fields), + Self::offset_variable(offsets, *ext), + ), + FlatType::TagUnion(union_tags, ext) => FlatType::TagUnion( + Self::offset_union_tags(offsets, *union_tags), + Self::offset_variable(offsets, *ext), + ), + FlatType::FunctionOrTagUnion(tag_name, symbol, ext) => FlatType::FunctionOrTagUnion( + Self::offset_tag_name_index(offsets, *tag_name), + *symbol, + Self::offset_variable(offsets, *ext), + ), + FlatType::RecursiveTagUnion(rec, union_tags, ext) => FlatType::RecursiveTagUnion( + Self::offset_variable(offsets, *rec), + Self::offset_union_tags(offsets, *union_tags), + Self::offset_variable(offsets, *ext), + ), + FlatType::Erroneous(problem) => FlatType::Erroneous(problem.clone()), + FlatType::EmptyRecord => FlatType::EmptyRecord, + FlatType::EmptyTagUnion => FlatType::EmptyTagUnion, + } + } + + fn offset_content(offsets: &StorageSubsOffsets, content: &Content) -> Content { + use Content::*; + + match content { + FlexVar(opt_name) => FlexVar(opt_name.clone()), + RigidVar(name) => RigidVar(name.clone()), + RecursionVar { + structure, + opt_name, + } => RecursionVar { + structure: Self::offset_variable(offsets, *structure), + opt_name: opt_name.clone(), + }, + Structure(flat_type) => Structure(Self::offset_flat_type(offsets, flat_type)), + Alias(symbol, alias_variables, actual) => Alias( + *symbol, + Self::offset_alias_variables(offsets, *alias_variables), + Self::offset_variable(offsets, *actual), + ), + Error => Content::Error, + } + } + + fn offset_alias_variables( + offsets: &StorageSubsOffsets, + mut alias_variables: AliasVariables, + ) -> AliasVariables { + alias_variables.lowercases_start += offsets.field_names; + alias_variables.variables_start += offsets.variables; + + alias_variables + } + + fn offset_union_tags(offsets: &StorageSubsOffsets, mut union_tags: UnionTags) -> UnionTags { + union_tags.tag_names_start += offsets.tag_names; + union_tags.variables_start += offsets.variable_slices; + + union_tags + } + + fn offset_record_fields( + offsets: &StorageSubsOffsets, + mut record_fields: RecordFields, + ) -> RecordFields { + record_fields.field_names_start += offsets.field_names; + record_fields.variables_start += offsets.variables; + record_fields.field_types_start += offsets.record_fields; + + record_fields + } + + fn offset_tag_name_index( + offsets: &StorageSubsOffsets, + mut tag_name: SubsIndex, + ) -> SubsIndex { + tag_name.start += offsets.tag_names; + + tag_name + } + + fn offset_variable(offsets: &StorageSubsOffsets, variable: Variable) -> Variable { + if variable.index() < Variable::FIRST_USER_SPACE_VAR.index() { + variable + } else { + let new_index = variable.0 + offsets.utable; + Variable(new_index) + } + } + + fn offset_variable_slice( + offsets: &StorageSubsOffsets, + mut slice: VariableSubsSlice, + ) -> VariableSubsSlice { + slice.slice.start += offsets.variables; + + slice + } +} + +pub fn deep_copy_var_to( + source: &mut Subs, // mut to set the copy + target: &mut Subs, + var: Variable, +) -> Variable { + let rank = Rank::toplevel(); + + // capacity based on the false hello world program + let arena = bumpalo::Bump::with_capacity(4 * 1024); + + let mut visited = bumpalo::collections::Vec::with_capacity_in(256, &arena); + + let copy = deep_copy_var_to_help(&arena, &mut visited, source, target, rank, var); + + // we have tracked all visited variables, and can now traverse them + // in one go (without looking at the UnificationTable) and clear the copy field + for var in visited { + let descriptor = source.get_ref_mut(var); + + if descriptor.copy.is_some() { + descriptor.rank = Rank::NONE; + descriptor.mark = Mark::NONE; + descriptor.copy = OptVariable::NONE; + } + } + + copy +} + +fn deep_copy_var_to_help<'a>( + arena: &'a bumpalo::Bump, + visited: &mut bumpalo::collections::Vec<'a, Variable>, + source: &mut Subs, + target: &mut Subs, + max_rank: Rank, + var: Variable, +) -> Variable { + use bumpalo::collections::Vec; + use Content::*; + use FlatType::*; + + let desc = source.get_without_compacting(var); + + if let Some(copy) = desc.copy.into_variable() { + debug_assert!(target.contains(copy)); + return copy; + } else if desc.rank != Rank::NONE { + // DO NOTHING, Fall through + // + // The original deep_copy_var can do + // return var; + // + // but we cannot, because this `var` is in the source, not the target, and we + // should only return variables in the target + } + + visited.push(var); + + let make_descriptor = |content| Descriptor { + content, + rank: max_rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + }; + + let copy = target.fresh_unnamed_flex_var(); + + // Link the original variable to the new variable. This lets us + // avoid making multiple copies of the variable we are instantiating. + // + // Need to do this before recursively copying to avoid looping. + source.modify(var, |descriptor| { + descriptor.mark = Mark::NONE; + descriptor.copy = copy.into(); + }); + + // Now we recursively copy the content of the variable. + // We have already marked the variable as copied, so we + // will not repeat this work or crawl this variable again. + match desc.content { + Structure(flat_type) => { + let new_flat_type = match flat_type { + Apply(symbol, args) => { + let mut new_args = Vec::with_capacity_in(args.len(), arena); + + for index in args.into_iter() { + let var = source[index]; + new_args.push(deep_copy_var_to_help( + arena, visited, source, target, max_rank, var, + )); + } + + let arg_vars = VariableSubsSlice::insert_into_subs(target, new_args); + + Apply(symbol, arg_vars) + } + + Func(arg_vars, closure_var, ret_var) => { + let new_ret_var = + deep_copy_var_to_help(arena, visited, source, target, max_rank, ret_var); + + let new_closure_var = deep_copy_var_to_help( + arena, + visited, + source, + target, + max_rank, + closure_var, + ); + + let mut new_arg_vars = Vec::with_capacity_in(arg_vars.len(), arena); + + for index in arg_vars.into_iter() { + let var = source[index]; + let copy_var = + deep_copy_var_to_help(arena, visited, source, target, max_rank, var); + new_arg_vars.push(copy_var); + } + + let arg_vars = VariableSubsSlice::insert_into_subs(target, new_arg_vars); + + Func(arg_vars, new_closure_var, new_ret_var) + } + + same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same, + + Record(fields, ext_var) => { + let record_fields = { + let mut new_vars = Vec::with_capacity_in(fields.len(), arena); + + for index in fields.iter_variables() { + let var = source[index]; + let copy_var = deep_copy_var_to_help( + arena, visited, source, target, max_rank, var, + ); + + new_vars.push(copy_var); + } + + let field_names_start = target.field_names.len() as u32; + let variables_start = target.variables.len() as u32; + let field_types_start = target.record_fields.len() as u32; + + let mut length = 0; + + for ((i1, _, i3), var) in fields.iter_all().zip(new_vars) { + let record_field = source[i3].map(|_| var); + + target.field_names.push(source[i1].clone()); + target.record_fields.push(record_field.map(|_| ())); + target.variables.push(*record_field.as_inner()); + + length += 1; + } + + RecordFields { + length, + field_names_start, + variables_start, + field_types_start, + } + }; + + Record( + record_fields, + deep_copy_var_to_help(arena, visited, source, target, max_rank, ext_var), + ) + } + + TagUnion(tags, ext_var) => { + let new_ext = + deep_copy_var_to_help(arena, visited, source, target, max_rank, ext_var); + + let mut new_variable_slices = Vec::with_capacity_in(tags.len(), arena); + + let mut new_variables = Vec::with_capacity_in(tags.len(), arena); + for index in tags.variables() { + let slice = source[index]; + for var_index in slice { + let var = source[var_index]; + let new_var = deep_copy_var_to_help( + arena, visited, source, target, max_rank, var, + ); + new_variables.push(new_var); + } + + let new_slice = + VariableSubsSlice::insert_into_subs(target, new_variables.drain(..)); + + new_variable_slices.push(new_slice); + } + + let new_variables = { + let start = target.variable_slices.len() as u32; + let length = new_variable_slices.len() as u16; + target.variable_slices.extend(new_variable_slices); + + SubsSlice::new(start, length) + }; + + let new_tag_names = { + let tag_names = tags.tag_names(); + let slice = &source.tag_names[tag_names.start as usize..] + [..tag_names.length as usize]; + + let start = target.tag_names.len() as u32; + let length = tag_names.len() as u16; + + target.tag_names.extend(slice.iter().cloned()); + + SubsSlice::new(start, length) + }; + + let union_tags = UnionTags::from_slices(new_tag_names, new_variables); + + TagUnion(union_tags, new_ext) + } + + FunctionOrTagUnion(tag_name, symbol, ext_var) => { + let new_tag_name = SubsIndex::new(target.tag_names.len() as u32); + + target.tag_names.push(source[tag_name].clone()); + + FunctionOrTagUnion( + new_tag_name, + symbol, + deep_copy_var_to_help(arena, visited, source, target, max_rank, ext_var), + ) + } + + RecursiveTagUnion(rec_var, tags, ext_var) => { + let mut new_variable_slices = Vec::with_capacity_in(tags.len(), arena); + + let mut new_variables = Vec::with_capacity_in(tags.len(), arena); + for index in tags.variables() { + let slice = source[index]; + for var_index in slice { + let var = source[var_index]; + let new_var = deep_copy_var_to_help( + arena, visited, source, target, max_rank, var, + ); + new_variables.push(new_var); + } + + let new_slice = + VariableSubsSlice::insert_into_subs(target, new_variables.drain(..)); + + new_variable_slices.push(new_slice); + } + + let new_variables = { + let start = target.variable_slices.len() as u32; + let length = new_variable_slices.len() as u16; + target.variable_slices.extend(new_variable_slices); + + SubsSlice::new(start, length) + }; + + let new_tag_names = { + let tag_names = tags.tag_names(); + let slice = &source.tag_names[tag_names.start as usize..] + [..tag_names.length as usize]; + + let start = target.tag_names.len() as u32; + let length = tag_names.len() as u16; + + target.tag_names.extend(slice.iter().cloned()); + + SubsSlice::new(start, length) + }; + + let union_tags = UnionTags::from_slices(new_tag_names, new_variables); + + let new_ext = + deep_copy_var_to_help(arena, visited, source, target, max_rank, ext_var); + let new_rec_var = + deep_copy_var_to_help(arena, visited, source, target, max_rank, rec_var); + + RecursiveTagUnion(new_rec_var, union_tags, new_ext) + } + }; + + target.set(copy, make_descriptor(Structure(new_flat_type))); + + copy + } + + FlexVar(_) | Error => copy, + + RecursionVar { + opt_name, + structure, + } => { + let new_structure = + deep_copy_var_to_help(arena, visited, source, target, max_rank, structure); + + debug_assert!((new_structure.index() as usize) < target.len()); + + target.set( + copy, + make_descriptor(RecursionVar { + opt_name, + structure: new_structure, + }), + ); + + copy + } + + RigidVar(name) => { + target.set(copy, make_descriptor(FlexVar(Some(name)))); + + copy + } + + Alias(symbol, mut args, real_type_var) => { + let mut new_vars = Vec::with_capacity_in(args.variables().len(), arena); + + for var_index in args.variables() { + let var = source[var_index]; + let new_var = deep_copy_var_to_help(arena, visited, source, target, max_rank, var); + + new_vars.push(new_var); + } + + args.replace_variables(target, new_vars); + + let lowercases = &source.field_names[args.lowercases_start as usize..] + [..args.lowercases_len as usize]; + + args.lowercases_start = target.field_names.len() as u32; + target.field_names.extend(lowercases.iter().cloned()); + + let new_real_type_var = + deep_copy_var_to_help(arena, visited, source, target, max_rank, real_type_var); + let new_content = Alias(symbol, args, new_real_type_var); + + target.set(copy, make_descriptor(new_content)); + + copy } } } diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 3ccefd5fd3..edcf6dbaab 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -3,6 +3,7 @@ use crate::subs::{ GetSubsSlice, RecordFields, Subs, UnionTags, VarStore, Variable, VariableSubsSlice, }; use roc_collections::all::{ImMap, ImSet, Index, MutSet, SendMap}; +use roc_module::called_via::CalledVia; use roc_module::ident::{ForeignSymbol, Ident, Lowercase, TagName}; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, ModuleId, Symbol}; @@ -1134,7 +1135,7 @@ pub enum Reason { #[derive(PartialEq, Debug, Clone)] pub enum Category { Lookup(Symbol), - CallResult(Option), + CallResult(Option, CalledVia), LowLevelOpResult(LowLevel), ForeignCall, TagApply { diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 51771efe07..f469b0be1c 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -133,12 +133,14 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { // println!("\n --- \n"); // dbg!(ctx.second, type2); // println!("\n --------------- \n"); + let content_1 = subs.get(ctx.first).content; + let content_2 = subs.get(ctx.second).content; println!( "{:?} {:?} ~ {:?} {:?}", ctx.first, - subs.get(ctx.first).content, + roc_types::subs::SubsFmtContent(&content_1, subs), ctx.second, - subs.get(ctx.second).content + roc_types::subs::SubsFmtContent(&content_2, subs), ); } match &ctx.first_desc.content { diff --git a/docs/tests/insert_syntax_highlighting.rs b/docs/tests/insert_syntax_highlighting.rs index 1c33d8ba71..e211786673 100644 --- a/docs/tests/insert_syntax_highlighting.rs +++ b/docs/tests/insert_syntax_highlighting.rs @@ -73,19 +73,19 @@ main = "Hello, world!" PathBuf::from([Uuid::new_v4().to_string(), ".roc".to_string()].join("")); let temp_file_full_path = temp_dir.path().join(temp_file_path_buf); - let mut file = File::create(temp_file_full_path.clone()).expect(&format!( - "Failed to create temporary file for path {:?}", - temp_file_full_path - )); + let mut file = File::create(temp_file_full_path.clone()).unwrap_or_else(|_| { + panic!( + "Failed to create temporary file for path {:?}", + temp_file_full_path + ) + }); let mut full_code_str = HELLO_WORLD.to_owned(); full_code_str.push_str("\n\n"); full_code_str.push_str(code_str); - writeln!(file, "{}", full_code_str).expect(&format!( - "Failed to write {:?} to file: {:?}", - HELLO_WORLD, file - )); + writeln!(file, "{}", full_code_str) + .unwrap_or_else(|_| panic!("Failed to write {:?} to file: {:?}", HELLO_WORLD, file)); load_module(&temp_file_full_path) } diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 69c00607c2..8cf82eec86 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -29,7 +29,7 @@ roc_module = { path = "../compiler/module" } roc_problem = { path = "../compiler/problem" } roc_types = { path = "../compiler/types" } roc_unify = { path = "../compiler/unify" } -roc_reporting = { path = "../compiler/reporting" } +roc_reporting = { path = "../reporting" } roc_solve = { path = "../compiler/solve" } ven_graph = { path = "../vendor/pathfinding" } bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/editor/README.md b/editor/README.md index cbae3eb8ff..c5b3116e45 100644 --- a/editor/README.md +++ b/editor/README.md @@ -5,7 +5,7 @@ Unlike most editors, we use projectional or structural editing to edit the [Abst ## Getting started -- Install the compiler, see [here](../BUILDING_FROM_SOURCE). +- Install the compiler, see [here](../BUILDING_FROM_SOURCE.md). - Run the following from the roc folder: ``` diff --git a/editor/src/editor/mvc/ed_model.rs b/editor/src/editor/mvc/ed_model.rs index db0d92433b..8749fb5f99 100644 --- a/editor/src/editor/mvc/ed_model.rs +++ b/editor/src/editor/mvc/ed_model.rs @@ -290,14 +290,14 @@ pub mod test_ed_model { PathBuf::from([Uuid::new_v4().to_string(), ".roc".to_string()].join("")); let temp_file_full_path = temp_dir.path().join(temp_file_path_buf); - let mut file = File::create(temp_file_full_path.clone()).expect(&format!( - "Failed to create temporary file for path {:?}", - temp_file_full_path - )); - writeln!(file, "{}", clean_code_str).expect(&format!( - "Failed to write {:?} to file: {:?}", - clean_code_str, file - )); + let mut file = File::create(temp_file_full_path.clone()).unwrap_or_else(|_| { + panic!( + "Failed to create temporary file for path {:?}", + temp_file_full_path + ) + }); + writeln!(file, "{}", clean_code_str) + .unwrap_or_else(|_| panic!("Failed to write {:?} to file: {:?}", clean_code_str, file)); let loaded_module = load_module(&temp_file_full_path); diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index e44d22166e..351d101d2e 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -501,7 +501,7 @@ impl<'a> EdModel<'a> { }; let arena = Bump::new(); - 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); @@ -1453,7 +1453,7 @@ pub mod test_ed_update { // add newlines like the editor's formatting would add them fn add_nls(lines: Vec) -> Vec { - let mut new_lines = lines.clone(); + let mut new_lines = lines; new_lines.append(&mut vec!["".to_owned(), "".to_owned()]); diff --git a/examples/cli/cli-example b/examples/cli/cli-example deleted file mode 100755 index 28e07a40f7..0000000000 Binary files a/examples/cli/cli-example and /dev/null differ diff --git a/examples/cli/hello-world b/examples/cli/hello-world deleted file mode 100755 index c3248ebe2c..0000000000 Binary files a/examples/cli/hello-world and /dev/null differ diff --git a/examples/hello-rust/hello-world b/examples/hello-rust/hello-world deleted file mode 100755 index 04439a158d..0000000000 Binary files a/examples/hello-rust/hello-world and /dev/null differ diff --git a/linker/src/lib.rs b/linker/src/lib.rs index 769ba7011e..9bd11cecb6 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -142,10 +142,17 @@ pub fn build_and_preprocess_host( target: &Triple, host_input_path: &Path, exposed_to_host: Vec, + target_valgrind: bool, ) -> io::Result<()> { let dummy_lib = host_input_path.with_file_name("libapp.so"); generate_dynamic_lib(target, exposed_to_host, &dummy_lib)?; - rebuild_host(opt_level, target, host_input_path, Some(&dummy_lib)); + rebuild_host( + opt_level, + target, + host_input_path, + Some(&dummy_lib), + target_valgrind, + ); let dynhost = host_input_path.with_file_name("dynhost"); let metadata = host_input_path.with_file_name("metadata"); let prehost = host_input_path.with_file_name("preprocessedhost"); diff --git a/nix/sources.json b/nix/sources.json index ed41deee13..51fc580d86 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -17,10 +17,10 @@ "homepage": "", "owner": "NixOS", "repo": "nixpkgs", - "rev": "51acb65b302551ac7993b437cc6863fe9fa8ae50", - "sha256": "0si8s2ji4prp614q3050x4sp282wxgp0mm5q50slcf5f75jw5yhh", + "rev": "5cb226a06c49f7a2d02863d0b5786a310599df6b", + "sha256": "0dzz207swwm5m0dyibhxg5psccrcqfh1lzkmzzfns27wc4ria6z3", "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/51acb65b302551ac7993b437cc6863fe9fa8ae50.tar.gz", + "url": "https://github.com/NixOS/nixpkgs/archive/5cb226a06c49f7a2d02863d0b5786a310599df6b.tar.gz", "url_template": "https://github.com///archive/.tar.gz" } } diff --git a/reporting/Cargo.toml b/reporting/Cargo.toml new file mode 100644 index 0000000000..7fa1d2cfee --- /dev/null +++ b/reporting/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "roc_reporting" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" + +[dependencies] +roc_collections = { path = "../compiler/collections" } +roc_region = { path = "../compiler/region" } +roc_module = { path = "../compiler/module" } +roc_parse = { path = "../compiler/parse" } +roc_problem = { path = "../compiler/problem" } +roc_types = { path = "../compiler/types" } +roc_can = { path = "../compiler/can" } +roc_solve = { path = "../compiler/solve" } +roc_mono = { path = "../compiler/mono" } +ven_pretty = { path = "../vendor/pretty" } +distance = "0.4.0" +bumpalo = { version = "3.8.0", features = ["collections"] } + +[dev-dependencies] +roc_constrain = { path = "../compiler/constrain" } +roc_builtins = { path = "../compiler/builtins" } +roc_problem = { path = "../compiler/problem" } +roc_parse = { path = "../compiler/parse" } +pretty_assertions = "1.0.0" +indoc = "1.0.3" diff --git a/compiler/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs similarity index 97% rename from compiler/reporting/src/error/canonicalize.rs rename to reporting/src/error/canonicalize.rs index 13d3406593..a3e5d7186a 100644 --- a/compiler/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -6,6 +6,7 @@ use roc_problem::can::{BadPattern, FloatErrorKind, IntErrorKind, Problem, Runtim use roc_region::all::{Located, Region}; use std::path::PathBuf; +use crate::error::r#type::suggest; use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; use ven_pretty::DocAllocator; @@ -874,16 +875,36 @@ fn pretty_runtime_error<'b>( module_name, ident, region, + exposed_values, } => { + let mut suggestions = suggest::sort(ident.as_ref(), exposed_values); + suggestions.truncate(4); + + let did_you_mean = if suggestions.is_empty() { + alloc.concat(vec![ + alloc.reflow("In fact, it looks like "), + alloc.module_name(module_name.clone()), + alloc.reflow(" doesn't expose any values!"), + ]) + } else { + let qualified_suggestions = suggestions + .into_iter() + .map(|v| alloc.string(module_name.to_string() + "." + v.as_str())); + alloc.stack(vec![ + alloc.reflow("Did you mean one of these?"), + alloc.vcat(qualified_suggestions).indent(4), + ]) + }; doc = alloc.stack(vec![ alloc.concat(vec![ alloc.reflow("The "), alloc.module_name(module_name), - alloc.reflow(" module does not expose a "), + alloc.reflow(" module does not expose `"), alloc.string(ident.to_string()), - alloc.reflow(" value:"), + alloc.reflow("`:"), ]), alloc.region(region), + did_you_mean, ]); title = VALUE_NOT_EXPOSED; @@ -1176,8 +1197,6 @@ fn not_found<'b>( thing: &'b str, options: MutSet>, ) -> RocDocBuilder<'b> { - use crate::error::r#type::suggest; - let mut suggestions = suggest::sort( name.as_inline_str().as_str(), options.iter().map(|v| v.as_ref()).collect(), @@ -1225,8 +1244,6 @@ fn module_not_found<'b>( name: &ModuleName, options: MutSet>, ) -> RocDocBuilder<'b> { - use crate::error::r#type::suggest; - let mut suggestions = suggest::sort(name.as_str(), options.iter().map(|v| v.as_ref()).collect()); suggestions.truncate(4); diff --git a/compiler/reporting/src/error/mod.rs b/reporting/src/error/mod.rs similarity index 100% rename from compiler/reporting/src/error/mod.rs rename to reporting/src/error/mod.rs diff --git a/compiler/reporting/src/error/mono.rs b/reporting/src/error/mono.rs similarity index 100% rename from compiler/reporting/src/error/mono.rs rename to reporting/src/error/mono.rs diff --git a/compiler/reporting/src/error/parse.rs b/reporting/src/error/parse.rs similarity index 96% rename from compiler/reporting/src/error/parse.rs rename to reporting/src/error/parse.rs index 2076ee81cc..505e41a736 100644 --- a/compiler/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -1019,16 +1019,16 @@ fn to_list_report<'a>( alloc: &'a RocDocAllocator<'a>, filename: PathBuf, context: Context, - parse_problem: &roc_parse::parser::List<'a>, + parse_problem: &roc_parse::parser::EList<'a>, start_row: Row, start_col: Col, ) -> Report<'a> { - use roc_parse::parser::List; + use roc_parse::parser::EList; match *parse_problem { - List::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), + EList::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), - List::Expr(expr, row, col) => to_expr_report( + EList::Expr(expr, row, col) => to_expr_report( alloc, filename, Context::InNode(Node::ListElement, start_row, start_col, Box::new(context)), @@ -1037,7 +1037,7 @@ fn to_list_report<'a>( col, ), - List::Open(row, col) | List::End(row, col) => { + EList::Open(row, col) | EList::End(row, col) => { match what_is_next(alloc.src_lines, row, col) { Next::Other(Some(',')) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); @@ -1098,7 +1098,7 @@ fn to_list_report<'a>( } } - List::IndentOpen(row, col) | List::IndentEnd(row, col) => { + EList::IndentOpen(row, col) | EList::IndentEnd(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = Region::from_row_col(row, col); @@ -1130,16 +1130,16 @@ fn to_if_report<'a>( alloc: &'a RocDocAllocator<'a>, filename: PathBuf, context: Context, - parse_problem: &roc_parse::parser::If<'a>, + parse_problem: &roc_parse::parser::EIf<'a>, start_row: Row, start_col: Col, ) -> Report<'a> { - use roc_parse::parser::If; + use roc_parse::parser::EIf; match *parse_problem { - If::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), + EIf::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), - If::Condition(expr, row, col) => to_expr_report( + EIf::Condition(expr, row, col) => to_expr_report( alloc, filename, Context::InNode(Node::IfCondition, start_row, start_col, Box::new(context)), @@ -1148,7 +1148,7 @@ fn to_if_report<'a>( col, ), - If::ThenBranch(expr, row, col) => to_expr_report( + EIf::ThenBranch(expr, row, col) => to_expr_report( alloc, filename, Context::InNode(Node::IfThenBranch, start_row, start_col, Box::new(context)), @@ -1157,7 +1157,7 @@ fn to_if_report<'a>( col, ), - If::ElseBranch(expr, row, col) => to_expr_report( + EIf::ElseBranch(expr, row, col) => to_expr_report( alloc, filename, Context::InNode(Node::IfElseBranch, start_row, start_col, Box::new(context)), @@ -1166,10 +1166,10 @@ fn to_if_report<'a>( col, ), - If::If(_row, _col) => unreachable!("another branch would be taken"), - If::IndentIf(_row, _col) => unreachable!("another branch would be taken"), + EIf::If(_row, _col) => unreachable!("another branch would be taken"), + EIf::IndentIf(_row, _col) => unreachable!("another branch would be taken"), - If::Then(row, col) | If::IndentThenBranch(row, col) | If::IndentThenToken(row, col) => { + EIf::Then(row, col) | EIf::IndentThenBranch(row, col) | EIf::IndentThenToken(row, col) => { to_unfinished_if_report( alloc, filename, @@ -1185,7 +1185,7 @@ fn to_if_report<'a>( ) } - If::Else(row, col) | If::IndentElseBranch(row, col) | If::IndentElseToken(row, col) => { + EIf::Else(row, col) | EIf::IndentElseBranch(row, col) | EIf::IndentElseToken(row, col) => { to_unfinished_if_report( alloc, filename, @@ -1201,7 +1201,7 @@ fn to_if_report<'a>( ) } - If::IndentCondition(row, col) => to_unfinished_if_report( + EIf::IndentCondition(row, col) => to_unfinished_if_report( alloc, filename, row, @@ -1249,14 +1249,14 @@ fn to_when_report<'a>( alloc: &'a RocDocAllocator<'a>, filename: PathBuf, context: Context, - parse_problem: &roc_parse::parser::When<'a>, + parse_problem: &roc_parse::parser::EWhen<'a>, start_row: Row, start_col: Col, ) -> Report<'a> { - use roc_parse::parser::When; + use roc_parse::parser::EWhen; match *parse_problem { - When::IfGuard(nested, row, col) => match what_is_next(alloc.src_lines, row, col) { + EWhen::IfGuard(nested, row, col) => match what_is_next(alloc.src_lines, row, col) { Next::Token("->") => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = Region::from_row_col(row, col); @@ -1287,7 +1287,7 @@ fn to_when_report<'a>( col, ), }, - When::Arrow(row, col) => { + EWhen::Arrow(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = Region::from_row_col(row, col); @@ -1310,9 +1310,9 @@ fn to_when_report<'a>( } } - When::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), + EWhen::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), - When::Branch(expr, row, col) => to_expr_report( + EWhen::Branch(expr, row, col) => to_expr_report( alloc, filename, Context::InNode(Node::WhenBranch, start_row, start_col, Box::new(context)), @@ -1321,7 +1321,7 @@ fn to_when_report<'a>( col, ), - When::Condition(expr, row, col) => to_expr_report( + EWhen::Condition(expr, row, col) => to_expr_report( alloc, filename, Context::InNode(Node::WhenCondition, start_row, start_col, Box::new(context)), @@ -1330,7 +1330,7 @@ fn to_when_report<'a>( col, ), - When::Bar(row, col) => to_unfinished_when_report( + EWhen::Bar(row, col) => to_unfinished_when_report( alloc, filename, row, @@ -1344,10 +1344,10 @@ fn to_when_report<'a>( ]), ), - When::IfToken(_row, _col) => unreachable!("the if-token is optional"), - When::When(_row, _col) => unreachable!("another branch would be taken"), + EWhen::IfToken(_row, _col) => unreachable!("the if-token is optional"), + EWhen::When(_row, _col) => unreachable!("another branch would be taken"), - When::Is(row, col) | When::IndentIs(row, col) => to_unfinished_when_report( + EWhen::Is(row, col) | EWhen::IndentIs(row, col) => to_unfinished_when_report( alloc, filename, row, @@ -1361,7 +1361,7 @@ fn to_when_report<'a>( ]), ), - When::IndentCondition(row, col) => to_unfinished_when_report( + EWhen::IndentCondition(row, col) => to_unfinished_when_report( alloc, filename, row, @@ -1373,7 +1373,7 @@ fn to_when_report<'a>( ]), ), - When::IndentPattern(row, col) => to_unfinished_when_report( + EWhen::IndentPattern(row, col) => to_unfinished_when_report( alloc, filename, row, @@ -1383,7 +1383,7 @@ fn to_when_report<'a>( alloc.concat(vec![alloc.reflow(r"I was expecting to see a pattern next")]), ), - When::IndentArrow(row, col) => to_unfinished_when_report( + EWhen::IndentArrow(row, col) => to_unfinished_when_report( alloc, filename, row, @@ -1397,7 +1397,7 @@ fn to_when_report<'a>( ]), ), - When::IndentIfGuard(row, col) => to_unfinished_when_report( + EWhen::IndentIfGuard(row, col) => to_unfinished_when_report( alloc, filename, row, @@ -1411,7 +1411,7 @@ fn to_when_report<'a>( ]), ), - When::IndentBranch(row, col) => to_unfinished_when_report( + EWhen::IndentBranch(row, col) => to_unfinished_when_report( alloc, filename, row, @@ -1424,7 +1424,7 @@ fn to_when_report<'a>( ]), ), - When::PatternAlignment(indent, row, col) => to_unfinished_when_report( + EWhen::PatternAlignment(indent, row, col) => to_unfinished_when_report( alloc, filename, row, @@ -1437,7 +1437,7 @@ fn to_when_report<'a>( alloc.reflow(" spaces)"), ]), ), - When::Pattern(ref pat, row, col) => to_pattern_report(alloc, filename, pat, row, col), + EWhen::Pattern(ref pat, row, col) => to_pattern_report(alloc, filename, pat, row, col), } } @@ -2000,23 +2000,23 @@ fn to_pattern_in_parens_report<'a>( fn to_type_report<'a>( alloc: &'a RocDocAllocator<'a>, filename: PathBuf, - parse_problem: &roc_parse::parser::Type<'a>, + parse_problem: &roc_parse::parser::EType<'a>, start_row: Row, start_col: Col, ) -> Report<'a> { - use roc_parse::parser::Type; + use roc_parse::parser::EType; match parse_problem { - Type::TRecord(record, row, col) => to_trecord_report(alloc, filename, record, *row, *col), - Type::TTagUnion(tag_union, row, col) => { + EType::TRecord(record, row, col) => to_trecord_report(alloc, filename, record, *row, *col), + EType::TTagUnion(tag_union, row, col) => { to_ttag_union_report(alloc, filename, tag_union, *row, *col) } - Type::TInParens(tinparens, row, col) => { + EType::TInParens(tinparens, row, col) => { to_tinparens_report(alloc, filename, tinparens, *row, *col) } - Type::TApply(tapply, row, col) => to_tapply_report(alloc, filename, tapply, *row, *col), + EType::TApply(tapply, row, col) => to_tapply_report(alloc, filename, tapply, *row, *col), - Type::TFunctionArgument(row, col) => match what_is_next(alloc.src_lines, *row, *col) { + EType::TFunctionArgument(row, col) => match what_is_next(alloc.src_lines, *row, *col) { Next::Other(Some(',')) => { let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); let region = Region::from_row_col(*row, *col); @@ -2037,7 +2037,7 @@ fn to_type_report<'a>( _ => todo!(), }, - Type::TStart(row, col) => { + EType::TStart(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); let region = Region::from_row_col(*row, *col); @@ -2061,7 +2061,7 @@ fn to_type_report<'a>( } } - Type::TIndentStart(row, col) => { + EType::TIndentStart(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); let region = Region::from_row_col(*row, *col); @@ -2079,7 +2079,7 @@ fn to_type_report<'a>( } } - Type::TIndentEnd(row, col) => { + EType::TIndentEnd(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); let region = Region::from_row_col(*row, *col); @@ -2097,7 +2097,7 @@ fn to_type_report<'a>( } } - Type::TAsIndentStart(row, col) => { + EType::TAsIndentStart(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); let region = Region::from_row_col(*row, *col); @@ -2115,7 +2115,7 @@ fn to_type_report<'a>( } } - Type::TBadTypeVariable(row, col) => { + EType::TBadTypeVariable(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); let region = Region::from_row_col(*row, *col); @@ -2139,14 +2139,14 @@ fn to_type_report<'a>( fn to_trecord_report<'a>( alloc: &'a RocDocAllocator<'a>, filename: PathBuf, - parse_problem: &roc_parse::parser::TRecord<'a>, + parse_problem: &roc_parse::parser::ETypeRecord<'a>, start_row: Row, start_col: Col, ) -> Report<'a> { - use roc_parse::parser::TRecord; + use roc_parse::parser::ETypeRecord; match *parse_problem { - TRecord::Open(row, col) => match what_is_next(alloc.src_lines, row, col) { + ETypeRecord::Open(row, col) => match what_is_next(alloc.src_lines, row, col) { Next::Keyword(keyword) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = to_keyword_region(row, col, keyword); @@ -2191,7 +2191,7 @@ fn to_trecord_report<'a>( } }, - TRecord::End(row, col) => { + ETypeRecord::End(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = Region::from_row_col(row, col); @@ -2237,7 +2237,7 @@ fn to_trecord_report<'a>( } } - TRecord::Field(row, col) => match what_is_next(alloc.src_lines, row, col) { + ETypeRecord::Field(row, col) => match what_is_next(alloc.src_lines, row, col) { Next::Keyword(keyword) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = to_keyword_region(row, col, keyword); @@ -2286,16 +2286,16 @@ fn to_trecord_report<'a>( } }, - TRecord::Colon(_, _) => { + ETypeRecord::Colon(_, _) => { unreachable!("because `{ foo }` is a valid field; the colon is not required") } - TRecord::Optional(_, _) => { + ETypeRecord::Optional(_, _) => { unreachable!("because `{ foo }` is a valid field; the question mark is not required") } - TRecord::Type(tipe, row, col) => to_type_report(alloc, filename, tipe, row, col), + ETypeRecord::Type(tipe, row, col) => to_type_report(alloc, filename, tipe, row, col), - TRecord::IndentOpen(row, col) => { + ETypeRecord::IndentOpen(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = Region::from_row_col(row, col); @@ -2318,7 +2318,7 @@ fn to_trecord_report<'a>( } } - TRecord::IndentEnd(row, col) => { + ETypeRecord::IndentEnd(row, col) => { match next_line_starts_with_close_curly(alloc.src_lines, row) { Some((curly_row, curly_col)) => { let surroundings = @@ -2370,29 +2370,29 @@ fn to_trecord_report<'a>( } } - TRecord::IndentColon(_, _) => { + ETypeRecord::IndentColon(_, _) => { unreachable!("because `{ foo }` is a valid field; the colon is not required") } - TRecord::IndentOptional(_, _) => { + ETypeRecord::IndentOptional(_, _) => { unreachable!("because `{ foo }` is a valid field; the question mark is not required") } - TRecord::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), + ETypeRecord::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), } } fn to_ttag_union_report<'a>( alloc: &'a RocDocAllocator<'a>, filename: PathBuf, - parse_problem: &roc_parse::parser::TTagUnion<'a>, + parse_problem: &roc_parse::parser::ETypeTagUnion<'a>, start_row: Row, start_col: Col, ) -> Report<'a> { - use roc_parse::parser::TTagUnion; + use roc_parse::parser::ETypeTagUnion; match *parse_problem { - TTagUnion::Open(row, col) => match what_is_next(alloc.src_lines, row, col) { + ETypeTagUnion::Open(row, col) => match what_is_next(alloc.src_lines, row, col) { Next::Keyword(keyword) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = to_keyword_region(row, col, keyword); @@ -2459,7 +2459,7 @@ fn to_ttag_union_report<'a>( } }, - TTagUnion::End(row, col) => { + ETypeTagUnion::End(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = Region::from_row_col(row, col); @@ -2523,9 +2523,9 @@ fn to_ttag_union_report<'a>( } } - TTagUnion::Type(tipe, row, col) => to_type_report(alloc, filename, tipe, row, col), + ETypeTagUnion::Type(tipe, row, col) => to_type_report(alloc, filename, tipe, row, col), - TTagUnion::IndentOpen(row, col) => { + ETypeTagUnion::IndentOpen(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = Region::from_row_col(row, col); @@ -2548,7 +2548,7 @@ fn to_ttag_union_report<'a>( } } - TTagUnion::IndentEnd(row, col) => { + ETypeTagUnion::IndentEnd(row, col) => { match next_line_starts_with_close_square_bracket(alloc.src_lines, row) { Some((curly_row, curly_col)) => { let surroundings = @@ -2600,21 +2600,21 @@ fn to_ttag_union_report<'a>( } } - TTagUnion::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), + ETypeTagUnion::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), } } fn to_tinparens_report<'a>( alloc: &'a RocDocAllocator<'a>, filename: PathBuf, - parse_problem: &roc_parse::parser::TInParens<'a>, + parse_problem: &roc_parse::parser::ETypeInParens<'a>, start_row: Row, start_col: Col, ) -> Report<'a> { - use roc_parse::parser::TInParens; + use roc_parse::parser::ETypeInParens; match *parse_problem { - TInParens::Open(row, col) => { + ETypeInParens::Open(row, col) => { match what_is_next(alloc.src_lines, row, col) { Next::Keyword(keyword) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); @@ -2686,7 +2686,7 @@ fn to_tinparens_report<'a>( } } - TInParens::End(row, col) => { + ETypeInParens::End(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = Region::from_row_col(row, col); @@ -2734,9 +2734,9 @@ fn to_tinparens_report<'a>( } } - TInParens::Type(tipe, row, col) => to_type_report(alloc, filename, tipe, row, col), + ETypeInParens::Type(tipe, row, col) => to_type_report(alloc, filename, tipe, row, col), - TInParens::IndentOpen(row, col) => { + ETypeInParens::IndentOpen(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = Region::from_row_col(row, col); @@ -2760,7 +2760,7 @@ fn to_tinparens_report<'a>( } } - TInParens::IndentEnd(row, col) => { + ETypeInParens::IndentEnd(row, col) => { match next_line_starts_with_close_parenthesis(alloc.src_lines, row) { Some((curly_row, curly_col)) => { let surroundings = @@ -2812,21 +2812,21 @@ fn to_tinparens_report<'a>( } } - TInParens::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), + ETypeInParens::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), } } fn to_tapply_report<'a>( alloc: &'a RocDocAllocator<'a>, filename: PathBuf, - parse_problem: &roc_parse::parser::TApply, + parse_problem: &roc_parse::parser::ETypeApply, _start_row: Row, _start_col: Col, ) -> Report<'a> { - use roc_parse::parser::TApply; + use roc_parse::parser::ETypeApply; match *parse_problem { - TApply::DoubleDot(row, col) => { + ETypeApply::DoubleDot(row, col) => { let region = Region::from_row_col(row, col); let doc = alloc.stack(vec![ @@ -2842,7 +2842,7 @@ fn to_tapply_report<'a>( severity: Severity::RuntimeError, } } - TApply::TrailingDot(row, col) => { + ETypeApply::TrailingDot(row, col) => { let region = Region::from_row_col(row, col); let doc = alloc.stack(vec![ @@ -2864,7 +2864,7 @@ fn to_tapply_report<'a>( severity: Severity::RuntimeError, } } - TApply::StartIsNumber(row, col) => { + ETypeApply::StartIsNumber(row, col) => { let region = Region::from_row_col(row, col); let doc = alloc.stack(vec![ @@ -2886,7 +2886,7 @@ fn to_tapply_report<'a>( severity: Severity::RuntimeError, } } - TApply::StartNotUppercase(row, col) => { + ETypeApply::StartNotUppercase(row, col) => { let region = Region::from_row_col(row, col); let doc = alloc.stack(vec![ @@ -2909,7 +2909,7 @@ fn to_tapply_report<'a>( } } - TApply::End(row, col) => { + ETypeApply::End(row, col) => { let region = Region::from_row_col(row, col); let doc = alloc.stack(vec![ @@ -2927,7 +2927,7 @@ fn to_tapply_report<'a>( } } - TApply::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), + ETypeApply::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), } } @@ -3093,6 +3093,7 @@ fn to_provides_report<'a>( use roc_parse::parser::EProvides; match *parse_problem { + EProvides::ListEnd(row, col) | // TODO: give this its own error message EProvides::Identifier(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = Region::from_row_col(row, col); @@ -3158,6 +3159,7 @@ fn to_exposes_report<'a>( use roc_parse::parser::EExposes; match *parse_problem { + EExposes::ListEnd(row, col) | // TODO: give this its own error message EExposes::Identifier(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = Region::from_row_col(row, col); diff --git a/compiler/reporting/src/error/type.rs b/reporting/src/error/type.rs similarity index 99% rename from compiler/reporting/src/error/type.rs rename to reporting/src/error/type.rs index 7b8049aa14..f80f3b63c9 100644 --- a/compiler/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -1,5 +1,6 @@ use roc_can::expected::{Expected, PExpected}; use roc_collections::all::{Index, MutSet, SendMap}; +use roc_module::called_via::{BinOp, CalledVia}; use roc_module::ident::{Ident, IdentStr, Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; @@ -1043,13 +1044,26 @@ fn add_category<'b>( alloc.record_field(field.to_owned()), alloc.text(" is a:"), ]), - - CallResult(Some(symbol)) => alloc.concat(vec![ + CallResult( + Some(_), + CalledVia::BinOp( + BinOp::Equals + | BinOp::NotEquals + | BinOp::LessThan + | BinOp::GreaterThan + | BinOp::LessThanOrEq + | BinOp::GreaterThanOrEq, + ), + ) => alloc.concat(vec![alloc.text("This comparison produces:")]), + CallResult(Some(_), CalledVia::StringInterpolation) => { + alloc.concat(vec![this_is, alloc.text(" a string of type:")]) + } + CallResult(Some(symbol), _) => alloc.concat(vec![ alloc.text("This "), alloc.symbol_foreign_qualified(*symbol), alloc.text(" call produces:"), ]), - CallResult(None) => alloc.concat(vec![this_is, alloc.text(":")]), + CallResult(None, _) => alloc.concat(vec![this_is, alloc.text(":")]), LowLevelOpResult(op) => { panic!( "Compiler bug: invalid return type from low-level op {:?}", diff --git a/reporting/src/lib.rs b/reporting/src/lib.rs new file mode 100644 index 0000000000..1f7520b931 --- /dev/null +++ b/reporting/src/lib.rs @@ -0,0 +1,38 @@ +#![warn(clippy::dbg_macro)] +// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. +#![allow(clippy::large_enum_variant)] + +pub mod error; +pub mod report; + +/// `internal_error!` should be used whenever a compiler invariant is broken. +/// It is a wrapper around panic that tells the user to file a bug. +/// This should only be used in cases where there would be a compiler bug and the user can't fix it. +/// If there is simply an unimplemented feature, please use `unimplemented!` +/// If there is a user error, please use roc_reporting to print a nice error message. +#[macro_export] +macro_rules! internal_error { + ($($arg:tt)*) => ({ + eprintln!("An internal compiler expectation was broken."); + eprintln!("This is definitely a compiler bug."); + // TODO: update this to the new bug template. + eprintln!("Please file an issue here: https://github.com/rtfeldman/roc/issues/new/choose"); + #[allow(clippy::panic)] { + panic!($($arg)*); + } + }) +} + +/// `user_error!` should only ever be used temporarily. +/// It is a way to document locations where we do not yet have nice error reporting. +/// All cases of `user_error!` should eventually be replaced with pretty error printing using roc_reporting. +#[macro_export] +macro_rules! user_error { + ($($arg:tt)*) => ({ + eprintln!("We ran into an issue while compiling your code."); + eprintln!("Sadly, we don't havs a pretty error message for this case yet."); + eprintln!("If you can't figure out the problem from the context below, please reach out at: https://roc.zulipchat.com/"); + eprintln!($($arg)*); + std::process::exit(1); + }) +} diff --git a/compiler/reporting/src/report.rs b/reporting/src/report.rs similarity index 99% rename from compiler/reporting/src/report.rs rename to reporting/src/report.rs index cce7db7f3d..8832ac27aa 100644 --- a/compiler/reporting/src/report.rs +++ b/reporting/src/report.rs @@ -353,7 +353,7 @@ impl<'a> RocDocAllocator<'a> { pub fn binop( &'a self, - content: roc_module::operator::BinOp, + content: roc_module::called_via::BinOp, ) -> DocBuilder<'a, Self, Annotation> { self.text(content.to_string()).annotate(Annotation::BinOp) } diff --git a/compiler/reporting/tests/helpers/mod.rs b/reporting/tests/helpers/mod.rs similarity index 100% rename from compiler/reporting/tests/helpers/mod.rs rename to reporting/tests/helpers/mod.rs diff --git a/compiler/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs similarity index 98% rename from compiler/reporting/tests/test_reporting.rs rename to reporting/tests/test_reporting.rs index 14fb7279ad..c3a623c2c8 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -66,7 +66,7 @@ mod test_reporting { problems: can_problems, .. } = can_expr(arena, expr_src)?; - let mut subs = Subs::new(var_store); + let mut subs = Subs::new_from_varstore(var_store); for (var, name) in output.introduced_variables.name_by_var { subs.rigid_var(var, name); @@ -298,17 +298,24 @@ mod test_reporting { report_problem_as( indoc!( r#" - List.foobar 1 2 + List.isempty 1 2 "# ), indoc!( r#" ── NOT EXPOSED ───────────────────────────────────────────────────────────────── - The List module does not expose a foobar value: + The List module does not expose `isempty`: - 1│ List.foobar 1 2 - ^^^^^^^^^^^ + 1│ List.isempty 1 2 + ^^^^^^^^^^^^ + + Did you mean one of these? + + List.isEmpty + List.set + List.get + List.keepIf "# ), ) @@ -547,7 +554,35 @@ mod test_reporting { baz Nat Str - U8 + Err + "# + ), + ) + } + + #[test] + fn lowercase_primitive_tag_bool() { + report_problem_as( + indoc!( + r#" + if true then 1 else 2 + "# + ), + indoc!( + r#" + ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + + I cannot find a `true` value + + 1│ if true then 1 else 2 + ^^^^ + + Did you mean one of these? + + True + Str + Num + Err "# ), ) @@ -1950,10 +1985,10 @@ mod test_reporting { Did you mean one of these? + Ok U8 f I8 - F64 "# ), ) @@ -5551,6 +5586,82 @@ mod test_reporting { ) } + #[test] + // https://github.com/rtfeldman/roc/issues/1714 + fn interpolate_concat_is_transparent_1714() { + report_problem_as( + indoc!( + r#" + greeting = "Privet" + + if True then 1 else "\(greeting), World!" + "#, + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + This `if` has an `else` branch with a different type from its `then` branch: + + 3│ if True then 1 else "\(greeting), World!" + ^^^^^^^^^^^^^^^^^^^^^ + + The `else` branch is a string of type: + + Str + + but the `then` branch has the type: + + Num a + + I need all branches in an `if` to have the same type! + "# + ), + ) + } + + macro_rules! comparison_binop_transparency_tests { + ($($op:expr, $name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + report_problem_as( + &format!(r#"if True then "abc" else 1 {} 2"#, $op), + &format!( +r#"── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + +This `if` has an `else` branch with a different type from its `then` branch: + +1│ if True then "abc" else 1 {} 2 + ^^{}^^ + +This comparison produces: + + Bool + +but the `then` branch has the type: + + Str + +I need all branches in an `if` to have the same type! +"#, + $op, "^".repeat($op.len()) + ), + ) + } + )* + } + } + + comparison_binop_transparency_tests! { + "<", lt_binop_is_transparent, + ">", gt_binop_is_transparent, + "==", eq_binop_is_transparent, + "!=", neq_binop_is_transparent, + "<=", leq_binop_is_transparent, + ">=", geq_binop_is_transparent, + } + #[test] fn keyword_record_field_access() { report_problem_as( @@ -5596,10 +5707,17 @@ mod test_reporting { r#" ── NOT EXPOSED ───────────────────────────────────────────────────────────────── - The Num module does not expose a if value: + The Num module does not expose `if`: 1│ Num.if ^^^^^^ + + Did you mean one of these? + + Num.sin + Num.div + Num.abs + Num.neg "# ), ) @@ -5802,8 +5920,8 @@ mod test_reporting { Nat Str + Err U8 - F64 "# ), ) diff --git a/roc-for-elm-programmers.md b/roc-for-elm-programmers.md index 5d75c244d1..da0215f5a4 100644 --- a/roc-for-elm-programmers.md +++ b/roc-for-elm-programmers.md @@ -819,7 +819,7 @@ Dict.fromList [ KV "Sam" True, KV "Ali" False KV firstName False This works, but is not nearly as nice to read. -Additionally, map literals can compile directly to efficient initialization code +Additionally, `Dict` literals can compile directly to efficient initialization code without needing to (hopefully be able to) optimize away the intermediate `List` involved in `fromList`. diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index 28ba317ab0..9002ef766d 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -6,8 +6,6 @@ use core::{fmt, mem, ptr, slice}; // A list of C functions that are being imported extern "C" { - pub fn printf(format: *const u8, ...) -> i32; - pub fn roc_alloc(size: usize, alignment: u32) -> *mut c_void; pub fn roc_realloc( ptr: *mut c_void, diff --git a/vendor/ena/src/unify/backing_vec.rs b/vendor/ena/src/unify/backing_vec.rs index 4b50aafa06..7dcc30cafe 100644 --- a/vendor/ena/src/unify/backing_vec.rs +++ b/vendor/ena/src/unify/backing_vec.rs @@ -15,7 +15,10 @@ type Key = ::Key; /// backing store types. The most common such type is `InPlace`, /// which indicates a standard, mutable unification table. pub trait UnificationStore: - ops::Index>> + Clone + Default + ops::Index>> + + ops::IndexMut>> + + Clone + + Default { type Key: UnifyKey; type Value: Clone + Debug; @@ -141,6 +144,15 @@ where } } +impl ops::IndexMut for InPlace +where + K: UnifyKey, +{ + fn index_mut(&mut self, index: usize) -> &mut VarValue { + &mut self.values[index] + } +} + #[derive(Copy, Clone, Debug)] struct Delegate(PhantomData); diff --git a/vendor/ena/src/unify/mod.rs b/vendor/ena/src/unify/mod.rs index 77f151f2e6..37823da931 100644 --- a/vendor/ena/src/unify/mod.rs +++ b/vendor/ena/src/unify/mod.rs @@ -284,6 +284,12 @@ impl UnificationTable { &self.values[key.index() as usize] } + /// Obtains the current value for a particular key. + /// Not for end-users; they can use `probe_value`. + pub fn value_mut(&mut self, key: S::Key) -> &mut VarValue { + &mut self.values[key.index() as usize] + } + /// Find the root node for `vid`. This uses the standard /// union-find algorithm with path compression: /// . @@ -451,6 +457,17 @@ where self.value(id) } + /// Returns the current value for the given key. If the key has + /// been union'd, this will give the value from the current root. + pub fn probe_value_ref_mut(&mut self, id: K1) -> &mut VarValue + where + K1: Into, + { + let id = id.into(); + let id = self.get_root_key_without_compacting(id); + self.value_mut(id) + } + /// This is for a debug_assert! in solve() only. Do not use it elsewhere! pub fn probe_value_without_compacting(&self, id: K1) -> V where