use crate::layout::{Builtin, Layout}; use bumpalo::collections::Vec; use bumpalo::Bump; use roc_can; use roc_can::pattern::Pattern; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_region::all::Located; use roc_types::subs::{Content, ContentHash, FlatType, Subs, Variable}; #[derive(Clone, Debug, PartialEq, Default)] pub struct Procs<'a> { user_defined: MutMap>, builtin: MutSet, } impl<'a> Procs<'a> { fn insert_user_defined(&mut self, symbol: Symbol, partial_proc: PartialProc<'a>) { self.user_defined.insert(symbol, partial_proc); } fn insert_specialization( &mut self, symbol: Symbol, hash: ContentHash, spec_name: Symbol, proc: Option>, ) { self.user_defined .get_mut(&symbol) .map(|partial_proc| partial_proc.specializations.insert(hash, (spec_name, proc))); } fn get_user_defined(&self, symbol: Symbol) -> Option<&PartialProc<'a>> { self.user_defined.get(&symbol) } pub fn len(&self) -> usize { self.user_defined .values() .map(|v| v.specializations.len()) .sum() } fn insert_builtin(&mut self, symbol: Symbol) { self.builtin.insert(symbol); } pub fn as_map(&self) -> MutMap>> { let mut result = MutMap::default(); for partial_proc in self.user_defined.values() { for (_, (symbol, opt_proc)) in partial_proc.specializations.clone().into_iter() { result.insert(symbol, opt_proc); } } for symbol in self.builtin.iter() { result.insert(*symbol, None); } result } } #[derive(Clone, Debug, PartialEq)] pub struct PartialProc<'a> { pub annotation: Variable, pub body: roc_can::expr::Expr, pub specializations: MutMap>)>, } #[derive(Clone, Debug, PartialEq)] pub struct Proc<'a> { pub name: Symbol, pub args: &'a [(Layout<'a>, Symbol)], pub body: Expr<'a>, pub closes_over: Layout<'a>, pub ret_layout: Layout<'a>, } struct Env<'a, 'i> { pub arena: &'a Bump, pub subs: &'a mut Subs, pub home: ModuleId, pub ident_ids: &'i mut IdentIds, pub pointer_size: u32, } #[derive(Clone, Debug, PartialEq)] pub enum Expr<'a> { // Literals Int(i64), Float(f64), Str(&'a str), /// Closed tag unions containing exactly two (0-arity) tags compile to Expr::Bool, /// so they can (at least potentially) be emitted as 1-bit machine bools. /// /// So [ True, False ] compiles to this, and so do [ A, B ] and [ Foo, Bar ]. /// However, a union like [ True, False, Other Int ] would not. Bool(bool), /// Closed tag unions containing between 3 and 256 tags (all of 0 arity) /// compile to bytes, e.g. [ Blue, Black, Red, Green, White ] Byte(u8), // Load/Store Load(Symbol), Store(&'a [(Symbol, Layout<'a>, Expr<'a>)], &'a Expr<'a>), // Functions FunctionPointer(Symbol), CallByName(Symbol, &'a [(Expr<'a>, Layout<'a>)]), CallByPointer(&'a Expr<'a>, &'a [Expr<'a>], Layout<'a>), // Exactly two conditional branches, e.g. if/else Cond { // The left-hand side of the conditional comparison and the right-hand side. // These are stored separately because there are different machine instructions // for e.g. "compare float and jump" vs. "compare integer and jump" cond_lhs: &'a Expr<'a>, cond_rhs: &'a Expr<'a>, cond_layout: Layout<'a>, // What to do if the condition either passes or fails pass: &'a Expr<'a>, fail: &'a Expr<'a>, ret_layout: Layout<'a>, }, /// More than two conditional branches, e.g. a 3-way when-expression Branches { /// The left-hand side of the conditional. We compile this to LLVM once, /// then reuse it to test against each different compiled cond_rhs value. cond: &'a Expr<'a>, /// ( cond_rhs, pass, fail ) branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)], default: &'a Expr<'a>, ret_layout: Layout<'a>, }, /// Conditional branches for integers. These are more efficient. Switch { /// This *must* be an integer, because Switch potentially compiles to a jump table. cond: &'a Expr<'a>, cond_layout: Layout<'a>, /// The u64 in the tuple will be compared directly to the condition Expr. /// If they are equal, this branch will be taken. branches: &'a [(u64, Expr<'a>)], /// If no other branches pass, this default branch will be taken. default_branch: &'a Expr<'a>, /// Each branch must return a value of this type. ret_layout: Layout<'a>, }, Tag { tag_layout: Layout<'a>, name: TagName, arguments: &'a [Expr<'a>], }, Struct { fields: &'a [(Lowercase, Expr<'a>)], layout: Layout<'a>, }, Access { label: Lowercase, field_layout: Layout<'a>, struct_layout: Layout<'a>, }, Array { elem_layout: Layout<'a>, elems: &'a [Expr<'a>], }, RuntimeError(&'a str), } impl<'a> Expr<'a> { pub fn new( arena: &'a Bump, subs: &'a mut Subs, can_expr: roc_can::expr::Expr, procs: &mut Procs<'a>, home: ModuleId, ident_ids: &mut IdentIds, pointer_size: u32, ) -> Self { let mut env = Env { arena, subs, home, ident_ids, pointer_size, }; from_can(&mut env, can_expr, procs, None) } } enum IntOrFloat { IntType, FloatType, } fn to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat { match subs.get_without_compacting(var).content { Content::Alias(Symbol::INT_INTEGER, args, _) => { debug_assert!(args.is_empty()); IntOrFloat::IntType } Content::FlexVar(_) => { // If this was still a (Num *), assume compiling it to an Int IntOrFloat::IntType } Content::Alias(Symbol::FLOAT_FLOATINGPOINT, args, _) => { debug_assert!(args.is_empty()); IntOrFloat::FloatType } Content::Alias(Symbol::NUM_NUM, args, _) => { debug_assert!(args.len() == 1); match subs.get_without_compacting(args[0].1).content { Content::Alias(Symbol::INT_INTEGER, args, _) => { debug_assert!(args.is_empty()); IntOrFloat::IntType } Content::FlexVar(_) => { // If this was still a (Num *), assume compiling it to an Int IntOrFloat::IntType } Content::Alias(Symbol::FLOAT_FLOATINGPOINT, args, _) => { debug_assert!(args.is_empty()); IntOrFloat::FloatType } Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, attr_args)) => { debug_assert!(attr_args.len() == 2); // Recurse on the second argument to_int_or_float(subs, attr_args[1]) } other => panic!( "Unrecognized Num.Num alias type argument Content: {:?}", other ), } } Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, attr_args)) => { debug_assert!(attr_args.len() == 2); // Recurse on the second argument to_int_or_float(subs, attr_args[1]) } other => panic!("Unrecognized Num type argument Content: {:?}", other), } } fn from_can<'a>( env: &mut Env<'a, '_>, can_expr: roc_can::expr::Expr, procs: &mut Procs<'a>, name: Option, ) -> Expr<'a> { use roc_can::expr::Expr::*; use roc_can::pattern::Pattern::*; match can_expr { Num(var, num) => match to_int_or_float(env.subs, var) { IntOrFloat::IntType => Expr::Int(num), IntOrFloat::FloatType => Expr::Float(num as f64), }, Int(_, num) => Expr::Int(num), Float(_, num) => Expr::Float(num), Str(string) | BlockStr(string) => Expr::Str(env.arena.alloc(string)), Var(symbol) => Expr::Load(symbol), LetNonRec(def, ret_expr, _, _) => { let arena = env.arena; let loc_pattern = def.loc_pattern; let loc_expr = def.loc_expr; let mut stored = Vec::with_capacity_in(1, arena); // If we're defining a named closure, insert it into Procs and then // remove the Let. When code gen later goes to look it up, it'll be in Procs! // // Before: // // identity = \a -> a // // identity 5 // // After: (`identity` is now in Procs) // // identity 5 // if let Identifier(symbol) = &loc_pattern.value { if let Closure(_, _, _, _, _) = &loc_expr.value { // Extract Procs, but discard the resulting Expr::Load. // That Load looks up the pointer, which we won't use here! from_can(env, loc_expr.value, procs, Some(*symbol)); // Discard this LetNonRec by replacing it with its ret_expr. return from_can(env, ret_expr.value, procs, None); } } // If it wasn't specifically an Identifier & Closure, proceed as normal. store_pattern( env, loc_pattern.value, loc_expr.value, def.expr_var, procs, &mut stored, ); // At this point, it's safe to assume we aren't assigning a Closure to a def. // Extract Procs from the def body and the ret expression, and return the result! let ret = from_can(env, ret_expr.value, procs, None); Expr::Store(stored.into_bump_slice(), arena.alloc(ret)) } Closure(annotation, _, _, _loc_args, boxed_body) => { let (loc_body, _ret_var) = *boxed_body; let symbol = name.unwrap_or_else(|| gen_closure_name(procs, &mut env.ident_ids, env.home)); procs.insert_user_defined( symbol, PartialProc { annotation, body: loc_body.value, specializations: MutMap::default(), }, ); Expr::FunctionPointer(symbol) } Call(boxed, loc_args, _) => { use IntOrFloat::*; let (fn_var, loc_expr, ret_var) = *boxed; let specialize_builtin_functions = { |symbol, subs: &Subs| match symbol { Symbol::NUM_ADD => match to_int_or_float(subs, ret_var) { FloatType => Symbol::FLOAT_ADD, IntType => Symbol::INT_ADD, }, Symbol::NUM_SUB => match to_int_or_float(subs, ret_var) { FloatType => Symbol::FLOAT_SUB, IntType => Symbol::INT_SUB, }, _ => symbol, } }; match from_can(env, loc_expr.value, procs, None) { Expr::Load(proc_name) => { // Some functions can potentially mutate in-place. // If we have one of those, switch to the in-place version if appropriate. match specialize_builtin_functions(proc_name, &env.subs) { Symbol::LIST_SET => { let subs = &env.subs; // The first arg is the one with the List in it. // List.set : List elem, Int, elem -> List elem let (list_arg_var, _) = loc_args.get(0).unwrap(); let content = subs.get_without_compacting(*list_arg_var).content; match content { Content::Structure(FlatType::Apply( Symbol::ATTR_ATTR, attr_args, )) => { debug_assert!(attr_args.len() == 2); // If the first argument (the List) is unique, // then we can safely upgrade to List.set_in_place let attr_arg_content = subs.get_without_compacting(attr_args[0]).content; let new_name = if attr_arg_content.is_unique(subs) { Symbol::LIST_SET_IN_PLACE } else { Symbol::LIST_SET }; call_by_name(env, procs, fn_var, ret_var, new_name, loc_args) } _ => call_by_name(env, procs, fn_var, ret_var, proc_name, loc_args), } } specialized_proc_symbol => call_by_name( env, procs, fn_var, ret_var, specialized_proc_symbol, loc_args, ), } } ptr => { // Call by pointer - the closure was anonymous, e.g. // // ((\a -> a) 5) // // It might even be the anonymous result of a conditional: // // ((if x > 0 then \a -> a else \_ -> 0) 5) let mut args = Vec::with_capacity_in(loc_args.len(), env.arena); for (_, loc_arg) in loc_args { args.push(from_can(env, loc_arg.value, procs, None)); } let layout = Layout::from_var(env.arena, fn_var, env.subs, env.pointer_size) .unwrap_or_else(|err| { panic!("TODO turn fn_var into a RuntimeError {:?}", err) }); Expr::CallByPointer(&*env.arena.alloc(ptr), args.into_bump_slice(), layout) } } } When { cond_var, expr_var, loc_cond, branches, } => from_can_when(env, cond_var, expr_var, *loc_cond, branches, procs), Record(ext_var, fields) => { let arena = env.arena; let mut field_bodies = Vec::with_capacity_in(fields.len(), arena); for (label, field) in fields { let expr = from_can(env, field.loc_expr.value, procs, None); field_bodies.push((label, expr)); } let struct_layout = match Layout::from_var(arena, ext_var, env.subs, env.pointer_size) { Ok(layout) => layout, Err(()) => { // Invalid field! panic!("TODO gracefully handle Record with invalid struct_layout"); } }; Expr::Struct { fields: field_bodies.into_bump_slice(), layout: struct_layout, } } Tag { variant_var, name, arguments: args, .. } => { let arena = env.arena; match Layout::from_var(arena, variant_var, &env.subs, env.pointer_size) { Ok(Layout::Builtin(Builtin::Bool(_smaller, larger))) => Expr::Bool(name == larger), Ok(Layout::Builtin(Builtin::Byte(tags))) => match tags.get(&name) { Some(v) => Expr::Byte(*v), None => panic!("Tag name is not part of the type"), }, Ok(layout) => { let mut arguments = Vec::with_capacity_in(args.len(), arena); for (_, arg) in args { arguments.push(from_can(env, arg.value, procs, None)); } Expr::Tag { tag_layout: layout, name, arguments: arguments.into_bump_slice(), } } Err(()) => { // Invalid field! panic!("TODO gracefully handle Access with invalid struct_layout"); } } } Access { ext_var, field_var, field, .. } => { let arena = env.arena; let struct_layout = match Layout::from_var(arena, ext_var, env.subs, env.pointer_size) { Ok(layout) => layout, Err(()) => { // Invalid field! panic!("TODO gracefully handle Access with invalid struct_layout"); } }; let field_layout = match Layout::from_var(arena, field_var, env.subs, env.pointer_size) { Ok(layout) => layout, Err(()) => { // Invalid field! panic!("TODO gracefully handle Access with invalid field_layout"); } }; Expr::Access { label: field, field_layout, struct_layout, } } List { elem_var, loc_elems, } => { let arena = env.arena; let elem_layout = match Layout::from_var(arena, elem_var, env.subs, env.pointer_size) { Ok(layout) => layout, Err(()) => { panic!("TODO gracefully handle List with invalid element layout"); } }; let mut elems = Vec::with_capacity_in(loc_elems.len(), arena); for loc_elem in loc_elems { elems.push(from_can(env, loc_elem.value, procs, None)); } Expr::Array { elem_layout, elems: elems.into_bump_slice(), } } other => panic!("TODO convert canonicalized {:?} to mono::Expr", other), } } fn store_pattern<'a>( env: &mut Env<'a, '_>, can_pat: Pattern, can_expr: roc_can::expr::Expr, var: Variable, procs: &mut Procs<'a>, stored: &mut Vec<'a, (Symbol, Layout<'a>, Expr<'a>)>, ) { use roc_can::pattern::Pattern::*; let layout = match Layout::from_var(env.arena, var, env.subs, env.pointer_size) { Ok(layout) => layout, Err(()) => { panic!("TODO gen a runtime error here"); } }; // If we're defining a named closure, insert it into Procs and then // remove the Let. When code gen later goes to look it up, it'll be in Procs! // // Before: // // identity = \a -> a // // identity 5 // // After: (`identity` is now in Procs) // // identity 5 // match can_pat { Identifier(symbol) => stored.push((symbol, layout, from_can(env, can_expr, procs, None))), Underscore => { // Since _ is never read, it's safe to reassign it. stored.push(( Symbol::UNDERSCORE, layout, from_can(env, can_expr, procs, None), )) } _ => { panic!("TODO store_pattern for {:?}", can_pat); } } } fn gen_closure_name(procs: &Procs<'_>, ident_ids: &mut IdentIds, home: ModuleId) -> Symbol { let ident_id = ident_ids.add(format!("_{}", procs.len()).into()); Symbol::new(home, ident_id) } fn from_can_when<'a>( env: &mut Env<'a, '_>, cond_var: Variable, expr_var: Variable, loc_cond: Located, branches: std::vec::Vec<( Located, Located, )>, procs: &mut Procs<'a>, ) -> Expr<'a> { use roc_can::pattern::Pattern::*; match branches.len() { 0 => { // A when-expression with no branches is a runtime error. // We can't know what to return! panic!("TODO compile a 0-branch when-expression to a RuntimeError"); } 1 => { // A when-expression with exactly 1 branch is essentially a LetNonRec. // As such, we can compile it direcly to a Store. let arena = env.arena; let mut stored = Vec::with_capacity_in(1, arena); let (loc_when_pattern, loc_branch) = branches.into_iter().next().unwrap(); store_pattern( env, loc_when_pattern.value, loc_cond.value, cond_var, procs, &mut stored, ); let ret = from_can(env, loc_branch.value, procs, None); Expr::Store(stored.into_bump_slice(), arena.alloc(ret)) } 2 => { // A when-expression with exactly 2 branches compiles to a Cond. let arena = env.arena; let mut iter = branches.into_iter(); let (loc_when_pat1, loc_then) = iter.next().unwrap(); let (loc_when_pat2, loc_else) = iter.next().unwrap(); match (&loc_when_pat1.value, &loc_when_pat2.value) { (NumLiteral(var, num), NumLiteral(_, _)) | (NumLiteral(var, num), Underscore) => { let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None)); let (builtin, cond_rhs_expr) = match to_int_or_float(env.subs, *var) { IntOrFloat::IntType => (Builtin::Int64, Expr::Int(*num)), IntOrFloat::FloatType => (Builtin::Float64, Expr::Float(*num as f64)), }; let cond_rhs = arena.alloc(cond_rhs_expr); let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size) .unwrap_or_else(|err| { panic!("TODO turn this into a RuntimeError {:?}", err) }); Expr::Cond { cond_layout: Layout::Builtin(builtin), cond_lhs, cond_rhs, pass, fail, ret_layout, } } (IntLiteral(int), IntLiteral(_)) | (IntLiteral(int), Underscore) => { let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None)); let cond_rhs = arena.alloc(Expr::Int(*int)); let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size) .unwrap_or_else(|err| { panic!("TODO turn this into a RuntimeError {:?}", err) }); Expr::Cond { cond_layout: Layout::Builtin(Builtin::Int64), cond_lhs, cond_rhs, pass, fail, ret_layout, } } (FloatLiteral(float), FloatLiteral(_)) | (FloatLiteral(float), Underscore) => { let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None)); let cond_rhs = arena.alloc(Expr::Float(*float)); let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size) .unwrap_or_else(|err| { panic!("TODO turn this into a RuntimeError {:?}", err) }); Expr::Cond { cond_layout: Layout::Builtin(Builtin::Float64), cond_lhs, cond_rhs, pass, fail, ret_layout, } } _ => { panic!("TODO handle more conds"); } } } _ => { // This is a when-expression with 3+ branches. let arena = env.arena; let cond = from_can(env, loc_cond.value, procs, None); let subs = &env.subs; let layout = Layout::from_var(arena, cond_var, subs, env.pointer_size) .unwrap_or_else(|_| panic!("TODO generate a runtime error in from_can_when here!")); // We can Switch on integers and tags, because they both have // representations that work as integer values. // // TODO we can also Switch on record fields if we're pattern matching // on a record field that's also Switchable. // // TODO we can also convert floats to integer representations. let is_switchable = match layout { Layout::Builtin(Builtin::Int64) => true, _ => false, }; // If the condition is an Int or Float, we can potentially use // a Switch for more efficiency. if is_switchable { // These are integer literals or underscore patterns, // so they're eligible for user in a jump table. let mut jumpable_branches = Vec::with_capacity_in(branches.len(), arena); let mut opt_default_branch = None; for (loc_when_pat, loc_expr) in branches { let mono_expr = from_can(env, loc_expr.value, procs, None); match &loc_when_pat.value { NumLiteral(var, num) => { // This is jumpable iff it's an int match to_int_or_float(env.subs, *var) { IntOrFloat::IntType => { jumpable_branches.push((*num as u64, mono_expr)); } IntOrFloat::FloatType => { // The type checker should have converted these mismatches into RuntimeErrors already! if cfg!(debug_assertions) { panic!("A type mismatch in a pattern was not converted to a runtime error: {:?}", loc_when_pat); } else { unreachable!(); } } }; } IntLiteral(int) => { // Switch only compares the condition to the // alternatives based on their bit patterns, // so casting from i64 to u64 makes no difference here. jumpable_branches.push((*int as u64, mono_expr)); } Identifier(_symbol) => { // Since this is an ident, it must be // the last pattern in the `when`. // We can safely treat this like an `_` // except that we need to wrap this branch // in a `Store` so the identifier is in scope! opt_default_branch = Some(arena.alloc(if true { // Using `if true` for this TODO panic to avoid a warning panic!("TODO wrap this expr in an Expr::Store: {:?}", mono_expr) } else { mono_expr })); } Underscore => { // We should always have exactly one default branch! debug_assert!(opt_default_branch.is_none()); opt_default_branch = Some(arena.alloc(mono_expr)); } Shadowed(_, _) => { panic!("TODO runtime error for shadowing in a pattern"); } // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! UnsupportedPattern(_region) => { panic!("TODO runtime error for unsupported pattern"); } AppliedTag(_, _, _) | StrLiteral(_) | RecordDestructure(_, _) | FloatLiteral(_) => { // The type checker should have converted these mismatches into RuntimeErrors already! if cfg!(debug_assertions) { panic!("A type mismatch in a pattern was not converted to a runtime error: {:?}", loc_when_pat); } else { unreachable!(); } } } } // If the default branch was never set, that means // our canonical Expr didn't have one. An earlier // step in the compilation process should have // ruled this out! debug_assert!(opt_default_branch.is_some()); let default_branch = opt_default_branch.unwrap(); let cond_layout = Layout::from_var(arena, cond_var, env.subs, env.pointer_size) .unwrap_or_else(|err| { panic!("TODO turn cond_layout into a RuntimeError {:?}", err) }); let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size) .unwrap_or_else(|err| { panic!("TODO turn ret_layout into a RuntimeError {:?}", err) }); Expr::Switch { cond: arena.alloc(cond), branches: jumpable_branches.into_bump_slice(), default_branch, ret_layout, cond_layout, } } else { // /// More than two conditional branches, e.g. a 3-way when-expression // Expr::Branches { // /// The left-hand side of the conditional. We compile this to LLVM once, // /// then reuse it to test against each different compiled cond_rhs value. // cond_lhs: &'a Expr<'a>, // /// ( cond_rhs, pass, fail ) // branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)], // ret_var: Variable, // }, panic!( "TODO support when-expressions of 3+ branches whose conditions aren't integers." ); } } } } fn call_by_name<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, fn_var: Variable, ret_var: Variable, proc_name: Symbol, loc_args: std::vec::Vec<(Variable, Located)>, ) -> Expr<'a> { // create specialized procedure to call // If we need to specialize the body, this will get populated with the info // we need to do that. This is defined outside the procs.get_user_defined(...) call // because if we tried to specialize the body inside that match, we would // get a borrow checker error about trying to borrow `procs` as mutable // while there is still an active immutable borrow. let opt_specialize_body: Option<(ContentHash, Variable, roc_can::expr::Expr)>; let specialized_proc_name = if let Some(partial_proc) = procs.get_user_defined(proc_name) { let content_hash = ContentHash::from_var(fn_var, env.subs); if let Some(specialization) = partial_proc.specializations.get(&content_hash) { opt_specialize_body = None; // a specialization with this type hash already exists, use its symbol specialization.0 } else { opt_specialize_body = Some(( content_hash, partial_proc.annotation, partial_proc.body.clone(), )); // generate a symbol for this specialization gen_closure_name(procs, &mut env.ident_ids, env.home) } } else { opt_specialize_body = None; // This happens for built-in symbols (they are never defined as a Closure) procs.insert_builtin(proc_name); proc_name }; if let Some((content_hash, annotation, body)) = opt_specialize_body { // register proc, so specialization doesn't loop infinitely // for recursive definitions // let mut temp = partial_proc.clone(); // temp.specializations // .insert(content_hash, (spec_proc_name, None)); // procs.insert_user_defined(proc_name, temp); procs.insert_specialization(proc_name, content_hash, specialized_proc_name, None); let proc = specialize_proc_body( env, procs, fn_var, ret_var, specialized_proc_name, &loc_args, annotation, body, ); procs.insert_specialization(proc_name, content_hash, specialized_proc_name, proc); } dbg!(proc_name); dbg!(specialized_proc_name); // generate actual call let mut args = Vec::with_capacity_in(loc_args.len(), env.arena); for (var, loc_arg) in loc_args { let layout = Layout::from_var(&env.arena, var, &env.subs, env.pointer_size) .unwrap_or_else(|err| panic!("TODO gracefully handle bad layout: {:?}", err)); args.push((from_can(env, loc_arg.value, procs, None), layout)); } Expr::CallByName(specialized_proc_name, args.into_bump_slice()) } fn specialize_proc_body<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, fn_var: Variable, ret_var: Variable, proc_name: Symbol, loc_args: &[(Variable, Located)], annotation: Variable, body: roc_can::expr::Expr, ) -> Option> { // unify the called function with the specialized signature, then specialize the function body let snapshot = env.subs.snapshot(); roc_unify::unify::unify(env.subs, annotation, fn_var); let specialized_body = from_can(env, body, procs, None); // reset subs, so we don't get type errors when specializing for a different signature env.subs.rollback_to(snapshot); let mut proc_args = Vec::with_capacity_in(loc_args.len(), &env.arena); for (arg_var, _loc_arg) in loc_args.iter() { let layout = match Layout::from_var(&env.arena, *arg_var, env.subs, env.pointer_size) { Ok(layout) => layout, Err(()) => { // Invalid closure! return None; } }; // TODO FIXME what is the idea here? arguments don't map to identifiers one-to-one // e.g. underscore and record patterns let arg_name = proc_name; // let arg_name: Symbol = match &loc_arg.value { // Pattern::Identifier(symbol) => *symbol, // _ => { // panic!("TODO determine arg_name for pattern {:?}", loc_arg.value); // } // }; proc_args.push((layout, arg_name)); } let ret_layout = Layout::from_var(&env.arena, ret_var, env.subs, env.pointer_size) .unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err)); let proc = Proc { name: proc_name, args: proc_args.into_bump_slice(), body: specialized_body, closes_over: Layout::Struct(&[]), ret_layout, }; Some(proc) }