diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52b83a507e..5b5728b7dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,14 @@ on: [pull_request] name: CI +env: + RUSTC_WRAPPER: /usr/local/bin/sccache + SCCACHE_BUCKET: ${{ secrets.SCCACHE_BUCKET }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + SCCACHE_S3_USE_SSL: ${{ secrets.SCCACHE_S3_USE_SSL }} + SCCACHE_REGION: ${{ secrets.SCCACHE_REGION }} + jobs: test: name: fmt, clippy, test, test --release @@ -10,9 +18,23 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Log CPU model + run: sudo cat /proc/cpuinfo | grep name | uniq + - name: Install CI Libraries run: sudo ./ci/install-ci-libraries.sh 10 + - name: scope sccache to CPU model + run: | + export SCCACHE_S3_KEY_PREFIX=`sudo cat /proc/cpuinfo | grep name | uniq | awk -F: {'print $2'} | awk -F@ {'print$1'} | xargs | sed -e 's/\s/_/g'` + echo "SCCACHE_S3_KEY_PREFIX=$SCCACHE_S3_KEY_PREFIX" >> $GITHUB_ENV + + - name: print SCCACHE_S3_KEY_PREFIX + run: echo $SCCACHE_S3_KEY_PREFIX + + - name: sccache version + run: /usr/local/bin/sccache -V + - name: Run Zig tests run: pushd compiler/builtins/bitcode; ./run-tests.sh; popd; @@ -28,24 +50,6 @@ jobs: - run: rustup component add rustfmt - - name: Cache cargo registry - uses: actions/cache@v1 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo index - uses: actions/cache@v1 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache compiled valgrind - uses: actions/cache@v1 - with: - path: ~/valgrind-3.6.1/ - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - uses: actions-rs/cargo@v1 name: rustfmt version with: @@ -72,15 +76,11 @@ jobs: command: clippy args: -- -D warnings - - uses: actions-rs/cargo@v1 - name: cargo test -- --ignored - continue-on-error: true - with: - command: test - args: -- --ignored - - uses: actions-rs/cargo@v1 name: cargo test --release with: command: test args: --release + + - name: sccache stats + run: /usr/local/bin/sccache --show-stats diff --git a/ci/install-ci-libraries.sh b/ci/install-ci-libraries.sh index dffa36436a..b916b68598 100755 --- a/ci/install-ci-libraries.sh +++ b/ci/install-ci-libraries.sh @@ -79,3 +79,8 @@ valgrind --version wget -c https://ziglang.org/builds/zig-linux-x86_64-0.6.0+0088efc4b.tar.xz --no-check-certificate tar -xf zig-linux-x86_64-0.6.0+0088efc4b.tar.xz ln -s "$PWD/zig-linux-x86_64-0.6.0+0088efc4b/zig" /usr/local/bin/zig + +# test sccache +./ci/sccache -V +# copy sccache to prevent current working dir problems +cp ./ci/sccache /usr/local/bin/sccache diff --git a/ci/sccache b/ci/sccache new file mode 100755 index 0000000000..57ecbea0db Binary files /dev/null and b/ci/sccache differ diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 4a20f0601d..5e4d07e6bc 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -20,6 +20,10 @@ struct Env<'a, 'env> { home: ModuleId, } +pub enum ToAstProblem { + FunctionLayout, +} + /// JIT execute the given main function, and then wrap its results in an Expr /// so we can display them to the user using the formatter. /// @@ -39,7 +43,7 @@ pub unsafe fn jit_to_ast<'a>( home: ModuleId, subs: &Subs, ptr_bytes: u32, -) -> Expr<'a> { +) -> Result, ToAstProblem> { let env = Env { arena, subs, @@ -57,55 +61,57 @@ fn jit_to_ast_help<'a>( main_fn_name: &str, layout: &Layout<'a>, content: &Content, -) -> Expr<'a> { +) -> Result, ToAstProblem> { match layout { - Layout::Builtin(Builtin::Int1) => { - run_jit_function!(lib, main_fn_name, bool, |num| bool_to_ast( - env, num, content - )) - } + Layout::Builtin(Builtin::Int1) => Ok(run_jit_function!(lib, main_fn_name, bool, |num| { + bool_to_ast(env, num, content) + })), Layout::Builtin(Builtin::Int8) => { - // NOTE: this is does not handle 8-bit numbers yet - run_jit_function!(lib, main_fn_name, u8, |num| byte_to_ast(env, num, content)) + Ok( + // NOTE: this is does not handle 8-bit numbers yet + run_jit_function!(lib, main_fn_name, u8, |num| byte_to_ast(env, num, content)), + ) } Layout::Builtin(Builtin::Int64) => { - run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast( + Ok(run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast( env, i64_to_ast(env.arena, num), content - )) + ))) } Layout::Builtin(Builtin::Float64) => { - run_jit_function!(lib, main_fn_name, f64, |num| num_to_ast( + Ok(run_jit_function!(lib, main_fn_name, f64, |num| num_to_ast( env, f64_to_ast(env.arena, num), content - )) + ))) } - Layout::Builtin(Builtin::Str) | Layout::Builtin(Builtin::EmptyStr) => { + Layout::Builtin(Builtin::Str) | Layout::Builtin(Builtin::EmptyStr) => Ok( run_jit_function!(lib, main_fn_name, &'static str, |string: &'static str| { str_to_ast(env.arena, env.arena.alloc(string)) - }) - } + }), + ), Layout::Builtin(Builtin::EmptyList) => { - run_jit_function!(lib, main_fn_name, &'static str, |_| { Expr::List(&[]) }) + Ok(run_jit_function!(lib, main_fn_name, &'static str, |_| { + Expr::List(&[]) + })) } - Layout::Builtin(Builtin::List(_, elem_layout)) => run_jit_function!( + Layout::Builtin(Builtin::List(_, elem_layout)) => Ok(run_jit_function!( lib, main_fn_name, (*const u8, usize), |(ptr, len): (*const u8, usize)| { list_to_ast(env, ptr, len, elem_layout, content) } - ), + )), Layout::Builtin(other) => { todo!("add support for rendering builtin {:?} to the REPL", other) } - Layout::PhantomEmptyStruct => run_jit_function!(lib, main_fn_name, &u8, |_| { + Layout::PhantomEmptyStruct => Ok(run_jit_function!(lib, main_fn_name, &u8, |_| { Expr::Record { update: None, fields: &[], final_comments: env.arena.alloc([]), } - }), + })), Layout::Struct(field_layouts) => { let ptr_to_ast = |ptr: *const u8| match content { Content::Structure(FlatType::Record(fields, _)) => { @@ -134,12 +140,12 @@ fn jit_to_ast_help<'a>( let result_stack_size = layout.stack_size(env.ptr_bytes); - run_jit_function_dynamic_type!( + Ok(run_jit_function_dynamic_type!( lib, main_fn_name, result_stack_size as usize, |bytes: *const u8| { ptr_to_ast(bytes as *const u8) } - ) + )) } Layout::Union(union_layouts) => match content { Content::Structure(FlatType::TagUnion(tags, _)) => { @@ -153,7 +159,7 @@ fn jit_to_ast_help<'a>( let size = layout.stack_size(env.ptr_bytes); match union_variant { UnionVariant::Wrapped(tags_and_layouts) => { - run_jit_function_dynamic_type!( + Ok(run_jit_function_dynamic_type!( lib, main_fn_name, size as usize, @@ -181,7 +187,7 @@ fn jit_to_ast_help<'a>( Expr::Apply(loc_tag_expr, output, CalledVia::Space) } - ) + )) } _ => unreachable!("any other variant would have a different layout"), } @@ -201,7 +207,7 @@ fn jit_to_ast_help<'a>( } Layout::FunctionPointer(_, _) | Layout::Closure(_, _, _) => { - todo!("add support for rendering functions in the REPL") + Err(ToAstProblem::FunctionLayout) } Layout::Pointer(_) => todo!("add support for rendering pointers in the REPL"), } diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index 1d9427842a..a67dda0ddb 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -131,11 +131,15 @@ pub fn gen_and_eval(src: &[u8], target: Triple, opt_level: OptLevel) -> Result layout.clone(), + None => { + return Ok(ReplOutput::NoProblems { + expr: "".to_string(), + expr_type: expr_type_str, + }); + } + }; let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; @@ -249,7 +253,7 @@ pub fn gen_and_eval(src: &[u8], target: Triple, opt_level: OptLevel) -> Result Result { + answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0); + } + Err(FunctionLayout) => { + expr.push_str(""); + } + } Ok(ReplOutput::NoProblems { expr: expr.into_bump_str().to_string(), diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index b8829ab8c1..c4de7ad25c 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -270,6 +270,41 @@ mod repl_eval { expect_success("Num.subWrap Num.minInt 1", "9223372036854775807 : I64"); } + #[test] + fn num_mul_wrap() { + expect_success("Num.mulWrap Num.maxInt 2", "-2 : I64"); + } + + #[test] + fn num_add_checked() { + expect_success("Num.addChecked 1 1", "Ok 2 : Result (Num *) [ Overflow ]*"); + expect_success( + "Num.addChecked Num.maxInt 1", + "Err (Overflow) : Result I64 [ Overflow ]*", + ); + } + + #[test] + fn num_sub_checked() { + expect_success("Num.subChecked 1 1", "Ok 0 : Result (Num *) [ Overflow ]*"); + expect_success( + "Num.subChecked Num.minInt 1", + "Err (Overflow) : Result I64 [ Overflow ]*", + ); + } + + #[test] + fn num_mul_checked() { + expect_success( + "Num.mulChecked 20 2", + "Ok 40 : Result (Num *) [ Overflow ]*", + ); + expect_success( + "Num.mulChecked Num.maxInt 2", + "Err (Overflow) : Result I64 [ Overflow ]*", + ); + } + #[test] fn list_concat() { expect_success( @@ -292,13 +327,16 @@ mod repl_eval { expect_success("List.sum [ 1.1, 2.2, 3.3 ]", "6.6 : F64"); } - // TODO add test cases for empty lists once error messages in the repl are correct #[test] fn list_first() { expect_success( "List.first [ 12, 9, 6, 3 ]", "Ok 12 : Result (Num *) [ ListWasEmpty ]*", ); + expect_success( + "List.first []", + "Err (ListWasEmpty) : Result * [ ListWasEmpty ]*", + ); } #[test] @@ -307,6 +345,11 @@ mod repl_eval { "List.last [ 12, 9, 6, 3 ]", "Ok 3 : Result (Num *) [ ListWasEmpty ]*", ); + + expect_success( + "List.last []", + "Err (ListWasEmpty) : Result * [ ListWasEmpty ]*", + ); } #[test] @@ -427,6 +470,40 @@ mod repl_eval { ); } + #[test] + fn identity_lambda() { + // Even though this gets unwrapped at runtime, the repl should still + // report it as a record + expect_success("\\x -> x", " : a -> a"); + } + + #[test] + fn stdlib_function() { + // Even though this gets unwrapped at runtime, the repl should still + // report it as a record + expect_success("Num.abs", " : Num a -> Num a"); + } + + #[test] + fn too_few_args() { + expect_failure( + "Num.add 2", + indoc!( + r#" + ── TOO FEW ARGS ──────────────────────────────────────────────────────────────── + + The add function expects 2 arguments, but it got only 1: + + 4│ Num.add 2 + ^^^^^^^ + + Roc does not allow functions to be partially applied. Use a closure to + make partial application explicit. + "# + ), + ); + } + #[test] fn type_problem() { expect_failure( diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 438fa32944..0f1e9dd32a 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -75,17 +75,19 @@ pub fn types() -> MutMap { ), ); - // addChecked : Num a, Num a -> Result (Num a) [ Overflow ]* - let overflow = SolvedType::TagUnion( - vec![(TagName::Global("Overflow".into()), vec![])], - Box::new(SolvedType::Wildcard), - ); + fn overflow() -> SolvedType { + SolvedType::TagUnion( + vec![(TagName::Global("Overflow".into()), vec![])], + Box::new(SolvedType::Wildcard), + ) + } + // addChecked : Num a, Num a -> Result (Num a) [ Overflow ]* add_type( Symbol::NUM_ADD_CHECKED, top_level_function( vec![num_type(flex(TVAR1)), num_type(flex(TVAR1))], - Box::new(result_type(num_type(flex(TVAR1)), overflow.clone())), + Box::new(result_type(num_type(flex(TVAR1)), overflow())), ), ); @@ -115,7 +117,7 @@ pub fn types() -> MutMap { Symbol::NUM_SUB_CHECKED, top_level_function( vec![num_type(flex(TVAR1)), num_type(flex(TVAR1))], - Box::new(result_type(num_type(flex(TVAR1)), overflow)), + Box::new(result_type(num_type(flex(TVAR1)), overflow())), ), ); @@ -128,6 +130,21 @@ pub fn types() -> MutMap { ), ); + // mulWrap : Int, Int -> Int + add_type( + Symbol::NUM_MUL_WRAP, + top_level_function(vec![int_type(), int_type()], Box::new(int_type())), + ); + + // mulChecked : Num a, Num a -> Result (Num a) [ Overflow ]* + add_type( + Symbol::NUM_MUL_CHECKED, + top_level_function( + vec![num_type(flex(TVAR1)), num_type(flex(TVAR1))], + Box::new(result_type(num_type(flex(TVAR1)), overflow())), + ), + ); + // abs : Num a -> Num a add_type( Symbol::NUM_ABS, diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index 879917d299..ce7a5e75c8 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -197,6 +197,21 @@ pub fn types() -> MutMap { unique_function(vec![num_type(u, num), num_type(v, num)], num_type(w, num)) }); + // mulWrap : Int, Int -> Int + add_type(Symbol::NUM_MUL_WRAP, { + let_tvars! { u, v, w }; + unique_function(vec![int_type(u), int_type(v)], int_type(w)) + }); + + // mulChecked : Num a, Num a -> Result (Num a) [ Overflow ]* + add_type(Symbol::NUM_MUL_CHECKED, { + let_tvars! { u, v, w, num, result, star }; + unique_function( + vec![num_type(u, num), num_type(v, num)], + result_type(result, num_type(w, num), lift(star, overflow())), + ) + }); + // abs : Num a -> Num a add_type(Symbol::NUM_ABS, { let_tvars! { u, v, num }; @@ -1227,36 +1242,34 @@ fn lift(u: VarId, a: SolvedType) -> SolvedType { #[inline(always)] fn float_type(u: VarId) -> SolvedType { + let b_64 = builtin_aliases::binary64_type(); + let attr_b_64 = lift(u, b_64); + let fp = builtin_aliases::floatingpoint_type(attr_b_64); + let attr_fb = lift(u, fp); + let num = builtin_aliases::num_type(attr_fb); + SolvedType::Apply( Symbol::ATTR_ATTR, vec![ flex(u), - SolvedType::Alias( - Symbol::NUM_F64, - Vec::new(), - Box::new(builtin_aliases::num_type(SolvedType::Apply( - Symbol::ATTR_ATTR, - vec![flex(u), builtin_aliases::floatingpoint_type()], - ))), - ), + SolvedType::Alias(Symbol::NUM_F64, Vec::new(), Box::new(num)), ], ) } #[inline(always)] fn int_type(u: VarId) -> SolvedType { + let signed_64 = builtin_aliases::signed64_type(); + let attr_signed_64 = lift(u, signed_64); + let integer = builtin_aliases::integer_type(attr_signed_64); + let attr_fb = lift(u, integer); + let num = builtin_aliases::num_type(attr_fb); + SolvedType::Apply( Symbol::ATTR_ATTR, vec![ flex(u), - SolvedType::Alias( - Symbol::NUM_I64, - Vec::new(), - Box::new(builtin_aliases::num_type(SolvedType::Apply( - Symbol::ATTR_ATTR, - vec![flex(u), builtin_aliases::integer_type()], - ))), - ), + SolvedType::Alias(Symbol::NUM_I64, Vec::new(), Box::new(num)), ], ) } diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index d88763af9b..8d9a0ae2f1 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -85,6 +85,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_SUB_WRAP => num_sub_wrap, NUM_SUB_CHECKED => num_sub_checked, NUM_MUL => num_mul, + NUM_MUL_WRAP => num_mul_wrap, + NUM_MUL_CHECKED => num_mul_checked, NUM_GT => num_gt, NUM_GTE => num_gte, NUM_LT => num_lt, @@ -573,6 +575,100 @@ fn num_mul(symbol: Symbol, var_store: &mut VarStore) -> Def { num_binop(symbol, var_store, LowLevel::NumMul) } +/// Num.mulWrap : Int, Int -> Int +fn num_mul_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_binop(symbol, var_store, LowLevel::NumMulWrap) +} + +/// Num.mulChecked : Num a, Num a -> Result (Num a) [ Overflow ]* +fn num_mul_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { + let bool_var = var_store.fresh(); + let num_var_1 = var_store.fresh(); + let num_var_2 = var_store.fresh(); + let num_var_3 = var_store.fresh(); + let ret_var = var_store.fresh(); + let record_var = var_store.fresh(); + + // let arg_3 = RunLowLevel NumMulChecked arg_1 arg_2 + // + // if arg_3.b then + // # overflow + // Err Overflow + // else + // # all is well + // Ok arg_3.a + + let cont = If { + branch_var: ret_var, + cond_var: bool_var, + branches: vec![( + // if-condition + no_region( + // arg_3.b + Access { + record_var, + ext_var: var_store.fresh(), + field: "b".into(), + field_var: var_store.fresh(), + loc_expr: Box::new(no_region(Var(Symbol::ARG_3))), + }, + ), + // overflow! + no_region(tag( + "Err", + vec![tag("Overflow", Vec::new(), var_store)], + var_store, + )), + )], + final_else: Box::new( + // all is well + no_region( + // Ok arg_3.a + tag( + "Ok", + vec![ + // arg_3.a + Access { + record_var, + ext_var: var_store.fresh(), + field: "a".into(), + field_var: num_var_3, + loc_expr: Box::new(no_region(Var(Symbol::ARG_3))), + }, + ], + var_store, + ), + ), + ), + }; + + // arg_3 = RunLowLevel NumMulChecked arg_1 arg_2 + let def = crate::def::Def { + loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)), + loc_expr: no_region(RunLowLevel { + op: LowLevel::NumMulChecked, + args: vec![ + (num_var_1, Var(Symbol::ARG_1)), + (num_var_2, Var(Symbol::ARG_2)), + ], + ret_var: record_var, + }), + expr_var: record_var, + pattern_vars: SendMap::default(), + annotation: None, + }; + + let body = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var); + + defn( + symbol, + vec![(num_var_1, Symbol::ARG_1), (num_var_2, Symbol::ARG_2)], + var_store, + body, + ret_var, + ) +} + /// Num.isGt : Num a, Num a -> Bool fn num_gt(symbol: Symbol, var_store: &mut VarStore) -> Def { num_num_other_binop(symbol, var_store, LowLevel::NumGt) diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 9d1f6ae642..5f100a7a1b 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -21,7 +21,7 @@ use roc_types::types::Alias; pub struct Module { pub module_id: ModuleId, pub exposed_imports: MutMap, - pub exposed_vars_by_symbol: Vec<(Symbol, Variable)>, + pub exposed_symbols: MutSet, pub references: MutSet, pub aliases: MutMap, pub rigid_variables: MutMap, @@ -36,7 +36,6 @@ pub struct ModuleOutput { pub lookups: Vec<(Symbol, Variable, Region)>, pub problems: Vec, pub ident_ids: IdentIds, - pub exposed_vars_by_symbol: Vec<(Symbol, Variable)>, pub references: MutSet, } @@ -51,7 +50,7 @@ pub fn canonicalize_module_defs<'a>( dep_idents: MutMap, aliases: MutMap, exposed_imports: MutMap, - mut exposed_symbols: MutSet, + exposed_symbols: &MutSet, var_store: &mut VarStore, ) -> Result { let mut can_exposed_imports = MutMap::default(); @@ -166,45 +165,39 @@ pub fn canonicalize_module_defs<'a>( // NOTE previously we inserted builtin defs into the list of defs here // this is now done later, in file.rs. + // assume all exposed symbols are not actually defined in the module + // then as we walk the module and encounter the definitions, remove + // symbols from this set + let mut exposed_but_not_defined = exposed_symbols.clone(); + match sort_can_defs(&mut env, defs, Output::default()) { (Ok(mut declarations), output) => { use crate::def::Declaration::*; - // Record the variables for all exposed symbols. - let mut exposed_vars_by_symbol = Vec::with_capacity(exposed_symbols.len()); - for decl in declarations.iter() { match decl { Declare(def) => { - for (symbol, variable) in def.pattern_vars.iter() { - if exposed_symbols.contains(symbol) { - // This is one of our exposed symbols; - // record the corresponding variable! - exposed_vars_by_symbol.push((*symbol, *variable)); - + for (symbol, _) in def.pattern_vars.iter() { + if exposed_but_not_defined.contains(symbol) { // Remove this from exposed_symbols, // so that at the end of the process, // we can see if there were any // exposed symbols which did not have // corresponding defs. - exposed_symbols.remove(symbol); + exposed_but_not_defined.remove(symbol); } } } DeclareRec(defs) => { for def in defs { - for (symbol, variable) in def.pattern_vars.iter() { - if exposed_symbols.contains(symbol) { - // This is one of our exposed symbols; - // record the corresponding variable! - exposed_vars_by_symbol.push((*symbol, *variable)); - + for (symbol, _) in def.pattern_vars.iter() { + if exposed_but_not_defined.contains(symbol) { // Remove this from exposed_symbols, // so that at the end of the process, // we can see if there were any // exposed symbols which did not have // corresponding defs. - exposed_symbols.remove(symbol); + exposed_but_not_defined.remove(symbol); } } } @@ -219,7 +212,7 @@ pub fn canonicalize_module_defs<'a>( debug_assert!(def .pattern_vars .iter() - .all(|(symbol, _)| !exposed_symbols.contains(symbol))); + .all(|(symbol, _)| !exposed_but_not_defined.contains(symbol))); } } } @@ -232,7 +225,7 @@ pub fn canonicalize_module_defs<'a>( // we can see if there were any // exposed symbols which did not have // corresponding defs. - exposed_symbols.remove(&symbol); + exposed_but_not_defined.remove(&symbol); aliases.insert(symbol, alias); } @@ -241,7 +234,7 @@ pub fn canonicalize_module_defs<'a>( // exposed_symbols and added to exposed_vars_by_symbol. If any were // not, that means they were declared as exposed but there was // no actual declaration with that name! - for symbol in exposed_symbols { + for symbol in exposed_but_not_defined { env.problem(Problem::ExposedButNotDefined(symbol)); // In case this exposed value is referenced by other modules, @@ -305,7 +298,6 @@ pub fn canonicalize_module_defs<'a>( exposed_imports: can_exposed_imports, problems: env.problems, lookups, - exposed_vars_by_symbol, ident_ids: env.ident_ids, }) } diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 9b57b9934a..1df15d5aef 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -76,33 +76,71 @@ pub fn num_float() -> Type { Type::Alias( Symbol::NUM_F64, vec![], - Box::new(num_num(num_floatingpoint())), + Box::new(num_num(num_floatingpoint(num_binary64()))), ) } #[inline(always)] -pub fn num_floatingpoint() -> Type { +pub fn num_floatingpoint(range: Type) -> Type { let alias_content = Type::TagUnion( - vec![(TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), vec![])], + vec![( + TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), + vec![range.clone()], + )], Box::new(Type::EmptyTagUnion), ); - Type::Alias(Symbol::NUM_FLOATINGPOINT, vec![], Box::new(alias_content)) + Type::Alias( + Symbol::NUM_FLOATINGPOINT, + vec![("range".into(), range)], + Box::new(alias_content), + ) +} + +#[inline(always)] +pub fn num_binary64() -> Type { + let alias_content = Type::TagUnion( + vec![(TagName::Private(Symbol::NUM_AT_BINARY64), vec![])], + Box::new(Type::EmptyTagUnion), + ); + + Type::Alias(Symbol::NUM_BINARY64, vec![], Box::new(alias_content)) } #[inline(always)] pub fn num_int() -> Type { - Type::Alias(Symbol::NUM_I64, vec![], Box::new(num_num(num_integer()))) + Type::Alias( + Symbol::NUM_I64, + vec![], + Box::new(num_num(num_integer(num_signed64()))), + ) } #[inline(always)] -pub fn num_integer() -> Type { +pub fn num_signed64() -> Type { let alias_content = Type::TagUnion( - vec![(TagName::Private(Symbol::NUM_AT_INTEGER), vec![])], + vec![(TagName::Private(Symbol::NUM_AT_SIGNED64), vec![])], Box::new(Type::EmptyTagUnion), ); - Type::Alias(Symbol::NUM_INTEGER, vec![], Box::new(alias_content)) + Type::Alias(Symbol::NUM_SIGNED64, vec![], Box::new(alias_content)) +} + +#[inline(always)] +pub fn num_integer(range: Type) -> Type { + let alias_content = Type::TagUnion( + vec![( + TagName::Private(Symbol::NUM_AT_INTEGER), + vec![range.clone()], + )], + Box::new(Type::EmptyTagUnion), + ); + + Type::Alias( + Symbol::NUM_INTEGER, + vec![("range".into(), range)], + Box::new(alias_content), + ) } #[inline(always)] diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 7258dfe6ad..164df10b34 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -157,7 +157,7 @@ pub fn constrain_pattern( IntLiteral(_) => { state.constraints.push(Constraint::Pattern( region, - PatternCategory::Float, + PatternCategory::Int, builtins::num_int(), expected, )); diff --git a/compiler/constrain/src/uniq.rs b/compiler/constrain/src/uniq.rs index 25352f73e0..c3ce919dd3 100644 --- a/compiler/constrain/src/uniq.rs +++ b/compiler/constrain/src/uniq.rs @@ -1,4 +1,4 @@ -use crate::builtins::{num_floatingpoint, num_integer, num_num}; +use crate::builtins::{num_binary64, num_floatingpoint, num_integer, num_num, num_signed64}; use crate::expr::{exists, Info}; use roc_can::annotation::IntroducedVariables; use roc_can::constraint::Constraint::{self, *}; @@ -166,24 +166,25 @@ fn constrain_pattern( } NumLiteral(inner_var, _) => { - let (num_uvar, val_uvar, num_type, num_var) = unique_unbound_num(*inner_var, var_store); + let (inner_uvar, num_var, num_type) = unique_unbound_num(*inner_var, var_store); state.constraints.push(exists( - vec![val_uvar, num_uvar, num_var, *inner_var], + vec![num_var, inner_uvar, *inner_var], Constraint::Pattern(pattern.region, PatternCategory::Num, num_type, expected), )); } IntLiteral(_) => { - let (num_uvar, int_uvar, num_type) = unique_int(var_store); + let (a, b, c, num_type) = unique_int(var_store); state.constraints.push(exists( - vec![num_uvar, int_uvar], + vec![a, b, c], Constraint::Pattern(pattern.region, PatternCategory::Int, num_type, expected), )); } FloatLiteral(_) => { - let (num_uvar, float_uvar, num_type) = unique_float(var_store); + let (a, b, c, num_type) = unique_float(var_store); + state.constraints.push(exists( - vec![num_uvar, float_uvar], + vec![a, b, c], Constraint::Pattern(pattern.region, PatternCategory::Float, num_type, expected), )); } @@ -405,41 +406,47 @@ fn constrain_pattern( } } -fn unique_unbound_num( - inner_var: Variable, - var_store: &mut VarStore, -) -> (Variable, Variable, Type, Variable) { - let num_var = var_store.fresh(); +fn unique_unbound_num(inner_var: Variable, var_store: &mut VarStore) -> (Variable, Variable, Type) { let num_uvar = var_store.fresh(); - let val_uvar = var_store.fresh(); + let inner_uvar = var_store.fresh(); let val_type = Type::Variable(inner_var); - let val_utype = attr_type(Bool::variable(val_uvar), val_type); + let val_utype = attr_type(Bool::variable(inner_uvar), val_type); let num_utype = num_num(val_utype); let num_type = attr_type(Bool::variable(num_uvar), num_utype); - (num_uvar, val_uvar, num_type, num_var) + (inner_uvar, num_uvar, num_type) } -fn unique_num(var_store: &mut VarStore, val_type: Type) -> (Variable, Variable, Type) { - let num_uvar = var_store.fresh(); - let val_uvar = var_store.fresh(); +fn unique_int(var_store: &mut VarStore) -> (Variable, Variable, Variable, Type) { + let num_uvar1 = var_store.fresh(); + let num_uvar2 = var_store.fresh(); + let num_uvar3 = var_store.fresh(); - let val_utype = attr_type(Bool::variable(val_uvar), val_type); + let signed_64 = num_signed64(); + let attr_signed_64 = attr_type(Bool::variable(num_uvar1), signed_64); + let integer = num_integer(attr_signed_64); + let attr_int = attr_type(Bool::variable(num_uvar2), integer); + let num = num_num(attr_int); + let attr_num = attr_type(Bool::variable(num_uvar3), num); - let num_utype = num_num(val_utype); - let num_type = attr_type(Bool::variable(num_uvar), num_utype); - - (num_uvar, val_uvar, num_type) + (num_uvar1, num_uvar2, num_uvar3, attr_num) } -fn unique_int(var_store: &mut VarStore) -> (Variable, Variable, Type) { - unique_num(var_store, num_integer()) -} +fn unique_float(var_store: &mut VarStore) -> (Variable, Variable, Variable, Type) { + let num_uvar1 = var_store.fresh(); + let num_uvar2 = var_store.fresh(); + let num_uvar3 = var_store.fresh(); -fn unique_float(var_store: &mut VarStore) -> (Variable, Variable, Type) { - unique_num(var_store, num_floatingpoint()) + let binary_64 = num_binary64(); + let attr_binary_64 = attr_type(Bool::variable(num_uvar1), binary_64); + let fp = num_floatingpoint(attr_binary_64); + let attr_fp = attr_type(Bool::variable(num_uvar2), fp); + let num = num_num(attr_fp); + let attr_num = attr_type(Bool::variable(num_uvar3), num); + + (num_uvar1, num_uvar2, num_uvar3, attr_num) } pub fn constrain_expr( @@ -456,10 +463,10 @@ pub fn constrain_expr( match expr { Num(inner_var, _) => { let var = var_store.fresh(); - let (num_uvar, val_uvar, num_type, num_var) = unique_unbound_num(*inner_var, var_store); + let (inner_uvar, num_var, num_type) = unique_unbound_num(*inner_var, var_store); exists( - vec![var, *inner_var, val_uvar, num_uvar, num_var], + vec![var, *inner_var, inner_uvar, num_var], And(vec![ Eq( Type::Variable(var), @@ -472,10 +479,10 @@ pub fn constrain_expr( ) } Int(var, _) => { - let (num_uvar, int_uvar, num_type) = unique_int(var_store); + let (a, b, c, num_type) = unique_int(var_store); exists( - vec![*var, num_uvar, int_uvar], + vec![*var, a, b, c], And(vec![ Eq( Type::Variable(*var), @@ -488,10 +495,10 @@ pub fn constrain_expr( ) } Float(var, _) => { - let (num_uvar, float_uvar, num_type) = unique_float(var_store); + let (a, b, c, num_type) = unique_float(var_store); exists( - vec![*var, num_uvar, float_uvar], + vec![*var, a, b, c], And(vec![ Eq( Type::Variable(*var), diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index f2bd4cb3c4..a14c7fae26 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -9,7 +9,8 @@ use crate::llvm::build_str::{ }; use crate::llvm::compare::{build_eq, build_neq}; use crate::llvm::convert::{ - basic_type_from_layout, block_of_memory, collection, get_fn_type, get_ptr_type, ptr_int, + basic_type_from_builtin, basic_type_from_layout, block_of_memory, collection, get_fn_type, + get_ptr_type, ptr_int, }; use crate::llvm::refcounting::{ decrement_refcount_layout, increment_refcount_layout, refcount_is_one_comparison, @@ -355,6 +356,12 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { ctx.struct_type(&fields, false) .fn_type(&[i64_type.into(), i64_type.into()], false) }); + + add_intrinsic(module, LLVM_SMUL_WITH_OVERFLOW_I64, { + let fields = [i64_type.into(), i1_type.into()]; + ctx.struct_type(&fields, false) + .fn_type(&[i64_type.into(), i64_type.into()], false) + }); } static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64"; @@ -369,6 +376,7 @@ static LLVM_CEILING_F64: &str = "llvm.ceil.f64"; static LLVM_FLOOR_F64: &str = "llvm.floor.f64"; pub static LLVM_SADD_WITH_OVERFLOW_I64: &str = "llvm.sadd.with.overflow.i64"; pub static LLVM_SSUB_WITH_OVERFLOW_I64: &str = "llvm.ssub.with.overflow.i64"; +pub static LLVM_SMUL_WITH_OVERFLOW_I64: &str = "llvm.smul.with.overflow.i64"; fn add_intrinsic<'ctx>( module: &Module<'ctx>, @@ -2902,7 +2910,7 @@ fn run_low_level<'a, 'ctx, 'env>( match arg_builtin { Int128 | Int64 | Int32 | Int16 | Int8 => { - build_int_unary_op(env, arg.into_int_value(), arg_layout, op) + build_int_unary_op(env, arg.into_int_value(), arg_builtin, op) } Float128 | Float64 | Float32 | Float16 => { build_float_unary_op(env, arg.into_float_value(), op) @@ -3003,7 +3011,7 @@ fn run_low_level<'a, 'ctx, 'env>( NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked | NumAddWrap | NumAddChecked | NumDivUnchecked | NumPow | NumPowInt | NumSubWrap - | NumSubChecked => { + | NumSubChecked | NumMulWrap | NumMulChecked => { debug_assert_eq!(args.len(), 2); let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]); @@ -3259,7 +3267,37 @@ fn build_int_binop<'a, 'ctx, 'env>( } NumSubWrap => bd.build_int_sub(lhs, rhs, "sub_int").into(), NumSubChecked => env.call_intrinsic(LLVM_SSUB_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]), - NumMul => bd.build_int_mul(lhs, rhs, "mul_int").into(), + NumMul => { + let context = env.context; + let result = env + .call_intrinsic(LLVM_SMUL_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]) + .into_struct_value(); + + let mul_result = bd.build_extract_value(result, 0, "mul_result").unwrap(); + let has_overflowed = bd.build_extract_value(result, 1, "has_overflowed").unwrap(); + + let condition = bd.build_int_compare( + IntPredicate::EQ, + has_overflowed.into_int_value(), + context.bool_type().const_zero(), + "has_not_overflowed", + ); + + let then_block = context.append_basic_block(parent, "then_block"); + let throw_block = context.append_basic_block(parent, "throw_block"); + + bd.build_conditional_branch(condition, then_block, throw_block); + + bd.position_at_end(throw_block); + + throw_exception(env, "integer multiplication overflowed!"); + + bd.position_at_end(then_block); + + mul_result + } + NumMulWrap => bd.build_int_mul(lhs, rhs, "mul_int").into(), + NumMulChecked => env.call_intrinsic(LLVM_SMUL_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]), NumGt => bd.build_int_compare(SGT, lhs, rhs, "int_gt").into(), NumGte => bd.build_int_compare(SGE, lhs, rhs, "int_gte").into(), NumLt => bd.build_int_compare(SLT, lhs, rhs, "int_lt").into(), @@ -3475,7 +3513,55 @@ fn build_float_binop<'a, 'ctx, 'env>( struct_value.into() } NumSubWrap => unreachable!("wrapping subtraction is not defined on floats"), - NumMul => bd.build_float_mul(lhs, rhs, "mul_float").into(), + NumMul => { + let builder = env.builder; + let context = env.context; + + let result = bd.build_float_mul(lhs, rhs, "mul_float"); + + let is_finite = + call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE).into_int_value(); + + let then_block = context.append_basic_block(parent, "then_block"); + let throw_block = context.append_basic_block(parent, "throw_block"); + + builder.build_conditional_branch(is_finite, then_block, throw_block); + + builder.position_at_end(throw_block); + + throw_exception(env, "float multiplication overflowed!"); + + builder.position_at_end(then_block); + + result.into() + } + NumMulChecked => { + let context = env.context; + + let result = bd.build_float_mul(lhs, rhs, "mul_float"); + + let is_finite = + call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE).into_int_value(); + let is_infinite = bd.build_not(is_finite, "negate"); + + let struct_type = context.struct_type( + &[context.f64_type().into(), context.bool_type().into()], + false, + ); + + let struct_value = { + let v1 = struct_type.const_zero(); + let v2 = bd.build_insert_value(v1, result, 0, "set_result").unwrap(); + let v3 = bd + .build_insert_value(v2, is_infinite, 1, "set_is_infinite") + .unwrap(); + + v3.into_struct_value() + }; + + struct_value.into() + } + NumMulWrap => unreachable!("wrapping multiplication is not defined on floats"), NumGt => bd.build_float_compare(OGT, lhs, rhs, "float_gt").into(), NumGte => bd.build_float_compare(OGE, lhs, rhs, "float_gte").into(), NumLt => bd.build_float_compare(OLT, lhs, rhs, "float_lt").into(), @@ -3489,10 +3575,37 @@ fn build_float_binop<'a, 'ctx, 'env>( } } +fn int_type_signed_min(int_type: IntType) -> IntValue { + let width = int_type.get_bit_width(); + + debug_assert!(width <= 128); + let shift = 128 - width as usize; + + if shift < 64 { + let min = i128::MIN >> shift; + let a = min as u64; + let b = (min >> 64) as u64; + + int_type.const_int_arbitrary_precision(&[b, a]) + } else { + int_type.const_int((i128::MIN >> shift) as u64, false) + } +} + +fn builtin_to_int_type<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + builtin: &Builtin<'a>, +) -> IntType<'ctx> { + let result = basic_type_from_builtin(env.arena, env.context, builtin, env.ptr_bytes); + debug_assert!(result.is_int_type()); + + result.into_int_type() +} + fn build_int_unary_op<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, arg: IntValue<'ctx>, - arg_layout: &Layout<'a>, + arg_layout: &Builtin<'a>, op: LowLevel, ) -> BasicValueEnum<'ctx> { use roc_module::low_level::LowLevel::*; @@ -3500,43 +3613,13 @@ fn build_int_unary_op<'a, 'ctx, 'env>( let bd = env.builder; match op { - NumNeg => bd.build_int_neg(arg, "negate_int").into(), + NumNeg => { + // integer abs overflows when applied to the minimum value of a signed type + int_neg_raise_on_overflow(env, arg, arg_layout) + } NumAbs => { - // This is how libc's abs() is implemented - it uses no branching! - // - // abs = \arg -> - // shifted = arg >>> 63 - // - // (xor arg shifted) - shifted - - let ctx = env.context; - let shifted_name = "abs_shift_right"; - let shifted_alloca = { - let bits_to_shift = ((arg_layout.stack_size(env.ptr_bytes) as u64) * 8) - 1; - let shift_val = ctx.i64_type().const_int(bits_to_shift, false); - let shifted = bd.build_right_shift(arg, shift_val, true, shifted_name); - let alloca = bd.build_alloca( - basic_type_from_layout(env.arena, ctx, arg_layout, env.ptr_bytes), - "#int_abs_help", - ); - - // shifted = arg >>> 63 - bd.build_store(alloca, shifted); - - alloca - }; - - let xored_arg = bd.build_xor( - arg, - bd.build_load(shifted_alloca, shifted_name).into_int_value(), - "xor_arg_shifted", - ); - - BasicValueEnum::IntValue(bd.build_int_sub( - xored_arg, - bd.build_load(shifted_alloca, shifted_name).into_int_value(), - "sub_xored_shifted", - )) + // integer abs overflows when applied to the minimum value of a signed type + int_abs_raise_on_overflow(env, arg, arg_layout) } NumToFloat => { // This is an Int, so we need to convert it. @@ -3553,6 +3636,109 @@ fn build_int_unary_op<'a, 'ctx, 'env>( } } +fn int_neg_raise_on_overflow<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + arg: IntValue<'ctx>, + builtin: &Builtin<'a>, +) -> BasicValueEnum<'ctx> { + let builder = env.builder; + + let min_val = int_type_signed_min(builtin_to_int_type(env, builtin)); + let condition = builder.build_int_compare(IntPredicate::EQ, arg, min_val, "is_min_val"); + + let block = env.builder.get_insert_block().expect("to be in a function"); + let parent = block.get_parent().expect("to be in a function"); + let then_block = env.context.append_basic_block(parent, "then"); + let else_block = env.context.append_basic_block(parent, "else"); + + env.builder + .build_conditional_branch(condition, then_block, else_block); + + builder.position_at_end(then_block); + + throw_exception( + env, + "integer negation overflowed because its argument is the minimum value", + ); + + builder.position_at_end(else_block); + + builder.build_int_neg(arg, "negate_int").into() +} + +fn int_abs_raise_on_overflow<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + arg: IntValue<'ctx>, + builtin: &Builtin<'a>, +) -> BasicValueEnum<'ctx> { + let builder = env.builder; + + let min_val = int_type_signed_min(builtin_to_int_type(env, builtin)); + let condition = builder.build_int_compare(IntPredicate::EQ, arg, min_val, "is_min_val"); + + let block = env.builder.get_insert_block().expect("to be in a function"); + let parent = block.get_parent().expect("to be in a function"); + let then_block = env.context.append_basic_block(parent, "then"); + let else_block = env.context.append_basic_block(parent, "else"); + + env.builder + .build_conditional_branch(condition, then_block, else_block); + + builder.position_at_end(then_block); + + throw_exception( + env, + "integer absolute overflowed because its argument is the minimum value", + ); + + builder.position_at_end(else_block); + + int_abs_with_overflow(env, arg, builtin) +} + +fn int_abs_with_overflow<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + arg: IntValue<'ctx>, + arg_layout: &Builtin<'a>, +) -> BasicValueEnum<'ctx> { + // This is how libc's abs() is implemented - it uses no branching! + // + // abs = \arg -> + // shifted = arg >>> 63 + // + // (xor arg shifted) - shifted + + let bd = env.builder; + let ctx = env.context; + let shifted_name = "abs_shift_right"; + let shifted_alloca = { + let bits_to_shift = ((arg_layout.stack_size(env.ptr_bytes) as u64) * 8) - 1; + let shift_val = ctx.i64_type().const_int(bits_to_shift, false); + let shifted = bd.build_right_shift(arg, shift_val, true, shifted_name); + let alloca = bd.build_alloca( + basic_type_from_builtin(env.arena, ctx, arg_layout, env.ptr_bytes), + "#int_abs_help", + ); + + // shifted = arg >>> 63 + bd.build_store(alloca, shifted); + + alloca + }; + + let xored_arg = bd.build_xor( + arg, + bd.build_load(shifted_alloca, shifted_name).into_int_value(), + "xor_arg_shifted", + ); + + BasicValueEnum::IntValue(bd.build_int_sub( + xored_arg, + bd.build_load(shifted_alloca, shifted_name).into_int_value(), + "sub_xored_shifted", + )) +} + fn build_float_unary_op<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, arg: FloatValue<'ctx>, diff --git a/compiler/gen/src/llvm/convert.rs b/compiler/gen/src/llvm/convert.rs index 97d23fd622..37e062ee63 100644 --- a/compiler/gen/src/llvm/convert.rs +++ b/compiler/gen/src/llvm/convert.rs @@ -4,7 +4,7 @@ use inkwell::context::Context; use inkwell::types::BasicTypeEnum::{self, *}; use inkwell::types::{ArrayType, BasicType, FunctionType, IntType, PointerType, StructType}; use inkwell::AddressSpace; -use roc_mono::layout::Layout; +use roc_mono::layout::{Builtin, Layout}; /// TODO could this be added to Inkwell itself as a method on BasicValueEnum? pub fn get_ptr_type<'ctx>( @@ -103,8 +103,7 @@ pub fn basic_type_from_layout<'ctx>( layout: &Layout<'_>, ptr_bytes: u32, ) -> BasicTypeEnum<'ctx> { - use roc_mono::layout::Builtin::*; - use roc_mono::layout::Layout::*; + use Layout::*; match layout { FunctionPointer(args, ret_layout) => { @@ -147,22 +146,33 @@ pub fn basic_type_from_layout<'ctx>( .as_basic_type_enum() } - Builtin(builtin) => match builtin { - Int128 => context.i128_type().as_basic_type_enum(), - Int64 => context.i64_type().as_basic_type_enum(), - Int32 => context.i32_type().as_basic_type_enum(), - Int16 => context.i16_type().as_basic_type_enum(), - Int8 => context.i8_type().as_basic_type_enum(), - Int1 => context.bool_type().as_basic_type_enum(), - Float128 => context.f128_type().as_basic_type_enum(), - Float64 => context.f64_type().as_basic_type_enum(), - Float32 => context.f32_type().as_basic_type_enum(), - Float16 => context.f16_type().as_basic_type_enum(), - Dict(_, _) | EmptyDict => panic!("TODO layout_to_basic_type for Builtin::Dict"), - Set(_) | EmptySet => panic!("TODO layout_to_basic_type for Builtin::Set"), - List(_, _) | Str | EmptyStr => collection(context, ptr_bytes).into(), - EmptyList => BasicTypeEnum::StructType(collection(context, ptr_bytes)), - }, + Builtin(builtin) => basic_type_from_builtin(arena, context, builtin, ptr_bytes), + } +} + +pub fn basic_type_from_builtin<'ctx>( + _arena: &Bump, + context: &'ctx Context, + builtin: &Builtin<'_>, + ptr_bytes: u32, +) -> BasicTypeEnum<'ctx> { + use Builtin::*; + + match builtin { + Int128 => context.i128_type().as_basic_type_enum(), + Int64 => context.i64_type().as_basic_type_enum(), + Int32 => context.i32_type().as_basic_type_enum(), + Int16 => context.i16_type().as_basic_type_enum(), + Int8 => context.i8_type().as_basic_type_enum(), + Int1 => context.bool_type().as_basic_type_enum(), + Float128 => context.f128_type().as_basic_type_enum(), + Float64 => context.f64_type().as_basic_type_enum(), + Float32 => context.f32_type().as_basic_type_enum(), + Float16 => context.f16_type().as_basic_type_enum(), + Dict(_, _) | EmptyDict => panic!("TODO layout_to_basic_type for Builtin::Dict"), + Set(_) | EmptySet => panic!("TODO layout_to_basic_type for Builtin::Set"), + List(_, _) | Str | EmptyStr => collection(context, ptr_bytes).into(), + EmptyList => BasicTypeEnum::StructType(collection(context, ptr_bytes)), } } diff --git a/compiler/gen/tests/gen_num.rs b/compiler/gen/tests/gen_num.rs index 0042295154..2dc79e07f8 100644 --- a/compiler/gen/tests/gen_num.rs +++ b/compiler/gen/tests/gen_num.rs @@ -40,6 +40,8 @@ mod gen_num { fn f64_abs() { assert_evals_to!("Num.abs -4.7", 4.7, f64); assert_evals_to!("Num.abs 5.8", 5.8, f64); + //assert_evals_to!("Num.abs Num.maxFloat", f64::MAX, f64); + //assert_evals_to!("Num.abs Num.minFloat", -f64::MIN, f64); } #[test] @@ -52,6 +54,24 @@ mod gen_num { assert_evals_to!("Num.abs 1", 1, i64); assert_evals_to!("Num.abs 9_000_000_000_000", 9_000_000_000_000, i64); assert_evals_to!("Num.abs -9_000_000_000_000", 9_000_000_000_000, i64); + assert_evals_to!("Num.abs Num.maxInt", i64::MAX, i64); + assert_evals_to!("Num.abs (Num.minInt + 1)", -(i64::MIN + 1), i64); + } + + #[test] + #[should_panic( + expected = r#"Roc failed with message: "integer absolute overflowed because its argument is the minimum value"# + )] + fn abs_min_int_overflow() { + assert_evals_to!( + indoc!( + r#" + Num.abs Num.minInt + "# + ), + 0, + i64 + ); } #[test] @@ -533,6 +553,24 @@ mod gen_num { #[test] fn int_negate() { assert_evals_to!("Num.neg 123", -123, i64); + assert_evals_to!("Num.neg Num.maxInt", -i64::MAX, i64); + assert_evals_to!("Num.neg (Num.minInt + 1)", i64::MAX, i64); + } + + #[test] + #[should_panic( + expected = r#"Roc failed with message: "integer negation overflowed because its argument is the minimum value"# + )] + fn neg_min_int_overflow() { + assert_evals_to!( + indoc!( + r#" + Num.neg Num.minInt + "# + ), + 0, + i64 + ); } #[test] @@ -555,7 +593,7 @@ mod gen_num { assert_evals_to!( indoc!( r#" - always42 : Num.Num Num.Integer -> Num.Num Num.Integer + always42 : Num.Num (Num.Integer Num.Signed64) -> Num.Num (Num.Integer Num.Signed64) always42 = \_ -> 42 always42 5 @@ -778,7 +816,7 @@ mod gen_num { indoc!( r#" -1.7976931348623157e308 - 1.7976931348623157e308 - "# + "# ), 0.0, f64 @@ -790,12 +828,12 @@ mod gen_num { assert_evals_to!( indoc!( r#" - when Num.subChecked 1 2 is + when Num.subChecked 5 2 is Ok v -> v _ -> -1 "# ), - -1, + 3, i64 ); @@ -838,4 +876,127 @@ mod gen_num { f64 ); } + + #[test] + #[should_panic(expected = r#"Roc failed with message: "integer multiplication overflowed!"#)] + fn int_positive_mul_overflow() { + assert_evals_to!( + indoc!( + r#" + 9_223_372_036_854_775_807 * 2 + "# + ), + 0, + i64 + ); + } + + #[test] + #[should_panic(expected = r#"Roc failed with message: "integer multiplication overflowed!"#)] + fn int_negative_mul_overflow() { + assert_evals_to!( + indoc!( + r#" + (-9_223_372_036_854_775_808) * 2 + "# + ), + 0, + i64 + ); + } + + #[test] + #[should_panic(expected = r#"Roc failed with message: "float multiplication overflowed!"#)] + fn float_positive_mul_overflow() { + assert_evals_to!( + indoc!( + r#" + 1.7976931348623157e308 * 2 + "# + ), + 0.0, + f64 + ); + } + + #[test] + #[should_panic(expected = r#"Roc failed with message: "float multiplication overflowed!"#)] + fn float_negative_mul_overflow() { + assert_evals_to!( + indoc!( + r#" + -1.7976931348623157e308 * 2 + "# + ), + 0.0, + f64 + ); + } + + #[test] + fn int_mul_wrap() { + assert_evals_to!( + indoc!( + r#" + Num.mulWrap Num.maxInt 2 + "# + ), + -2, + i64 + ); + } + + #[test] + fn int_mul_checked() { + assert_evals_to!( + indoc!( + r#" + when Num.mulChecked 20 2 is + Ok v -> v + _ -> -1 + "# + ), + 40, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + when Num.mulChecked Num.maxInt 2 is + Err Overflow -> -1 + Ok v -> v + "# + ), + -1, + i64 + ); + } + + #[test] + fn float_mul_checked() { + assert_evals_to!( + indoc!( + r#" + when Num.mulChecked 20.0 2.0 is + Ok v -> v + Err Overflow -> -1.0 + "# + ), + 40.0, + f64 + ); + + assert_evals_to!( + indoc!( + r#" + when Num.mulChecked 1.7976931348623157e308 2 is + Err Overflow -> -1 + Ok v -> v + "# + ), + -1.0, + f64 + ); + } } diff --git a/compiler/gen/tests/gen_primitives.rs b/compiler/gen/tests/gen_primitives.rs index 4c2ace6a05..56c39caef0 100644 --- a/compiler/gen/tests/gen_primitives.rs +++ b/compiler/gen/tests/gen_primitives.rs @@ -1732,4 +1732,20 @@ mod gen_primitives { |_| 0 ); } + + #[test] + fn hof_conditional() { + // exposed issue with the if condition being just a symbol + assert_evals_to!( + indoc!( + r#" + passTrue = \f -> f True + + passTrue (\trueVal -> if trueVal then False else True) + "# + ), + 0, + u8 + ); + } } diff --git a/compiler/gen/tests/gen_tags.rs b/compiler/gen/tests/gen_tags.rs index 62152d6d4c..bf65dad6a8 100644 --- a/compiler/gen/tests/gen_tags.rs +++ b/compiler/gen/tests/gen_tags.rs @@ -920,4 +920,25 @@ mod gen_tags { (i64, i64) ); } + + #[test] + fn result_never() { + assert_evals_to!( + indoc!( + r"# + res : Result I64 [] + res = Ok 4 + + # we should provide this in the stdlib + never : [] -> a + + when res is + Ok v -> v + Err empty -> never empty + #" + ), + 4, + i64 + ); + } } diff --git a/compiler/load/src/effect_module.rs b/compiler/load/src/effect_module.rs index 9de9f3b348..5dab90a4f8 100644 --- a/compiler/load/src/effect_module.rs +++ b/compiler/load/src/effect_module.rs @@ -3,7 +3,7 @@ use roc_can::env::Env; use roc_can::expr::{Expr, Recursive}; use roc_can::pattern::Pattern; use roc_can::scope::Scope; -use roc_collections::all::SendMap; +use roc_collections::all::{MutSet, SendMap}; use roc_module::ident::TagName; use roc_module::operator::CalledVia; use roc_module::symbol::Symbol; @@ -48,7 +48,7 @@ pub fn build_effect_builtins( scope: &mut Scope, effect_symbol: Symbol, var_store: &mut VarStore, - exposed_vars_by_symbol: &mut Vec<(Symbol, Variable)>, + exposed_symbols: &mut MutSet, declarations: &mut Vec, ) { for (_, f) in BUILTIN_EFFECT_FUNCTIONS.iter() { @@ -60,7 +60,7 @@ pub fn build_effect_builtins( var_store, ); - exposed_vars_by_symbol.push((symbol, def.expr_var)); + exposed_symbols.insert(symbol); declarations.push(Declaration::Declare(def)); } } diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index fd61954feb..9a76a70eda 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -20,7 +20,7 @@ use roc_module::symbol::{ use roc_mono::ir::{ CapturedSymbols, ExternalSpecializations, PartialProc, PendingSpecialization, Proc, Procs, }; -use roc_mono::layout::{Layout, LayoutCache}; +use roc_mono::layout::{Layout, LayoutCache, LayoutProblem}; use roc_parse::ast::{self, Attempting, StrLiteral, TypeAnnotation}; use roc_parse::header::{ ExposesEntry, ImportsEntry, PackageEntry, PackageOrPath, PlatformHeader, To, TypedIdent, @@ -663,7 +663,7 @@ enum Msg<'a> { }, FinishedAllTypeChecking { solved_subs: Solved, - exposed_vars_by_symbol: Vec<(Symbol, Variable)>, + exposed_vars_by_symbol: MutMap, documentation: MutMap, }, FoundSpecializations { @@ -1638,9 +1638,12 @@ fn update<'a>( }; if is_host_exposed { - state - .exposed_to_host - .extend(solved_module.exposed_vars_by_symbol.iter().copied()); + state.exposed_to_host.extend( + solved_module + .exposed_vars_by_symbol + .iter() + .map(|(k, v)| (*k, *v)), + ); } if module_id == state.root_id && state.goal_phase == Phase::SolveTypes { @@ -1904,7 +1907,7 @@ fn finish_specialization<'a>( fn finish<'a>( state: State<'a>, solved: Solved, - exposed_vars_by_symbol: Vec<(Symbol, Variable)>, + exposed_vars_by_symbol: MutMap, documentation: MutMap, ) -> LoadedModule { let module_ids = Arc::try_unwrap(state.arc_modules) @@ -2791,7 +2794,7 @@ fn run_solve<'a>( let module_id = module.module_id; let Module { - exposed_vars_by_symbol, + exposed_symbols, aliases, rigid_variables, .. @@ -2800,6 +2803,9 @@ fn run_solve<'a>( let (solved_subs, solved_env, problems) = roc_solve::module::run_solve(aliases, rigid_variables, constraint, var_store); + let mut exposed_vars_by_symbol: MutMap = solved_env.vars_by_symbol.clone(); + exposed_vars_by_symbol.retain(|k, _| exposed_symbols.contains(k)); + let solved_types = roc_solve::module::make_solved_types(&solved_env, &solved_subs, &exposed_vars_by_symbol); @@ -3001,8 +3007,8 @@ fn fabricate_effects_module<'a>( let mut declarations = Vec::new(); - let exposed_vars_by_symbol = { - let mut exposed_vars_by_symbol = Vec::new(); + let exposed_symbols: MutSet = { + let mut exposed_symbols = MutSet::default(); { for (ident, ann) in effect_entries { @@ -3035,7 +3041,7 @@ fn fabricate_effects_module<'a>( annotation, ); - exposed_vars_by_symbol.push((symbol, def.expr_var)); + exposed_symbols.insert(symbol); declarations.push(Declaration::Declare(def)); } @@ -3047,11 +3053,11 @@ fn fabricate_effects_module<'a>( &mut scope, effect_symbol, &mut var_store, - &mut exposed_vars_by_symbol, + &mut exposed_symbols, &mut declarations, ); - exposed_vars_by_symbol + exposed_symbols }; use roc_can::module::ModuleOutput; @@ -3063,7 +3069,6 @@ fn fabricate_effects_module<'a>( lookups: Vec::new(), problems: can_env.problems, ident_ids: can_env.ident_ids, - exposed_vars_by_symbol, references: MutSet::default(), }; @@ -3072,7 +3077,7 @@ fn fabricate_effects_module<'a>( let module = Module { module_id, exposed_imports: module_output.exposed_imports, - exposed_vars_by_symbol: module_output.exposed_vars_by_symbol, + exposed_symbols, references: module_output.references, aliases: module_output.aliases, rigid_variables: module_output.rigid_variables, @@ -3182,7 +3187,7 @@ fn canonicalize_and_constrain<'a>( dep_idents, aliases, exposed_imports, - exposed_symbols, + &exposed_symbols, &mut var_store, ); let canonicalize_end = SystemTime::now(); @@ -3196,7 +3201,7 @@ fn canonicalize_and_constrain<'a>( let module = Module { module_id, exposed_imports: module_output.exposed_imports, - exposed_vars_by_symbol: module_output.exposed_vars_by_symbol, + exposed_symbols, references: module_output.references, aliases: module_output.aliases, rigid_variables: module_output.rigid_variables, @@ -3503,9 +3508,20 @@ fn add_def_to_module<'a>( mono_env.subs, ) { Ok(l) => l, - Err(err) => { - // a host-exposed function is not monomorphized - todo!("The host-exposed function {:?} does not have a valid layout (e.g. maybe the function wasn't monomorphic): {:?}", symbol, err) + 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; } }; @@ -3537,9 +3553,29 @@ fn add_def_to_module<'a>( // get specialized! if is_exposed { let annotation = def.expr_var; - let layout = layout_cache.from_var(mono_env.arena, annotation, mono_env.subs).unwrap_or_else(|err| - todo!("TODO gracefully handle the situation where we expose a function to the host which doesn't have a valid layout (e.g. maybe the function wasn't monomorphic): {:?}", err) - ); + + let layout = match layout_cache.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; + } + }; procs.insert_exposed( symbol, diff --git a/compiler/load/tests/helpers/mod.rs b/compiler/load/tests/helpers/mod.rs index 22feefb073..b7750b4adb 100644 --- a/compiler/load/tests/helpers/mod.rs +++ b/compiler/load/tests/helpers/mod.rs @@ -8,7 +8,7 @@ use roc_can::expected::Expected; use roc_can::expr::{canonicalize_expr, Expr, Output}; use roc_can::operator; use roc_can::scope::Scope; -use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet}; +use roc_collections::all::{ImMap, ImSet, MutMap, SendSet}; use roc_constrain::expr::constrain_expr; use roc_constrain::module::{constrain_imported_values, Import}; use roc_module::ident::Ident; @@ -37,7 +37,7 @@ pub fn infer_expr( ) -> (Content, Subs) { let env = solve::Env { aliases: MutMap::default(), - vars_by_symbol: SendMap::default(), + vars_by_symbol: MutMap::default(), }; let (solved, _) = solve::run(&env, problems, subs, constraint); diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index f033e6f35f..989c113da8 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -34,6 +34,8 @@ pub enum LowLevel { NumSubWrap, NumSubChecked, NumMul, + NumMulWrap, + NumMulChecked, NumGt, NumGte, NumLt, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 8a4f92f1a7..efa7f13cc1 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -797,9 +797,35 @@ define_builtins! { 54 NUM_ATAN: "atan" 55 NUM_ACOS: "acos" 56 NUM_ASIN: "asin" - 57 NUM_BITWISE_AND: "bitwiseAnd" - 58 NUM_SUB_WRAP: "subWrap" - 59 NUM_SUB_CHECKED: "subChecked" + 57 NUM_AT_SIGNED128: "@Signed128" + 58 NUM_SIGNED128: "Signed128" imported + 59 NUM_AT_SIGNED64: "@Signed64" + 60 NUM_SIGNED64: "Signed64" imported + 61 NUM_AT_SIGNED32: "@Signed32" + 62 NUM_SIGNED32: "Signed32" imported + 63 NUM_AT_SIGNED16: "@Signed16" + 64 NUM_SIGNED16: "Signed16" imported + 65 NUM_AT_SIGNED8: "@Signed8" + 66 NUM_SIGNED8: "Signed8" imported + 67 NUM_AT_UNSIGNED128: "@Unsigned128" + 68 NUM_UNSIGNED128: "Unsigned128" imported + 69 NUM_AT_UNSIGNED64: "@Unsigned64" + 70 NUM_UNSIGNED64: "Unsigned64" imported + 71 NUM_AT_UNSIGNED32: "@Unsigned32" + 72 NUM_UNSIGNED32: "Unsigned32" imported + 73 NUM_AT_UNSIGNED16: "@Unsigned16" + 74 NUM_UNSIGNED16: "Unsigned16" imported + 75 NUM_AT_UNSIGNED8: "@Unsigned8" + 76 NUM_UNSIGNED8: "Unsigned8" imported + 77 NUM_AT_BINARY64: "@Binary64" + 78 NUM_BINARY64: "Binary64" imported + 79 NUM_AT_BINARY32: "@Binary32" + 80 NUM_BINARY32: "Binary32" imported + 81 NUM_BITWISE_AND: "bitwiseAnd" + 82 NUM_SUB_WRAP: "subWrap" + 83 NUM_SUB_CHECKED: "subChecked" + 84 NUM_MUL_WRAP: "mulWrap" + 85 NUM_MUL_CHECKED: "mulChecked" } 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 2b90dea87a..49b553710d 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -540,8 +540,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { ListSum => arena.alloc_slice_copy(&[borrowed]), Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap - | NumSubChecked | NumMul | NumGt | NumGte | NumLt | NumLte | NumCompare - | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt | NumBitwiseAnd => { + | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte + | NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt | NumBitwiseAnd => { arena.alloc_slice_copy(&[irrelevant, irrelevant]) } diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 5ccfd4ee64..f8138d74d5 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -1508,7 +1508,6 @@ pub fn specialize_all<'a>( )); procs.runtime_errors.insert(name, error_msg); - panic!("failed to specialize {:?}", name); } } } @@ -2585,13 +2584,23 @@ pub fn with_hole<'a>( let mut field_symbols_temp = Vec::with_capacity_in(args.len(), env.arena); - for (var, arg) in args.drain(..) { + for (var, mut arg) in args.drain(..) { // Layout will unpack this unwrapped tack if it only has one (non-zero-sized) field - let layout = layout_cache - .from_var(env.arena, var, env.subs) - .unwrap_or_else(|err| { - panic!("TODO turn fn_var into a RuntimeError {:?}", err) - }); + let layout = match layout_cache.from_var(env.arena, var, env.subs) { + Ok(cached) => cached, + Err(LayoutProblem::UnresolvedTypeVar(_)) => { + // this argument has type `forall a. a`, which is isomorphic to + // the empty type (Void, Never, the empty tag union `[]`) + use roc_can::expr::Expr; + use roc_problem::can::RuntimeError; + arg.value = Expr::RuntimeError(RuntimeError::VoidValue); + Layout::Struct(&[]) + } + Err(LayoutProblem::Erroneous) => { + // something went very wrong + panic!("TODO turn fn_var into a RuntimeError") + } + }; let alignment = layout.alignment_bytes(8); @@ -3575,9 +3584,23 @@ pub fn with_hole<'a>( let arg_symbols = arg_symbols.into_bump_slice(); // layout of the return type - let layout = layout_cache - .from_var(env.arena, ret_var, env.subs) - .unwrap_or_else(|err| todo!("TODO turn fn_var into a RuntimeError {:?}", err)); + let layout = match layout_cache.from_var(env.arena, ret_var, env.subs) { + Ok(cached) => cached, + Err(LayoutProblem::UnresolvedTypeVar(_)) => { + return Stmt::RuntimeError(env.arena.alloc(format!( + "UnresolvedTypeVar {} line {}", + file!(), + line!() + ))); + } + Err(LayoutProblem::Erroneous) => { + return Stmt::RuntimeError(env.arena.alloc(format!( + "Erroneous {} line {}", + file!(), + line!() + ))); + } + }; let result = Stmt::Let(assigned, Expr::RunLowLevel(op, arg_symbols), layout, hole); @@ -3654,7 +3677,7 @@ pub fn from_can<'a>( let mut stmt = from_can(env, branch_var, final_else.value, procs, layout_cache); for (loc_cond, loc_then) in branches.into_iter().rev() { - let branching_symbol = env.unique_symbol(); + let branching_symbol = possible_reuse_symbol(env, procs, &loc_cond.value); let then = from_can(env, branch_var, loc_then.value, procs, layout_cache); stmt = Stmt::Cond { @@ -3667,15 +3690,14 @@ pub fn from_can<'a>( ret_layout: ret_layout.clone(), }; - // add condition - stmt = with_hole( + stmt = assign_to_symbol( env, - loc_cond.value, - cond_var, procs, layout_cache, + cond_var, + loc_cond, branching_symbol, - env.arena.alloc(stmt), + stmt, ); } @@ -4869,9 +4891,7 @@ fn reuse_function_symbol<'a>( // this symbol is a function, that is used by-name (e.g. as an argument to another // function). Register it with the current variable, then create a function pointer // to it in the IR. - let layout = layout_cache - .from_var(env.arena, arg_var, env.subs) - .expect("creating layout does not fail"); + let res_layout = layout_cache.from_var(env.arena, arg_var, env.subs); // we have three kinds of functions really. Plain functions, closures by capture, // and closures by unification. Here we record whether this function captures @@ -4879,8 +4899,8 @@ fn reuse_function_symbol<'a>( let captures = partial_proc.captured_symbols.captures(); let captured = partial_proc.captured_symbols.clone(); - match layout { - Layout::Closure(argument_layouts, closure_layout, ret_layout) if captures => { + match res_layout { + Ok(Layout::Closure(argument_layouts, closure_layout, ret_layout)) if captures => { // this is a closure by capture, meaning it itself captures local variables. // we've defined the closure as a (function_ptr, closure_data) pair already @@ -4958,7 +4978,7 @@ fn reuse_function_symbol<'a>( stmt } - _ => { + Ok(layout) => { procs.insert_passed_by_name( env, arg_var, @@ -4974,6 +4994,17 @@ fn reuse_function_symbol<'a>( env.arena.alloc(result), ) } + Err(LayoutProblem::Erroneous) => { + let message = format!("The {:?} symbol has an erroneous type", symbol); + Stmt::RuntimeError(env.arena.alloc(message)) + } + Err(LayoutProblem::UnresolvedTypeVar(v)) => { + let message = format!( + "The {:?} symbol contains a unresolved type var {:?}", + symbol, v + ); + Stmt::RuntimeError(env.arena.alloc(message)) + } } } } @@ -5832,7 +5863,9 @@ pub enum IntOrFloat { pub fn num_argument_to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat { match subs.get_without_compacting(var).content { Content::Alias(Symbol::NUM_INTEGER, args, _) => { - debug_assert!(args.is_empty()); + debug_assert!(args.len() == 1); + + // TODO: we probably need to match on the type of the arg IntOrFloat::IntType } Content::FlexVar(_) => { @@ -5840,7 +5873,9 @@ pub fn num_argument_to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat { IntOrFloat::IntType } Content::Alias(Symbol::NUM_FLOATINGPOINT, args, _) => { - debug_assert!(args.is_empty()); + debug_assert!(args.len() == 1); + + // TODO: we probably need to match on the type of the arg IntOrFloat::FloatType } Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, attr_args)) => { @@ -5849,6 +5884,11 @@ pub fn num_argument_to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat { // Recurse on the second argument num_argument_to_int_or_float(subs, attr_args[1]) } + Content::Alias(Symbol::NUM_F64, args, _) | Content::Alias(Symbol::NUM_F32, args, _) => { + debug_assert!(args.is_empty()); + + IntOrFloat::FloatType + } other => { panic!( "Unrecognized Num type argument for var {:?} with Content: {:?}", diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 4b74489946..2d746d1a29 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -1196,39 +1196,40 @@ pub fn layout_from_tag_union<'a>( let tags_vec: std::vec::Vec<_> = tags.into_iter().collect(); - if tags_vec[0].0 != TagName::Private(Symbol::NUM_AT_NUM) { - let opt_rec_var = None; - let variant = union_sorted_tags_help(arena, tags_vec, opt_rec_var, subs); + match tags_vec.get(0) { + Some((tag_name, arguments)) if *tag_name == TagName::Private(Symbol::NUM_AT_NUM) => { + debug_assert_eq!(arguments.len(), 1); - match variant { - Never => panic!("TODO gracefully handle trying to instantiate Never"), - Unit | UnitWithArguments => Layout::Struct(&[]), - BoolUnion { .. } => Layout::Builtin(Builtin::Int1), - ByteUnion(_) => Layout::Builtin(Builtin::Int8), - Unwrapped(mut field_layouts) => { - if field_layouts.len() == 1 { - field_layouts.pop().unwrap() - } else { - Layout::Struct(field_layouts.into_bump_slice()) - } - } - Wrapped(tags) => { - let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); + let var = arguments.iter().next().unwrap(); - for (_, tag_layout) in tags { - tag_layouts.push(tag_layout); + unwrap_num_tag(subs, *var).expect("invalid Num argument") + } + _ => { + let opt_rec_var = None; + let variant = union_sorted_tags_help(arena, tags_vec, opt_rec_var, subs); + + match variant { + Never => Layout::Union(&[]), + Unit | UnitWithArguments => Layout::Struct(&[]), + BoolUnion { .. } => Layout::Builtin(Builtin::Int1), + ByteUnion(_) => Layout::Builtin(Builtin::Int8), + Unwrapped(mut field_layouts) => { + if field_layouts.len() == 1 { + field_layouts.pop().unwrap() + } else { + Layout::Struct(field_layouts.into_bump_slice()) + } + } + Wrapped(tags) => { + let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); + + for (_, tag_layout) in tags { + tag_layouts.push(tag_layout); + } + Layout::Union(tag_layouts.into_bump_slice()) } - Layout::Union(tag_layouts.into_bump_slice()) } } - } else { - let arguments = &tags_vec[0].1; - - debug_assert_eq!(arguments.len(), 1); - - let var = arguments.iter().next().unwrap(); - - unwrap_num_tag(subs, *var).expect("invalid Num argument") } } @@ -1296,11 +1297,17 @@ fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result, LayoutPr } }, Content::Alias(Symbol::NUM_INTEGER, args, _) => { - debug_assert!(args.is_empty()); + debug_assert!(args.len() == 1); + + // TODO: we probably need to match on the type of the arg + // and return the correct builtin ex: Builtin::{Int32, Int16} Ok(Layout::Builtin(Builtin::Int64)) } Content::Alias(Symbol::NUM_FLOATINGPOINT, args, _) => { - debug_assert!(args.is_empty()); + debug_assert!(args.len() == 1); + + // TODO: we probably need to match on the type of the arg + // and return the correct builtin ex: Builtin::Float32 Ok(Layout::Builtin(Builtin::Float64)) } Content::FlexVar(_) | Content::RigidVar(_) => { diff --git a/compiler/mono/tests/helpers/mod.rs b/compiler/mono/tests/helpers/mod.rs index cdbcd68ef8..8272bbae95 100644 --- a/compiler/mono/tests/helpers/mod.rs +++ b/compiler/mono/tests/helpers/mod.rs @@ -8,7 +8,7 @@ use roc_can::expected::Expected; use roc_can::expr::{canonicalize_expr, Expr, Output}; use roc_can::operator; use roc_can::scope::Scope; -use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet}; +use roc_collections::all::{ImMap, ImSet, MutMap, SendSet}; use roc_constrain::expr::constrain_expr; use roc_constrain::module::{constrain_imported_values, Import}; use roc_module::ident::Ident; @@ -37,7 +37,7 @@ pub fn infer_expr( ) -> (Content, Subs) { let env = solve::Env { aliases: MutMap::default(), - vars_by_symbol: SendMap::default(), + vars_by_symbol: MutMap::default(), }; let (solved, _) = solve::run(&env, problems, subs, constraint); diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index 59c1b431cd..6cf91131fc 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -147,6 +147,9 @@ pub enum RuntimeError { /// When the author specifies a type annotation but no implementation NoImplementation, + /// cases where the `[]` value (or equivalently, `forall a. a`) pops up + VoidValue, + ExposedButNotDefined(Symbol), } diff --git a/compiler/reporting/src/error/canonicalize.rs b/compiler/reporting/src/error/canonicalize.rs index c216473b98..e13c0d1c42 100644 --- a/compiler/reporting/src/error/canonicalize.rs +++ b/compiler/reporting/src/error/canonicalize.rs @@ -338,6 +338,12 @@ fn pretty_runtime_error<'b>( runtime_error: RuntimeError, ) -> RocDocBuilder<'b> { match runtime_error { + RuntimeError::VoidValue => { + // is used to communicate to the compiler that + // a branch is unreachable; this should never reach a user + unreachable!("") + } + RuntimeError::Shadowing { original_region, shadow, diff --git a/compiler/reporting/src/error/type.rs b/compiler/reporting/src/error/type.rs index 1decdf059d..c5beb5dfc3 100644 --- a/compiler/reporting/src/error/type.rs +++ b/compiler/reporting/src/error/type.rs @@ -1643,6 +1643,17 @@ fn to_diff<'b>( ErrorType::Type(Symbol::NUM_F64, _) => true, ErrorType::Alias(Symbol::NUM_F64, _, _) => true, + ErrorType::Type(Symbol::NUM_NUM, args) => match &args.get(0) { + Some(ErrorType::Type(Symbol::NUM_FLOATINGPOINT, _)) => true, + Some(ErrorType::Alias(Symbol::NUM_FLOATINGPOINT, _, _)) => true, + _ => false, + }, + ErrorType::Alias(Symbol::NUM_NUM, args, _) => match &args.get(0) { + Some((_, ErrorType::Type(Symbol::NUM_FLOATINGPOINT, _))) => true, + Some((_, ErrorType::Alias(Symbol::NUM_FLOATINGPOINT, _, _))) => true, + _ => false, + }, + _ => false, }; diff --git a/compiler/reporting/tests/helpers/mod.rs b/compiler/reporting/tests/helpers/mod.rs index a116b0643e..4d1b7784b7 100644 --- a/compiler/reporting/tests/helpers/mod.rs +++ b/compiler/reporting/tests/helpers/mod.rs @@ -7,7 +7,7 @@ use roc_can::expected::Expected; use roc_can::expr::{canonicalize_expr, Expr, Output}; use roc_can::operator; use roc_can::scope::Scope; -use roc_collections::all::{ImMap, MutMap, SendMap, SendSet}; +use roc_collections::all::{ImMap, MutMap, SendSet}; use roc_constrain::expr::constrain_expr; use roc_constrain::module::{constrain_imported_values, Import}; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; @@ -35,7 +35,7 @@ pub fn infer_expr( ) -> (Content, Subs) { let env = solve::Env { aliases: MutMap::default(), - vars_by_symbol: SendMap::default(), + vars_by_symbol: MutMap::default(), }; let (solved, _) = solve::run(&env, problems, subs, constraint); diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index 0f3b3dfc56..8903fe25dc 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -448,8 +448,8 @@ mod test_reporting { baz Str - main U8 + F64 "# ), ) @@ -620,9 +620,9 @@ mod test_reporting { these names seem close though: + Result Num Set - Result U8 "# ), @@ -1374,7 +1374,7 @@ mod test_reporting { Bool U8 F64 - Num + Str "# ), ) @@ -2094,7 +2094,7 @@ mod test_reporting { But `add` needs the 2nd argument to be: - Num Integer + Num (Integer Signed64) "# ), ) @@ -2123,7 +2123,7 @@ mod test_reporting { But `add` needs the 2nd argument to be: - Num Integer + Num (Integer Signed64) Tip: You can convert between Int and Float using functions like `Num.toFloat` and `Num.round`. diff --git a/compiler/solve/src/module.rs b/compiler/solve/src/module.rs index d53bef598f..e54c6ee711 100644 --- a/compiler/solve/src/module.rs +++ b/compiler/solve/src/module.rs @@ -1,7 +1,6 @@ use crate::solve; use roc_can::constraint::Constraint; -use roc_can::module::Module; -use roc_collections::all::{MutMap, SendMap}; +use roc_collections::all::MutMap; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; use roc_types::solved_types::{Solved, SolvedType}; @@ -12,37 +11,10 @@ use roc_types::types::Alias; pub struct SolvedModule { pub solved_types: MutMap, pub aliases: MutMap, - pub exposed_vars_by_symbol: Vec<(Symbol, Variable)>, + pub exposed_vars_by_symbol: MutMap, pub problems: Vec, } -pub fn solve_module( - module: Module, - constraint: Constraint, - var_store: VarStore, -) -> (Solved, SolvedModule) { - let Module { - exposed_vars_by_symbol, - aliases, - rigid_variables, - .. - } = module; - - let (solved_subs, solved_env, problems) = - run_solve(aliases, rigid_variables, constraint, var_store); - - let solved_types = make_solved_types(&solved_env, &solved_subs, &exposed_vars_by_symbol); - - let solved_module = SolvedModule { - exposed_vars_by_symbol, - solved_types, - problems, - aliases: solved_env.aliases, - }; - - (solved_subs, solved_module) -} - pub fn run_solve( aliases: MutMap, rigid_variables: MutMap, @@ -50,7 +22,7 @@ pub fn run_solve( var_store: VarStore, ) -> (Solved, solve::Env, Vec) { let env = solve::Env { - vars_by_symbol: SendMap::default(), + vars_by_symbol: MutMap::default(), aliases, }; @@ -73,7 +45,7 @@ pub fn run_solve( pub fn make_solved_types( solved_env: &solve::Env, solved_subs: &Solved, - exposed_vars_by_symbol: &[(Symbol, Variable)], + exposed_vars_by_symbol: &MutMap, ) -> MutMap { let mut solved_types = MutMap::default(); diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 5b6bd1a379..cc43bc4382 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1,6 +1,6 @@ use roc_can::constraint::Constraint::{self, *}; use roc_can::expected::{Expected, PExpected}; -use roc_collections::all::{ImMap, MutMap, SendMap}; +use roc_collections::all::{ImMap, MutMap}; use roc_module::ident::TagName; use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; @@ -73,7 +73,7 @@ pub enum TypeError { #[derive(Clone, Debug, Default)] pub struct Env { - pub vars_by_symbol: SendMap, + pub vars_by_symbol: MutMap, pub aliases: MutMap, } @@ -138,24 +138,9 @@ pub fn run( mut subs: Subs, constraint: &Constraint, ) -> (Solved, Env) { - let mut pools = Pools::default(); - let state = State { - env: env.clone(), - mark: Mark::NONE.next(), - }; - let rank = Rank::toplevel(); - let state = solve( - env, - state, - rank, - &mut pools, - problems, - &mut MutMap::default(), - &mut subs, - constraint, - ); + let env = run_in_place(env, problems, &mut subs, constraint); - (Solved(subs), state.env) + (Solved(subs), env) } /// Modify an existing subs in-place instead diff --git a/compiler/solve/tests/helpers/mod.rs b/compiler/solve/tests/helpers/mod.rs index dcf196fccb..3fc30dbf6e 100644 --- a/compiler/solve/tests/helpers/mod.rs +++ b/compiler/solve/tests/helpers/mod.rs @@ -8,7 +8,7 @@ use roc_can::expected::Expected; use roc_can::expr::{canonicalize_expr, Expr, Output}; use roc_can::operator; use roc_can::scope::Scope; -use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet}; +use roc_collections::all::{ImMap, ImSet, MutMap, SendSet}; use roc_constrain::expr::constrain_expr; use roc_constrain::module::{constrain_imported_values, Import}; use roc_module::ident::Ident; @@ -37,7 +37,7 @@ pub fn infer_expr( ) -> (Content, Subs) { let env = solve::Env { aliases: MutMap::default(), - vars_by_symbol: SendMap::default(), + vars_by_symbol: MutMap::default(), }; let (solved, _) = solve::run(&env, problems, subs, constraint); diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 835473f518..c1fbb2dba9 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -1326,7 +1326,7 @@ mod solve_expr { infer_eq( indoc!( r#" - int : Num.Num Num.Integer + int : Num.Num (Num.Integer Num.Signed64) int "# @@ -1339,7 +1339,7 @@ mod solve_expr { infer_eq( indoc!( r#" - int : Num.Num Num.Integer + int : Num.Num (Num.Integer Num.Signed64) int = 5 int @@ -1353,7 +1353,7 @@ mod solve_expr { infer_eq( indoc!( r#" - int : Num Integer + int : Num (Integer Signed64) int "# @@ -1366,7 +1366,7 @@ mod solve_expr { infer_eq( indoc!( r#" - int : Num Integer + int : Num (Integer Signed64) int = 5 int @@ -1931,7 +1931,7 @@ mod solve_expr { infer_eq( indoc!( r#" - float : Num.Num Num.FloatingPoint + float : Num.Num (Num.FloatingPoint Num.Binary64) float "# @@ -1944,7 +1944,7 @@ mod solve_expr { infer_eq( indoc!( r#" - float : Num.Num Num.FloatingPoint + float : Num.Num (Num.FloatingPoint Num.Binary64) float = 5.5 float @@ -1958,7 +1958,7 @@ mod solve_expr { infer_eq( indoc!( r#" - float : Num FloatingPoint + float : Num (FloatingPoint Binary64) float "# @@ -1971,7 +1971,7 @@ mod solve_expr { infer_eq( indoc!( r#" - float : Num FloatingPoint + float : Num (FloatingPoint Binary64) float = 5.5 float @@ -2216,7 +2216,7 @@ mod solve_expr { infer_eq_without_problem( indoc!( r#" - x : Num.Num Num.Integer + x : Num.Num (Num.Integer Num.Signed64) x = when 2 is 3 -> 4 @@ -2428,7 +2428,7 @@ mod solve_expr { r#" Foo a : { foo : a } - v : Foo (Num.Num Num.Integer) + v : Foo (Num.Num (Num.Integer Num.Signed64)) v = { foo: 42 } v @@ -2492,7 +2492,7 @@ mod solve_expr { r#" Peano : [ S Peano, Z ] - length : Peano -> Num.Num Num.Integer + length : Peano -> Num.Num (Num.Integer Num.Signed64) length = \peano -> when peano is Z -> 0 @@ -2592,10 +2592,10 @@ mod solve_expr { infer_eq( indoc!( r#" - r : { x : (Num.Num Num.Integer) } + r : { x : (Num.Num (Num.Integer Signed64)) } r = { x : 1 } - s : { left : { x : Num.Num Num.FloatingPoint } } + s : { left : { x : Num.Num (Num.FloatingPoint Num.Binary64) } } s = { left: { x : 3.14 } } when 0 is @@ -2757,7 +2757,7 @@ mod solve_expr { infer_eq_without_problem( indoc!( r#" - { x, y } : { x : Str.Str, y : Num.Num Num.FloatingPoint } + { x, y } : { x : Str.Str, y : Num.Num (Num.FloatingPoint Num.Binary64) } { x, y } = { x : "foo", y : 3.14 } x @@ -2772,7 +2772,7 @@ mod solve_expr { infer_eq( indoc!( r#" - Foo : { x : Str.Str, y : Num.Num Num.FloatingPoint } + Foo : { x : Str.Str, y : Num.Num (Num.FloatingPoint Num.Binary64) } { x, y } : Foo { x, y } = { x : "foo", y : 3.14 } @@ -2830,7 +2830,7 @@ mod solve_expr { infer_eq_without_problem( indoc!( r#" - Foo : { x : Str.Str, y : Num.Num Num.FloatingPoint } + Foo : { x : Str.Str, y : Num.Num (Num.FloatingPoint Num.Binary64) } { x, y } : Foo { x, y } = { x : "foo", y : 3.14 } diff --git a/compiler/solve/tests/solve_uniq_expr.rs b/compiler/solve/tests/solve_uniq_expr.rs index adf9f098bf..6f943c8bbe 100644 --- a/compiler/solve/tests/solve_uniq_expr.rs +++ b/compiler/solve/tests/solve_uniq_expr.rs @@ -1118,7 +1118,7 @@ mod solve_uniq_expr { infer_eq( indoc!( r#" - x : Num.Num Num.Integer + x : Num.Num (Num.Integer Num.Signed64) x = 4 x @@ -1368,7 +1368,7 @@ mod solve_uniq_expr { infer_eq( indoc!( r#" - x : Num.Num Num.Integer + x : I64 x = when 2 is 3 -> 4 @@ -1816,7 +1816,7 @@ mod solve_uniq_expr { infer_eq( indoc!( r#" - { x, y } : { x : Str.Str, y : Num.Num Num.FloatingPoint } + { x, y } : { x : Str.Str, y : F64 } { x, y } = { x : "foo", y : 3.14 } x @@ -2662,7 +2662,7 @@ mod solve_uniq_expr { f "# ), - "Attr * (Attr a I64, Attr b I64 -> Attr c I64)", + "Attr * (Attr b I64, Attr c I64 -> Attr d I64)", ); } @@ -3150,7 +3150,7 @@ mod solve_uniq_expr { indoc!( r#" empty : List I64 - empty = + empty = [] List.walkBackwards empty (\a, b -> a + b) 0 @@ -3159,4 +3159,55 @@ mod solve_uniq_expr { "Attr a I64", ); } + + #[test] + fn list_set_out_of_bounds_num() { + infer_eq( + indoc!( + r#" + List.set [2] 1337 0 + "# + ), + "Attr * (List (Attr * (Num (Attr * *))))", + ); + } + + #[test] + fn list_set_out_of_bounds_int() { + infer_eq( + indoc!( + r#" + List.set [0x2] 1337 0 + "# + ), + "Attr * (List (Attr * I64))", + ); + } + + #[test] + fn list_set_out_of_bounds_float() { + infer_eq( + indoc!( + r#" + List.set [0.2] 1337 0 + "# + ), + "Attr * (List (Attr * F64))", + ); + } + + #[test] + #[ignore] + fn list_set_out_of_bounds_int_int() { + // the unification of an integer list with a new integer element is a problem + // same for floats, but it's fine with the unspecified Num + infer_eq( + indoc!( + r#" + List.set [0x2] 1337 0x1 + "# + ), + "Attr * (List (Attr a I64))", + ); + } } diff --git a/compiler/types/src/builtin_aliases.rs b/compiler/types/src/builtin_aliases.rs index 885d9fa395..8e734fe99a 100644 --- a/compiler/types/src/builtin_aliases.rs +++ b/compiler/types/src/builtin_aliases.rs @@ -38,143 +38,213 @@ pub fn aliases() -> MutMap { }, ); - // Integer : [ @Integer ] + // Integer range : [ @Integer range ] add_alias( Symbol::NUM_INTEGER, BuiltinAlias { region: Region::zero(), - vars: Vec::new(), - typ: integer_alias_content(), + vars: vec![Located::at(Region::zero(), "range".into())], + typ: integer_alias_content(flex(TVAR1)), }, ); - // I128 Num Integer + // Signed128 : [ @Signed128 ] + add_alias( + Symbol::NUM_SIGNED128, + BuiltinAlias { + region: Region::zero(), + vars: vec![], + typ: signed128_alias_content(), + }, + ); + + // I128 : Num (Integer Signed128) add_alias( Symbol::NUM_I128, BuiltinAlias { region: Region::zero(), vars: Vec::new(), - typ: int_alias_content(), + typ: int_alias_content(signed128_type()), }, ); - // U128 : Num Integer + // U128 : Num (Integer Unsigned128) add_alias( Symbol::NUM_U128, BuiltinAlias { region: Region::zero(), vars: Vec::new(), - typ: int_alias_content(), + typ: int_alias_content(unsigned128_type()), }, ); - // I64 Num Integer + // Signed64 : [ @Signed64 ] + add_alias( + Symbol::NUM_SIGNED64, + BuiltinAlias { + region: Region::zero(), + vars: vec![], + typ: signed64_alias_content(), + }, + ); + + // I64 : Num (Integer Signed64) add_alias( Symbol::NUM_I64, BuiltinAlias { region: Region::zero(), vars: Vec::new(), - typ: int_alias_content(), + typ: int_alias_content(signed64_type()), }, ); - // U64 : Num Integer + // U64 : Num (Integer Unsigned64) add_alias( Symbol::NUM_U64, BuiltinAlias { region: Region::zero(), vars: Vec::new(), - typ: int_alias_content(), + typ: int_alias_content(unsigned64_type()), }, ); - // I32 Num Integer + // Signed32 : [ @Signed32 ] + add_alias( + Symbol::NUM_SIGNED32, + BuiltinAlias { + region: Region::zero(), + vars: vec![], + typ: signed32_alias_content(), + }, + ); + + // I32 : Num (Integer Signed32) add_alias( Symbol::NUM_I32, BuiltinAlias { region: Region::zero(), vars: Vec::new(), - typ: int_alias_content(), + typ: int_alias_content(signed32_type()), }, ); - // U32 : Num Integer + // U32 : Num (Integer Unsigned32) add_alias( Symbol::NUM_U32, BuiltinAlias { region: Region::zero(), vars: Vec::new(), - typ: int_alias_content(), + typ: int_alias_content(unsigned32_type()), }, ); - // I16 Num Integer + // Signed16 : [ @Signed16 ] + add_alias( + Symbol::NUM_SIGNED16, + BuiltinAlias { + region: Region::zero(), + vars: vec![], + typ: signed16_alias_content(), + }, + ); + + // I16 : Num (Integer Signed16) add_alias( Symbol::NUM_I16, BuiltinAlias { region: Region::zero(), vars: Vec::new(), - typ: int_alias_content(), + typ: int_alias_content(signed16_type()), }, ); - // U16 : Num Integer + // U16 : Num (Integer Unsigned16) add_alias( Symbol::NUM_U16, BuiltinAlias { region: Region::zero(), vars: Vec::new(), - typ: int_alias_content(), + typ: int_alias_content(unsigned16_type()), }, ); - // I8 Num Integer + // Signed8 : [ @Signed8 ] + add_alias( + Symbol::NUM_SIGNED8, + BuiltinAlias { + region: Region::zero(), + vars: vec![], + typ: signed8_alias_content(), + }, + ); + + // I8 : Num (Integer Signed8) add_alias( Symbol::NUM_I8, BuiltinAlias { region: Region::zero(), vars: Vec::new(), - typ: int_alias_content(), + typ: int_alias_content(signed8_type()), }, ); - // U8 : Num Integer + // U8 : Num (Integer Unsigned8) add_alias( Symbol::NUM_U8, BuiltinAlias { region: Region::zero(), vars: Vec::new(), - typ: int_alias_content(), + typ: int_alias_content(unsigned8_type()), }, ); - // FloatingPoint : [ @FloatingPoint ] + // Binary64 : [ @Binary64 ] + add_alias( + Symbol::NUM_BINARY64, + BuiltinAlias { + region: Region::zero(), + vars: vec![], + typ: binary64_alias_content(), + }, + ); + + // Binary32 : [ @Binary32 ] + add_alias( + Symbol::NUM_BINARY32, + BuiltinAlias { + region: Region::zero(), + vars: vec![], + typ: binary32_alias_content(), + }, + ); + + // FloatingPoint range : [ @FloatingPoint range ] add_alias( Symbol::NUM_FLOATINGPOINT, BuiltinAlias { region: Region::zero(), - vars: Vec::new(), - typ: floatingpoint_alias_content(), + vars: vec![Located::at(Region::zero(), "range".into())], + typ: floatingpoint_alias_content(flex(TVAR1)), }, ); - // F64 : Num FloatingPoint + // F64 : Num (FloatingPoint Binary64) add_alias( Symbol::NUM_F64, BuiltinAlias { region: Region::zero(), vars: Vec::new(), - typ: float_alias_content(), + typ: float_alias_content(binary64_type()), }, ); - // F32 : Num FloatingPoint + // F32 : Num (FloatingPoint Binary32) add_alias( Symbol::NUM_F32, BuiltinAlias { region: Region::zero(), vars: Vec::new(), - typ: float_alias_content(), + typ: float_alias_content(binary32_type()), }, ); @@ -226,57 +296,233 @@ fn num_alias_content(range: SolvedType) -> SolvedType { // FLOATING POINT #[inline(always)] -pub fn floatingpoint_type() -> SolvedType { +pub fn floatingpoint_type(range: SolvedType) -> SolvedType { SolvedType::Alias( Symbol::NUM_FLOATINGPOINT, - Vec::new(), - Box::new(floatingpoint_alias_content()), + vec![("range".into(), range.clone())], + Box::new(floatingpoint_alias_content(range)), ) } #[inline(always)] -fn floatingpoint_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_FLOATINGPOINT, Vec::new()) +fn floatingpoint_alias_content(range: SolvedType) -> SolvedType { + single_private_tag(Symbol::NUM_AT_FLOATINGPOINT, vec![range]) } // FLOAT #[inline(always)] pub fn float_type() -> SolvedType { - SolvedType::Alias(Symbol::NUM_F64, Vec::new(), Box::new(float_alias_content())) + SolvedType::Alias( + Symbol::NUM_F64, + Vec::new(), + Box::new(float_alias_content(binary64_type())), + ) } #[inline(always)] -fn float_alias_content() -> SolvedType { - num_type(floatingpoint_type()) +fn float_alias_content(typ: SolvedType) -> SolvedType { + num_type(floatingpoint_type(typ)) } // INT #[inline(always)] pub fn int_type() -> SolvedType { - SolvedType::Alias(Symbol::NUM_I64, Vec::new(), Box::new(int_alias_content())) + SolvedType::Alias( + Symbol::NUM_I64, + Vec::new(), + Box::new(int_alias_content(signed64_type())), + ) } #[inline(always)] -fn int_alias_content() -> SolvedType { - num_type(integer_type()) +fn int_alias_content(range: SolvedType) -> SolvedType { + num_type(integer_type(range)) } // INTEGER #[inline(always)] -pub fn integer_type() -> SolvedType { +pub fn integer_type(range: SolvedType) -> SolvedType { SolvedType::Alias( Symbol::NUM_INTEGER, - Vec::new(), - Box::new(integer_alias_content()), + vec![("range".into(), range.clone())], + Box::new(integer_alias_content(range)), ) } #[inline(always)] -fn integer_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_INTEGER, Vec::new()) +fn integer_alias_content(range: SolvedType) -> SolvedType { + single_private_tag(Symbol::NUM_AT_INTEGER, vec![range]) +} + +#[inline(always)] +pub fn binary64_type() -> SolvedType { + SolvedType::Alias( + Symbol::NUM_BINARY64, + vec![], + Box::new(binary64_alias_content()), + ) +} + +#[inline(always)] +pub fn binary64_alias_content() -> SolvedType { + single_private_tag(Symbol::NUM_AT_BINARY64, vec![]) +} + +#[inline(always)] +pub fn binary32_type() -> SolvedType { + SolvedType::Alias( + Symbol::NUM_BINARY32, + vec![], + Box::new(binary32_alias_content()), + ) +} + +#[inline(always)] +fn binary32_alias_content() -> SolvedType { + single_private_tag(Symbol::NUM_AT_BINARY32, vec![]) +} + +#[inline(always)] +pub fn signed128_type() -> SolvedType { + SolvedType::Alias( + Symbol::NUM_SIGNED128, + vec![], + Box::new(signed128_alias_content()), + ) +} + +#[inline(always)] +fn signed128_alias_content() -> SolvedType { + single_private_tag(Symbol::NUM_AT_SIGNED128, vec![]) +} + +#[inline(always)] +pub fn signed64_type() -> SolvedType { + SolvedType::Alias( + Symbol::NUM_SIGNED64, + vec![], + Box::new(signed64_alias_content()), + ) +} + +#[inline(always)] +fn signed64_alias_content() -> SolvedType { + single_private_tag(Symbol::NUM_AT_SIGNED64, vec![]) +} + +#[inline(always)] +pub fn signed32_type() -> SolvedType { + SolvedType::Alias( + Symbol::NUM_SIGNED32, + vec![], + Box::new(signed32_alias_content()), + ) +} + +#[inline(always)] +fn signed32_alias_content() -> SolvedType { + single_private_tag(Symbol::NUM_AT_SIGNED32, vec![]) +} + +#[inline(always)] +pub fn signed16_type() -> SolvedType { + SolvedType::Alias( + Symbol::NUM_SIGNED16, + vec![], + Box::new(signed16_alias_content()), + ) +} + +#[inline(always)] +fn signed16_alias_content() -> SolvedType { + single_private_tag(Symbol::NUM_AT_SIGNED16, vec![]) +} + +#[inline(always)] +pub fn signed8_type() -> SolvedType { + SolvedType::Alias( + Symbol::NUM_SIGNED8, + vec![], + Box::new(signed8_alias_content()), + ) +} + +#[inline(always)] +fn signed8_alias_content() -> SolvedType { + single_private_tag(Symbol::NUM_AT_SIGNED8, vec![]) +} + +#[inline(always)] +pub fn unsigned128_type() -> SolvedType { + SolvedType::Alias( + Symbol::NUM_UNSIGNED128, + vec![], + Box::new(unsigned128_alias_content()), + ) +} + +#[inline(always)] +fn unsigned128_alias_content() -> SolvedType { + single_private_tag(Symbol::NUM_AT_UNSIGNED128, vec![]) +} + +#[inline(always)] +pub fn unsigned64_type() -> SolvedType { + SolvedType::Alias( + Symbol::NUM_UNSIGNED64, + vec![], + Box::new(unsigned64_alias_content()), + ) +} + +#[inline(always)] +fn unsigned64_alias_content() -> SolvedType { + single_private_tag(Symbol::NUM_AT_UNSIGNED64, vec![]) +} + +#[inline(always)] +pub fn unsigned32_type() -> SolvedType { + SolvedType::Alias( + Symbol::NUM_UNSIGNED32, + vec![], + Box::new(unsigned32_alias_content()), + ) +} + +#[inline(always)] +fn unsigned32_alias_content() -> SolvedType { + single_private_tag(Symbol::NUM_AT_UNSIGNED32, vec![]) +} + +#[inline(always)] +pub fn unsigned16_type() -> SolvedType { + SolvedType::Alias( + Symbol::NUM_UNSIGNED16, + vec![], + Box::new(unsigned16_alias_content()), + ) +} + +#[inline(always)] +fn unsigned16_alias_content() -> SolvedType { + single_private_tag(Symbol::NUM_AT_UNSIGNED16, vec![]) +} + +#[inline(always)] +pub fn unsigned8_type() -> SolvedType { + SolvedType::Alias( + Symbol::NUM_UNSIGNED8, + vec![], + Box::new(unsigned8_alias_content()), + ) +} + +#[inline(always)] +fn unsigned8_alias_content() -> SolvedType { + single_private_tag(Symbol::NUM_AT_UNSIGNED8, vec![]) } #[inline(always)] @@ -351,7 +597,7 @@ pub fn dict_type(key: SolvedType, value: SolvedType) -> SolvedType { SolvedType::Apply(Symbol::DICT_DICT, vec![key, value]) } -fn single_private_tag(symbol: Symbol, type_arguments: Vec) -> SolvedType { +pub fn single_private_tag(symbol: Symbol, type_arguments: Vec) -> SolvedType { SolvedType::TagUnion( vec![(TagName::Private(symbol), type_arguments)], Box::new(SolvedType::EmptyTagUnion), diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index d2dd43db1c..b4891ce080 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -753,10 +753,10 @@ fn write_apply( match &arg_content { Content::Structure(FlatType::Apply(symbol, nested_args)) => match *symbol { - Symbol::NUM_INTEGER if nested_args.is_empty() => { + Symbol::NUM_INTEGER if nested_args.len() == 1 => { buf.push_str("I64"); } - Symbol::NUM_FLOATINGPOINT if nested_args.is_empty() => { + Symbol::NUM_FLOATINGPOINT if nested_args.len() == 1 => { buf.push_str("F64"); } Symbol::ATTR_ATTR => match nested_args @@ -767,10 +767,10 @@ fn write_apply( double_nested_symbol, double_nested_args, ))) => match double_nested_symbol { - Symbol::NUM_INTEGER if double_nested_args.is_empty() => { + Symbol::NUM_INTEGER if double_nested_args.len() == 1 => { buf.push_str("I64"); } - Symbol::NUM_FLOATINGPOINT if double_nested_args.is_empty() => { + Symbol::NUM_FLOATINGPOINT if double_nested_args.len() == 1 => { buf.push_str("F64"); } _ => default_case(subs, arg_content), diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index c3767540ad..96935b1e90 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -198,7 +198,7 @@ fn unify_alias( problems } else { - mismatch!() + mismatch!("{}", symbol) } } else { unify_pool(subs, pool, real_var, *other_real_var) diff --git a/compiler/uniq/tests/helpers/mod.rs b/compiler/uniq/tests/helpers/mod.rs index 9d298728e0..121eaeeee0 100644 --- a/compiler/uniq/tests/helpers/mod.rs +++ b/compiler/uniq/tests/helpers/mod.rs @@ -8,7 +8,7 @@ use roc_can::expected::Expected; use roc_can::expr::{canonicalize_expr, Expr, Output}; use roc_can::operator; use roc_can::scope::Scope; -use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet}; +use roc_collections::all::{ImMap, ImSet, MutMap, SendSet}; use roc_constrain::expr::constrain_expr; use roc_constrain::module::{constrain_imported_values, Import}; use roc_module::ident::Ident; @@ -37,7 +37,7 @@ pub fn infer_expr( ) -> (Content, Subs) { let env = solve::Env { aliases: MutMap::default(), - vars_by_symbol: SendMap::default(), + vars_by_symbol: MutMap::default(), }; let (solved, _) = solve::run(&env, problems, subs, constraint); diff --git a/examples/effect/Main.roc b/examples/effect/Main.roc index 71902f9efb..9b10bb3bc8 100644 --- a/examples/effect/Main.roc +++ b/examples/effect/Main.roc @@ -3,6 +3,6 @@ app "effect-example" imports [base.Task] provides [ main ] to base -main : Task.Task {} F64 +main : Task.Task {} [] main = Task.after Task.getLine \lineThisThing -> Task.putLine lineThisThing diff --git a/examples/effect/thing/platform-dir/Pkg-Config.roc b/examples/effect/thing/platform-dir/Pkg-Config.roc index a4475061cc..643cb4ebcd 100644 --- a/examples/effect/thing/platform-dir/Pkg-Config.roc +++ b/examples/effect/thing/platform-dir/Pkg-Config.roc @@ -12,5 +12,5 @@ platform folkertdev/foo } -mainForHost : Task.Task {} F64 as Fx +mainForHost : Task.Task {} [] as Fx mainForHost = main