#[macro_use] extern crate pretty_assertions; #[macro_use] extern crate indoc; extern crate bumpalo; extern crate roc_mono; mod helpers; // Test monomorphization #[cfg(test)] mod test_mono { use crate::helpers::{can_expr, infer_expr, test_home, CanExprOut}; use bumpalo::Bump; use roc_module::ident::TagName; use roc_module::symbol::{Interns, Symbol}; use roc_mono::expr::Expr::{self, *}; use roc_mono::expr::{InProgressProc, Procs}; use roc_mono::layout; use roc_mono::layout::Ownership::Owned; use roc_mono::layout::{Builtin, Layout, LayoutCache}; use roc_types::subs::Subs; // HELPERS const I64_LAYOUT: Layout<'static> = Layout::Builtin(Builtin::Int64); const F64_LAYOUT: Layout<'static> = Layout::Builtin(Builtin::Float64); fn compiles_to(src: &str, expected: Expr<'_>) { compiles_to_with_interns(src, |_| expected) } fn compiles_to_with_interns<'a, F>(src: &str, get_expected: F) where F: FnOnce(Interns) -> Expr<'a>, { let arena = Bump::new(); let CanExprOut { loc_expr, var_store, var, constraint, home, mut interns, .. } = can_expr(src); let subs = Subs::new(var_store.into()); let mut unify_problems = Vec::new(); let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); // Compile and add all the Procs before adding main let mut procs = Procs::default(); let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); // assume 64-bit pointers let pointer_size = std::mem::size_of::() as u32; // Populate Procs and Subs, and get the low-level Expr from the canonical Expr let mut mono_problems = Vec::new(); let mut mono_env = roc_mono::expr::Env { arena: &arena, subs: &mut subs, problems: &mut mono_problems, home, ident_ids: &mut ident_ids, pointer_size, jump_counter: arena.alloc(0), }; let mono_expr = Expr::new(&mut mono_env, loc_expr.value, &mut procs); let procs = roc_mono::expr::specialize_all(&mut mono_env, procs, &mut LayoutCache::default()); assert_eq!( procs.runtime_errors, roc_collections::all::MutMap::default() ); // Put this module's ident_ids back in the interns interns.all_ident_ids.insert(home, ident_ids); assert_eq!(get_expected(interns), mono_expr); } fn compiles_to_string(src: &str, expected: &str) { let arena = Bump::new(); let CanExprOut { loc_expr, var_store, var, constraint, home, mut interns, .. } = can_expr(src); let subs = Subs::new(var_store.into()); let mut unify_problems = Vec::new(); let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); // Compile and add all the Procs before adding main let mut procs = Procs::default(); let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); // assume 64-bit pointers let pointer_size = std::mem::size_of::() as u32; // Populate Procs and Subs, and get the low-level Expr from the canonical Expr let mut mono_problems = Vec::new(); let mut mono_env = roc_mono::expr::Env { arena: &arena, subs: &mut subs, problems: &mut mono_problems, home, ident_ids: &mut ident_ids, pointer_size, jump_counter: arena.alloc(0), }; dbg!(&procs); let mono_expr = Expr::new(&mut mono_env, loc_expr.value, &mut procs); let procs = roc_mono::expr::specialize_all(&mut mono_env, procs, &mut LayoutCache::default()); dbg!(&procs); assert_eq!( procs.runtime_errors, roc_collections::all::MutMap::default() ); // Put this module's ident_ids back in the interns interns.all_ident_ids.insert(home, ident_ids); let mut procs_string = procs .specialized .iter() .map(|(_, value)| { if let InProgressProc::Done(proc) = value { proc.to_pretty(200) } else { String::new() } }) .collect::>(); dbg!(&mono_expr); procs_string.push(mono_expr.to_pretty(200)); let result = procs_string.join("\n"); // assert_eq!(result, expected); () } #[test] fn int_literal() { compiles_to("5", Int(5)); } #[test] fn float_literal() { compiles_to("0.5", Float(0.5)); } #[test] fn float_addition() { compiles_to( "3.0 + 4", CallByName { name: Symbol::NUM_ADD, layout: Layout::FunctionPointer( &[ Layout::Builtin(Builtin::Float64), Layout::Builtin(Builtin::Float64), ], &Layout::Builtin(Builtin::Float64), ), args: &[ (Float(3.0), Layout::Builtin(Builtin::Float64)), (Float(4.0), Layout::Builtin(Builtin::Float64)), ], }, ); } #[test] fn int_addition() { compiles_to( "0xDEADBEEF + 4", CallByName { name: Symbol::NUM_ADD, layout: Layout::FunctionPointer( &[ Layout::Builtin(Builtin::Int64), Layout::Builtin(Builtin::Int64), ], &Layout::Builtin(Builtin::Int64), ), args: &[ (Int(3735928559), Layout::Builtin(Builtin::Int64)), (Int(4), Layout::Builtin(Builtin::Int64)), ], }, ); } #[test] fn num_addition() { // Default to Int for `Num *` compiles_to( "3 + 5", CallByName { name: Symbol::NUM_ADD, layout: Layout::FunctionPointer( &[ Layout::Builtin(Builtin::Int64), Layout::Builtin(Builtin::Int64), ], &Layout::Builtin(Builtin::Int64), ), args: &[ (Int(3), Layout::Builtin(Builtin::Int64)), (Int(5), Layout::Builtin(Builtin::Int64)), ], }, ); } #[test] fn specialize_closure() { compiles_to( r#" f = \x -> x + 5 { y: f 3.14, x: f 0x4 } "#, { use self::Builtin::*; let home = test_home(); let gen_symbol_0 = Interns::from_index(home, 0); Struct(&[ ( CallByName { name: gen_symbol_0, layout: Layout::FunctionPointer( &[Layout::Builtin(Builtin::Int64)], &Layout::Builtin(Builtin::Int64), ), args: &[(Int(4), Layout::Builtin(Int64))], }, Layout::Builtin(Int64), ), ( CallByName { name: gen_symbol_0, layout: Layout::FunctionPointer( &[Layout::Builtin(Builtin::Float64)], &Layout::Builtin(Builtin::Float64), ), args: &[(Float(3.14), Layout::Builtin(Float64))], }, Layout::Builtin(Float64), ), ]) }, ) } #[test] fn if_expression() { compiles_to( r#" if True then "bar" else "foo" "#, { use self::Builtin::*; use Layout::Builtin; let home = test_home(); let gen_symbol_0 = Interns::from_index(home, 0); Store( &[( gen_symbol_0, Layout::Builtin(layout::Builtin::Int1), Expr::Bool(true), )], &Cond { cond_symbol: gen_symbol_0, branching_symbol: gen_symbol_0, cond_layout: Builtin(Int1), branching_layout: Builtin(Int1), pass: (&[] as &[_], &Expr::Str("bar")), fail: (&[] as &[_], &Expr::Str("foo")), ret_layout: Builtin(Str), }, ) }, ) } #[test] fn multiway_if_expression() { compiles_to( r#" if True then "bar" else if False then "foo" else "baz" "#, { use self::Builtin::*; use Layout::Builtin; let home = test_home(); let gen_symbol_0 = Interns::from_index(home, 1); let gen_symbol_1 = Interns::from_index(home, 0); Store( &[( gen_symbol_0, Layout::Builtin(layout::Builtin::Int1), Expr::Bool(true), )], &Cond { cond_symbol: gen_symbol_0, branching_symbol: gen_symbol_0, cond_layout: Builtin(Int1), branching_layout: Builtin(Int1), pass: (&[] as &[_], &Expr::Str("bar")), fail: ( &[] as &[_], &Store( &[( gen_symbol_1, Layout::Builtin(layout::Builtin::Int1), Expr::Bool(false), )], &Cond { cond_symbol: gen_symbol_1, branching_symbol: gen_symbol_1, branching_layout: Builtin(Int1), cond_layout: Builtin(Int1), pass: (&[] as &[_], &Expr::Str("foo")), fail: (&[] as &[_], &Expr::Str("baz")), ret_layout: Builtin(Str), }, ), ), ret_layout: Builtin(Str), }, ) }, ) } #[test] fn annotated_if_expression() { // an if with an annotation gets constrained differently. Make sure the result is still correct. compiles_to( r#" x : Str x = if True then "bar" else "foo" x "#, { use self::Builtin::*; use Layout::Builtin; let home = test_home(); let gen_symbol_0 = Interns::from_index(home, 1); let symbol_x = Interns::from_index(home, 0); Store( &[( symbol_x, Builtin(Str), Store( &[( gen_symbol_0, Layout::Builtin(layout::Builtin::Int1), Expr::Bool(true), )], &Cond { cond_symbol: gen_symbol_0, branching_symbol: gen_symbol_0, cond_layout: Builtin(Int1), branching_layout: Builtin(Int1), pass: (&[] as &[_], &Expr::Str("bar")), fail: (&[] as &[_], &Expr::Str("foo")), ret_layout: Builtin(Str), }, ), )], &DecAfter(symbol_x, &Load(symbol_x)), ) }, ) } // #[test] // fn record_pattern() { // compiles_to( // r#" // \{ x } -> x + 0x5 // "#, // { Float(3.45) }, // ) // } // // #[test] // fn tag_pattern() { // compiles_to( // r#" // \Foo x -> x + 0x5 // "#, // { Float(3.45) }, // ) // } #[test] fn polymorphic_identity() { compiles_to( r#" id = \x -> x id { x: id 0x4, y: 0.1 } "#, { let home = test_home(); let gen_symbol_0 = Interns::from_index(home, 0); let struct_layout = Layout::Struct(&[I64_LAYOUT, F64_LAYOUT]); CallByName { name: gen_symbol_0, layout: Layout::FunctionPointer( &[struct_layout.clone()], &struct_layout.clone(), ), args: &[( Struct(&[ ( CallByName { name: gen_symbol_0, layout: Layout::FunctionPointer(&[I64_LAYOUT], &I64_LAYOUT), args: &[(Int(4), I64_LAYOUT)], }, I64_LAYOUT, ), (Float(0.1), F64_LAYOUT), ]), struct_layout, )], } }, ) } // #[test] // fn list_get_unique() { // compiles_to( // r#" // unique = [ 2, 4 ] // List.get unique 1 // "#, // { // use self::Builtin::*; // let home = test_home(); // let gen_symbol_0 = Interns::from_index(home, 0); // let list_layout = Layout::Builtin(Builtin::List(&I64_LAYOUT)); // CallByName { // name: gen_symbol_0, // layout: Layout::FunctionPointer(&[list_layout.clone()], &list_layout.clone()), // args: &[( // Struct(&[( // CallByName { // name: gen_symbol_0, // layout: Layout::FunctionPointer( // &[Layout::Builtin(Builtin::Int64)], // &Layout::Builtin(Builtin::Int64), // ), // args: &[(Int(4), Layout::Builtin(Int64))], // }, // Layout::Builtin(Int64), // )]), // Layout::Struct(&[Layout::Builtin(Int64)]), // )], // } // }, // ) // } // needs LetRec to be converted to mono // #[test] // fn polymorphic_recursive() { // compiles_to( // r#" // f = \x -> // when x < 10 is // True -> f (x + 1) // False -> x // // { x: f 0x4, y: f 3.14 } // "#, // { // use self::Builtin::*; // use Layout::Builtin; // let home = test_home(); // // let gen_symbol_3 = Interns::from_index(home, 3); // let gen_symbol_4 = Interns::from_index(home, 4); // // Float(3.4) // // }, // ) // } // needs layout for non-empty tag union // #[test] // fn is_nil() { // let arena = Bump::new(); // // compiles_to_with_interns( // r#" // LinkedList a : [ Cons a (LinkedList a), Nil ] // // isNil : LinkedList a -> Bool // isNil = \list -> // when list is // Nil -> True // Cons _ _ -> False // // listInt : LinkedList Int // listInt = Nil // // isNil listInt // "#, // |interns| { // let home = test_home(); // let var_is_nil = interns.symbol(home, "isNil".into()); // }, // ); // } #[test] fn bool_literal() { let arena = Bump::new(); compiles_to_with_interns( r#" x : Bool x = True x "#, |interns| { let home = test_home(); let var_x = interns.symbol(home, "x".into()); let stores = [(var_x, Layout::Builtin(Builtin::Int1), Bool(true))]; let load = Load(var_x); let dec = DecAfter(var_x, arena.alloc(load)); Store(arena.alloc(stores), arena.alloc(dec)) }, ); } #[test] fn two_element_enum() { let arena = Bump::new(); compiles_to_with_interns( r#" x : [ Yes, No ] x = No x "#, |interns| { let home = test_home(); let var_x = interns.symbol(home, "x".into()); let stores = [(var_x, Layout::Builtin(Builtin::Int1), Bool(false))]; let load = Load(var_x); let dec = DecAfter(var_x, arena.alloc(load)); Store(arena.alloc(stores), arena.alloc(dec)) }, ); } #[test] fn three_element_enum() { let arena = Bump::new(); compiles_to_with_interns( r#" # this test is brought to you by fruits.com! x : [ Apple, Orange, Banana ] x = Orange x "#, |interns| { let home = test_home(); let var_x = interns.symbol(home, "x".into()); // orange gets index (and therefore tag_id) 1 let stores = [(var_x, Layout::Builtin(Builtin::Int8), Byte(2))]; let load = Load(var_x); let dec = DecAfter(var_x, arena.alloc(load)); Store(arena.alloc(stores), arena.alloc(dec)) }, ); } #[test] fn set_unique_int_list() { compiles_to("List.get (List.set [ 12, 9, 7, 3 ] 1 42) 1", { CallByName { name: Symbol::LIST_GET, layout: Layout::FunctionPointer( &[ Layout::Builtin(Builtin::List(Owned, &I64_LAYOUT)), I64_LAYOUT, ], &Layout::Union(&[&[I64_LAYOUT], &[I64_LAYOUT, I64_LAYOUT]]), ), args: &vec![ ( CallByName { name: Symbol::LIST_SET, layout: Layout::FunctionPointer( &[ Layout::Builtin(Builtin::List(Owned, &I64_LAYOUT)), I64_LAYOUT, I64_LAYOUT, ], &Layout::Builtin(Builtin::List(Owned, &I64_LAYOUT)), ), args: &vec![ ( Array { elem_layout: I64_LAYOUT, elems: &vec![Int(12), Int(9), Int(7), Int(3)], }, Layout::Builtin(Builtin::List(Owned, &I64_LAYOUT)), ), (Int(1), I64_LAYOUT), (Int(42), I64_LAYOUT), ], }, Layout::Builtin(Builtin::List(Owned, &I64_LAYOUT)), ), (Int(1), I64_LAYOUT), ], } }); } // #[test] // fn when_on_result() { // compiles_to( // r#" // when 1 is // 1 -> 12 // _ -> 34 // "#, // { // use self::Builtin::*; // use Layout::Builtin; // let home = test_home(); // // let gen_symbol_3 = Interns::from_index(home, 3); // let gen_symbol_4 = Interns::from_index(home, 4); // // CallByName( // gen_symbol_3, // &[( // Struct(&[( // CallByName(gen_symbol_4, &[(Int(4), Builtin(Int64))]), // Builtin(Int64), // )]), // Layout::Struct(&[("x".into(), Builtin(Int64))]), // )], // ) // }, // ) // } #[test] fn simple_to_string() { compiles_to_string( r#" x = 3 x "#, indoc!( r#" Store Test.0: 3i64 Load Test.0 Dec Test.0 "# ), ) } #[test] fn if_to_string() { compiles_to_string( r#" if True then 1 else 2 "#, indoc!( r#" Store Test.0: true if Test.0 then 1i64 else 2i64 "# ), ) } #[test] fn maybe_map_to_string() { compiles_to_string( r#" Maybe a : [ Nothing, Just a ] maybe : Maybe Int maybe = Just 0x3 when maybe is Just x -> Just (x + 1) Nothing -> Nothing "#, indoc!( r#" procedure Num.14 (#Attr.2, #Attr.3): Lowlevel.NumAdd (Load #Attr.2) (Load #Attr.3) Store Test.1: Store Test.3: 0i64 Store Test.4: 3i64 Just Test.3 Test.4 Store Test.8: Lowlevel.And (Lowlevel.Eq 0i64 (Access @0 Load Test.1)) true if Test.8 then Reset Test.1 Store Test.5: 0i64 Store Test.6: Call Num.14 (Load Test.2) 1i64 Reuse Test.1 Just Test.5 Test.6 else Reset Test.1 Store Test.7: 1i64 Reuse Test.1 Nothing Test.7 Dec Test.1 "# ), ) } #[test] fn these_map_to_string() { compiles_to_string( r#" These a b : [ This a, That b, These a b ] these : These Int Int these = These 1 2 when these is This a -> This a That b -> That b These a b -> These b a "#, indoc!( r#" Store Test.1: Store Test.6: 1i64 Store Test.7: 1i64 Store Test.8: 2i64 These Test.6 Test.7 Test.8 switch Test.1: case 2: Reset Test.1 Store Test.9: 2i64 Reuse Test.1 This Test.9 Test.2 case 0: Reset Test.1 Store Test.11: 0i64 Reuse Test.1 That Test.11 Test.3 default: Reset Test.1 Store Test.13: 1i64 Reuse Test.1 These Test.13 Test.5 Test.4 Dec Test.1 "# ), ) } #[test] fn list_length() { compiles_to_string( r#" x = [ 1,2,3 ] List.len x "#, indoc!( r#" procedure List.7 (#Attr.2): Lowlevel.ListLen (Load #Attr.2) Store Test.0: [ 1i64, 2i64, 3i64 ] Call List.7 (Load Test.0) Dec Test.0 "# ), ) } #[test] fn pass_list_to_function() { compiles_to_string( r#" x : List Int x = [1,2,3] id : a -> a id = \y -> y id x "#, indoc!( r#" procedure Test.1 (Test.3): Load Test.3 Dec Test.3 Store Test.0: [ 1i64, 2i64, 3i64 ] Call Test.1 (Load Test.0) Dec Test.0 "# ), ) } #[test] fn double_list_len() { compiles_to_string( r#" x : List Int x = [1,2,3] List.len x + List.len x "#, indoc!( r#" procedure Num.14 (#Attr.2, #Attr.3): Lowlevel.NumAdd (Load #Attr.2) (Load #Attr.3) procedure List.7 (#Attr.2): Lowlevel.ListLen (Load #Attr.2) Store Test.0: [ 1i64, 2i64, 3i64 ] Call Num.14 (Call List.7 (Load Test.0)) (Call List.7 (Load Test.0)) Dec Test.0 "# ), ) } #[test] fn is_nil() { compiles_to_string( r#" isNil = \xs -> when xs is Nil -> True Cons _ _ -> False isNil Nil "#, indoc!( r#" procedure Test.0 (Test.2): Store Test.3: Lowlevel.And (Lowlevel.Eq true (Load Test.2)) true if Test.3 then true else false Dec Test.2 Call Test.0 true "# ), ) } #[test] fn y_is_dead() { compiles_to_string( r#" f = \y -> Pair y y f [1] "#, indoc!( r#" procedure Test.0 (Test.2): Struct { Load Test.2, Load Test.2 } Dec Test.2 Call Test.0 [ 1i64 ] "# ), ) } fn compiles_to_ir(src: &str, expected: &str) { let arena = Bump::new(); let CanExprOut { loc_expr, var_store, var, constraint, home, mut interns, .. } = can_expr(src); let subs = Subs::new(var_store.into()); let mut unify_problems = Vec::new(); let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); // Compile and add all the Procs before adding main let mut procs = roc_mono::experiment::Procs::default(); let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); // assume 64-bit pointers let pointer_size = std::mem::size_of::() as u32; // Populate Procs and Subs, and get the low-level Expr from the canonical Expr let mut mono_problems = Vec::new(); let mut mono_env = roc_mono::experiment::Env { arena: &arena, subs: &mut subs, problems: &mut mono_problems, home, ident_ids: &mut ident_ids, pointer_size, jump_counter: arena.alloc(0), }; let mut layout_cache = LayoutCache::default(); let ir_expr = roc_mono::experiment::from_can( &mut mono_env, loc_expr.value, &mut procs, &mut layout_cache, ); // let mono_expr = Expr::new(&mut mono_env, loc_expr.value, &mut procs); let procs = roc_mono::experiment::specialize_all(&mut mono_env, procs, &mut LayoutCache::default()); assert_eq!( procs.runtime_errors, roc_collections::all::MutMap::default() ); // Put this module's ident_ids back in the interns interns.all_ident_ids.insert(home, ident_ids); let mut procs_string = procs .specialized .iter() .map(|(_, value)| { if let roc_mono::experiment::InProgressProc::Done(proc) = value { proc.to_pretty(200) } else { String::new() } }) .collect::>(); procs_string.push(ir_expr.to_pretty(200)); let result = procs_string.join("\n"); let the_same = result == expected; if !the_same { println!("{}", result); } assert_eq!(result, expected); } #[test] fn ir_int_literal() { compiles_to_ir( r#" 5 "#, indoc!( r#" let Test.0 = 5i64; ret Test.0; "# ), ) } #[test] fn ir_assignment() { compiles_to_ir( r#" x = 5 x "#, indoc!( r#" let Test.0 = 5i64; ret Test.0; "# ), ) } #[test] fn ir_if() { compiles_to_ir( r#" if True then 1 else 2 "#, indoc!( r#" let Test.1 = true; if Test.1 then let Test.2 = 1i64; ret Test.2; else let Test.0 = 2i64; ret Test.0; "# ), ) } #[test] fn ir_when_enum() { compiles_to_ir( r#" when Blue is Red -> 1 White -> 2 Blue -> 3 "#, indoc!( r#" let Test.0 = 0u8; switch Test.0: case 1: let Test.1 = 1i64; ret Test.1; case 2: let Test.2 = 2i64; ret Test.2; default: let Test.3 = 3i64; ret Test.3; "# ), ) } #[test] fn ir_when_maybe() { compiles_to_ir( r#" when Just 3 is Just n -> n Nothing -> 0 "#, indoc!( r#" let Test.10 = 0i64; let Test.11 = 3i64; let Test.1 = Just Test.10 Test.11; let Test.5 = true; let Test.7 = Index 0 Test.1; let Test.6 = 0i64; let Test.8 = lowlevel Eq Test.6 Test.7; let Test.4 = lowlevel And Test.8 Test.5; if Test.4 then let Test.0 = Index 1 Test.1; ret Test.0; else let Test.3 = 0i64; ret Test.3; "# ), ) } #[test] fn ir_when_these() { // NOTE apparently loading the tag_id is not required? compiles_to_ir( r#" when These 1 2 is This x -> x That y -> y These x _ -> x "#, indoc!( r#" let Test.7 = 1i64; let Test.8 = 1i64; let Test.9 = 2i64; let Test.3 = These Test.7 Test.8 Test.9; switch Test.3: case 2: let Test.0 = Index 1 Test.3; ret Test.0; case 0: let Test.1 = Index 1 Test.3; ret Test.1; default: let Test.2 = Index 1 Test.3; ret Test.2; "# ), ) } #[test] fn ir_when_record() { // NOTE apparently loading the tag_id is not required? compiles_to_ir( r#" when { x: 1, y: 3.14 } is { x } -> x "#, indoc!( r#" let Test.4 = 1i64; let Test.5 = 3.14f64; let Test.1 = Struct {Test.4, Test.5}; let Test.0 = Index 0 Test.1; ret Test.0; "# ), ) } #[test] fn ir_plus() { compiles_to_ir( r#" 1 + 2 "#, indoc!( r#" procedure Num.14 (#Attr.2, #Attr.3): let Test.3 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.3; let Test.1 = 1i64; let Test.2 = 2i64; let Test.0 = CallByName Num.14 Test.1 Test.2; ret Test.0; "# ), ) } #[test] fn ir_round() { compiles_to_ir( r#" Num.round 3.6 "#, indoc!( r#" procedure Num.36 (#Attr.2): let Test.2 = lowlevel NumRound #Attr.2; ret Test.2; let Test.1 = 3.6f64; let Test.0 = CallByName Num.36 Test.1; ret Test.0; "# ), ) } #[test] fn ir_when_idiv() { compiles_to_ir( r#" when 1000 // 10 is Ok val -> val Err _ -> -1 "#, indoc!( r#" procedure Num.32 (#Attr.2, #Attr.3): let Test.19 = 0i64; let Test.15 = lowlevel NotEq #Attr.3 Test.19; if Test.15 then let Test.17 = 1i64; let Test.18 = lowlevel NumDivUnchecked #Attr.2 #Attr.3; let Test.16 = Ok Test.17 Test.18; ret Test.16; else let Test.13 = 0i64; let Test.14 = Struct {}; let Test.12 = Err Test.13 Test.14; ret Test.12; let Test.10 = 1000i64; let Test.11 = 10i64; let Test.1 = CallByName Num.32 Test.10 Test.11; let Test.5 = true; let Test.7 = Index 0 Test.1; let Test.6 = 1i64; let Test.8 = lowlevel Eq Test.6 Test.7; let Test.4 = lowlevel And Test.8 Test.5; if Test.4 then let Test.0 = Index 1 Test.1; ret Test.0; else let Test.3 = -1i64; ret Test.3; "# ), ) } #[test] fn ir_two_defs() { compiles_to_ir( r#" x = 3 y = 4 x + y "#, indoc!( r#" procedure Num.14 (#Attr.2, #Attr.3): let Test.3 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.3; let Test.1 = 4i64; let Test.0 = 3i64; let Test.2 = CallByName Num.14 Test.0 Test.1; ret Test.2; "# ), ) } #[test] fn ir_when_just() { compiles_to_ir( r#" x : [ Nothing, Just Int ] x = Just 41 when x is Just v -> v + 0x1 Nothing -> 0x1 "#, indoc!( r#" procedure Num.14 (#Attr.2, #Attr.3): let Test.4 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.4; let Test.12 = 0i64; let Test.13 = 41i64; let Test.0 = Just Test.12 Test.13; let Test.7 = true; let Test.9 = Index 0 Test.0; let Test.8 = 0i64; let Test.10 = lowlevel Eq Test.8 Test.9; let Test.6 = lowlevel And Test.10 Test.7; if Test.6 then let Test.1 = Index 1 Test.0; let Test.3 = 1i64; let Test.2 = CallByName Num.14 Test.1 Test.3; ret Test.2; else let Test.5 = 1i64; ret Test.5; "# ), ) } #[test] fn one_element_tag() { compiles_to_ir( r#" x : [ Pair Int ] x = Pair 2 x "#, indoc!( r#" let Test.2 = 2i64; let Test.0 = Pair Test.2; ret Test.0; "# ), ) } #[test] fn join_points() { compiles_to_ir( r#" x = if True then 1 else 2 x "#, indoc!( r#" let Test.3 = true; if Test.3 then let Test.0 = 1i64; jump Test.2; else let Test.0 = 2i64; jump Test.2; joinpoint Test.2: ret Test.0; "# ), ) } #[test] fn guard_pattern_true() { compiles_to_ir( r#" when 2 is 2 if True -> 42 _ -> 0 "#, indoc!( r#" let Test.0 = 2i64; let Test.6 = true; let Test.10 = lowlevel Eq Test.6 Test.2; let Test.9 = lowlevel And Test.10 Test.5; let Test.7 = 2i64; let Test.8 = lowlevel Eq Test.7 Test.0; let Test.5 = lowlevel And Test.8 Test.6; let Test.2 = true; jump Test.3; joinpoint Test.3: if Test.5 then let Test.1 = 42i64; ret Test.1; else let Test.4 = 0i64; ret Test.4; "# ), ) } #[test] fn when_on_record() { compiles_to_ir( r#" when { x: 0x2 } is { x } -> x + 3 "#, indoc!( r#" let Test.5 = 2i64; let Test.1 = Struct {Test.5}; let Test.0 = Index 0 Test.1; let Test.3 = 3i64; let Test.2 = CallByName Num.14 Test.0 Test.3; ret Test.2; "# ), ) } #[test] fn let_on_record() { compiles_to_ir( r#" { x } = { x: 0x2, y: 3.14 } x "#, indoc!( r#" let Test.4 = 2i64; let Test.5 = 3.14f64; let Test.1 = Struct {Test.4, Test.5}; let Test.0 = Index 0 Test.1; ret Test.0; "# ), ) } #[test] fn when_nested_maybe() { compiles_to_ir( r#" Maybe a : [ Nothing, Just a ] x : Maybe (Maybe Int) x = Just (Just 41) when x is Just (Just v) -> v + 0x1 _ -> 0x1 "#, indoc!( r#" procedure Num.14 (#Attr.2, #Attr.3): let Test.5 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.5; let Test.16 = 2i64; let Test.17 = 3i64; let Test.2 = Struct {Test.16, Test.17}; let Test.7 = true; let Test.8 = 4i64; let Test.9 = Index 0 Test.2; let Test.14 = lowlevel Eq Test.8 Test.9; let Test.13 = lowlevel And Test.14 Test.6; let Test.10 = 3i64; let Test.11 = Index 1 Test.2; let Test.12 = lowlevel Eq Test.10 Test.11; let Test.6 = lowlevel And Test.12 Test.7; if Test.6 then let Test.3 = 9i64; ret Test.3; else let Test.1 = Index 1 Test.2; let Test.0 = Index 0 Test.2; let Test.4 = CallByName Num.14 Test.0 Test.1; ret Test.4; "# ), ) } #[test] fn when_on_two_values() { compiles_to_ir( r#" when Pair 2 3 is Pair 4 3 -> 9 Pair a b -> a + b "#, indoc!( r#" procedure Num.14 (#Attr.2, #Attr.3): let Test.5 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.5; let Test.16 = 2i64; let Test.17 = 3i64; let Test.2 = Struct {Test.16, Test.17}; let Test.7 = true; let Test.8 = 4i64; let Test.9 = Index 0 Test.2; let Test.14 = lowlevel Eq Test.8 Test.9; let Test.13 = lowlevel And Test.14 Test.6; let Test.10 = 3i64; let Test.11 = Index 1 Test.2; let Test.12 = lowlevel Eq Test.10 Test.11; let Test.6 = lowlevel And Test.12 Test.7; if Test.6 then let Test.3 = 9i64; ret Test.3; else let Test.1 = Index 1 Test.2; let Test.0 = Index 0 Test.2; let Test.4 = CallByName Num.14 Test.0 Test.1; ret Test.4; "# ), ) } #[test] fn list_push() { compiles_to_ir( r#" List.push [1] 2 "#, indoc!( r#" procedure List.5 (#Attr.2, #Attr.3): let Test.3 = lowlevel ListPush #Attr.2 #Attr.3; ret Test.3; let Test.4 = 1i64; let Test.1 = Array [Test.4]; let Test.2 = 2i64; let Test.0 = CallByName List.5 Test.1 Test.2; ret Test.0; "# ), ) } }