Merge branch 'trunk' of github.com:rtfeldman/roc into str-split

This commit is contained in:
Chad Stearns 2020-11-13 00:58:37 -05:00
commit c9e3531ecd
29 changed files with 1599 additions and 408 deletions

View file

@ -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);

View file

@ -6,7 +6,7 @@ use roc_gen::{run_jit_function, run_jit_function_dynamic_type};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::operator::CalledVia; use roc_module::operator::CalledVia;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionVariant};
use roc_parse::ast::{AssignedField, Expr, StrLiteral}; use roc_parse::ast::{AssignedField, Expr, StrLiteral};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{Content, FlatType, Subs, Variable}; use roc_types::subs::{Content, FlatType, Subs, Variable};
@ -64,6 +64,10 @@ fn jit_to_ast_help<'a>(
env, num, content env, num, content
)) ))
} }
Layout::Builtin(Builtin::Int8) => {
// NOTE: this is does not handle 8-bit numbers yet
run_jit_function!(lib, main_fn_name, u8, |num| byte_to_ast(env, num, content))
}
Layout::Builtin(Builtin::Int64) => { Layout::Builtin(Builtin::Int64) => {
run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast( run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast(
env, env,
@ -89,19 +93,20 @@ fn jit_to_ast_help<'a>(
Layout::Builtin(Builtin::List(_, elem_layout)) => run_jit_function!( Layout::Builtin(Builtin::List(_, elem_layout)) => run_jit_function!(
lib, lib,
main_fn_name, main_fn_name,
(*const libc::c_void, usize), (*const u8, usize),
|(ptr, len): (*const libc::c_void, usize)| { |(ptr, len): (*const u8, usize)| { list_to_ast(env, ptr, len, elem_layout, content) }
list_to_ast(env, ptr, len, elem_layout, content)
}
), ),
Layout::PhantomEmptyStruct => run_jit_function!(lib, main_fn_name, &'static str, |_| { Layout::Builtin(other) => {
todo!("add support for rendering builtin {:?} to the REPL", other)
}
Layout::PhantomEmptyStruct => run_jit_function!(lib, main_fn_name, &u8, |_| {
Expr::Record { Expr::Record {
update: None, update: None,
fields: &[], fields: &[],
} }
}), }),
Layout::Struct(field_layouts) => { Layout::Struct(field_layouts) => {
let ptr_to_ast = |ptr: *const libc::c_void| match content { let ptr_to_ast = |ptr: *const u8| match content {
Content::Structure(FlatType::Record(fields, _)) => { Content::Structure(FlatType::Record(fields, _)) => {
struct_to_ast(env, ptr, field_layouts, fields) struct_to_ast(env, ptr, field_layouts, fields)
} }
@ -113,13 +118,7 @@ fn jit_to_ast_help<'a>(
let (tag_name, payload_vars) = tags.iter().next().unwrap(); let (tag_name, payload_vars) = tags.iter().next().unwrap();
// We expect anything with payload vars single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), payload_vars)
// that is a single Tag TagUnion
// has a Record content so the above case
// should match instead
debug_assert_eq!(payload_vars.len(), 0);
single_tag_union_to_ast(env, field_layouts, tag_name.clone(), payload_vars)
} }
other => { other => {
unreachable!( unreachable!(
@ -132,76 +131,93 @@ fn jit_to_ast_help<'a>(
let fields = [Layout::Builtin(Builtin::Int64), layout.clone()]; let fields = [Layout::Builtin(Builtin::Int64), layout.clone()];
let layout = Layout::Struct(&fields); let layout = Layout::Struct(&fields);
match env.ptr_bytes { let result_stack_size = layout.stack_size(env.ptr_bytes);
// 64-bit target (8-byte pointers, 16-byte structs)
8 => match layout.stack_size(env.ptr_bytes) {
8 => {
// just one eightbyte, 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)
})
}
16 => {
// two eightbytes, returned as-is
run_jit_function!(lib, main_fn_name, [u8; 16], |bytes: [u8; 16]| {
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
})
}
larger_size => {
// anything more than 2 eightbytes
// the return "value" is a pointer to the result
run_jit_function_dynamic_type!( run_jit_function_dynamic_type!(
lib, lib,
main_fn_name, main_fn_name,
larger_size as usize, result_stack_size as usize,
|bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) } |bytes: *const u8| { ptr_to_ast(bytes as *const u8) }
) )
} }
Layout::Union(union_layouts) => match content {
Content::Structure(FlatType::TagUnion(tags, _)) => {
debug_assert_eq!(union_layouts.len(), tags.len());
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);
let size = layout.stack_size(env.ptr_bytes);
match union_variant {
UnionVariant::Wrapped(tags_and_layouts) => {
run_jit_function_dynamic_type!(
lib,
main_fn_name,
size as usize,
|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"),
}
}
Content::Structure(FlatType::RecursiveTagUnion(_, _, _)) => {
todo!("print recursive tag unions in the REPL")
}
other => unreachable!("Weird content for Union layout: {:?}", other),
}, },
// 32-bit target (4-byte pointers, 8-byte structs) Layout::RecursiveUnion(_) | Layout::RecursivePointer => {
4 => { todo!("add support for rendering recursive tag unions in the REPL")
// 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 Layout::FunctionPointer(_, _) | Layout::Closure(_, _, _) => {
run_jit_function!(lib, main_fn_name, [u8; 8], |bytes: [u8; 8]| { todo!("add support for rendering functions in the REPL")
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
})
} }
larger_size => { Layout::Pointer(_) => todo!("add support for rendering pointers in the REPL"),
// 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 => { fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> {
panic!("Unsupported target: Roc cannot currently compile to systems where pointers are {} bytes in length.", other); match tag_name {
} TagName::Global(_) => Expr::GlobalTag(
} env.arena
} .alloc_str(&tag_name.as_string(env.interns, env.home)),
other => { ),
todo!("TODO add support for rendering {:?} in the REPL", other); 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,6 +394,33 @@ fn struct_to_ast<'a>(
vec vec
}; };
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();
let inner_content = env.subs.get_without_compacting(field.into_inner()).content;
let loc_expr = &*arena.alloc(Located {
value: ptr_to_ast(env, ptr, &Layout::Struct(field_layouts), &inner_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(),
};
let output = env.arena.alloc([loc_field]);
Expr::Record {
update: None,
fields: output,
}
} else {
debug_assert_eq!(sorted_fields.len(), field_layouts.len()); debug_assert_eq!(sorted_fields.len(), field_layouts.len());
// We'll advance this as we iterate through the fields // We'll advance this as we iterate through the fields
@ -373,7 +445,8 @@ fn struct_to_ast<'a>(
output.push(loc_field); output.push(loc_field);
// Advance the field pointer to the next field. // Advance the field pointer to the next field.
field_ptr = unsafe { field_ptr.offset(field_layout.stack_size(env.ptr_bytes) as isize) }; field_ptr =
unsafe { field_ptr.offset(field_layout.stack_size(env.ptr_bytes) as isize) };
} }
let output = output.into_bump_slice(); let output = output.into_bump_slice();
@ -383,6 +456,7 @@ fn struct_to_ast<'a>(
fields: output, fields: output,
} }
} }
}
fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a> { fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a> {
use Content::*; use Content::*;
@ -468,20 +542,17 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
debug_assert!(payload_vars_1.is_empty()); debug_assert!(payload_vars_1.is_empty());
debug_assert!(payload_vars_2.is_empty()); debug_assert!(payload_vars_2.is_empty());
let tag_name_as_str_1 = &tag_name_1.as_string(env.interns, env.home);
let tag_name_as_str_2 = &tag_name_2.as_string(env.interns, env.home);
let tag_name = if value { let tag_name = if value {
tag_name_as_str_1.max(tag_name_as_str_2) max_by_key(tag_name_1, tag_name_2, |n| {
n.as_string(env.interns, env.home)
})
} else { } else {
tag_name_as_str_1.min(tag_name_as_str_2) min_by_key(tag_name_1, tag_name_2, |n| {
n.as_string(env.interns, env.home)
})
}; };
if tag_name.starts_with('@') { tag_name_to_expr(env, tag_name)
Expr::PrivateTag(arena.alloc_str(tag_name))
} else {
Expr::GlobalTag(arena.alloc_str(tag_name))
}
} }
other => { other => {
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
@ -499,6 +570,117 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
} }
} }
fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> {
use Content::*;
let arena = env.arena;
match content {
Structure(flat_type) => {
match flat_type {
FlatType::Record(fields, _) => {
debug_assert_eq!(fields.len(), 1);
let (label, field) = fields.iter().next().unwrap();
let loc_label = Located {
value: &*arena.alloc_str(label.as_str()),
region: Region::zero(),
};
let assigned_field = {
// We may be multiple levels deep in nested tag unions
// and/or records (e.g. { a: { b: { c: True } } }),
// so we need to do this recursively on the field type.
let field_var = *field.as_inner();
let field_content = env.subs.get_without_compacting(field_var).content;
let loc_expr = Located {
value: byte_to_ast(env, value, &field_content),
region: Region::zero(),
};
AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr))
};
let loc_assigned_field = Located {
value: assigned_field,
region: Region::zero(),
};
Expr::Record {
update: None,
fields: arena.alloc([loc_assigned_field]),
}
}
FlatType::TagUnion(tags, _) if tags.len() == 1 => {
let (tag_name, payload_vars) = tags.iter().next().unwrap();
let loc_tag_expr = {
let tag_name = &tag_name.as_string(env.interns, env.home);
let tag_expr = if tag_name.starts_with('@') {
Expr::PrivateTag(arena.alloc_str(tag_name))
} else {
Expr::GlobalTag(arena.alloc_str(tag_name))
};
&*arena.alloc(Located {
value: tag_expr,
region: Region::zero(),
})
};
let payload = {
// Since this has the layout of a number, there should be
// exactly one payload in this tag.
debug_assert_eq!(payload_vars.len(), 1);
let var = *payload_vars.iter().next().unwrap();
let content = env.subs.get_without_compacting(var).content;
let loc_payload = &*arena.alloc(Located {
value: byte_to_ast(env, value, &content),
region: Region::zero(),
});
arena.alloc([loc_payload])
};
Expr::Apply(loc_tag_expr, payload, CalledVia::Space)
}
FlatType::TagUnion(tags, _) => {
// anything with fewer tags is not a byte
debug_assert!(tags.len() > 2);
let tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)> =
tags.iter().map(|(a, b)| (a.clone(), b.clone())).collect();
let union_variant = union_sorted_tags_help(env.arena, tags_vec, None, env.subs);
match union_variant {
UnionVariant::ByteUnion(tagnames) => {
let tag_name = &tagnames[value as usize];
let tag_expr = tag_name_to_expr(env, tag_name);
let loc_tag_expr = Located::at_zero(tag_expr);
Expr::Apply(env.arena.alloc(loc_tag_expr), &[], CalledVia::Space)
}
_ => unreachable!("invalid union variant for a Byte!"),
}
}
other => {
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
}
}
}
Alias(_, _, var) => {
let content = env.subs.get_without_compacting(*var).content;
byte_to_ast(env, value, &content)
}
other => {
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
}
}
}
fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> Expr<'a> { fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> Expr<'a> {
use Content::*; use Content::*;
@ -648,3 +830,30 @@ fn str_slice_to_ast<'a>(_arena: &'a Bump, string: &'a str) -> Expr<'a> {
Expr::Str(StrLiteral::PlainLine(string)) Expr::Str(StrLiteral::PlainLine(string))
} }
} }
// TODO this is currently nighly-only: use the implementation in std once it's stabilized
pub fn max_by<T, F: FnOnce(&T, &T) -> std::cmp::Ordering>(v1: T, v2: T, compare: F) -> T {
use std::cmp::Ordering;
match compare(&v1, &v2) {
Ordering::Less | Ordering::Equal => v2,
Ordering::Greater => v1,
}
}
pub fn min_by<T, F: FnOnce(&T, &T) -> std::cmp::Ordering>(v1: T, v2: T, compare: F) -> T {
use std::cmp::Ordering;
match compare(&v1, &v2) {
Ordering::Less | Ordering::Equal => v1,
Ordering::Greater => v2,
}
}
pub fn max_by_key<T, F: FnMut(&T) -> K, K: Ord>(v1: T, v2: T, mut f: F) -> T {
max_by(v1, v2, |v1, v2| f(v1).cmp(&f(v2)))
}
pub fn min_by_key<T, F: FnMut(&T) -> K, K: Ord>(v1: T, v2: T, mut f: F) -> T {
min_by(v1, v2, |v1, v2| f(v1).cmp(&f(v2)))
}

View file

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

View file

@ -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

View file

@ -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,8 +563,50 @@ 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
groups.push(cycle);
}
}
// 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_to_declaration(
cycle, group,
&env.closures, &env.closures,
&mut all_successors_with_self, &mut all_successors_with_self,
&can_defs_by_symbol, &can_defs_by_symbol,
@ -580,6 +614,9 @@ pub fn sort_can_defs(
); );
} }
} }
}
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>,

View file

@ -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);

View file

@ -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`"
match (&def.loc_expr.value, &signature) {
(
Closure {
function_type: fn_var,
closure_type: closure_var,
closure_ext_var,
return_type: ret_var,
captured_symbols,
arguments,
loc_body,
name,
..
}, },
def.loc_expr.region, Type::Function(arg_types, _, _),
&def.loc_expr.value, ) => {
annotation_expected.clone(), let expected = annotation_expected;
let region = def.loc_expr.region;
let loc_body_expr = &**loc_body;
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 closure_var = *closure_var;
let closure_ext_var = *closure_ext_var;
let ret_type = Type::Variable(ret_var);
vars.push(ret_var);
vars.push(closure_var);
vars.push(closure_ext_var);
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;
let pattern_expected = PExpected::ForReason(
PReason::TypedArg {
index: Index::zero_based(index),
opt_name: opt_label,
},
pattern_type.clone(),
loc_pattern.region,
); );
// ensure expected type unifies with annotated type constrain_pattern(
let storage_con = Eq( env,
expr_type, &loc_pattern.value,
annotation_expected.clone(), 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!()), Category::Storage(std::file!(), std::line!()),
def.loc_expr.region, loc_pattern.region,
); );
// TODO investigate if this let can be safely removed def_pattern_state.constraints.push(pattern_con);
let def_con = Let(Box::new(LetConstraint { }
}
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(), rigid_vars: Vec::new(),
flex_vars: Vec::new(), // empty because Roc function defs have no args flex_vars: state.vars,
def_types: SendMap::default(), // empty because Roc function defs have no args def_types: state.headers,
defs_constraint: storage_con, defs_constraint: And(state.constraints),
ret_constraint: expr_con, 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.vars.extend(&new_rigids);
rigid_info.constraints.push(Let(Box::new(LetConstraint { rigid_info.constraints.push(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: SendMap::default(), // no headers introduced (at this level) def_types: SendMap::default(), // no headers introduced (at this level)
defs_constraint: def_con, defs_constraint: def_con,
ret_constraint: True, ret_constraint: True,
}))); })));
rigid_info.def_types.extend(pattern_state.headers); rigid_info.def_types.extend(def_pattern_state.headers);
}
_ => todo!(),
}
} }
} }
} }

View file

@ -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>,

View file

@ -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
),
} }
} }

View file

@ -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
);
}
} }

View file

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

View file

@ -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);

View file

@ -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));
} }

View file

@ -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

View file

@ -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>,

View file

@ -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);

View file

@ -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

View file

@ -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)

View file

@ -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
^^^^^^^
"#
),
)
}
} }

View file

@ -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

View file

@ -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);

View file

@ -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)) ]",
);
}
} }

View file

@ -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.

View file

@ -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);

View file

@ -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" }

View file

@ -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,

View file

@ -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)

View file

@ -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,6 +195,7 @@ 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
@ -204,12 +208,14 @@ getMin = \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) ->
when rLeft is
Node Red rlK rlV rlL rlR -> Node Red rlK rlV rlL rlR ->
Node Node
Red Red
@ -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,7 +280,9 @@ 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 left is
Node lColor _ _ lLeft _ ->
when lColor is when lColor is
Black -> Black ->
when lLeft is when lLeft is
@ -294,10 +302,12 @@ removeMin = \dict ->
_ -> _ ->
Empty 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

View file

@ -16,8 +16,7 @@ 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
@ -29,12 +28,24 @@ let
] ]
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
@ -51,12 +62,7 @@ let
# llb deps # llb deps
libffi libffi
libxml2 libxml2
xorg.libX11
zlib zlib
vulkan-headers
vulkan-loader
vulkan-tools
vulkan-validation-layers
# faster builds - see https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#use-lld-for-the-linker # faster builds - see https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#use-lld-for-the-linker
lldPkg lldPkg
# dev tools # dev tools
@ -64,24 +70,13 @@ let
# (import ./nix/zls.nix { inherit pkgs zig; }) # (import ./nix/zls.nix { inherit pkgs zig; })
ccls 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 = ''