Merge pull request #678 from rtfeldman/record-bool-tests

More REPL Improvements
This commit is contained in:
Richard Feldman 2020-11-12 00:07:18 -05:00 committed by GitHub
commit d21ffb7eee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 472 additions and 212 deletions

View file

@ -6,7 +6,7 @@ use roc_gen::{run_jit_function, run_jit_function_dynamic_type};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::operator::CalledVia; use roc_module::operator::CalledVia;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionVariant};
use roc_parse::ast::{AssignedField, Expr, StrLiteral}; use roc_parse::ast::{AssignedField, Expr, StrLiteral};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{Content, FlatType, Subs, Variable}; use roc_types::subs::{Content, FlatType, Subs, Variable};
@ -64,6 +64,10 @@ fn jit_to_ast_help<'a>(
env, num, content env, num, content
)) ))
} }
Layout::Builtin(Builtin::Int8) => {
// NOTE: this is does not handle 8-bit numbers yet
run_jit_function!(lib, main_fn_name, u8, |num| byte_to_ast(env, num, content))
}
Layout::Builtin(Builtin::Int64) => { Layout::Builtin(Builtin::Int64) => {
run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast( run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast(
env, env,
@ -89,19 +93,20 @@ fn jit_to_ast_help<'a>(
Layout::Builtin(Builtin::List(_, elem_layout)) => run_jit_function!( Layout::Builtin(Builtin::List(_, elem_layout)) => run_jit_function!(
lib, lib,
main_fn_name, main_fn_name,
(*const libc::c_void, usize), (*const u8, usize),
|(ptr, len): (*const libc::c_void, usize)| { |(ptr, len): (*const u8, usize)| { list_to_ast(env, ptr, len, elem_layout, content) }
list_to_ast(env, ptr, len, elem_layout, content)
}
), ),
Layout::PhantomEmptyStruct => run_jit_function!(lib, main_fn_name, &'static str, |_| { Layout::Builtin(other) => {
todo!("add support for rendering builtin {:?} to the REPL", other)
}
Layout::PhantomEmptyStruct => run_jit_function!(lib, main_fn_name, &u8, |_| {
Expr::Record { Expr::Record {
update: None, update: None,
fields: &[], fields: &[],
} }
}), }),
Layout::Struct(field_layouts) => { Layout::Struct(field_layouts) => {
let ptr_to_ast = |ptr: *const libc::c_void| match content { let ptr_to_ast = |ptr: *const u8| match content {
Content::Structure(FlatType::Record(fields, _)) => { Content::Structure(FlatType::Record(fields, _)) => {
struct_to_ast(env, ptr, field_layouts, fields) struct_to_ast(env, ptr, field_layouts, fields)
} }
@ -113,13 +118,7 @@ fn jit_to_ast_help<'a>(
let (tag_name, payload_vars) = tags.iter().next().unwrap(); let (tag_name, payload_vars) = tags.iter().next().unwrap();
// We expect anything with payload vars single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), payload_vars)
// that is a single Tag TagUnion
// has a Record content so the above case
// should match instead
debug_assert_eq!(payload_vars.len(), 0);
single_tag_union_to_ast(env, field_layouts, tag_name.clone(), payload_vars)
} }
other => { other => {
unreachable!( unreachable!(
@ -132,76 +131,93 @@ fn jit_to_ast_help<'a>(
let fields = [Layout::Builtin(Builtin::Int64), layout.clone()]; let fields = [Layout::Builtin(Builtin::Int64), layout.clone()];
let layout = Layout::Struct(&fields); let layout = Layout::Struct(&fields);
match env.ptr_bytes { let result_stack_size = layout.stack_size(env.ptr_bytes);
// 64-bit target (8-byte pointers, 16-byte structs)
8 => match layout.stack_size(env.ptr_bytes) { run_jit_function_dynamic_type!(
8 => { lib,
// just one eightbyte, returned as-is main_fn_name,
run_jit_function!(lib, main_fn_name, [u8; 8], |bytes: [u8; 8]| { result_stack_size as usize,
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) |bytes: *const u8| { ptr_to_ast(bytes as *const u8) }
}) )
} }
16 => { Layout::Union(union_layouts) => match content {
// two eightbytes, returned as-is Content::Structure(FlatType::TagUnion(tags, _)) => {
run_jit_function!(lib, main_fn_name, [u8; 16], |bytes: [u8; 16]| { debug_assert_eq!(union_layouts.len(), tags.len());
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
}) let tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)> =
} tags.iter().map(|(a, b)| (a.clone(), b.clone())).collect();
larger_size => {
// anything more than 2 eightbytes let union_variant = union_sorted_tags_help(env.arena, tags_vec, None, env.subs);
// the return "value" is a pointer to the result
let size = layout.stack_size(env.ptr_bytes);
match union_variant {
UnionVariant::Wrapped(tags_and_layouts) => {
run_jit_function_dynamic_type!( run_jit_function_dynamic_type!(
lib, lib,
main_fn_name, main_fn_name,
larger_size as usize, size as usize,
|bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) } |ptr: *const u8| {
// Because this is a `Wrapped`, the first 8 bytes encode the tag ID
let tag_id = *(ptr as *const i64);
// use the tag ID as an index, to get its name and layout of any arguments
let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize];
let tag_expr = tag_name_to_expr(env, tag_name);
let loc_tag_expr = &*env.arena.alloc(Located::at_zero(tag_expr));
let variables = &tags[tag_name];
// because the arg_layouts include the tag ID, it is one longer
debug_assert_eq!(arg_layouts.len() - 1, variables.len());
// skip forward to the start of the first element, ignoring the tag id
let ptr = ptr.offset(8);
let it = variables.iter().copied().zip(&arg_layouts[1..]);
let output = sequence_of_expr(env, ptr, it);
let output = output.into_bump_slice();
Expr::Apply(loc_tag_expr, output, CalledVia::Space)
}
) )
} }
}, _ => unreachable!("any other variant would have a different layout"),
// 32-bit target (4-byte pointers, 8-byte structs)
4 => {
// TODO what are valid return sizes here?
// this is just extrapolated from the 64-bit case above
// and not (yet) actually tested on a 32-bit system
match layout.stack_size(env.ptr_bytes) {
4 => {
// just one fourbyte, returned as-is
run_jit_function!(lib, main_fn_name, [u8; 4], |bytes: [u8; 4]| {
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
})
}
8 => {
// just one fourbyte, returned as-is
run_jit_function!(lib, main_fn_name, [u8; 8], |bytes: [u8; 8]| {
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
})
}
larger_size => {
// anything more than 2 fourbytes
// the return "value" is a pointer to the result
run_jit_function_dynamic_type!(
lib,
main_fn_name,
larger_size as usize,
|bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) }
)
}
}
}
other => {
panic!("Unsupported target: Roc cannot currently compile to systems where pointers are {} bytes in length.", other);
} }
} }
Content::Structure(FlatType::RecursiveTagUnion(_, _, _)) => {
todo!("print recursive tag unions in the REPL")
}
other => unreachable!("Weird content for Union layout: {:?}", other),
},
Layout::RecursiveUnion(_) | Layout::RecursivePointer => {
todo!("add support for rendering recursive tag unions in the REPL")
} }
other => {
todo!("TODO add support for rendering {:?} in the REPL", other); Layout::FunctionPointer(_, _) | Layout::Closure(_, _, _) => {
todo!("add support for rendering functions in the REPL")
} }
Layout::Pointer(_) => todo!("add support for rendering pointers in the REPL"),
}
}
fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> {
match tag_name {
TagName::Global(_) => Expr::GlobalTag(
env.arena
.alloc_str(&tag_name.as_string(env.interns, env.home)),
),
TagName::Private(_) => Expr::PrivateTag(
env.arena
.alloc_str(&tag_name.as_string(env.interns, env.home)),
),
TagName::Closure(_) => unreachable!("User cannot type this"),
} }
} }
fn ptr_to_ast<'a>( fn ptr_to_ast<'a>(
env: &Env<'a, '_>, env: &Env<'a, '_>,
ptr: *const libc::c_void, ptr: *const u8,
layout: &Layout<'a>, layout: &Layout<'a>,
content: &Content, content: &Content,
) -> Expr<'a> { ) -> Expr<'a> {
@ -227,7 +243,7 @@ fn ptr_to_ast<'a>(
Layout::Builtin(Builtin::List(_, elem_layout)) => { Layout::Builtin(Builtin::List(_, elem_layout)) => {
// Turn the (ptr, len) wrapper struct into actual ptr and len values. // Turn the (ptr, len) wrapper struct into actual ptr and len values.
let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) }; let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) };
let ptr = unsafe { *(ptr as *const *const libc::c_void) }; let ptr = unsafe { *(ptr as *const *const u8) };
list_to_ast(env, ptr, len, elem_layout, content) list_to_ast(env, ptr, len, elem_layout, content)
} }
@ -241,6 +257,12 @@ fn ptr_to_ast<'a>(
Content::Structure(FlatType::Record(fields, _)) => { Content::Structure(FlatType::Record(fields, _)) => {
struct_to_ast(env, ptr, field_layouts, fields) struct_to_ast(env, ptr, field_layouts, fields)
} }
Content::Structure(FlatType::TagUnion(tags, _)) => {
debug_assert_eq!(tags.len(), 1);
let (tag_name, payload_vars) = tags.iter().next().unwrap();
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), payload_vars)
}
other => { other => {
unreachable!( unreachable!(
"Something had a Struct layout, but instead of a Record type, it had: {:?}", "Something had a Struct layout, but instead of a Record type, it had: {:?}",
@ -259,7 +281,7 @@ fn ptr_to_ast<'a>(
fn list_to_ast<'a>( fn list_to_ast<'a>(
env: &Env<'a, '_>, env: &Env<'a, '_>,
ptr: *const libc::c_void, ptr: *const u8,
len: usize, len: usize,
elem_layout: &Layout<'a>, elem_layout: &Layout<'a>,
content: &Content, content: &Content,
@ -282,11 +304,11 @@ fn list_to_ast<'a>(
let arena = env.arena; let arena = env.arena;
let mut output = Vec::with_capacity_in(len, &arena); let mut output = Vec::with_capacity_in(len, &arena);
let elem_size = elem_layout.stack_size(env.ptr_bytes); let elem_size = elem_layout.stack_size(env.ptr_bytes) as usize;
for index in 0..(len as isize) { for index in 0..len {
let offset_bytes: isize = index * elem_size as isize; let offset_bytes = index * elem_size;
let elem_ptr = unsafe { ptr.offset(offset_bytes) }; let elem_ptr = unsafe { ptr.add(offset_bytes) };
let loc_expr = &*arena.alloc(Located { let loc_expr = &*arena.alloc(Located {
value: ptr_to_ast(env, elem_ptr, elem_layout, &elem_content), value: ptr_to_ast(env, elem_ptr, elem_layout, &elem_content),
region: Region::zero(), region: Region::zero(),
@ -302,7 +324,8 @@ fn list_to_ast<'a>(
fn single_tag_union_to_ast<'a>( fn single_tag_union_to_ast<'a>(
env: &Env<'a, '_>, env: &Env<'a, '_>,
field_layouts: &[Layout<'a>], ptr: *const u8,
field_layouts: &'a [Layout<'a>],
tag_name: TagName, tag_name: TagName,
payload_vars: &[Variable], payload_vars: &[Variable],
) -> Expr<'a> { ) -> Expr<'a> {
@ -310,28 +333,50 @@ fn single_tag_union_to_ast<'a>(
let arena = env.arena; let arena = env.arena;
let tag_expr = match tag_name { let tag_expr = tag_name_to_expr(env, &tag_name);
TagName::Global(_) => {
Expr::GlobalTag(arena.alloc_str(&tag_name.as_string(env.interns, env.home)))
}
TagName::Private(_) => {
Expr::PrivateTag(arena.alloc_str(&tag_name.as_string(env.interns, env.home)))
}
TagName::Closure(_) => unreachable!("User cannot type this"),
};
let loc_tag_expr = &*arena.alloc(Located { let loc_tag_expr = &*arena.alloc(Located::at_zero(tag_expr));
value: tag_expr,
region: Region::zero(),
});
Expr::Apply(loc_tag_expr, &[], CalledVia::Space) let it = payload_vars.iter().copied().zip(field_layouts);
let output = sequence_of_expr(env, ptr as *const u8, it).into_bump_slice();
Expr::Apply(loc_tag_expr, output, CalledVia::Space)
}
fn sequence_of_expr<'a, I>(
env: &Env<'a, '_>,
ptr: *const u8,
sequence: I,
) -> Vec<'a, &'a Located<Expr<'a>>>
where
I: Iterator<Item = (Variable, &'a Layout<'a>)>,
I: ExactSizeIterator<Item = (Variable, &'a Layout<'a>)>,
{
let arena = env.arena;
let subs = env.subs;
let mut output = Vec::with_capacity_in(sequence.len(), &arena);
// We'll advance this as we iterate through the fields
let mut field_ptr = ptr as *const u8;
for (var, layout) in sequence {
let content = subs.get_without_compacting(var).content;
let expr = ptr_to_ast(env, field_ptr, layout, &content);
let loc_expr = Located::at_zero(expr);
output.push(&*arena.alloc(loc_expr));
// Advance the field pointer to the next field.
field_ptr = unsafe { field_ptr.offset(layout.stack_size(env.ptr_bytes) as isize) };
}
output
} }
fn struct_to_ast<'a>( fn struct_to_ast<'a>(
env: &Env<'a, '_>, env: &Env<'a, '_>,
ptr: *const libc::c_void, ptr: *const u8,
field_layouts: &[Layout<'a>], field_layouts: &'a [Layout<'a>],
fields: &MutMap<Lowercase, RecordField<Variable>>, fields: &MutMap<Lowercase, RecordField<Variable>>,
) -> Expr<'a> { ) -> Expr<'a> {
let arena = env.arena; let arena = env.arena;
@ -339,7 +384,7 @@ fn struct_to_ast<'a>(
let mut output = Vec::with_capacity_in(field_layouts.len(), &arena); let mut output = Vec::with_capacity_in(field_layouts.len(), &arena);
// The fields, sorted alphabetically // The fields, sorted alphabetically
let sorted_fields = { let mut sorted_fields = {
let mut vec = fields let mut vec = fields
.iter() .iter()
.collect::<std::vec::Vec<(&Lowercase, &RecordField<Variable>)>>(); .collect::<std::vec::Vec<(&Lowercase, &RecordField<Variable>)>>();
@ -349,15 +394,14 @@ fn struct_to_ast<'a>(
vec vec
}; };
debug_assert_eq!(sorted_fields.len(), field_layouts.len()); if sorted_fields.len() == 1 {
// this is a 1-field wrapper record around another record or 1-tag tag union
let (label, field) = sorted_fields.pop().unwrap();
// We'll advance this as we iterate through the fields let inner_content = env.subs.get_without_compacting(field.into_inner()).content;
let mut field_ptr = ptr;
for ((label, field), field_layout) in sorted_fields.iter().zip(field_layouts.iter()) {
let content = subs.get_without_compacting(*field.as_inner()).content;
let loc_expr = &*arena.alloc(Located { let loc_expr = &*arena.alloc(Located {
value: ptr_to_ast(env, field_ptr, field_layout, &content), value: ptr_to_ast(env, ptr, &Layout::Struct(field_layouts), &inner_content),
region: Region::zero(), region: Region::zero(),
}); });
@ -370,17 +414,47 @@ fn struct_to_ast<'a>(
region: Region::zero(), region: Region::zero(),
}; };
output.push(loc_field); let output = env.arena.alloc([loc_field]);
// Advance the field pointer to the next field. Expr::Record {
field_ptr = unsafe { field_ptr.offset(field_layout.stack_size(env.ptr_bytes) as isize) }; update: None,
} fields: output,
}
} else {
debug_assert_eq!(sorted_fields.len(), field_layouts.len());
let output = output.into_bump_slice(); // We'll advance this as we iterate through the fields
let mut field_ptr = ptr;
Expr::Record { for ((label, field), field_layout) in sorted_fields.iter().zip(field_layouts.iter()) {
update: None, let content = subs.get_without_compacting(*field.as_inner()).content;
fields: output, let loc_expr = &*arena.alloc(Located {
value: ptr_to_ast(env, field_ptr, field_layout, &content),
region: Region::zero(),
});
let field_name = Located {
value: &*arena.alloc_str(label.as_str()),
region: Region::zero(),
};
let loc_field = Located {
value: AssignedField::RequiredValue(field_name, &[], loc_expr),
region: Region::zero(),
};
output.push(loc_field);
// Advance the field pointer to the next field.
field_ptr =
unsafe { field_ptr.offset(field_layout.stack_size(env.ptr_bytes) as isize) };
}
let output = output.into_bump_slice();
Expr::Record {
update: None,
fields: output,
}
} }
} }
@ -468,20 +542,17 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
debug_assert!(payload_vars_1.is_empty()); debug_assert!(payload_vars_1.is_empty());
debug_assert!(payload_vars_2.is_empty()); debug_assert!(payload_vars_2.is_empty());
let tag_name_as_str_1 = &tag_name_1.as_string(env.interns, env.home);
let tag_name_as_str_2 = &tag_name_2.as_string(env.interns, env.home);
let tag_name = if value { let tag_name = if value {
tag_name_as_str_1.max(tag_name_as_str_2) max_by_key(tag_name_1, tag_name_2, |n| {
n.as_string(env.interns, env.home)
})
} else { } else {
tag_name_as_str_1.min(tag_name_as_str_2) min_by_key(tag_name_1, tag_name_2, |n| {
n.as_string(env.interns, env.home)
})
}; };
if tag_name.starts_with('@') { tag_name_to_expr(env, tag_name)
Expr::PrivateTag(arena.alloc_str(tag_name))
} else {
Expr::GlobalTag(arena.alloc_str(tag_name))
}
} }
other => { other => {
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
@ -499,6 +570,117 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
} }
} }
fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> {
use Content::*;
let arena = env.arena;
match content {
Structure(flat_type) => {
match flat_type {
FlatType::Record(fields, _) => {
debug_assert_eq!(fields.len(), 1);
let (label, field) = fields.iter().next().unwrap();
let loc_label = Located {
value: &*arena.alloc_str(label.as_str()),
region: Region::zero(),
};
let assigned_field = {
// We may be multiple levels deep in nested tag unions
// and/or records (e.g. { a: { b: { c: True } } }),
// so we need to do this recursively on the field type.
let field_var = *field.as_inner();
let field_content = env.subs.get_without_compacting(field_var).content;
let loc_expr = Located {
value: byte_to_ast(env, value, &field_content),
region: Region::zero(),
};
AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr))
};
let loc_assigned_field = Located {
value: assigned_field,
region: Region::zero(),
};
Expr::Record {
update: None,
fields: arena.alloc([loc_assigned_field]),
}
}
FlatType::TagUnion(tags, _) if tags.len() == 1 => {
let (tag_name, payload_vars) = tags.iter().next().unwrap();
let loc_tag_expr = {
let tag_name = &tag_name.as_string(env.interns, env.home);
let tag_expr = if tag_name.starts_with('@') {
Expr::PrivateTag(arena.alloc_str(tag_name))
} else {
Expr::GlobalTag(arena.alloc_str(tag_name))
};
&*arena.alloc(Located {
value: tag_expr,
region: Region::zero(),
})
};
let payload = {
// Since this has the layout of a number, there should be
// exactly one payload in this tag.
debug_assert_eq!(payload_vars.len(), 1);
let var = *payload_vars.iter().next().unwrap();
let content = env.subs.get_without_compacting(var).content;
let loc_payload = &*arena.alloc(Located {
value: byte_to_ast(env, value, &content),
region: Region::zero(),
});
arena.alloc([loc_payload])
};
Expr::Apply(loc_tag_expr, payload, CalledVia::Space)
}
FlatType::TagUnion(tags, _) => {
// anything with fewer tags is not a byte
debug_assert!(tags.len() > 2);
let tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)> =
tags.iter().map(|(a, b)| (a.clone(), b.clone())).collect();
let union_variant = union_sorted_tags_help(env.arena, tags_vec, None, env.subs);
match union_variant {
UnionVariant::ByteUnion(tagnames) => {
let tag_name = &tagnames[value as usize];
let tag_expr = tag_name_to_expr(env, tag_name);
let loc_tag_expr = Located::at_zero(tag_expr);
Expr::Apply(env.arena.alloc(loc_tag_expr), &[], CalledVia::Space)
}
_ => unreachable!("invalid union variant for a Byte!"),
}
}
other => {
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
}
}
}
Alias(_, _, var) => {
let content = env.subs.get_without_compacting(*var).content;
byte_to_ast(env, value, &content)
}
other => {
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
}
}
}
fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> Expr<'a> { fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> Expr<'a> {
use Content::*; use Content::*;
@ -648,3 +830,30 @@ fn str_slice_to_ast<'a>(_arena: &'a Bump, string: &'a str) -> Expr<'a> {
Expr::Str(StrLiteral::PlainLine(string)) Expr::Str(StrLiteral::PlainLine(string))
} }
} }
// TODO this is currently nighly-only: use the implementation in std once it's stabilized
pub fn max_by<T, F: FnOnce(&T, &T) -> std::cmp::Ordering>(v1: T, v2: T, compare: F) -> T {
use std::cmp::Ordering;
match compare(&v1, &v2) {
Ordering::Less | Ordering::Equal => v2,
Ordering::Greater => v1,
}
}
pub fn min_by<T, F: FnOnce(&T, &T) -> std::cmp::Ordering>(v1: T, v2: T, compare: F) -> T {
use std::cmp::Ordering;
match compare(&v1, &v2) {
Ordering::Less | Ordering::Equal => v1,
Ordering::Greater => v2,
}
}
pub fn max_by_key<T, F: FnMut(&T) -> K, K: Ord>(v1: T, v2: T, mut f: F) -> T {
max_by(v1, v2, |v1, v2| f(v1).cmp(&f(v2)))
}
pub fn min_by_key<T, F: FnMut(&T) -> K, K: Ord>(v1: T, v2: T, mut f: F) -> T {
min_by(v1, v2, |v1, v2| f(v1).cmp(&f(v2)))
}

View file

@ -90,12 +90,15 @@ mod repl_eval {
#[test] #[test]
fn bool_in_record() { fn bool_in_record() {
expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }"); expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }");
expect_success(
"{ z: { y: { x: 1 == 1 } } }",
"{ z: { y: { x: True } } } : { z : { y : { x : Bool } } }",
);
expect_success("{ x: 1 != 1 }", "{ x: False } : { x : Bool }"); expect_success("{ x: 1 != 1 }", "{ x: False } : { x : Bool }");
// TODO: see ptr_to_ast expect_success(
// expect_success( "{ x: 1 == 1, y: 1 != 1 }",
// "{ x: 1 == 1, y: 1 != 1 }", "{ x: True, y: False } : { x : Bool, y : Bool }",
// "{ x: True, y: False } : { x : Bool, y : Bool }", );
// );
} }
#[test] #[test]
@ -116,14 +119,46 @@ mod repl_eval {
expect_success("False", "False : [ False ]*"); expect_success("False", "False : [ False ]*");
} }
#[test]
fn byte_tag_union() {
expect_success(
"if 1 == 1 then Red else if 1 == 1 then Green else Blue",
"Red : [ Blue, Green, Red ]*",
);
expect_success(
"{ y: { x: if 1 == 1 then Red else if 1 == 1 then Green else Blue } }",
"{ y: { x: Red } } : { y : { x : [ Blue, Green, Red ]* } }",
);
}
#[test]
fn tag_in_record() {
expect_success(
"{ x: Foo 1 2 3, y : 4 }",
"{ x: Foo 1 2 3, y: 4 } : { x : [ Foo (Num *) (Num *) (Num *) ]*, y : Num * }",
);
expect_success(
"{ x: Foo 1 2 3 }",
"{ x: Foo 1 2 3 } : { x : [ Foo (Num *) (Num *) (Num *) ]* }",
);
expect_success("{ x: Unit }", "{ x: Unit } : { x : [ Unit ]* }");
}
#[test]
fn single_element_tag_union() {
expect_success("True 1", "True 1 : [ True (Num *) ]*");
expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) Float ]*");
}
#[test] #[test]
fn tag_with_arguments() { fn tag_with_arguments() {
expect_success("True 1", "True 1 : [ True (Num *) ]*"); expect_success("True 1", "True 1 : [ True (Num *) ]*");
// TODO handle more situations
// expect_success( expect_success(
// "if 1 == 1 then True 1 else False 3.14", "if 1 == 1 then True 3 else False 3.14",
// "True 1 : [ True (Num *), False Float ]*", "True 3 : [ False Float, True (Num *) ]*",
// ) )
} }
#[test] #[test]
@ -273,32 +308,29 @@ mod repl_eval {
); );
} }
// TODO uncomment this once https://github.com/rtfeldman/roc/issues/295 is done #[test]
// #[test] fn basic_2_field_f64_record() {
// fn basic_2_field_f64_record() { expect_success(
// expect_success( "{ foo: 4.1, bar: 2.3 }",
// "{ foo: 4.1, bar: 2.3 }", "{ bar: 2.3, foo: 4.1 } : { bar : Float, foo : Float }",
// "{ bar: 2.3, foo: 4.1 } : { bar : Float, foo : Float }", );
// ); }
// }
// #[test] #[test]
// fn basic_2_field_mixed_record() { fn basic_2_field_mixed_record() {
// expect_success( expect_success(
// "{ foo: 4.1, bar: 2 }", "{ foo: 4.1, bar: 2 }",
// "{ bar: 2, foo: 4.1 } : { bar : Num *, foo : Float }", "{ bar: 2, foo: 4.1 } : { bar : Num *, foo : Float }",
// ); );
// } }
// TODO uncomment this once https://github.com/rtfeldman/roc/issues/295 is done #[test]
// fn basic_3_field_record() {
// #[test] expect_success(
// fn basic_3_field_record() { "{ foo: 4.1, bar: 2, baz: 0x5 }",
// expect_success( "{ bar: 2, baz: 5, foo: 4.1 } : { bar : Num *, baz : Int, foo : Float }",
// "{ foo: 4.1, bar: 2, baz: 0x5 }", );
// "{ foo: 4.1, bar: 2, baz: 0x5 } : { foo : Float, bar : Num *, baz : Int }", }
// );
// }
#[test] #[test]
fn list_of_1_field_records() { fn list_of_1_field_records() {

View file

@ -820,4 +820,28 @@ mod gen_records {
i64 i64
); );
} }
#[test]
fn booleans_in_record() {
assert_evals_to!(
indoc!("{ x: 1 == 1, y: 1 == 1 }"),
(true, true),
(bool, bool)
);
assert_evals_to!(
indoc!("{ x: 1 != 1, y: 1 == 1 }"),
(false, true),
(bool, bool)
);
assert_evals_to!(
indoc!("{ x: 1 == 1, y: 1 != 1 }"),
(true, false),
(bool, bool)
);
assert_evals_to!(
indoc!("{ x: 1 != 1, y: 1 != 1 }"),
(false, false),
(bool, bool)
);
}
} }

View file

@ -936,7 +936,7 @@ fn get_recursion_var(subs: &Subs, var: Variable) -> Option<Variable> {
} }
} }
fn union_sorted_tags_help<'a>( pub fn union_sorted_tags_help<'a>(
arena: &'a Bump, arena: &'a Bump,
mut tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)>, mut tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)>,
opt_rec_var: Option<Variable>, opt_rec_var: Option<Variable>,

113
shell.nix
View file

@ -16,72 +16,67 @@ with {
with (pkgs); with (pkgs);
let let
darwin-frameworks = darwin-frameworks = if isMacOS then
if isMacOS then with pkgs.darwin.apple_sdk.frameworks; [
with pkgs.darwin.apple_sdk.frameworks; [ AppKit
AppKit CoreFoundation
CoreFoundation CoreServices
CoreServices CoreVideo
CoreVideo Foundation
Foundation Metal
Metal Security
Security ]
] else
else [ ];
[ ];
linux-only = if !isMacOS then [
vulkan-headers
vulkan-loader
vulkan-tools
vulkan-validation-layers
xorg.libX11
xorg.libXcursor
xorg.libXrandr
xorg.libXi
] else
[ ];
llvmPkg = pkgs.llvm_10; llvmPkg = pkgs.llvm_10;
lldPkg = pkgs.lld_10; # this should match llvm's version lldPkg = pkgs.lld_10; # this should match llvm's version
clangPkg = pkgs.clang_10; # this should match llvm's version clangPkg = pkgs.clang_10; # this should match llvm's version
zig = import ./nix/zig.nix { inherit pkgs isMacOS; }; zig = import ./nix/zig.nix { inherit pkgs isMacOS; };
inputs = inputs = [
[ # build libraries
# build libraries rustc
rustc cargo
cargo clippy
clippy rustfmt
rustfmt cmake
cmake git
git python3
python3 llvmPkg
llvmPkg clangPkg
clangPkg valgrind
valgrind pkg-config
pkg-config zig
zig # llb deps
# llb deps libffi
libffi libxml2
libxml2 zlib
xorg.libX11 # faster builds - see https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#use-lld-for-the-linker
zlib lldPkg
vulkan-headers # dev tools
vulkan-loader rust-analyzer
vulkan-tools # (import ./nix/zls.nix { inherit pkgs zig; })
vulkan-validation-layers ccls
# faster builds - see https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#use-lld-for-the-linker ];
lldPkg
# dev tools
rust-analyzer
# (import ./nix/zls.nix { inherit pkgs zig; })
ccls
];
in mkShell { in mkShell {
buildInputs = inputs ++ darwin-frameworks; buildInputs = inputs ++ darwin-frameworks ++ linux-only;
LLVM_SYS_100_PREFIX = "${llvmPkg}"; LLVM_SYS_100_PREFIX = "${llvmPkg}";
APPEND_LIBRARY_PATH = stdenv.lib.makeLibraryPath [ APPEND_LIBRARY_PATH = stdenv.lib.makeLibraryPath
pkgconfig ([ pkgconfig libcxx libcxxabi libunwind ] ++ linux-only);
vulkan-headers
vulkan-loader
vulkan-tools
vulkan-validation-layers
xorg.libX11
xorg.libXcursor
xorg.libXrandr
xorg.libXi
libcxx
libcxxabi
libunwind
];
# Aliases don't work cross shell, so we do this # Aliases don't work cross shell, so we do this
shellHook = '' shellHook = ''