diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 6ca4a083e4..4ca01fbf19 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1,11 +1,12 @@ use crate::def::Def; +use crate::expr::Expr; use crate::expr::Recursive; use roc_collections::all::SendMap; use roc_module::ident::TagName; use roc_module::symbol::Symbol; use roc_parse::operator::CalledVia; use roc_region::all::{Located, Region}; -use roc_types::subs::VarStore; +use roc_types::subs::{VarStore, Variable}; /// Some builtins cannot be constructed in code gen alone, and need to be defined /// as separate Roc defs. For example, List.get has this type: @@ -44,77 +45,63 @@ fn list_get(var_store: &VarStore) -> Def { ), ]; - // Perform a bounds check. If it passes, delegate to List.getUnsafe + // Perform a bounds check. If it passes, delegate to List.#getUnsafe let body = If { cond_var: var_store.fresh(), branch_var: var_store.fresh(), branches: vec![( // if-condition - Located { - region: Region::zero(), - value: Call( - Box::new(( - var_store.fresh(), - Located { - region: Region::zero(), - value: Var(Symbol::LIST_IS_EMPTY), // TODO actually check if (index < List.len list) - }, - var_store.fresh(), - )), - vec![( - var_store.fresh(), - Located { - region: Region::zero(), - value: Var(Symbol::LIST_GET_ARG_LIST), - }, - )], - CalledVia::Space, + no_region( + // index < List.len list + call( + Symbol::NUM_LT, + vec![ + Var(Symbol::LIST_GET_ARG_INDEX), + call( + Symbol::LIST_LEN, + vec![Var(Symbol::LIST_GET_ARG_LIST)], + var_store, + ), + ], + var_store, ), - }, + ), // then-branch - Located { - region: Region::zero(), - value: + no_region( // Ok - Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), // TODO Variable::EMPTY_TAG_UNION should work here - name: TagName::Global("Ok".into()), - arguments: vec![( - var_store.fresh(), - no_region( - // List.getUnsafe list index - Call( - Box::new((var_store.fresh(), no_region(Var(Symbol::LIST_GET_UNSAFE)), var_store.fresh())), - vec![ - (var_store.fresh(), no_region(Var(Symbol::LIST_GET_ARG_LIST))), - (var_store.fresh(), no_region(Var(Symbol::LIST_GET_ARG_INDEX))), - ], - CalledVia::Space, - ) - ) - )] - }, - }, + tag( + "Ok", + vec![ + // List.getUnsafe list index + Call( + Box::new(( + var_store.fresh(), + no_region(Var(Symbol::LIST_GET_UNSAFE)), + var_store.fresh(), + )), + vec![ + (var_store.fresh(), no_region(Var(Symbol::LIST_GET_ARG_LIST))), + ( + var_store.fresh(), + no_region(Var(Symbol::LIST_GET_ARG_INDEX)), + ), + ], + CalledVia::Space, + ), + ], + var_store, + ), + ), )], final_else: Box::new( // else-branch no_region( // Err - Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: TagName::Global("Err".into()), - arguments: vec![( - var_store.fresh(), - no_region(Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: TagName::Global("OutOfBounds".into()), - arguments: std::vec::Vec::new(), - }), - )], - }, + tag( + "Err", + vec![tag("OutOfBounds", Vec::new(), var_store)], + var_store, + ), ), ), }; @@ -147,79 +134,47 @@ fn list_first(var_store: &VarStore) -> Def { )]; // Perform a bounds check. If it passes, delegate to List.getUnsafe. - let body = - // Use "when" instead of "if" so that we can have False be the first branch. + let body = If { + // TODO Use "when" instead of "if" so that we can have False be the first branch. // We want that for branch prediction; usually we expect the list to be nonempty. - If { cond_var: var_store.fresh(), branch_var: var_store.fresh(), branches: vec![( // if-condition - Located { - region: Region::zero(), - value: Call( - Box::new(( - var_store.fresh(), - Located { - region: Region::zero(), - value: Var(Symbol::LIST_IS_EMPTY), - }, - var_store.fresh(), - )), - vec![( - var_store.fresh(), - Located { - region: Region::zero(), - value: Var(Symbol::LIST_GET_ARG_LIST), - }, - )], - CalledVia::Space, + no_region( + // List.isEmpty list + call( + Symbol::LIST_IS_EMPTY, + vec![Var(Symbol::LIST_FIRST_ARG)], + var_store, ), - }, + ), // then-branch - Located { - region: Region::zero(), - value: - // Ok - Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: TagName::Global("Ok".into()), - arguments: vec![( - var_store.fresh(), - no_region( - // List.getUnsafe list index - Call( - Box::new((var_store.fresh(), no_region(Var(Symbol::LIST_GET_UNSAFE)), var_store.fresh())), - vec![ - (var_store.fresh(), no_region(Var(Symbol::LIST_GET_ARG_LIST))), - (var_store.fresh(), no_region(Var(Symbol::LIST_GET_ARG_INDEX))), - ], - CalledVia::Space, - ) - ) - )] - }, - }, + no_region( + // Err OutOfBounds + tag( + "Err", + vec![tag("OutOfBounds", Vec::new(), var_store)], + var_store, + ), + ), )], final_else: Box::new( // default branch no_region( - // Err - Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: TagName::Global("Err".into()), - arguments: vec![( - var_store.fresh(), - no_region(Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: TagName::Global("OutOfBounds".into()), - arguments: std::vec::Vec::new(), - }), - )], - }, + // Ok (List.#getUnsafe list 0) + tag( + "Ok", + vec![ + // List.#getUnsafe list 0 + call( + Symbol::LIST_GET_UNSAFE, + vec![(Var(Symbol::LIST_FIRST_ARG)), (Int(var_store.fresh(), 0))], + var_store, + ), + ], + var_store, + ), ), ), }; @@ -248,3 +203,31 @@ fn no_region(value: T) -> Located { value, } } + +#[inline(always)] +fn tag(name: &'static str, args: Vec, var_store: &VarStore) -> Expr { + Expr::Tag { + variant_var: var_store.fresh(), + ext_var: var_store.fresh(), // TODO Variable::EMPTY_TAG_UNION should work here + name: TagName::Global(name.into()), + arguments: args + .into_iter() + .map(|expr| (var_store.fresh(), no_region(expr))) + .collect::)>>(), + } +} + +#[inline(always)] +fn call(symbol: Symbol, args: Vec, var_store: &VarStore) -> Expr { + Expr::Call( + Box::new(( + var_store.fresh(), + no_region(Expr::Var(symbol)), + var_store.fresh(), + )), + args.into_iter() + .map(|expr| (var_store.fresh(), no_region(expr))) + .collect::)>>(), + CalledVia::Space, + ) +}