diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 6a4639e41a..3b71227f9c 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -46,7 +46,6 @@ pub enum OptLevel { Optimize, } -// pub type Scope<'a, 'ctx> = ImMap, PointerValue<'ctx>)>; #[derive(Default, Debug, Clone, PartialEq)] pub struct Scope<'a, 'ctx> { symbols: ImMap, PointerValue<'ctx>)>, diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index 4afb502eee..dac344d9e5 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -100,10 +100,8 @@ mod gen_list { empty : List Int empty = [] - - + List.map empty (\x -> x) - main {} "# ), @@ -117,10 +115,8 @@ mod gen_list { nonEmpty : List Int nonEmpty = [ 1 ] - - + List.map nonEmpty (\x -> x) - main {} "# ), @@ -134,10 +130,8 @@ mod gen_list { nonEmpty : List Int nonEmpty = [ 1 ] - - + List.map nonEmpty (\x -> x + 1) - main {} "# ), @@ -152,9 +146,7 @@ mod gen_list { nonEmpty : List Int nonEmpty = [ 1, 2, 3, 4, 5 ] - List.map nonEmpty (\x -> x * 2) - main {} "# ), @@ -180,39 +172,39 @@ mod gen_list { &'static [bool] ); - // assert_evals_to!( - // indoc!( - // r#" - // main = \{} -> - // nonEmpty : List Int - // nonEmpty = - // [ 1, 1, -4, 1, 2 ] - // - // greaterThanOne : Int -> Bool - // greaterThanOne i = - // i > 0 - // - // List.map nonEmpty greaterThanOne - // - // main {} - // "# - // ), - // &[true, true, false, true, true], - // &'static [bool] - // ); + assert_evals_to!( + indoc!( + r#" + main = \{} -> + nonEmpty : List Int + nonEmpty = + [ 1, 1, -4, 1, 2 ] + + greaterThanOne : Int -> Bool + greaterThanOne = \i -> + i > 0 + + List.map nonEmpty greaterThanOne + + main {} + "# + ), + &[true, true, false, true, true], + &'static [bool] + ); - // assert_evals_to!( - // indoc!( - // r#" - // main = \{} -> - // List.map [] (\x -> x > 0) - // - // main {} - // "# - // ), - // &[], - // &'static [bool] - // ); + assert_evals_to!( + indoc!( + r#" + main = \{} -> + List.map [] (\x -> x > 0) + + main {} + "# + ), + &[], + &'static [bool] + ); } #[test] diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index db0d61a1c9..ba9487b5e3 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -56,7 +56,7 @@ pub fn infer_borrow<'a>( } #[derive(Debug, PartialEq, Eq, Hash, Clone)] -enum Key { +pub enum Key { Declaration(Symbol), JoinPoint(JoinPointId), } @@ -66,6 +66,24 @@ pub struct ParamMap<'a> { items: MutMap]>, } +impl<'a> IntoIterator for ParamMap<'a> { + type Item = (Key, &'a [Param<'a>]); + type IntoIter = ]> as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.items.into_iter() + } +} + +impl<'a> IntoIterator for &'a ParamMap<'a> { + type Item = (&'a Key, &'a &'a [Param<'a>]); + type IntoIter = <&'a std::collections::HashMap]> as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.items.iter() + } +} + impl<'a> ParamMap<'a> { pub fn get_symbol(&self, symbol: Symbol) -> Option<&'a [Param<'a>]> { let key = Key::Declaration(symbol); diff --git a/compiler/mono/src/inc_dec.rs b/compiler/mono/src/inc_dec.rs index c0b43138f4..b0ef3f5d5a 100644 --- a/compiler/mono/src/inc_dec.rs +++ b/compiler/mono/src/inc_dec.rs @@ -213,9 +213,24 @@ fn consume_expr(m: &VarMap, e: &Expr<'_>) -> bool { impl<'a> Context<'a> { pub fn new(arena: &'a Bump, param_map: &'a ParamMap<'a>) -> Self { + let mut vars = MutMap::default(); + + for (key, _) in param_map.into_iter() { + if let crate::borrow::Key::Declaration(symbol) = key { + vars.insert( + *symbol, + VarInfo { + reference: false, // assume function symbols are global constants + persistent: true, // assume function symbols are global constants + consume: false, // no need to consume this variable + }, + ); + } + } + Self { arena, - vars: MutMap::default(), + vars, jp_live_vars: MutMap::default(), local_context: LocalContext::default(), param_map, diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index d6dff5a32b..3df0b1334b 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -362,6 +362,89 @@ impl<'a> Procs<'a> { None => unreachable!("insert_exposed was called after the pending specializations phase had already completed!"), } } + + /// TODO + pub fn insert_passed_by_name( + &mut self, + env: &mut Env<'a, '_>, + fn_var: Variable, + name: Symbol, + layout: Layout<'a>, + layout_cache: &mut LayoutCache<'a>, + ) { + let tuple = (name, layout); + + // If we've already specialized this one, no further work is needed. + if self.specialized.contains_key(&tuple) { + return; + } + + // We're done with that tuple, so move layout back out to avoid cloning it. + let (name, layout) = tuple; + + // now we have to pull some tricks to extract the return var and pattern vars from Subs + match get_args_ret_var(env.subs, fn_var) { + Some((pattern_vars, ret_var)) => { + let pattern_vars = Vec::from_iter_in(pattern_vars.into_iter(), env.arena); + let pending = PendingSpecialization { + pattern_vars, + ret_var, + fn_var, + }; + + // This should only be called when pending_specializations is Some. + // Otherwise, it's being called in the wrong pass! + match &mut self.pending_specializations { + Some(pending_specializations) => { + // register the pending specialization, so this gets code genned later + add_pending(pending_specializations, name, layout, pending) + } + None => { + let symbol = name; + + // TODO should pending_procs hold a Rc? + let partial_proc = self.partial_procs.get(&symbol).unwrap().clone(); + + // Mark this proc as in-progress, so if we're dealing with + // mutually recursive functions, we don't loop forever. + // (We had a bug around this before this system existed!) + self.specialized + .insert((symbol, layout.clone()), InProgress); + + match specialize(env, self, symbol, layout_cache, pending, partial_proc) { + Ok(proc) => { + self.specialized + .insert((symbol, layout.clone()), Done(proc)); + } + Err(error) => { + let error_msg = + format!("TODO generate a RuntimeError message for {:?}", error); + self.runtime_errors + .insert(symbol, env.arena.alloc(error_msg)); + } + } + } + } + } + other => { + unreachable!( + "trying to insert a symbol that is not a function: {:?} {:?}", + name, other + ); + } + } + } +} + +fn get_args_ret_var(subs: &Subs, var: Variable) -> Option<(std::vec::Vec, Variable)> { + match subs.get_without_compacting(var).content { + Content::Structure(FlatType::Func(pattern_vars, ret_var)) => Some((pattern_vars, ret_var)), + Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, args)) => { + get_args_ret_var(subs, args[1]) + } + Content::Alias(_, _, actual) => get_args_ret_var(subs, actual), + _ => None, + } } fn add_pending<'a>( @@ -621,7 +704,9 @@ impl<'a> Expr<'a> { match self { Literal(lit) => lit.to_doc(alloc), - FunctionPointer(symbol, _) => symbol_to_doc(alloc, *symbol), + FunctionPointer(symbol, _) => alloc + .text("FunctionPointer ") + .append(symbol_to_doc(alloc, *symbol)), FunctionCall { call_type, args, .. @@ -1364,12 +1449,9 @@ pub fn with_hole<'a>( let mut field_symbols = Vec::with_capacity_in(field_layouts.len(), env.arena); for (_, arg) in args.iter() { - if let roc_can::expr::Expr::Var(symbol) = arg.value { - field_symbols.push(symbol); - } else { - field_symbols.push(env.unique_symbol()); - } + field_symbols.push(possible_reuse_symbol(env, procs, &arg.value)); } + let field_symbols = field_symbols.into_bump_slice(); // Layout will unpack this unwrapped tack if it only has one (non-zero-sized) field let layout = layout_cache @@ -1379,30 +1461,10 @@ pub fn with_hole<'a>( }); // even though this was originally a Tag, we treat it as a Struct from now on - let mut stmt = Stmt::Let( - assigned, - Expr::Struct(field_symbols.clone().into_bump_slice()), - layout, - hole, - ); + let stmt = Stmt::Let(assigned, Expr::Struct(field_symbols), layout, hole); - for ((_, arg), symbol) in args.into_iter().rev().zip(field_symbols.iter().rev()) - { - // if this argument is already a symbol, we don't need to re-define it - if let roc_can::expr::Expr::Var(_) = arg.value { - continue; - } - stmt = with_hole( - env, - arg.value, - procs, - layout_cache, - *symbol, - env.arena.alloc(stmt), - ); - } - - stmt + let iter = args.into_iter().rev().zip(field_symbols.iter().rev()); + assign_to_symbols(env, procs, layout_cache, iter, stmt) } Wrapped(sorted_tag_layouts) => { let union_size = sorted_tag_layouts.len() as u8; @@ -1417,11 +1479,7 @@ pub fn with_hole<'a>( field_symbols.push(tag_id_symbol); for (_, arg) in args.iter() { - if let roc_can::expr::Expr::Var(symbol) = arg.value { - field_symbols.push(symbol); - } else { - field_symbols.push(env.unique_symbol()); - } + field_symbols.push(possible_reuse_symbol(env, procs, &arg.value)); } let mut layouts: Vec<&'a [Layout<'a>]> = @@ -1442,23 +1500,9 @@ pub fn with_hole<'a>( }; let mut stmt = Stmt::Let(assigned, tag, layout, hole); + let iter = args.into_iter().rev().zip(field_symbols.iter().rev()); - for ((_, arg), symbol) in args.into_iter().rev().zip(field_symbols.iter().rev()) - { - // if this argument is already a symbol, we don't need to re-define it - if let roc_can::expr::Expr::Var(_) = arg.value { - continue; - } - - stmt = with_hole( - env, - arg.value, - procs, - layout_cache, - *symbol, - env.arena.alloc(stmt), - ); - } + stmt = assign_to_symbols(env, procs, layout_cache, iter, stmt); // define the tag id stmt = Stmt::Let( @@ -1487,16 +1531,18 @@ pub fn with_hole<'a>( for (label, layout) in sorted_fields.into_iter() { field_layouts.push(layout); + // TODO how should function pointers be handled here? match fields.remove(&label) { - Some(field) => { - if let roc_can::expr::Expr::Var(symbol) = field.loc_expr.value { - field_symbols.push(symbol); + Some(field) => match can_reuse_symbol(procs, &field.loc_expr.value) { + Some(reusable) => { + field_symbols.push(reusable); can_fields.push(None); - } else { + } + None => { field_symbols.push(env.unique_symbol()); can_fields.push(Some(field)); } - } + }, None => { // this field was optional, but not given continue; @@ -1667,11 +1713,7 @@ pub fn with_hole<'a>( loc_cond, branches, } => { - let cond_symbol = if let roc_can::expr::Expr::Var(symbol) = loc_cond.value { - symbol - } else { - env.unique_symbol() - }; + let cond_symbol = possible_reuse_symbol(env, procs, &loc_cond.value); let id = JoinPointId(env.unique_symbol()); @@ -1688,18 +1730,15 @@ pub fn with_hole<'a>( ); // define the `when` condition - if let roc_can::expr::Expr::Var(_) = loc_cond.value { - // do nothing - } else { - stmt = with_hole( - env, - loc_cond.value, - procs, - layout_cache, - cond_symbol, - env.arena.alloc(stmt), - ); - }; + stmt = assign_to_symbol( + env, + procs, + layout_cache, + cond_var, + *loc_cond, + cond_symbol, + stmt, + ); let layout = layout_cache .from_var(env.arena, expr_var, env.subs) @@ -1732,11 +1771,7 @@ pub fn with_hole<'a>( } => { let mut arg_symbols = Vec::with_capacity_in(loc_elems.len(), env.arena); for arg_expr in loc_elems.iter() { - if let roc_can::expr::Expr::Var(symbol) = arg_expr.value { - arg_symbols.push(symbol); - } else { - arg_symbols.push(env.unique_symbol()); - } + arg_symbols.push(possible_reuse_symbol(env, procs, &arg_expr.value)); } let arg_symbols = arg_symbols.into_bump_slice(); @@ -1751,30 +1786,20 @@ pub fn with_hole<'a>( let mode = crate::layout::mode_from_var(list_var, env.subs); - let mut stmt = Stmt::Let( + let stmt = Stmt::Let( assigned, expr, Layout::Builtin(Builtin::List(mode, env.arena.alloc(elem_layout))), hole, ); - for (arg_expr, symbol) in loc_elems.into_iter().rev().zip(arg_symbols.iter().rev()) { - // if this argument is already a symbol, we don't need to re-define it - if let roc_can::expr::Expr::Var(_) = arg_expr.value { - continue; - } + let iter = loc_elems + .into_iter() + .rev() + .map(|e| (elem_var, e)) + .zip(arg_symbols.iter().rev()); - stmt = with_hole( - env, - arg_expr.value, - procs, - layout_cache, - *symbol, - env.arena.alloc(stmt), - ); - } - - stmt + assign_to_symbols(env, procs, layout_cache, iter, stmt) } Access { @@ -1808,11 +1833,7 @@ pub fn with_hole<'a>( } } - let record_symbol = if let roc_can::expr::Expr::Var(symbol) = loc_expr.value { - symbol - } else { - env.unique_symbol() - }; + let record_symbol = possible_reuse_symbol(env, procs, &loc_expr.value); let expr = Expr::AccessAtIndex { index: index.expect("field not in its own type") as u64, @@ -1827,18 +1848,15 @@ pub fn with_hole<'a>( let mut stmt = Stmt::Let(assigned, expr, layout, hole); - if let roc_can::expr::Expr::Var(_) = loc_expr.value { - // do nothing - } else { - stmt = with_hole( - env, - loc_expr.value, - procs, - layout_cache, - record_symbol, - env.arena.alloc(stmt), - ); - }; + stmt = assign_to_symbol( + env, + procs, + layout_cache, + record_var, + *loc_expr, + record_symbol, + stmt, + ); stmt } @@ -1869,35 +1887,8 @@ pub fn with_hole<'a>( Call(boxed, loc_args, _) => { let (fn_var, loc_expr, ret_var) = *boxed; - /* - Var(symbol) => { - if procs.module_thunks.contains(&symbol) { - let partial_proc = procs.partial_procs.get(&symbol).unwrap(); - let fn_var = partial_proc.annotation; - let ret_var = fn_var; // These are the same for a thunk. - - // This is a top-level declaration, which will code gen to a 0-arity thunk. - call_by_name( - env, - procs, - fn_var, - ret_var, - symbol, - std::vec::Vec::new(), - layout_cache, - ) - } else { - // NOTE Load will always increment the refcount - Expr::Load(symbol) - } - } - */ - // match from_can(env, loc_expr.value, procs, layout_cache) { match loc_expr.value { - roc_can::expr::Expr::Var(proc_name) if procs.module_thunks.contains(&proc_name) => { - todo!() - } roc_can::expr::Expr::Var(proc_name) => call_by_name( env, procs, @@ -1993,11 +1984,7 @@ pub fn with_hole<'a>( let mut arg_symbols = Vec::with_capacity_in(args.len(), env.arena); for (_, arg_expr) in args.iter() { - if let roc_can::expr::Expr::Var(symbol) = arg_expr { - arg_symbols.push(*symbol); - } else { - arg_symbols.push(env.unique_symbol()); - } + arg_symbols.push(possible_reuse_symbol(env, procs, &arg_expr)); } let arg_symbols = arg_symbols.into_bump_slice(); @@ -2006,27 +1993,14 @@ pub fn with_hole<'a>( .from_var(env.arena, ret_var, env.subs) .unwrap_or_else(|err| todo!("TODO turn fn_var into a RuntimeError {:?}", err)); - let mut result = Stmt::Let(assigned, Expr::RunLowLevel(op, arg_symbols), layout, hole); + let result = Stmt::Let(assigned, Expr::RunLowLevel(op, arg_symbols), layout, hole); - for ((_arg_var, arg_expr), symbol) in - args.into_iter().rev().zip(arg_symbols.iter().rev()) - { - // if this argument is already a symbol, we don't need to re-define it - if let roc_can::expr::Expr::Var(_) = arg_expr { - continue; - } - - result = with_hole( - env, - arg_expr, - procs, - layout_cache, - *symbol, - env.arena.alloc(result), - ); - } - - result + let iter = args + .into_iter() + .rev() + .map(|(a, b)| (a, Located::at_zero(b))) + .zip(arg_symbols.iter().rev()); + assign_to_symbols(env, procs, layout_cache, iter, result) } RuntimeError(e) => Stmt::RuntimeError(env.arena.alloc(format!("{:?}", e))), } @@ -2048,13 +2022,9 @@ pub fn from_can<'a>( loc_cond, branches, } => { - let cond_symbol = if let roc_can::expr::Expr::Var(symbol) = loc_cond.value { - symbol - } else { - env.unique_symbol() - }; + let cond_symbol = possible_reuse_symbol(env, procs, &loc_cond.value); - let mut stmt = from_can_when( + let stmt = from_can_when( env, cond_var, expr_var, @@ -2067,20 +2037,15 @@ pub fn from_can<'a>( ); // define the `when` condition - if let roc_can::expr::Expr::Var(_) = loc_cond.value { - // do nothing - } else { - stmt = with_hole( - env, - loc_cond.value, - procs, - layout_cache, - cond_symbol, - env.arena.alloc(stmt), - ); - }; - - stmt + assign_to_symbol( + env, + procs, + layout_cache, + cond_var, + *loc_cond, + cond_symbol, + stmt, + ) } If { cond_var, @@ -2978,6 +2943,88 @@ fn store_record_destruct<'a>( Ok(stmt) } +/// We want to re-use symbols that are not function symbols +/// for any other expression, we create a new symbol, and will +/// later make sure it gets assigned the correct value. +fn can_reuse_symbol<'a>(procs: &Procs<'a>, expr: &roc_can::expr::Expr) -> Option { + if let roc_can::expr::Expr::Var(symbol) = expr { + if procs.partial_procs.contains_key(&symbol) { + None + } else { + Some(*symbol) + } + } else { + None + } +} + +fn possible_reuse_symbol<'a>( + env: &mut Env<'a, '_>, + procs: &Procs<'a>, + expr: &roc_can::expr::Expr, +) -> Symbol { + match can_reuse_symbol(procs, expr) { + Some(s) => s, + None => env.unique_symbol(), + } +} + +fn assign_to_symbol<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + arg_var: Variable, + loc_arg: Located, + symbol: Symbol, + result: Stmt<'a>, +) -> Stmt<'a> { + // if this argument is already a symbol, we don't need to re-define it + if let roc_can::expr::Expr::Var(original) = loc_arg.value { + if procs.partial_procs.contains_key(&original) { + // 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"); + procs.insert_passed_by_name(env, arg_var, original, layout.clone(), layout_cache); + + return Stmt::Let( + symbol, + Expr::FunctionPointer(original, layout.clone()), + layout, + env.arena.alloc(result), + ); + } + return result; + } + with_hole( + env, + loc_arg.value, + procs, + layout_cache, + symbol, + env.arena.alloc(result), + ) +} + +fn assign_to_symbols<'a, I>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + iter: I, + mut result: Stmt<'a>, +) -> Stmt<'a> +where + I: Iterator), &'a Symbol)>, +{ + for ((arg_var, loc_arg), symbol) in iter { + result = assign_to_symbol(env, procs, layout_cache, arg_var, loc_arg, *symbol, result); + } + + result +} + #[allow(clippy::too_many_arguments)] fn call_by_name<'a>( env: &mut Env<'a, '_>, @@ -2997,16 +3044,13 @@ fn call_by_name<'a>( let arena = env.arena; let mut pattern_vars = Vec::with_capacity_in(loc_args.len(), arena); - let mut field_symbols = Vec::with_capacity_in(loc_args.len(), env.arena); - - for (_, arg_expr) in loc_args.iter() { - if let roc_can::expr::Expr::Var(symbol) = arg_expr.value { - field_symbols.push(symbol); - } else { - field_symbols.push(env.unique_symbol()); - } - } - let field_symbols = field_symbols.into_bump_slice(); + let field_symbols = Vec::from_iter_in( + loc_args + .iter() + .map(|(_, arg_expr)| possible_reuse_symbol(env, procs, &arg_expr.value)), + arena, + ) + .into_bump_slice(); for (var, _) in &loc_args { match layout_cache.from_var(&env.arena, *var, &env.subs) { @@ -3044,26 +3088,10 @@ fn call_by_name<'a>( args: field_symbols, }; - let mut result = Stmt::Let(assigned, call, ret_layout.clone(), hole); + let result = Stmt::Let(assigned, call, ret_layout.clone(), hole); - for ((_, loc_arg), symbol) in - loc_args.into_iter().rev().zip(field_symbols.iter().rev()) - { - // if this argument is already a symbol, we don't need to re-define it - if let roc_can::expr::Expr::Var(_) = loc_arg.value { - continue; - } - result = with_hole( - env, - loc_arg.value, - procs, - layout_cache, - *symbol, - env.arena.alloc(result), - ); - } - - result + let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev()); + assign_to_symbols(env, procs, layout_cache, iter, result) } else { let pending = PendingSpecialization { pattern_vars, @@ -3100,26 +3128,10 @@ fn call_by_name<'a>( args: field_symbols, }; - let mut result = Stmt::Let(assigned, call, ret_layout.clone(), hole); + let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev()); - for ((_, loc_arg), symbol) in - loc_args.into_iter().rev().zip(field_symbols.iter().rev()) - { - // if this argument is already a symbol, we don't need to re-define it - if let roc_can::expr::Expr::Var(_) = loc_arg.value { - continue; - } - result = with_hole( - env, - loc_arg.value, - procs, - layout_cache, - *symbol, - env.arena.alloc(result), - ); - } - - result + let result = Stmt::Let(assigned, call, ret_layout.clone(), hole); + assign_to_symbols(env, procs, layout_cache, iter, result) } None => { let opt_partial_proc = procs.partial_procs.get(&proc_name); @@ -3157,29 +3169,15 @@ fn call_by_name<'a>( args: field_symbols, }; - let mut result = - Stmt::Let(assigned, call, ret_layout.clone(), hole); - - for ((_, loc_arg), symbol) in loc_args + let iter = loc_args .into_iter() .rev() - .zip(field_symbols.iter().rev()) - { - // if this argument is already a symbol, we don't need to re-define it - if let roc_can::expr::Expr::Var(_) = loc_arg.value { - continue; - } - result = with_hole( - env, - loc_arg.value, - procs, - layout_cache, - *symbol, - env.arena.alloc(result), - ); - } + .zip(field_symbols.iter().rev()); - result + let result = + Stmt::Let(assigned, call, ret_layout.clone(), hole); + + assign_to_symbols(env, procs, layout_cache, iter, result) } Err(error) => { let error_msg = env.arena.alloc(format!( diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index c3b8bb3311..6a0f82a4d6 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -1528,4 +1528,57 @@ mod test_mono { ), ) } + #[test] + fn list_map() { + compiles_to_ir( + indoc!( + r#" + main = \{} -> + nonEmpty : List Int + nonEmpty = + [ 1, 1, -4, 1, 2 ] + + + greaterThanOne : Int -> Bool + greaterThanOne = \i -> + i > 0 + + List.map nonEmpty greaterThanOne + + main {} + "# + ), + indoc!( + r#" + procedure Test.0 (Test.6): + let Test.15 = 1i64; + let Test.16 = 1i64; + let Test.17 = -4i64; + let Test.18 = 1i64; + let Test.19 = 2i64; + let Test.2 = Array [Test.15, Test.16, Test.17, Test.18, Test.19]; + let Test.10 = FunctionPointer Test.3; + let Test.9 = CallByName List.6 Test.2 Test.10; + ret Test.9; + + procedure List.6 (#Attr.2, #Attr.3): + let Test.11 = lowlevel ListMap #Attr.2 #Attr.3; + ret Test.11; + + procedure Num.19 (#Attr.2, #Attr.3): + let Test.14 = lowlevel NumGt #Attr.2 #Attr.3; + ret Test.14; + + procedure Test.3 (Test.5): + let Test.13 = 0i64; + let Test.12 = CallByName Num.19 Test.5 Test.13; + ret Test.12; + + let Test.8 = Struct {}; + let Test.7 = CallByName Test.0 Test.8; + ret Test.7; + "# + ), + ) + } }