mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 14:54:47 +00:00
1558 lines
44 KiB
Rust
1558 lines
44 KiB
Rust
#[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::<u64>() 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::<u64>() 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::<Vec<_>>();
|
|
|
|
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::<u64>() 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::<Vec<_>>();
|
|
|
|
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;
|
|
"#
|
|
),
|
|
)
|
|
}
|
|
}
|