mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 14:54:47 +00:00
Merge branch 'trunk' of github.com:rtfeldman/roc into str-split
This commit is contained in:
commit
c9e3531ecd
29 changed files with 1599 additions and 408 deletions
|
@ -765,6 +765,13 @@ fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mu
|
||||||
used.insert(v);
|
used.insert(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Store(tipe, var, _, _) => {
|
||||||
|
for v in tipe.variables() {
|
||||||
|
used.insert(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
used.insert(*var);
|
||||||
|
}
|
||||||
Lookup(_, expectation, _) => {
|
Lookup(_, expectation, _) => {
|
||||||
for v in expectation.get_type_ref().variables() {
|
for v in expectation.get_type_ref().variables() {
|
||||||
used.insert(v);
|
used.insert(v);
|
||||||
|
|
|
@ -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)))
|
||||||
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -8,6 +8,7 @@ use roc_types::types::{Category, PatternCategory, Type};
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Constraint {
|
pub enum Constraint {
|
||||||
Eq(Type, Expected<Type>, Category, Region),
|
Eq(Type, Expected<Type>, Category, Region),
|
||||||
|
Store(Type, Variable, &'static str, u32),
|
||||||
Lookup(Symbol, Expected<Type>, Region),
|
Lookup(Symbol, Expected<Type>, Region),
|
||||||
Pattern(Region, PatternCategory, Type, PExpected<Type>),
|
Pattern(Region, PatternCategory, Type, PExpected<Type>),
|
||||||
True, // Used for things that always unify, e.g. blanks and runtime errors
|
True, // Used for things that always unify, e.g. blanks and runtime errors
|
||||||
|
|
|
@ -470,14 +470,17 @@ pub fn sort_can_defs(
|
||||||
|
|
||||||
// TODO also do the same `addDirects` check elm/compiler does, so we can
|
// TODO also do the same `addDirects` check elm/compiler does, so we can
|
||||||
// report an error if a recursive definition can't possibly terminate!
|
// report an error if a recursive definition can't possibly terminate!
|
||||||
match topological_sort_into_groups(defined_symbols.as_slice(), all_successors_without_self) {
|
match ven_graph::topological_sort_into_groups(
|
||||||
|
defined_symbols.as_slice(),
|
||||||
|
all_successors_without_self,
|
||||||
|
) {
|
||||||
Ok(groups) => {
|
Ok(groups) => {
|
||||||
let mut declarations = Vec::new();
|
let mut declarations = Vec::new();
|
||||||
|
|
||||||
// groups are in reversed order
|
// groups are in reversed order
|
||||||
for group in groups.into_iter().rev() {
|
for group in groups.into_iter().rev() {
|
||||||
group_to_declaration(
|
group_to_declaration(
|
||||||
group,
|
&group,
|
||||||
&env.closures,
|
&env.closures,
|
||||||
&mut all_successors_with_self,
|
&mut all_successors_with_self,
|
||||||
&can_defs_by_symbol,
|
&can_defs_by_symbol,
|
||||||
|
@ -487,21 +490,10 @@ pub fn sort_can_defs(
|
||||||
|
|
||||||
(Ok(declarations), output)
|
(Ok(declarations), output)
|
||||||
}
|
}
|
||||||
Err((groups, nodes_in_cycle)) => {
|
Err((mut groups, nodes_in_cycle)) => {
|
||||||
let mut declarations = Vec::new();
|
let mut declarations = Vec::new();
|
||||||
let mut problems = Vec::new();
|
let mut problems = Vec::new();
|
||||||
|
|
||||||
// groups are in reversed order
|
|
||||||
for group in groups.into_iter().rev() {
|
|
||||||
group_to_declaration(
|
|
||||||
group,
|
|
||||||
&env.closures,
|
|
||||||
&mut all_successors_with_self,
|
|
||||||
&can_defs_by_symbol,
|
|
||||||
&mut declarations,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// nodes_in_cycle are symbols that form a syntactic cycle. That isn't always a problem,
|
// nodes_in_cycle are symbols that form a syntactic cycle. That isn't always a problem,
|
||||||
// and in general it's impossible to decide whether it is. So we use a crude heuristic:
|
// and in general it's impossible to decide whether it is. So we use a crude heuristic:
|
||||||
//
|
//
|
||||||
|
@ -571,16 +563,61 @@ pub fn sort_can_defs(
|
||||||
declarations.push(Declaration::InvalidCycle(symbols_in_cycle, regions));
|
declarations.push(Declaration::InvalidCycle(symbols_in_cycle, regions));
|
||||||
} else {
|
} else {
|
||||||
// slightly inefficient, because we know this becomes exactly one DeclareRec already
|
// slightly inefficient, because we know this becomes exactly one DeclareRec already
|
||||||
group_to_declaration(
|
groups.push(cycle);
|
||||||
cycle,
|
|
||||||
&env.closures,
|
|
||||||
&mut all_successors_with_self,
|
|
||||||
&can_defs_by_symbol,
|
|
||||||
&mut declarations,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// now we have a collection of groups whose dependencies are not cyclic.
|
||||||
|
// They are however not yet topologically sorted. Here we have to get a bit
|
||||||
|
// creative to get all the definitions in the correct sorted order.
|
||||||
|
|
||||||
|
let mut group_ids = Vec::with_capacity(groups.len());
|
||||||
|
let mut symbol_to_group_index = MutMap::default();
|
||||||
|
for (i, group) in groups.iter().enumerate() {
|
||||||
|
for symbol in group {
|
||||||
|
symbol_to_group_index.insert(*symbol, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
group_ids.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
let successors_of_group = |group_id: &usize| {
|
||||||
|
let mut result = ImSet::default();
|
||||||
|
|
||||||
|
// for each symbol in this group
|
||||||
|
for symbol in &groups[*group_id] {
|
||||||
|
// find its successors
|
||||||
|
for succ in all_successors_without_self(symbol) {
|
||||||
|
// and add its group to the result
|
||||||
|
result.insert(symbol_to_group_index[&succ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't introduce any cycles to self
|
||||||
|
result.remove(group_id);
|
||||||
|
|
||||||
|
result
|
||||||
|
};
|
||||||
|
|
||||||
|
match ven_graph::topological_sort_into_groups(&group_ids, successors_of_group) {
|
||||||
|
Ok(sorted_group_ids) => {
|
||||||
|
for sorted_group in sorted_group_ids.iter().rev() {
|
||||||
|
for group_id in sorted_group.iter().rev() {
|
||||||
|
let group = &groups[*group_id];
|
||||||
|
|
||||||
|
group_to_declaration(
|
||||||
|
group,
|
||||||
|
&env.closures,
|
||||||
|
&mut all_successors_with_self,
|
||||||
|
&can_defs_by_symbol,
|
||||||
|
&mut declarations,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => unreachable!("there should be no cycles now!"),
|
||||||
|
}
|
||||||
|
|
||||||
for problem in problems {
|
for problem in problems {
|
||||||
env.problem(problem);
|
env.problem(problem);
|
||||||
}
|
}
|
||||||
|
@ -591,7 +628,7 @@ pub fn sort_can_defs(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn group_to_declaration(
|
fn group_to_declaration(
|
||||||
group: Vec<Symbol>,
|
group: &[Symbol],
|
||||||
closures: &MutMap<Symbol, References>,
|
closures: &MutMap<Symbol, References>,
|
||||||
successors: &mut dyn FnMut(&Symbol) -> ImSet<Symbol>,
|
successors: &mut dyn FnMut(&Symbol) -> ImSet<Symbol>,
|
||||||
can_defs_by_symbol: &MutMap<Symbol, Def>,
|
can_defs_by_symbol: &MutMap<Symbol, Def>,
|
||||||
|
|
|
@ -725,10 +725,11 @@ pub fn canonicalize_expr<'a>(
|
||||||
}
|
}
|
||||||
ast::Expr::MalformedIdent(name) => {
|
ast::Expr::MalformedIdent(name) => {
|
||||||
use roc_problem::can::RuntimeError::*;
|
use roc_problem::can::RuntimeError::*;
|
||||||
(
|
|
||||||
RuntimeError(MalformedIdentifier((*name).into(), region)),
|
let problem = MalformedIdentifier((*name).into(), region);
|
||||||
Output::default(),
|
env.problem(Problem::RuntimeError(problem.clone()));
|
||||||
)
|
|
||||||
|
(RuntimeError(problem), Output::default())
|
||||||
}
|
}
|
||||||
ast::Expr::Nested(sub_expr) => {
|
ast::Expr::Nested(sub_expr) => {
|
||||||
let (answer, output) = canonicalize_expr(env, var_store, scope, region, sub_expr);
|
let (answer, output) = canonicalize_expr(env, var_store, scope, region, sub_expr);
|
||||||
|
|
|
@ -55,6 +55,40 @@ pub struct Env {
|
||||||
pub home: ModuleId,
|
pub home: ModuleId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn constrain_untyped_args(
|
||||||
|
env: &Env,
|
||||||
|
arguments: &[(Variable, Located<Pattern>)],
|
||||||
|
closure_type: Type,
|
||||||
|
return_type: Type,
|
||||||
|
) -> (Vec<Variable>, PatternState, Type) {
|
||||||
|
let mut vars = Vec::with_capacity(arguments.len());
|
||||||
|
let mut pattern_types = Vec::with_capacity(arguments.len());
|
||||||
|
|
||||||
|
let mut pattern_state = PatternState::default();
|
||||||
|
|
||||||
|
for (pattern_var, loc_pattern) in arguments {
|
||||||
|
let pattern_type = Type::Variable(*pattern_var);
|
||||||
|
let pattern_expected = PExpected::NoExpectation(pattern_type.clone());
|
||||||
|
|
||||||
|
pattern_types.push(pattern_type);
|
||||||
|
|
||||||
|
constrain_pattern(
|
||||||
|
env,
|
||||||
|
&loc_pattern.value,
|
||||||
|
loc_pattern.region,
|
||||||
|
pattern_expected,
|
||||||
|
&mut pattern_state,
|
||||||
|
);
|
||||||
|
|
||||||
|
vars.push(*pattern_var);
|
||||||
|
}
|
||||||
|
|
||||||
|
let function_type =
|
||||||
|
Type::Function(pattern_types, Box::new(closure_type), Box::new(return_type));
|
||||||
|
|
||||||
|
(vars, pattern_state, function_type)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn constrain_expr(
|
pub fn constrain_expr(
|
||||||
env: &Env,
|
env: &Env,
|
||||||
region: Region,
|
region: Region,
|
||||||
|
@ -313,46 +347,25 @@ pub fn constrain_expr(
|
||||||
} => {
|
} => {
|
||||||
// NOTE defs are treated somewhere else!
|
// NOTE defs are treated somewhere else!
|
||||||
let loc_body_expr = &**boxed;
|
let loc_body_expr = &**boxed;
|
||||||
let mut state = PatternState {
|
|
||||||
headers: SendMap::default(),
|
|
||||||
vars: Vec::with_capacity(arguments.len()),
|
|
||||||
constraints: Vec::with_capacity(1),
|
|
||||||
};
|
|
||||||
let mut vars = Vec::with_capacity(state.vars.capacity() + 1);
|
|
||||||
let mut pattern_types = Vec::with_capacity(state.vars.capacity());
|
|
||||||
let ret_var = *ret_var;
|
let ret_var = *ret_var;
|
||||||
let closure_var = *closure_var;
|
let closure_var = *closure_var;
|
||||||
let closure_ext_var = *closure_ext_var;
|
let closure_ext_var = *closure_ext_var;
|
||||||
let ret_type = Type::Variable(ret_var);
|
|
||||||
|
let closure_type = Type::Variable(closure_var);
|
||||||
|
let return_type = Type::Variable(ret_var);
|
||||||
|
let (mut vars, pattern_state, function_type) =
|
||||||
|
constrain_untyped_args(env, arguments, closure_type, return_type.clone());
|
||||||
|
|
||||||
vars.push(ret_var);
|
vars.push(ret_var);
|
||||||
vars.push(closure_var);
|
vars.push(closure_var);
|
||||||
vars.push(closure_ext_var);
|
vars.push(closure_ext_var);
|
||||||
|
vars.push(*fn_var);
|
||||||
|
|
||||||
for (pattern_var, loc_pattern) in arguments {
|
let body_type = NoExpectation(return_type);
|
||||||
let pattern_type = Type::Variable(*pattern_var);
|
|
||||||
let pattern_expected = PExpected::NoExpectation(pattern_type.clone());
|
|
||||||
|
|
||||||
pattern_types.push(pattern_type);
|
|
||||||
|
|
||||||
constrain_pattern(
|
|
||||||
env,
|
|
||||||
&loc_pattern.value,
|
|
||||||
loc_pattern.region,
|
|
||||||
pattern_expected,
|
|
||||||
&mut state,
|
|
||||||
);
|
|
||||||
|
|
||||||
vars.push(*pattern_var);
|
|
||||||
}
|
|
||||||
|
|
||||||
let body_type = NoExpectation(ret_type.clone());
|
|
||||||
let ret_constraint =
|
let ret_constraint =
|
||||||
constrain_expr(env, loc_body_expr.region, &loc_body_expr.value, body_type);
|
constrain_expr(env, loc_body_expr.region, &loc_body_expr.value, body_type);
|
||||||
|
|
||||||
vars.push(*fn_var);
|
|
||||||
let defs_constraint = And(state.constraints);
|
|
||||||
|
|
||||||
// make sure the captured symbols are sorted!
|
// make sure the captured symbols are sorted!
|
||||||
debug_assert_eq!(captured_symbols.clone(), {
|
debug_assert_eq!(captured_symbols.clone(), {
|
||||||
let mut copy = captured_symbols.clone();
|
let mut copy = captured_symbols.clone();
|
||||||
|
@ -369,28 +382,22 @@ pub fn constrain_expr(
|
||||||
&mut vars,
|
&mut vars,
|
||||||
);
|
);
|
||||||
|
|
||||||
let fn_type = Type::Function(
|
|
||||||
pattern_types,
|
|
||||||
Box::new(Type::Variable(closure_var)),
|
|
||||||
Box::new(ret_type),
|
|
||||||
);
|
|
||||||
|
|
||||||
exists(
|
exists(
|
||||||
vars,
|
vars,
|
||||||
And(vec![
|
And(vec![
|
||||||
Let(Box::new(LetConstraint {
|
Let(Box::new(LetConstraint {
|
||||||
rigid_vars: Vec::new(),
|
rigid_vars: Vec::new(),
|
||||||
flex_vars: state.vars,
|
flex_vars: pattern_state.vars,
|
||||||
def_types: state.headers,
|
def_types: pattern_state.headers,
|
||||||
defs_constraint,
|
defs_constraint: And(pattern_state.constraints),
|
||||||
ret_constraint,
|
ret_constraint,
|
||||||
})),
|
})),
|
||||||
// "the closure's type is equal to expected type"
|
// "the closure's type is equal to expected type"
|
||||||
Eq(fn_type.clone(), expected, Category::Lambda, region),
|
Eq(function_type.clone(), expected, Category::Lambda, region),
|
||||||
// "fn_var is equal to the closure's type" - fn_var is used in code gen
|
// "fn_var is equal to the closure's type" - fn_var is used in code gen
|
||||||
Eq(
|
Eq(
|
||||||
Type::Variable(*fn_var),
|
Type::Variable(*fn_var),
|
||||||
NoExpectation(fn_type),
|
NoExpectation(function_type),
|
||||||
Category::Storage(std::file!(), std::line!()),
|
Category::Storage(std::file!(), std::line!()),
|
||||||
region,
|
region,
|
||||||
),
|
),
|
||||||
|
@ -1052,9 +1059,9 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
||||||
let expr_var = def.expr_var;
|
let expr_var = def.expr_var;
|
||||||
let expr_type = Type::Variable(expr_var);
|
let expr_type = Type::Variable(expr_var);
|
||||||
|
|
||||||
let mut pattern_state = constrain_def_pattern(env, &def.loc_pattern, expr_type.clone());
|
let mut def_pattern_state = constrain_def_pattern(env, &def.loc_pattern, expr_type.clone());
|
||||||
|
|
||||||
pattern_state.vars.push(expr_var);
|
def_pattern_state.vars.push(expr_var);
|
||||||
|
|
||||||
let mut new_rigids = Vec::new();
|
let mut new_rigids = Vec::new();
|
||||||
|
|
||||||
|
@ -1070,7 +1077,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
||||||
&mut new_rigids,
|
&mut new_rigids,
|
||||||
&mut ftv,
|
&mut ftv,
|
||||||
&def.loc_pattern,
|
&def.loc_pattern,
|
||||||
&mut pattern_state.headers,
|
&mut def_pattern_state.headers,
|
||||||
);
|
);
|
||||||
|
|
||||||
let env = &Env {
|
let env = &Env {
|
||||||
|
@ -1087,7 +1094,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
||||||
signature.clone(),
|
signature.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
pattern_state.constraints.push(Eq(
|
def_pattern_state.constraints.push(Eq(
|
||||||
expr_type,
|
expr_type,
|
||||||
annotation_expected.clone(),
|
annotation_expected.clone(),
|
||||||
Category::Storage(std::file!(), std::line!()),
|
Category::Storage(std::file!(), std::line!()),
|
||||||
|
@ -1165,19 +1172,19 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// record the correct type in pattern_var
|
// NOTE: because we perform an equality with part of the signature
|
||||||
let pattern_type = Type::Variable(*pattern_var);
|
// this constraint must be to the def_pattern_state's constraints
|
||||||
pattern_types.push(pattern_type.clone());
|
def_pattern_state.vars.push(*pattern_var);
|
||||||
|
pattern_types.push(Type::Variable(*pattern_var));
|
||||||
|
|
||||||
state.vars.push(*pattern_var);
|
let pattern_con = Eq(
|
||||||
state.constraints.push(Constraint::Eq(
|
Type::Variable(*pattern_var),
|
||||||
pattern_type.clone(),
|
|
||||||
Expected::NoExpectation(loc_ann.clone()),
|
Expected::NoExpectation(loc_ann.clone()),
|
||||||
Category::Storage(std::file!(), std::line!()),
|
Category::Storage(std::file!(), std::line!()),
|
||||||
loc_pattern.region,
|
loc_pattern.region,
|
||||||
));
|
);
|
||||||
|
|
||||||
vars.push(*pattern_var);
|
def_pattern_state.constraints.push(pattern_con);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1213,14 +1220,10 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
||||||
ret_constraint,
|
ret_constraint,
|
||||||
})),
|
})),
|
||||||
// "the closure's type is equal to expected type"
|
// "the closure's type is equal to expected type"
|
||||||
Eq(fn_type.clone(), expected, Category::Lambda, region),
|
Eq(fn_type, expected, Category::Lambda, region),
|
||||||
// "fn_var is equal to the closure's type" - fn_var is used in code gen
|
// Store type into AST vars. We use Store so errors aren't reported twice
|
||||||
Eq(
|
Store(signature.clone(), *fn_var, std::file!(), std::line!()),
|
||||||
Type::Variable(*fn_var),
|
Store(signature, expr_var, std::file!(), std::line!()),
|
||||||
NoExpectation(fn_type),
|
|
||||||
Category::Storage(std::file!(), std::line!()),
|
|
||||||
region,
|
|
||||||
),
|
|
||||||
closure_constraint,
|
closure_constraint,
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
|
@ -1248,13 +1251,13 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
||||||
|
|
||||||
Let(Box::new(LetConstraint {
|
Let(Box::new(LetConstraint {
|
||||||
rigid_vars: new_rigids,
|
rigid_vars: new_rigids,
|
||||||
flex_vars: pattern_state.vars,
|
flex_vars: def_pattern_state.vars,
|
||||||
def_types: pattern_state.headers,
|
def_types: def_pattern_state.headers,
|
||||||
defs_constraint: Let(Box::new(LetConstraint {
|
defs_constraint: Let(Box::new(LetConstraint {
|
||||||
rigid_vars: Vec::new(), // always empty
|
rigid_vars: Vec::new(), // always empty
|
||||||
flex_vars: Vec::new(), // empty, because our functions have no arguments
|
flex_vars: Vec::new(), // empty, because our functions have no arguments
|
||||||
def_types: SendMap::default(), // empty, because our functions have no arguments!
|
def_types: SendMap::default(), // empty, because our functions have no arguments!
|
||||||
defs_constraint: And(pattern_state.constraints),
|
defs_constraint: And(def_pattern_state.constraints),
|
||||||
ret_constraint: expr_con,
|
ret_constraint: expr_con,
|
||||||
})),
|
})),
|
||||||
ret_constraint: body_con,
|
ret_constraint: body_con,
|
||||||
|
@ -1369,23 +1372,9 @@ pub fn rec_defs_help(
|
||||||
let expr_var = def.expr_var;
|
let expr_var = def.expr_var;
|
||||||
let expr_type = Type::Variable(expr_var);
|
let expr_type = Type::Variable(expr_var);
|
||||||
|
|
||||||
let pattern_expected = PExpected::NoExpectation(expr_type.clone());
|
let mut def_pattern_state = constrain_def_pattern(env, &def.loc_pattern, expr_type.clone());
|
||||||
|
|
||||||
let mut pattern_state = PatternState {
|
def_pattern_state.vars.push(expr_var);
|
||||||
headers: SendMap::default(),
|
|
||||||
vars: flex_info.vars.clone(),
|
|
||||||
constraints: Vec::with_capacity(1),
|
|
||||||
};
|
|
||||||
|
|
||||||
constrain_pattern(
|
|
||||||
env,
|
|
||||||
&def.loc_pattern.value,
|
|
||||||
def.loc_pattern.region,
|
|
||||||
pattern_expected,
|
|
||||||
&mut pattern_state,
|
|
||||||
);
|
|
||||||
|
|
||||||
pattern_state.vars.push(expr_var);
|
|
||||||
|
|
||||||
let mut new_rigids = Vec::new();
|
let mut new_rigids = Vec::new();
|
||||||
match &def.annotation {
|
match &def.annotation {
|
||||||
|
@ -1406,9 +1395,9 @@ pub fn rec_defs_help(
|
||||||
ret_constraint: expr_con,
|
ret_constraint: expr_con,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
flex_info.vars = pattern_state.vars;
|
flex_info.vars = def_pattern_state.vars;
|
||||||
flex_info.constraints.push(def_con);
|
flex_info.constraints.push(def_con);
|
||||||
flex_info.def_types.extend(pattern_state.headers);
|
flex_info.def_types.extend(def_pattern_state.headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(annotation) => {
|
Some(annotation) => {
|
||||||
|
@ -1421,7 +1410,7 @@ pub fn rec_defs_help(
|
||||||
&mut new_rigids,
|
&mut new_rigids,
|
||||||
&mut ftv,
|
&mut ftv,
|
||||||
&def.loc_pattern,
|
&def.loc_pattern,
|
||||||
&mut pattern_state.headers,
|
&mut def_pattern_state.headers,
|
||||||
);
|
);
|
||||||
|
|
||||||
let annotation_expected = FromAnnotation(
|
let annotation_expected = FromAnnotation(
|
||||||
|
@ -1433,43 +1422,149 @@ pub fn rec_defs_help(
|
||||||
signature.clone(),
|
signature.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let expr_con = constrain_expr(
|
// when a def is annotated, and it's body is a closure, treat this
|
||||||
&Env {
|
// as a named function (in elm terms) for error messages.
|
||||||
rigids: ftv,
|
//
|
||||||
home: env.home,
|
// This means we get errors like "the first argument of `f` is weird"
|
||||||
},
|
// instead of the more generic "something is wrong with the body of `f`"
|
||||||
def.loc_expr.region,
|
match (&def.loc_expr.value, &signature) {
|
||||||
&def.loc_expr.value,
|
(
|
||||||
annotation_expected.clone(),
|
Closure {
|
||||||
);
|
function_type: fn_var,
|
||||||
|
closure_type: closure_var,
|
||||||
|
closure_ext_var,
|
||||||
|
return_type: ret_var,
|
||||||
|
captured_symbols,
|
||||||
|
arguments,
|
||||||
|
loc_body,
|
||||||
|
name,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
Type::Function(arg_types, _, _),
|
||||||
|
) => {
|
||||||
|
let expected = annotation_expected;
|
||||||
|
let region = def.loc_expr.region;
|
||||||
|
|
||||||
// ensure expected type unifies with annotated type
|
let loc_body_expr = &**loc_body;
|
||||||
let storage_con = Eq(
|
let mut state = PatternState {
|
||||||
expr_type,
|
headers: SendMap::default(),
|
||||||
annotation_expected.clone(),
|
vars: Vec::with_capacity(arguments.len()),
|
||||||
Category::Storage(std::file!(), std::line!()),
|
constraints: Vec::with_capacity(1),
|
||||||
def.loc_expr.region,
|
};
|
||||||
);
|
let mut vars = Vec::with_capacity(state.vars.capacity() + 1);
|
||||||
|
let mut pattern_types = Vec::with_capacity(state.vars.capacity());
|
||||||
|
let ret_var = *ret_var;
|
||||||
|
let closure_var = *closure_var;
|
||||||
|
let closure_ext_var = *closure_ext_var;
|
||||||
|
let ret_type = Type::Variable(ret_var);
|
||||||
|
|
||||||
// TODO investigate if this let can be safely removed
|
vars.push(ret_var);
|
||||||
let def_con = Let(Box::new(LetConstraint {
|
vars.push(closure_var);
|
||||||
rigid_vars: Vec::new(),
|
vars.push(closure_ext_var);
|
||||||
flex_vars: Vec::new(), // empty because Roc function defs have no args
|
|
||||||
def_types: SendMap::default(), // empty because Roc function defs have no args
|
|
||||||
defs_constraint: storage_con,
|
|
||||||
ret_constraint: expr_con,
|
|
||||||
}));
|
|
||||||
|
|
||||||
rigid_info.vars.extend(&new_rigids);
|
let it = arguments.iter().zip(arg_types.iter()).enumerate();
|
||||||
|
for (index, ((pattern_var, loc_pattern), loc_ann)) in it {
|
||||||
|
{
|
||||||
|
// ensure type matches the one in the annotation
|
||||||
|
let opt_label =
|
||||||
|
if let Pattern::Identifier(label) = def.loc_pattern.value {
|
||||||
|
Some(label)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let pattern_type: &Type = loc_ann;
|
||||||
|
|
||||||
rigid_info.constraints.push(Let(Box::new(LetConstraint {
|
let pattern_expected = PExpected::ForReason(
|
||||||
rigid_vars: new_rigids,
|
PReason::TypedArg {
|
||||||
flex_vars: pattern_state.vars,
|
index: Index::zero_based(index),
|
||||||
def_types: SendMap::default(), // no headers introduced (at this level)
|
opt_name: opt_label,
|
||||||
defs_constraint: def_con,
|
},
|
||||||
ret_constraint: True,
|
pattern_type.clone(),
|
||||||
})));
|
loc_pattern.region,
|
||||||
rigid_info.def_types.extend(pattern_state.headers);
|
);
|
||||||
|
|
||||||
|
constrain_pattern(
|
||||||
|
env,
|
||||||
|
&loc_pattern.value,
|
||||||
|
loc_pattern.region,
|
||||||
|
pattern_expected,
|
||||||
|
&mut state,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// NOTE: because we perform an equality with part of the signature
|
||||||
|
// this constraint must be to the def_pattern_state's constraints
|
||||||
|
def_pattern_state.vars.push(*pattern_var);
|
||||||
|
pattern_types.push(Type::Variable(*pattern_var));
|
||||||
|
|
||||||
|
let pattern_con = Eq(
|
||||||
|
Type::Variable(*pattern_var),
|
||||||
|
Expected::NoExpectation(loc_ann.clone()),
|
||||||
|
Category::Storage(std::file!(), std::line!()),
|
||||||
|
loc_pattern.region,
|
||||||
|
);
|
||||||
|
|
||||||
|
def_pattern_state.constraints.push(pattern_con);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let closure_constraint = constrain_closure_size(
|
||||||
|
*name,
|
||||||
|
region,
|
||||||
|
captured_symbols,
|
||||||
|
closure_var,
|
||||||
|
closure_ext_var,
|
||||||
|
&mut vars,
|
||||||
|
);
|
||||||
|
|
||||||
|
let fn_type = Type::Function(
|
||||||
|
pattern_types,
|
||||||
|
Box::new(Type::Variable(closure_var)),
|
||||||
|
Box::new(ret_type.clone()),
|
||||||
|
);
|
||||||
|
let body_type = NoExpectation(ret_type);
|
||||||
|
let expr_con = constrain_expr(
|
||||||
|
env,
|
||||||
|
loc_body_expr.region,
|
||||||
|
&loc_body_expr.value,
|
||||||
|
body_type,
|
||||||
|
);
|
||||||
|
|
||||||
|
vars.push(*fn_var);
|
||||||
|
|
||||||
|
let def_con = exists(
|
||||||
|
vars,
|
||||||
|
And(vec![
|
||||||
|
Let(Box::new(LetConstraint {
|
||||||
|
rigid_vars: Vec::new(),
|
||||||
|
flex_vars: state.vars,
|
||||||
|
def_types: state.headers,
|
||||||
|
defs_constraint: And(state.constraints),
|
||||||
|
ret_constraint: expr_con,
|
||||||
|
})),
|
||||||
|
Eq(fn_type.clone(), expected.clone(), Category::Lambda, region),
|
||||||
|
// "fn_var is equal to the closure's type" - fn_var is used in code gen
|
||||||
|
// Store type into AST vars. We use Store so errors aren't reported twice
|
||||||
|
Store(signature.clone(), *fn_var, std::file!(), std::line!()),
|
||||||
|
Store(signature, expr_var, std::file!(), std::line!()),
|
||||||
|
closure_constraint,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
rigid_info.vars.extend(&new_rigids);
|
||||||
|
|
||||||
|
rigid_info.constraints.push(Let(Box::new(LetConstraint {
|
||||||
|
rigid_vars: new_rigids,
|
||||||
|
flex_vars: def_pattern_state.vars,
|
||||||
|
def_types: SendMap::default(), // no headers introduced (at this level)
|
||||||
|
defs_constraint: def_con,
|
||||||
|
ret_constraint: True,
|
||||||
|
})));
|
||||||
|
rigid_info.def_types.extend(def_pattern_state.headers);
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ use roc_region::all::{Located, Region};
|
||||||
use roc_types::subs::Variable;
|
use roc_types::subs::Variable;
|
||||||
use roc_types::types::{Category, PReason, PatternCategory, Reason, RecordField, Type};
|
use roc_types::types::{Category, PReason, PatternCategory, Reason, RecordField, Type};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
pub struct PatternState {
|
pub struct PatternState {
|
||||||
pub headers: SendMap<Symbol, Located<Type>>,
|
pub headers: SendMap<Symbol, Located<Type>>,
|
||||||
pub vars: Vec<Variable>,
|
pub vars: Vec<Variable>,
|
||||||
|
|
|
@ -1369,7 +1369,10 @@ pub fn load_symbol<'a, 'ctx, 'env>(
|
||||||
Some((_, ptr)) => env
|
Some((_, ptr)) => env
|
||||||
.builder
|
.builder
|
||||||
.build_load(*ptr, symbol.ident_string(&env.interns)),
|
.build_load(*ptr, symbol.ident_string(&env.interns)),
|
||||||
None => panic!("There was no entry for {:?} in scope {:?}", symbol, scope),
|
None => panic!(
|
||||||
|
"There was no entry for {:?} {} in scope {:?}",
|
||||||
|
symbol, symbol, scope
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1148,7 +1148,7 @@ mod gen_primitives {
|
||||||
|
|
||||||
ConsList a : [ Cons a (ConsList a), Nil ]
|
ConsList a : [ Cons a (ConsList a), Nil ]
|
||||||
|
|
||||||
# isEmpty : ConsList a -> Bool
|
isEmpty : ConsList a -> Bool
|
||||||
isEmpty = \list ->
|
isEmpty = \list ->
|
||||||
when list is
|
when list is
|
||||||
Cons _ _ ->
|
Cons _ _ ->
|
||||||
|
@ -1195,4 +1195,167 @@ mod gen_primitives {
|
||||||
i64
|
i64
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn rbtree_insert() {
|
||||||
|
assert_non_opt_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
app Test provides [ main ] imports []
|
||||||
|
|
||||||
|
NodeColor : [ Red, Black ]
|
||||||
|
|
||||||
|
Dict k v : [ Node NodeColor k v (Dict k v) (Dict k v), Empty ]
|
||||||
|
|
||||||
|
Key k : Num k
|
||||||
|
|
||||||
|
insert : Key k, v, Dict (Key k) v -> Dict (Key k) v
|
||||||
|
insert = \key, value, dict ->
|
||||||
|
when insertHelp key value dict is
|
||||||
|
Node Red k v l r ->
|
||||||
|
Node Black k v l r
|
||||||
|
|
||||||
|
x ->
|
||||||
|
x
|
||||||
|
|
||||||
|
insertHelp : (Key k), v, Dict (Key k) v -> Dict (Key k) v
|
||||||
|
insertHelp = \key, value, dict ->
|
||||||
|
when dict is
|
||||||
|
Empty ->
|
||||||
|
# New nodes are always red. If it violates the rules, it will be fixed
|
||||||
|
# when balancing.
|
||||||
|
Node Red key value Empty Empty
|
||||||
|
|
||||||
|
Node nColor nKey nValue nLeft nRight ->
|
||||||
|
when Num.compare key nKey is
|
||||||
|
LT ->
|
||||||
|
balance nColor nKey nValue (insertHelp key value nLeft) nRight
|
||||||
|
|
||||||
|
EQ ->
|
||||||
|
Node nColor nKey value nLeft nRight
|
||||||
|
|
||||||
|
GT ->
|
||||||
|
balance nColor nKey nValue nLeft (insertHelp key value nRight)
|
||||||
|
|
||||||
|
balance : NodeColor, k, v, Dict k v, Dict k v -> Dict k v
|
||||||
|
balance = \color, key, value, left, right ->
|
||||||
|
when right is
|
||||||
|
Node Red rK rV rLeft rRight ->
|
||||||
|
when left is
|
||||||
|
Node Red lK lV lLeft lRight ->
|
||||||
|
Node
|
||||||
|
Red
|
||||||
|
key
|
||||||
|
value
|
||||||
|
(Node Black lK lV lLeft lRight)
|
||||||
|
(Node Black rK rV rLeft rRight)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Node color rK rV (Node Red key value left rLeft) rRight
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
when left is
|
||||||
|
Node Red lK lV (Node Red llK llV llLeft llRight) lRight ->
|
||||||
|
Node
|
||||||
|
Red
|
||||||
|
lK
|
||||||
|
lV
|
||||||
|
(Node Black llK llV llLeft llRight)
|
||||||
|
(Node Black key value lRight right)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Node color key value left right
|
||||||
|
|
||||||
|
main : Dict Int {}
|
||||||
|
main =
|
||||||
|
insert 0 {} Empty
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn rbtree_balance() {
|
||||||
|
assert_non_opt_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
app Test provides [ main ] imports []
|
||||||
|
|
||||||
|
NodeColor : [ Red, Black ]
|
||||||
|
|
||||||
|
Dict k v : [ Node NodeColor k v (Dict k v) (Dict k v), Empty ]
|
||||||
|
|
||||||
|
Key k : Num k
|
||||||
|
|
||||||
|
balance : NodeColor, k, v, Dict k v, Dict k v -> Dict k v
|
||||||
|
balance = \color, key, value, left, right ->
|
||||||
|
when right is
|
||||||
|
Node Red lK lV (Node Red llK llV llLeft llRight) lRight -> Empty
|
||||||
|
Empty -> Empty
|
||||||
|
|
||||||
|
|
||||||
|
main : Dict Int {}
|
||||||
|
main =
|
||||||
|
balance Red 0 {} Empty Empty
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn linked_list_double_pattern_match() {
|
||||||
|
assert_non_opt_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
app Test provides [ main ] imports []
|
||||||
|
|
||||||
|
ConsList a : [ Cons a (ConsList a), Nil ]
|
||||||
|
|
||||||
|
foo : ConsList Int -> Int
|
||||||
|
foo = \list ->
|
||||||
|
when list is
|
||||||
|
Cons _ (Cons x _) -> x
|
||||||
|
_ -> 0
|
||||||
|
|
||||||
|
main : Int
|
||||||
|
main =
|
||||||
|
foo (Cons 1 (Cons 32 Nil))
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
32,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn binary_tree_double_pattern_match() {
|
||||||
|
assert_non_opt_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
app Test provides [ main ] imports []
|
||||||
|
|
||||||
|
BTree : [ Node BTree BTree, Leaf Int ]
|
||||||
|
|
||||||
|
foo : BTree -> Int
|
||||||
|
foo = \btree ->
|
||||||
|
when btree is
|
||||||
|
Node (Node (Leaf x) _) _ -> x
|
||||||
|
_ -> 0
|
||||||
|
|
||||||
|
main : Int
|
||||||
|
main =
|
||||||
|
foo (Node (Node (Leaf 32) (Leaf 0)) (Leaf 0))
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
32,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -389,6 +389,13 @@ fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mu
|
||||||
used.insert(v);
|
used.insert(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Store(tipe, var, _, _) => {
|
||||||
|
for v in tipe.variables() {
|
||||||
|
used.insert(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
used.insert(*var);
|
||||||
|
}
|
||||||
Lookup(_, expectation, _) => {
|
Lookup(_, expectation, _) => {
|
||||||
for v in expectation.get_type_ref().variables() {
|
for v in expectation.get_type_ref().variables() {
|
||||||
used.insert(v);
|
used.insert(v);
|
||||||
|
|
|
@ -979,7 +979,7 @@ fn path_to_expr_help<'a>(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Some(wrapped) => {
|
Some(wrapped) => {
|
||||||
let field_layouts = match layout {
|
let field_layouts = match &layout {
|
||||||
Layout::Union(layouts) | Layout::RecursiveUnion(layouts) => {
|
Layout::Union(layouts) | Layout::RecursiveUnion(layouts) => {
|
||||||
layouts[*tag_id as usize].to_vec()
|
layouts[*tag_id as usize].to_vec()
|
||||||
}
|
}
|
||||||
|
@ -989,7 +989,10 @@ fn path_to_expr_help<'a>(
|
||||||
|
|
||||||
debug_assert!(*index < field_layouts.len() as u64);
|
debug_assert!(*index < field_layouts.len() as u64);
|
||||||
|
|
||||||
let inner_layout = field_layouts[*index as usize].clone();
|
let inner_layout = match &field_layouts[*index as usize] {
|
||||||
|
Layout::RecursivePointer => layout.clone(),
|
||||||
|
other => other.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
let inner_expr = Expr::AccessAtIndex {
|
let inner_expr = Expr::AccessAtIndex {
|
||||||
index: *index,
|
index: *index,
|
||||||
|
@ -1057,7 +1060,7 @@ fn test_to_equality<'a>(
|
||||||
let rhs_symbol = env.unique_symbol();
|
let rhs_symbol = env.unique_symbol();
|
||||||
|
|
||||||
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int64), lhs));
|
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int64), lhs));
|
||||||
stores.insert(0, (rhs_symbol, Layout::Builtin(Builtin::Int64), rhs));
|
stores.push((rhs_symbol, Layout::Builtin(Builtin::Int64), rhs));
|
||||||
|
|
||||||
(
|
(
|
||||||
stores,
|
stores,
|
||||||
|
@ -1324,7 +1327,8 @@ fn decide_to_branching<'a>(
|
||||||
arena.alloc(cond),
|
arena.alloc(cond),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (symbol, layout, expr) in new_stores.into_iter() {
|
// stores are in top-to-bottom order, so we have to add them in reverse
|
||||||
|
for (symbol, layout, expr) in new_stores.into_iter().rev() {
|
||||||
cond = Stmt::Let(symbol, expr, layout, arena.alloc(cond));
|
cond = Stmt::Let(symbol, expr, layout, arena.alloc(cond));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2171,17 +2171,22 @@ pub fn with_hole<'a>(
|
||||||
|
|
||||||
// a variable is aliased
|
// a variable is aliased
|
||||||
if let roc_can::expr::Expr::Var(original) = def.loc_expr.value {
|
if let roc_can::expr::Expr::Var(original) = def.loc_expr.value {
|
||||||
substitute_in_exprs(env.arena, &mut stmt, symbol, original);
|
// a variable is aliased, e.g.
|
||||||
|
//
|
||||||
|
// foo = bar
|
||||||
|
//
|
||||||
|
// or
|
||||||
|
//
|
||||||
|
// foo = RBTRee.empty
|
||||||
|
|
||||||
// if the substituted variable is a function, make sure we specialize it
|
stmt = handle_variable_aliasing(
|
||||||
stmt = reuse_function_symbol(
|
|
||||||
env,
|
env,
|
||||||
procs,
|
procs,
|
||||||
layout_cache,
|
layout_cache,
|
||||||
Some(def.expr_var),
|
def.expr_var,
|
||||||
|
symbol,
|
||||||
original,
|
original,
|
||||||
stmt,
|
stmt,
|
||||||
original,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
stmt
|
stmt
|
||||||
|
@ -3583,19 +3588,23 @@ pub fn from_can<'a>(
|
||||||
|
|
||||||
match def.loc_expr.value {
|
match def.loc_expr.value {
|
||||||
roc_can::expr::Expr::Var(original) => {
|
roc_can::expr::Expr::Var(original) => {
|
||||||
|
// a variable is aliased, e.g.
|
||||||
|
//
|
||||||
|
// foo = bar
|
||||||
|
//
|
||||||
|
// or
|
||||||
|
//
|
||||||
|
// foo = RBTRee.empty
|
||||||
let mut rest = from_can(env, def.expr_var, cont.value, procs, layout_cache);
|
let mut rest = from_can(env, def.expr_var, cont.value, procs, layout_cache);
|
||||||
// a variable is aliased
|
|
||||||
substitute_in_exprs(env.arena, &mut rest, *symbol, original);
|
|
||||||
|
|
||||||
// if the substituted variable is a function, make sure we specialize it
|
rest = handle_variable_aliasing(
|
||||||
rest = reuse_function_symbol(
|
|
||||||
env,
|
env,
|
||||||
procs,
|
procs,
|
||||||
layout_cache,
|
layout_cache,
|
||||||
Some(def.expr_var),
|
def.expr_var,
|
||||||
|
*symbol,
|
||||||
original,
|
original,
|
||||||
rest,
|
rest,
|
||||||
original,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return rest;
|
return rest;
|
||||||
|
@ -4555,6 +4564,46 @@ fn possible_reuse_symbol<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_variable_aliasing<'a>(
|
||||||
|
env: &mut Env<'a, '_>,
|
||||||
|
procs: &mut Procs<'a>,
|
||||||
|
layout_cache: &mut LayoutCache<'a>,
|
||||||
|
variable: Variable,
|
||||||
|
left: Symbol,
|
||||||
|
right: Symbol,
|
||||||
|
mut result: Stmt<'a>,
|
||||||
|
) -> Stmt<'a> {
|
||||||
|
let is_imported = left.module_id() != right.module_id();
|
||||||
|
// builtins are currently (re)defined in each module, so not really imported
|
||||||
|
let is_builtin = right.is_builtin();
|
||||||
|
|
||||||
|
if is_imported && !is_builtin {
|
||||||
|
// if this is an imported symbol, then we must make sure it is
|
||||||
|
// specialized, and wrap the original in a function pointer.
|
||||||
|
add_needed_external(procs, env, variable, right);
|
||||||
|
|
||||||
|
let layout = layout_cache
|
||||||
|
.from_var(env.arena, variable, env.subs)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let expr = Expr::FunctionPointer(right, layout.clone());
|
||||||
|
Stmt::Let(left, expr, layout, env.arena.alloc(result))
|
||||||
|
} else {
|
||||||
|
substitute_in_exprs(env.arena, &mut result, left, right);
|
||||||
|
|
||||||
|
// if the substituted variable is a function, make sure we specialize it
|
||||||
|
reuse_function_symbol(
|
||||||
|
env,
|
||||||
|
procs,
|
||||||
|
layout_cache,
|
||||||
|
Some(variable),
|
||||||
|
right,
|
||||||
|
result,
|
||||||
|
right,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// If the symbol is a function, make sure it is properly specialized
|
/// If the symbol is a function, make sure it is properly specialized
|
||||||
fn reuse_function_symbol<'a>(
|
fn reuse_function_symbol<'a>(
|
||||||
env: &mut Env<'a, '_>,
|
env: &mut Env<'a, '_>,
|
||||||
|
@ -4566,7 +4615,15 @@ fn reuse_function_symbol<'a>(
|
||||||
original: Symbol,
|
original: Symbol,
|
||||||
) -> Stmt<'a> {
|
) -> Stmt<'a> {
|
||||||
match procs.partial_procs.get(&original) {
|
match procs.partial_procs.get(&original) {
|
||||||
None => result,
|
None => {
|
||||||
|
// danger: a foreign symbol may not be specialized!
|
||||||
|
debug_assert!(
|
||||||
|
env.home == original.module_id() || original.module_id() == ModuleId::ATTR
|
||||||
|
);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
Some(partial_proc) => {
|
Some(partial_proc) => {
|
||||||
let arg_var = arg_var.unwrap_or(partial_proc.annotation);
|
let arg_var = arg_var.unwrap_or(partial_proc.annotation);
|
||||||
// this symbol is a function, that is used by-name (e.g. as an argument to another
|
// this symbol is a function, that is used by-name (e.g. as an argument to another
|
||||||
|
|
|
@ -315,7 +315,10 @@ impl<'a> Layout<'a> {
|
||||||
|
|
||||||
match content {
|
match content {
|
||||||
FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)),
|
FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)),
|
||||||
RecursionVar { .. } => todo!("recursion var"),
|
RecursionVar { structure, .. } => {
|
||||||
|
let structure_content = env.subs.get_without_compacting(structure).content;
|
||||||
|
Self::new_help(env, structure, structure_content)
|
||||||
|
}
|
||||||
Structure(flat_type) => layout_from_flat_type(env, flat_type),
|
Structure(flat_type) => layout_from_flat_type(env, flat_type),
|
||||||
|
|
||||||
Alias(Symbol::NUM_INT, args, _) => {
|
Alias(Symbol::NUM_INT, args, _) => {
|
||||||
|
@ -751,7 +754,7 @@ fn layout_from_flat_type<'a>(
|
||||||
// Determine the layouts of the fields, maintaining sort order
|
// Determine the layouts of the fields, maintaining sort order
|
||||||
let mut layouts = Vec::with_capacity_in(sorted_fields.len(), arena);
|
let mut layouts = Vec::with_capacity_in(sorted_fields.len(), arena);
|
||||||
|
|
||||||
for (_, field) in sorted_fields {
|
for (label, field) in sorted_fields {
|
||||||
use LayoutProblem::*;
|
use LayoutProblem::*;
|
||||||
|
|
||||||
let field_var = {
|
let field_var = {
|
||||||
|
@ -776,7 +779,14 @@ fn layout_from_flat_type<'a>(
|
||||||
layouts.push(layout);
|
layouts.push(layout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(UnresolvedTypeVar(_)) | Err(Erroneous) => {
|
Err(UnresolvedTypeVar(v)) => {
|
||||||
|
// Invalid field!
|
||||||
|
panic!(
|
||||||
|
r"I hit an unresolved type var {:?} when determining the layout of {:?} of record field: {:?} : {:?}",
|
||||||
|
field_var, v, label, field
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(Erroneous) => {
|
||||||
// Invalid field!
|
// Invalid field!
|
||||||
panic!("TODO gracefully handle record with invalid field.var");
|
panic!("TODO gracefully handle record with invalid field.var");
|
||||||
}
|
}
|
||||||
|
@ -936,7 +946,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>,
|
||||||
|
|
|
@ -402,6 +402,13 @@ fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mu
|
||||||
used.insert(v);
|
used.insert(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Store(tipe, var, _, _) => {
|
||||||
|
for v in tipe.variables() {
|
||||||
|
used.insert(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
used.insert(*var);
|
||||||
|
}
|
||||||
Lookup(_, expectation, _) => {
|
Lookup(_, expectation, _) => {
|
||||||
for v in expectation.get_type_ref().variables() {
|
for v in expectation.get_type_ref().variables() {
|
||||||
used.insert(v);
|
used.insert(v);
|
||||||
|
|
|
@ -1827,7 +1827,9 @@ mod test_mono {
|
||||||
let Test.8 = true;
|
let Test.8 = true;
|
||||||
let Test.10 = 0i64;
|
let Test.10 = 0i64;
|
||||||
let Test.9 = Index 1 Test.2;
|
let Test.9 = Index 1 Test.2;
|
||||||
|
inc Test.9;
|
||||||
let Test.11 = Index 0 Test.9;
|
let Test.11 = Index 0 Test.9;
|
||||||
|
dec Test.9;
|
||||||
let Test.12 = lowlevel Eq Test.10 Test.11;
|
let Test.12 = lowlevel Eq Test.10 Test.11;
|
||||||
let Test.7 = lowlevel And Test.12 Test.8;
|
let Test.7 = lowlevel And Test.12 Test.8;
|
||||||
if Test.7 then
|
if Test.7 then
|
||||||
|
|
|
@ -427,8 +427,15 @@ fn pretty_runtime_error<'b>(
|
||||||
// do nothing, reported with PrecedenceProblem
|
// do nothing, reported with PrecedenceProblem
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
RuntimeError::MalformedIdentifier(_, _) => {
|
RuntimeError::MalformedIdentifier(box_str, region) => {
|
||||||
todo!("malformed identifier, currently gives a parse error and thus is unreachable")
|
alloc.stack(vec![
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("The ")
|
||||||
|
.append(format!("`{}`", box_str))
|
||||||
|
.append(alloc.reflow(" identifier is malformed:")),
|
||||||
|
]),
|
||||||
|
alloc.region(region),
|
||||||
|
])
|
||||||
}
|
}
|
||||||
RuntimeError::MalformedClosure(_) => todo!(""),
|
RuntimeError::MalformedClosure(_) => todo!(""),
|
||||||
RuntimeError::InvalidFloat(sign @ FloatErrorKind::PositiveInfinity, region, _raw_str)
|
RuntimeError::InvalidFloat(sign @ FloatErrorKind::PositiveInfinity, region, _raw_str)
|
||||||
|
|
|
@ -3930,4 +3930,25 @@ mod test_reporting {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn qualified_global_tag() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
Foo.Bar
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
The `Foo.Bar` identifier is malformed:
|
||||||
|
|
||||||
|
1│ Foo.Bar
|
||||||
|
^^^^^^^
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -244,6 +244,34 @@ fn solve(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Store(source, target, _filename, _linenr) => {
|
||||||
|
// a special version of Eq that is used to store types in the AST.
|
||||||
|
// IT DOES NOT REPORT ERRORS!
|
||||||
|
let actual = type_to_var(subs, rank, pools, cached_aliases, source);
|
||||||
|
let target = *target;
|
||||||
|
|
||||||
|
match unify(subs, actual, target) {
|
||||||
|
Success(vars) => {
|
||||||
|
introduce(subs, rank, pools, &vars);
|
||||||
|
|
||||||
|
state
|
||||||
|
}
|
||||||
|
Failure(vars, _actual_type, _expected_type) => {
|
||||||
|
introduce(subs, rank, pools, &vars);
|
||||||
|
|
||||||
|
// ERROR NOT REPORTED
|
||||||
|
|
||||||
|
state
|
||||||
|
}
|
||||||
|
BadType(vars, _problem) => {
|
||||||
|
introduce(subs, rank, pools, &vars);
|
||||||
|
|
||||||
|
// ERROR NOT REPORTED
|
||||||
|
|
||||||
|
state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Lookup(symbol, expectation, region) => {
|
Lookup(symbol, expectation, region) => {
|
||||||
let var = *env.vars_by_symbol.get(&symbol).unwrap_or_else(|| {
|
let var = *env.vars_by_symbol.get(&symbol).unwrap_or_else(|| {
|
||||||
// TODO Instead of panicking, solve this as True and record
|
// TODO Instead of panicking, solve this as True and record
|
||||||
|
|
|
@ -417,6 +417,13 @@ fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mu
|
||||||
used.insert(v);
|
used.insert(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Store(tipe, var, _, _) => {
|
||||||
|
for v in tipe.variables() {
|
||||||
|
used.insert(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
used.insert(*var);
|
||||||
|
}
|
||||||
Lookup(_, expectation, _) => {
|
Lookup(_, expectation, _) => {
|
||||||
for v in expectation.get_type_ref().variables() {
|
for v in expectation.get_type_ref().variables() {
|
||||||
used.insert(v);
|
used.insert(v);
|
||||||
|
|
|
@ -3106,4 +3106,417 @@ mod solve_expr {
|
||||||
"Dict Int Int",
|
"Dict Int Int",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rbtree_insert() {
|
||||||
|
// exposed an issue where pattern variables were not introduced
|
||||||
|
// at the correct level in the constraint
|
||||||
|
//
|
||||||
|
// see 22592eff805511fbe1da63849771ee5f367a6a16
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
app Test provides [ main ] imports []
|
||||||
|
|
||||||
|
Dict k : [ Node k (Dict k), Empty ]
|
||||||
|
|
||||||
|
balance : Dict k -> Dict k
|
||||||
|
balance = \left ->
|
||||||
|
when left is
|
||||||
|
Node _ Empty -> Empty
|
||||||
|
|
||||||
|
_ -> Empty
|
||||||
|
|
||||||
|
main : Dict {}
|
||||||
|
main =
|
||||||
|
balance Empty
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Dict {}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rbtree_full_remove_min() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
app Test provides [ main ] imports []
|
||||||
|
|
||||||
|
NodeColor : [ Red, Black ]
|
||||||
|
|
||||||
|
Dict k v : [ Node NodeColor k v (Dict k v) (Dict k v), Empty ]
|
||||||
|
|
||||||
|
moveRedLeft : Dict k v -> Dict k v
|
||||||
|
moveRedLeft = \dict ->
|
||||||
|
when dict is
|
||||||
|
# Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV ((Node Red rlK rlV rlL rlR) as rLeft) rRight) ->
|
||||||
|
# Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV rLeft rRight) ->
|
||||||
|
Node clr k v (Node _ lK lV lLeft lRight) (Node _ rK rV rLeft rRight) ->
|
||||||
|
when rLeft is
|
||||||
|
Node Red rlK rlV rlL rlR ->
|
||||||
|
Node
|
||||||
|
Red
|
||||||
|
rlK
|
||||||
|
rlV
|
||||||
|
(Node Black k v (Node Red lK lV lLeft lRight) rlL)
|
||||||
|
(Node Black rK rV rlR rRight)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
when clr is
|
||||||
|
Black ->
|
||||||
|
Node
|
||||||
|
Black
|
||||||
|
k
|
||||||
|
v
|
||||||
|
(Node Red lK lV lLeft lRight)
|
||||||
|
(Node Red rK rV rLeft rRight)
|
||||||
|
|
||||||
|
Red ->
|
||||||
|
Node
|
||||||
|
Black
|
||||||
|
k
|
||||||
|
v
|
||||||
|
(Node Red lK lV lLeft lRight)
|
||||||
|
(Node Red rK rV rLeft rRight)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
dict
|
||||||
|
|
||||||
|
balance : NodeColor, k, v, Dict k v, Dict k v -> Dict k v
|
||||||
|
balance = \color, key, value, left, right ->
|
||||||
|
when right is
|
||||||
|
Node Red rK rV rLeft rRight ->
|
||||||
|
when left is
|
||||||
|
Node Red lK lV lLeft lRight ->
|
||||||
|
Node
|
||||||
|
Red
|
||||||
|
key
|
||||||
|
value
|
||||||
|
(Node Black lK lV lLeft lRight)
|
||||||
|
(Node Black rK rV rLeft rRight)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Node color rK rV (Node Red key value left rLeft) rRight
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
when left is
|
||||||
|
Node Red lK lV (Node Red llK llV llLeft llRight) lRight ->
|
||||||
|
Node
|
||||||
|
Red
|
||||||
|
lK
|
||||||
|
lV
|
||||||
|
(Node Black llK llV llLeft llRight)
|
||||||
|
(Node Black key value lRight right)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Node color key value left right
|
||||||
|
|
||||||
|
|
||||||
|
Key k : Num k
|
||||||
|
|
||||||
|
removeHelpEQGT : Key k, Dict (Key k) v -> Dict (Key k) v
|
||||||
|
removeHelpEQGT = \targetKey, dict ->
|
||||||
|
when dict is
|
||||||
|
Node color key value left right ->
|
||||||
|
if targetKey == key then
|
||||||
|
when getMin right is
|
||||||
|
Node _ minKey minValue _ _ ->
|
||||||
|
balance color minKey minValue left (removeMin right)
|
||||||
|
|
||||||
|
Empty ->
|
||||||
|
Empty
|
||||||
|
else
|
||||||
|
balance color key value left (removeHelp targetKey right)
|
||||||
|
|
||||||
|
Empty ->
|
||||||
|
Empty
|
||||||
|
|
||||||
|
getMin : Dict k v -> Dict k v
|
||||||
|
getMin = \dict ->
|
||||||
|
when dict is
|
||||||
|
# Node _ _ _ ((Node _ _ _ _ _) as left) _ ->
|
||||||
|
Node _ _ _ left _ ->
|
||||||
|
when left is
|
||||||
|
Node _ _ _ _ _ -> getMin left
|
||||||
|
_ -> dict
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
dict
|
||||||
|
|
||||||
|
|
||||||
|
moveRedRight : Dict k v -> Dict k v
|
||||||
|
moveRedRight = \dict ->
|
||||||
|
when dict is
|
||||||
|
Node clr k v (Node lClr lK lV (Node Red llK llV llLeft llRight) lRight) (Node rClr rK rV rLeft rRight) ->
|
||||||
|
Node
|
||||||
|
Red
|
||||||
|
lK
|
||||||
|
lV
|
||||||
|
(Node Black llK llV llLeft llRight)
|
||||||
|
(Node Black k v lRight (Node Red rK rV rLeft rRight))
|
||||||
|
|
||||||
|
Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV rLeft rRight) ->
|
||||||
|
when clr is
|
||||||
|
Black ->
|
||||||
|
Node
|
||||||
|
Black
|
||||||
|
k
|
||||||
|
v
|
||||||
|
(Node Red lK lV lLeft lRight)
|
||||||
|
(Node Red rK rV rLeft rRight)
|
||||||
|
|
||||||
|
Red ->
|
||||||
|
Node
|
||||||
|
Black
|
||||||
|
k
|
||||||
|
v
|
||||||
|
(Node Red lK lV lLeft lRight)
|
||||||
|
(Node Red rK rV rLeft rRight)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
dict
|
||||||
|
|
||||||
|
|
||||||
|
removeHelpPrepEQGT : Key k, Dict (Key k) v, NodeColor, (Key k), v, Dict (Key k) v, Dict (Key k) v -> Dict (Key k) v
|
||||||
|
removeHelpPrepEQGT = \_, dict, color, key, value, left, right ->
|
||||||
|
when left is
|
||||||
|
Node Red lK lV lLeft lRight ->
|
||||||
|
Node
|
||||||
|
color
|
||||||
|
lK
|
||||||
|
lV
|
||||||
|
lLeft
|
||||||
|
(Node Red key value lRight right)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
when right is
|
||||||
|
Node Black _ _ (Node Black _ _ _ _) _ ->
|
||||||
|
moveRedRight dict
|
||||||
|
|
||||||
|
Node Black _ _ Empty _ ->
|
||||||
|
moveRedRight dict
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
dict
|
||||||
|
|
||||||
|
|
||||||
|
removeMin : Dict k v -> Dict k v
|
||||||
|
removeMin = \dict ->
|
||||||
|
when dict is
|
||||||
|
Node color key value left right ->
|
||||||
|
when left is
|
||||||
|
Node lColor _ _ lLeft _ ->
|
||||||
|
when lColor is
|
||||||
|
Black ->
|
||||||
|
when lLeft is
|
||||||
|
Node Red _ _ _ _ ->
|
||||||
|
Node color key value (removeMin left) right
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
when moveRedLeft dict is # here 1
|
||||||
|
Node nColor nKey nValue nLeft nRight ->
|
||||||
|
balance nColor nKey nValue (removeMin nLeft) nRight
|
||||||
|
|
||||||
|
Empty ->
|
||||||
|
Empty
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Node color key value (removeMin left) right
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Empty
|
||||||
|
_ ->
|
||||||
|
Empty
|
||||||
|
|
||||||
|
removeHelp : Key k, Dict (Key k) v -> Dict (Key k) v
|
||||||
|
removeHelp = \targetKey, dict ->
|
||||||
|
when dict is
|
||||||
|
Empty ->
|
||||||
|
Empty
|
||||||
|
|
||||||
|
Node color key value left right ->
|
||||||
|
if targetKey < key then
|
||||||
|
when left is
|
||||||
|
Node Black _ _ lLeft _ ->
|
||||||
|
when lLeft is
|
||||||
|
Node Red _ _ _ _ ->
|
||||||
|
Node color key value (removeHelp targetKey left) right
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
when moveRedLeft dict is # here 2
|
||||||
|
Node nColor nKey nValue nLeft nRight ->
|
||||||
|
balance nColor nKey nValue (removeHelp targetKey nLeft) nRight
|
||||||
|
|
||||||
|
Empty ->
|
||||||
|
Empty
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Node color key value (removeHelp targetKey left) right
|
||||||
|
else
|
||||||
|
removeHelpEQGT targetKey (removeHelpPrepEQGT targetKey dict color key value left right)
|
||||||
|
|
||||||
|
|
||||||
|
main : Dict Int Int
|
||||||
|
main =
|
||||||
|
removeHelp 1 Empty
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Dict Int Int",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rbtree_remove_min_1() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
app Test provides [ main ] imports []
|
||||||
|
|
||||||
|
Dict k : [ Node k (Dict k) (Dict k), Empty ]
|
||||||
|
|
||||||
|
removeHelp : Num k, Dict (Num k) -> Dict (Num k)
|
||||||
|
removeHelp = \targetKey, dict ->
|
||||||
|
when dict is
|
||||||
|
Empty ->
|
||||||
|
Empty
|
||||||
|
|
||||||
|
Node key left right ->
|
||||||
|
if targetKey < key then
|
||||||
|
when left is
|
||||||
|
Node _ lLeft _ ->
|
||||||
|
when lLeft is
|
||||||
|
Node _ _ _ ->
|
||||||
|
Empty
|
||||||
|
|
||||||
|
_ -> Empty
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Node key (removeHelp targetKey left) right
|
||||||
|
else
|
||||||
|
Empty
|
||||||
|
|
||||||
|
|
||||||
|
main : Dict Int
|
||||||
|
main =
|
||||||
|
removeHelp 1 Empty
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Dict Int",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rbtree_foobar() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
app Test provides [ main ] imports []
|
||||||
|
|
||||||
|
NodeColor : [ Red, Black ]
|
||||||
|
|
||||||
|
Dict k v : [ Node NodeColor k v (Dict k v) (Dict k v), Empty ]
|
||||||
|
|
||||||
|
removeHelp : Num k, Dict (Num k) v -> Dict (Num k) v
|
||||||
|
removeHelp = \targetKey, dict ->
|
||||||
|
when dict is
|
||||||
|
Empty ->
|
||||||
|
Empty
|
||||||
|
|
||||||
|
Node color key value left right ->
|
||||||
|
if targetKey < key then
|
||||||
|
when left is
|
||||||
|
Node Black _ _ lLeft _ ->
|
||||||
|
when lLeft is
|
||||||
|
Node Red _ _ _ _ ->
|
||||||
|
Node color key value (removeHelp targetKey left) right
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
when moveRedLeft dict is # here 2
|
||||||
|
Node nColor nKey nValue nLeft nRight ->
|
||||||
|
balance nColor nKey nValue (removeHelp targetKey nLeft) nRight
|
||||||
|
|
||||||
|
Empty ->
|
||||||
|
Empty
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Node color key value (removeHelp targetKey left) right
|
||||||
|
else
|
||||||
|
removeHelpEQGT targetKey (removeHelpPrepEQGT targetKey dict color key value left right)
|
||||||
|
|
||||||
|
Key k : Num k
|
||||||
|
|
||||||
|
balance : NodeColor, k, v, Dict k v, Dict k v -> Dict k v
|
||||||
|
|
||||||
|
moveRedLeft : Dict k v -> Dict k v
|
||||||
|
|
||||||
|
removeHelpPrepEQGT : Key k, Dict (Key k) v, NodeColor, (Key k), v, Dict (Key k) v, Dict (Key k) v -> Dict (Key k) v
|
||||||
|
|
||||||
|
removeHelpEQGT : Key k, Dict (Key k) v -> Dict (Key k) v
|
||||||
|
removeHelpEQGT = \targetKey, dict ->
|
||||||
|
when dict is
|
||||||
|
Node color key value left right ->
|
||||||
|
if targetKey == key then
|
||||||
|
when getMin right is
|
||||||
|
Node _ minKey minValue _ _ ->
|
||||||
|
balance color minKey minValue left (removeMin right)
|
||||||
|
|
||||||
|
Empty ->
|
||||||
|
Empty
|
||||||
|
else
|
||||||
|
balance color key value left (removeHelp targetKey right)
|
||||||
|
|
||||||
|
Empty ->
|
||||||
|
Empty
|
||||||
|
|
||||||
|
getMin : Dict k v -> Dict k v
|
||||||
|
|
||||||
|
removeMin : Dict k v -> Dict k v
|
||||||
|
|
||||||
|
main : Dict Int Int
|
||||||
|
main =
|
||||||
|
removeHelp 1 Empty
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Dict Int Int",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn quicksort_partition_help() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
app Test provides [ partitionHelp ] imports []
|
||||||
|
|
||||||
|
swap : Int, Int, List a -> List a
|
||||||
|
swap = \i, j, list ->
|
||||||
|
when Pair (List.get list i) (List.get list j) is
|
||||||
|
Pair (Ok atI) (Ok atJ) ->
|
||||||
|
list
|
||||||
|
|> List.set i atJ
|
||||||
|
|> List.set j atI
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
|
||||||
|
partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ]
|
||||||
|
partitionHelp = \i, j, list, high, pivot ->
|
||||||
|
if j < high then
|
||||||
|
when List.get list j is
|
||||||
|
Ok value ->
|
||||||
|
if value <= pivot then
|
||||||
|
partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot
|
||||||
|
else
|
||||||
|
partitionHelp i (j + 1) list high pivot
|
||||||
|
|
||||||
|
Err _ ->
|
||||||
|
Pair i list
|
||||||
|
else
|
||||||
|
Pair i list
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"Int, Int, List (Num a), Int, Num a -> [ Pair Int (List (Num a)) ]",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1089,7 +1089,7 @@ fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content
|
||||||
RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _) => {
|
RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _) => {
|
||||||
// Type mismatch! Rigid can only unify with flex, even if the
|
// Type mismatch! Rigid can only unify with flex, even if the
|
||||||
// rigid names are the same.
|
// rigid names are the same.
|
||||||
mismatch!("Rigid with {:?}", &other)
|
mismatch!("Rigid {:?} with {:?}", ctx.first, &other)
|
||||||
}
|
}
|
||||||
Error => {
|
Error => {
|
||||||
// Error propagates.
|
// Error propagates.
|
||||||
|
|
|
@ -414,6 +414,13 @@ fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mu
|
||||||
used.insert(v);
|
used.insert(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Store(tipe, var, _, _) => {
|
||||||
|
for v in tipe.variables() {
|
||||||
|
used.insert(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
used.insert(*var);
|
||||||
|
}
|
||||||
Lookup(_, expectation, _) => {
|
Lookup(_, expectation, _) => {
|
||||||
for v in expectation.get_type_ref().variables() {
|
for v in expectation.get_type_ref().variables() {
|
||||||
used.insert(v);
|
used.insert(v);
|
||||||
|
|
|
@ -5,6 +5,7 @@ authors = ["Richard Feldman <oss@rtfeldman.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "An editor for Roc"
|
description = "An editor for Roc"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
exclude = ["src/shaders/*.spv"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
roc_collections = { path = "../compiler/collections" }
|
roc_collections = { path = "../compiler/collections" }
|
||||||
|
|
|
@ -64,9 +64,6 @@ fn main() -> Result<()> {
|
||||||
// be better just to only compile shaders that have been changed
|
// be better just to only compile shaders that have been changed
|
||||||
// recently.
|
// recently.
|
||||||
for shader in shaders {
|
for shader in shaders {
|
||||||
// This tells cargo to rerun this script if something in /src/ changes.
|
|
||||||
println!("cargo:rerun-if-changed={:?}", shader.src_path);
|
|
||||||
|
|
||||||
let compiled = compiler.compile_into_spirv(
|
let compiled = compiler.compile_into_spirv(
|
||||||
&shader.src,
|
&shader.src,
|
||||||
shader.kind,
|
shader.kind,
|
||||||
|
|
|
@ -1,12 +1,22 @@
|
||||||
app Main provides [ main ] imports [ Effect, RBTree ]
|
app Main provides [ main ] imports [ Effect, RBTree ]
|
||||||
|
|
||||||
foo : RBTree.Dict Int Int
|
toAndFro : Int
|
||||||
foo = Empty # RBTree.empty
|
toAndFro =
|
||||||
|
empty : RBTree.Dict Int {}
|
||||||
|
empty = RBTree.empty
|
||||||
|
|
||||||
|
empty
|
||||||
|
|> (\d -> RBTree.insert 1 {} d)
|
||||||
|
|> RBTree.toList
|
||||||
|
|> List.len
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
main : Effect.Effect {} as Fx
|
main : Effect.Effect {} as Fx
|
||||||
main =
|
main =
|
||||||
# if RBTree.isEmpty empty then
|
# if RBTree.isEmpty empty then
|
||||||
if RBTree.size foo == 0 then
|
if toAndFro == 2 then
|
||||||
Effect.putLine "Yay"
|
Effect.putLine "Yay"
|
||||||
|> Effect.after (\{} -> Effect.getLine)
|
|> Effect.after (\{} -> Effect.getLine)
|
||||||
|> Effect.after (\line -> Effect.putLine line)
|
|> Effect.after (\line -> Effect.putLine line)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
interface RBTree exposes [ Dict, empty, size, singleton ] imports []
|
interface RBTree exposes [ Dict, empty, size, singleton, isEmpty, insert, remove, update, fromList, toList ] imports []
|
||||||
|
|
||||||
# The color of a node. Leaves are considered Black.
|
# The color of a node. Leaves are considered Black.
|
||||||
NodeColor : [ Red, Black ]
|
NodeColor : [ Red, Black ]
|
||||||
|
@ -147,6 +147,8 @@ removeHelp = \targetKey, dict ->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
removeHelpPrepEQGT : Key k, Dict (Key k) v, NodeColor, (Key k), v, Dict (Key k) v, Dict (Key k) v -> Dict (Key k) v
|
removeHelpPrepEQGT : Key k, Dict (Key k) v, NodeColor, (Key k), v, Dict (Key k) v, Dict (Key k) v -> Dict (Key k) v
|
||||||
removeHelpPrepEQGT = \_, dict, color, key, value, left, right ->
|
removeHelpPrepEQGT = \_, dict, color, key, value, left, right ->
|
||||||
when left is
|
when left is
|
||||||
|
@ -171,6 +173,7 @@ removeHelpPrepEQGT = \_, dict, color, key, value, left, right ->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# When we find the node we are looking for, we can remove by replacing the key-value
|
# When we find the node we are looking for, we can remove by replacing the key-value
|
||||||
# pair with the key-value pair of the left-most node on the right side (the closest pair).
|
# pair with the key-value pair of the left-most node on the right side (the closest pair).
|
||||||
removeHelpEQGT : Key k, Dict (Key k) v -> Dict (Key k) v
|
removeHelpEQGT : Key k, Dict (Key k) v -> Dict (Key k) v
|
||||||
|
@ -192,25 +195,28 @@ removeHelpEQGT = \targetKey, dict ->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
getMin : Dict k v -> Dict k v
|
getMin : Dict k v -> Dict k v
|
||||||
getMin = \dict ->
|
getMin = \dict ->
|
||||||
when dict is
|
when dict is
|
||||||
# Node _ _ _ ((Node _ _ _ _ _) as left) _ ->
|
# Node _ _ _ ((Node _ _ _ _ _) as left) _ ->
|
||||||
Node _ _ _ left _ ->
|
Node _ _ _ left _ ->
|
||||||
when left is
|
when left is
|
||||||
Node _ _ _ _ _ -> getMin left
|
Node _ _ _ _ _ -> getMin left
|
||||||
_ -> dict
|
_ -> dict
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
dict
|
dict
|
||||||
|
|
||||||
|
|
||||||
moveRedLeft : Dict k v -> Dict k v
|
moveRedLeft : Dict k v -> Dict k v
|
||||||
moveRedLeft = \dict ->
|
moveRedLeft = \dict ->
|
||||||
when dict is
|
when dict is
|
||||||
# Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV ((Node Red rlK rlV rlL rlR) as rLeft) rRight) ->
|
# Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV ((Node Red rlK rlV rlL rlR) as rLeft) rRight) ->
|
||||||
Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV rLeft rRight) ->
|
# Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV rLeft rRight) ->
|
||||||
when rList is
|
Node clr k v (Node _ lK lV lLeft lRight) (Node _ rK rV rLeft rRight) ->
|
||||||
Node Red rlK rlV rlL rlR ->
|
when rLeft is
|
||||||
|
Node Red rlK rlV rlL rlR ->
|
||||||
Node
|
Node
|
||||||
Red
|
Red
|
||||||
rlK
|
rlK
|
||||||
|
@ -218,7 +224,7 @@ moveRedLeft = \dict ->
|
||||||
(Node Black k v (Node Red lK lV lLeft lRight) rlL)
|
(Node Black k v (Node Red lK lV lLeft lRight) rlL)
|
||||||
(Node Black rK rV rlR rRight)
|
(Node Black rK rV rlR rRight)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
when clr is
|
when clr is
|
||||||
Black ->
|
Black ->
|
||||||
Node
|
Node
|
||||||
|
@ -242,7 +248,7 @@ moveRedLeft = \dict ->
|
||||||
moveRedRight : Dict k v -> Dict k v
|
moveRedRight : Dict k v -> Dict k v
|
||||||
moveRedRight = \dict ->
|
moveRedRight = \dict ->
|
||||||
when dict is
|
when dict is
|
||||||
Node clr k v (Node lClr lK lV (Node Red llK llV llLeft llRight) lRight) (Node rClr rK rV rLeft rRight) ->
|
Node _ k v (Node _ lK lV (Node Red llK llV llLeft llRight) lRight) (Node _ rK rV rLeft rRight) ->
|
||||||
Node
|
Node
|
||||||
Red
|
Red
|
||||||
lK
|
lK
|
||||||
|
@ -250,7 +256,7 @@ moveRedRight = \dict ->
|
||||||
(Node Black llK llV llLeft llRight)
|
(Node Black llK llV llLeft llRight)
|
||||||
(Node Black k v lRight (Node Red rK rV rLeft rRight))
|
(Node Black k v lRight (Node Red rK rV rLeft rRight))
|
||||||
|
|
||||||
Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV rLeft rRight) ->
|
Node clr k v (Node _ lK lV lLeft lRight) (Node _ rK rV rLeft rRight) ->
|
||||||
when clr is
|
when clr is
|
||||||
Black ->
|
Black ->
|
||||||
Node
|
Node
|
||||||
|
@ -274,30 +280,34 @@ moveRedRight = \dict ->
|
||||||
removeMin : Dict k v -> Dict k v
|
removeMin : Dict k v -> Dict k v
|
||||||
removeMin = \dict ->
|
removeMin = \dict ->
|
||||||
when dict is
|
when dict is
|
||||||
Node color key value ((Node lColor _ _ lLeft _) as left) right ->
|
Node color key value left right ->
|
||||||
when lColor is
|
when left is
|
||||||
Black ->
|
Node lColor _ _ lLeft _ ->
|
||||||
when lLeft is
|
when lColor is
|
||||||
Node Red _ _ _ _ ->
|
Black ->
|
||||||
Node color key value (removeMin left) right
|
when lLeft is
|
||||||
|
Node Red _ _ _ _ ->
|
||||||
|
Node color key value (removeMin left) right
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
when moveRedLeft dict is
|
||||||
|
Node nColor nKey nValue nLeft nRight ->
|
||||||
|
balance nColor nKey nValue (removeMin nLeft) nRight
|
||||||
|
|
||||||
|
Empty ->
|
||||||
|
Empty
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Node color key value (removeMin left) right
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
when moveRedLeft dict is
|
Empty
|
||||||
Node nColor nKey nValue nLeft nRight ->
|
|
||||||
balance nColor nKey nValue (removeMin nLeft) nRight
|
|
||||||
|
|
||||||
Empty ->
|
|
||||||
Empty
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
Node color key value (removeMin left) right
|
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
Empty
|
Empty
|
||||||
|
|
||||||
|
|
||||||
# Update the value of a dictionary for a specific key with a given function.
|
# Update the value of a dictionary for a specific key with a given function.
|
||||||
update : Key k, (Maybe v, Maybe v), Dict (Key k) v -> Dict (Key k) v
|
update : Key k, (Maybe v -> Maybe v), Dict (Key k) v -> Dict (Key k) v
|
||||||
update = \targetKey, alter, dictionary ->
|
update = \targetKey, alter, dictionary ->
|
||||||
when alter (get targetKey dictionary) is
|
when alter (get targetKey dictionary) is
|
||||||
Just value ->
|
Just value ->
|
||||||
|
@ -305,3 +315,38 @@ update = \targetKey, alter, dictionary ->
|
||||||
|
|
||||||
Nothing ->
|
Nothing ->
|
||||||
remove targetKey dictionary
|
remove targetKey dictionary
|
||||||
|
|
||||||
|
get : Key k, Dict (Key k) v -> Maybe v
|
||||||
|
get = \targetKey, dict ->
|
||||||
|
when dict is
|
||||||
|
Empty ->
|
||||||
|
Nothing
|
||||||
|
|
||||||
|
Node _ key value left right ->
|
||||||
|
when Num.compare targetKey key is
|
||||||
|
LT ->
|
||||||
|
get targetKey left
|
||||||
|
|
||||||
|
EQ ->
|
||||||
|
Just value
|
||||||
|
|
||||||
|
GT ->
|
||||||
|
get targetKey right
|
||||||
|
|
||||||
|
fromList : List {key : Num k, value : v } -> Dict (Num k) v
|
||||||
|
fromList = \xs ->
|
||||||
|
List.walkRight xs (\{key, value}, dict -> insert key value dict) empty
|
||||||
|
|
||||||
|
foldr : (k, v, b -> b), b, Dict k v -> b
|
||||||
|
foldr = \func, acc, t ->
|
||||||
|
when t is
|
||||||
|
Empty ->
|
||||||
|
acc
|
||||||
|
|
||||||
|
Node _ key value left right ->
|
||||||
|
foldr func (func key value (foldr func acc right)) left
|
||||||
|
|
||||||
|
# Convert a dictionary into an association list of key-value pairs, sorted by keys.
|
||||||
|
toList : Dict k v -> List { key : k, value : v }
|
||||||
|
toList = \dict ->
|
||||||
|
foldr (\key, value, list -> List.append list {key,value}) [] dict
|
||||||
|
|
113
shell.nix
113
shell.nix
|
@ -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 = ''
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue