diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 5dd954fe56..aeb77a803b 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -4,8 +4,8 @@ use roc_module::symbol::Symbol; use roc_region::all::Region; use roc_types::builtin_aliases::{ bool_type, dict_type, float_type, i128_type, int_type, list_type, nat_type, num_type, - ordering_type, result_type, set_type, str_type, str_utf8_byte_problem_type, u32_type, u64_type, - u8_type, u16_type + ordering_type, result_type, set_type, str_type, str_utf8_byte_problem_type, u16_type, u32_type, + u64_type, u8_type, }; use roc_types::solved_types::SolvedType; use roc_types::subs::VarId; @@ -509,11 +509,17 @@ pub fn types() -> MutMap { ); // bytesToU16 : List U8, Nat -> U16 - add_top_level_function_type!( - Symbol::NUM_BYTES_TO_U16, - vec![list_type(u8_type()), nat_type()], - Box::new(u16_type()), - ); + { + let position_out_of_bounds = SolvedType::TagUnion( + vec![(TagName::Global("OutOfBounds".into()), vec![])], + Box::new(SolvedType::Wildcard), + ); + add_top_level_function_type!( + Symbol::NUM_BYTES_TO_U16, + vec![list_type(u8_type()), nat_type()], + Box::new(result_type(u16_type(), position_out_of_bounds)), + ); + } // bytesToU32 : List U8, Nat -> U32 add_top_level_function_type!( diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index f34e317858..a645fb917e 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1092,26 +1092,113 @@ fn num_asin(symbol: Symbol, var_store: &mut VarStore) -> Def { /// Num.bytesToU16 : List U8, Nat -> U16 fn num_bytes_to_u16(symbol: Symbol, var_store: &mut VarStore) -> Def { + let len_var = var_store.fresh(); let list_var = var_store.fresh(); - let nat_var = var_store.fresh(); - let ret_var = var_store.fresh(); + let elem_var = var_store.fresh(); - let body = RunLowLevel { - op: LowLevel::NumBytesToU16, - args: vec![ - (list_var, Var(Symbol::ARG_1)), - (nat_var, Var(Symbol::ARG_2)), - ], - ret_var, + let ret_var = var_store.fresh(); + let bool_var = var_store.fresh(); + let add_var = var_store.fresh(); + let cast_var = var_store.fresh(); + + // Perform a bounds check. If it passes, run LowLevel::NumBytesToU16 + let body = If { + cond_var: bool_var, + branch_var: var_store.fresh(), + branches: vec![( + // if-condition + no_region( + // index + 1 < List.len list + RunLowLevel { + op: LowLevel::NumLt, + args: vec![ + ( + len_var, + RunLowLevel { + op: LowLevel::NumAdd, + args: vec![ + (add_var, Var(Symbol::ARG_2)), + ( + add_var, + RunLowLevel { + ret_var: cast_var, + args: vec![(cast_var, Num(var_store.fresh(), 1))], + op: LowLevel::NumCastToNat, + }, + ), + ], + ret_var: add_var, + }, + ), + ( + len_var, + RunLowLevel { + op: LowLevel::ListLen, + args: vec![(list_var, Var(Symbol::ARG_1))], + ret_var: len_var, + }, + ), + ], + ret_var: bool_var, + }, + ), + // then-branch + no_region( + // Ok + tag( + "Ok", + vec![RunLowLevel { + op: LowLevel::NumBytesToU16, + args: vec![ + (list_var, Var(Symbol::ARG_1)), + (len_var, Var(Symbol::ARG_2)), + ], + ret_var: elem_var, + }], + var_store, + ), + ), + )], + final_else: Box::new( + // else-branch + no_region( + // Err + tag( + "Err", + vec![tag("OutOfBounds", Vec::new(), var_store)], + var_store, + ), + ), + ), }; defn( symbol, - vec![(list_var, Symbol::ARG_1), (nat_var, Symbol::ARG_2)], + vec![(list_var, Symbol::ARG_1), (len_var, Symbol::ARG_2)], var_store, body, ret_var, ) + // let list_var = var_store.fresh(); + // let nat_var = var_store.fresh(); + // let ret_var = var_store.fresh(); + + // let body = RunLowLevel { + // op: LowLevel::NumBytesToU16, + // args: vec![ + // (list_var, Var(Symbol::ARG_1)), + // (nat_var, Var(Symbol::ARG_2)), + // ], + // ret_var, + // }; + + // defn( + // symbol, + // vec![(list_var, Symbol::ARG_1), (nat_var, Symbol::ARG_2)], + // var_store, + // body, + // ret_var, + // ) } /// Num.bytesToU32 : List U8, Nat -> U32 diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index 4f9162fcc7..c2e78941c1 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -1628,21 +1628,59 @@ mod gen_num { // TODO: Once we know what the function should do, let's give these real names! #[test] fn potato_1() { - assert_evals_to!("Num.bytesToU16 [] 1", 40, u16); + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Num.bytesToU16 bytes 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 40, + usize + ); } #[test] fn potato_2() { - assert_evals_to!("Num.bytesToU16 [] 0", 40, u16); + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Num.bytesToU16 bytes 234 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 1, + usize + ); } #[test] fn potato_3() { - assert_evals_to!("Num.bytesToU32 [] 1", 41, u32); + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Num.bytesToU16 bytes 4 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 1, + usize + ); } #[test] fn potato_4() { + assert_evals_to!("Num.bytesToU32 [] 1", 41, u32); + } + + #[test] + fn potato_5() { assert_evals_to!("Num.bytesToU32 [] 0", 41, u32); } }