Merge branch 'trunk' into platform

This commit is contained in:
Richard Feldman 2020-10-18 23:59:44 -04:00 committed by GitHub
commit db2d99f56d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 402 additions and 138 deletions

View file

@ -974,9 +974,7 @@ mod gen_primitives {
}
#[test]
#[ignore]
fn io_poc() {
use roc_std::RocStr;
fn io_poc_effect() {
assert_evals_to!(
indoc!(
r#"
@ -984,23 +982,51 @@ mod gen_primitives {
Effect a : [ @Effect ({} -> a) ]
succeed : a -> Effect a
# succeed : a -> Effect a
succeed = \x -> @Effect \{} -> x
foo : Effect Float
# runEffect : Effect a -> a
runEffect = \@Effect thunk -> thunk {}
# foo : Effect Float
foo =
succeed 3.14
runEffect : Effect a -> a
runEffect = \@Effect thunk -> thunk {}
main : Float
main =
runEffect foo
"#
),
RocStr::from_slice(&"Foo".as_bytes()),
RocStr
3.14,
f64
);
}
#[test]
fn io_poc_desugared() {
assert_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
# succeed : a -> ({} -> a)
succeed = \x -> \{} -> x
foo : {} -> Float
foo =
succeed 3.14
# runEffect : ({} -> a) -> a
runEffect = \thunk -> thunk {}
main : Float
main =
runEffect foo
"#
),
3.14,
f64
);
}
}

View file

@ -402,6 +402,34 @@ mod gen_records {
#[test]
fn optional_field_when_use_default() {
assert_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
f = \r ->
when r is
{ x: Blue, y ? 3 } -> y
{ x: Red, y ? 5 } -> y
main =
a = f { x: Blue, y: 7 }
b = f { x: Blue }
c = f { x: Red, y: 11 }
d = f { x: Red }
a * b * c * d
"#
),
3 * 5 * 7 * 11,
i64
);
}
#[test]
#[ignore]
fn optional_field_when_use_default_nested() {
assert_evals_to!(
indoc!(
r#"
@ -425,6 +453,27 @@ mod gen_records {
#[test]
fn optional_field_when_no_use_default() {
assert_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
f = \r ->
{ x ? 10, y } = r
x + y
main =
f { x: 4, y: 9 }
"#
),
13,
i64
);
}
#[test]
#[ignore]
fn optional_field_when_no_use_default_nested() {
assert_evals_to!(
indoc!(
r#"
@ -445,10 +494,13 @@ mod gen_records {
assert_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
f = \r ->
{ x ? 10, y } = r
x + y
main =
f { y: 9 }
"#
),
@ -459,6 +511,27 @@ mod gen_records {
#[test]
fn optional_field_let_no_use_default() {
assert_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
f = \r ->
{ x ? 10, y } = r
x + y
main =
f { x: 4, y: 9 }
"#
),
13,
i64
);
}
#[test]
#[ignore]
fn optional_field_let_no_use_default_nested() {
assert_evals_to!(
indoc!(
r#"
@ -492,6 +565,25 @@ mod gen_records {
#[test]
fn optional_field_function_no_use_default() {
assert_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
f = \{ x ? 10, y } -> x + y
main =
f { x: 4, y: 9 }
"#
),
13,
i64
);
}
#[test]
#[ignore]
fn optional_field_function_no_use_default_nested() {
assert_evals_to!(
indoc!(
r#"

View file

@ -413,6 +413,31 @@ mod gen_tags {
#[test]
fn maybe_is_just() {
assert_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
Maybe a : [ Just a, Nothing ]
isJust : Maybe a -> Bool
isJust = \list ->
when list is
Nothing -> False
Just _ -> True
main =
isJust (Just 42)
"#
),
true,
bool
);
}
#[test]
#[ignore]
fn maybe_is_just_nested() {
assert_evals_to!(
indoc!(
r#"

View file

@ -348,7 +348,7 @@ macro_rules! assert_evals_to {
assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak);
}
{
// assert_opt_evals_to!($src, $expected, $ty, $transform, $leak);
assert_opt_evals_to!($src, $expected, $ty, $transform, $leak);
}
};
}

View file

@ -2244,8 +2244,12 @@ fn add_def_to_module<'a>(
return_type: ret_var,
arguments: loc_args,
loc_body,
captured_symbols,
..
} => {
// this is a top-level definition, it should not capture anything
debug_assert!(captured_symbols.is_empty());
// If this is an exposed symbol, we need to
// register it as such. Otherwise, since it
// never gets called by Roc code, it will never

View file

@ -51,7 +51,6 @@ pub struct Proc<'a> {
pub name: Symbol,
pub args: &'a [(Layout<'a>, Symbol)],
pub body: Stmt<'a>,
pub closes_over: Layout<'a>,
pub ret_layout: Layout<'a>,
pub is_self_recursive: SelfRecursive,
}
@ -1434,7 +1433,7 @@ fn specialize_external<'a>(
}
}
let (proc_args, closes_over, ret_layout) =
let (proc_args, ret_layout) =
build_specialized_proc_from_var(env, layout_cache, pattern_symbols, fn_var)?;
// reset subs, so we don't get type errors when specializing for a different signature
@ -1451,7 +1450,6 @@ fn specialize_external<'a>(
name: proc_name,
args: proc_args,
body: specialized_body,
closes_over,
ret_layout,
is_self_recursive: recursivity,
};
@ -1465,15 +1463,42 @@ fn build_specialized_proc_from_var<'a>(
layout_cache: &mut LayoutCache<'a>,
pattern_symbols: &[Symbol],
fn_var: Variable,
) -> Result<(&'a [(Layout<'a>, Symbol)], Layout<'a>, Layout<'a>), LayoutProblem> {
) -> Result<(&'a [(Layout<'a>, Symbol)], Layout<'a>), LayoutProblem> {
match layout_cache.from_var(env.arena, fn_var, env.subs) {
Ok(Layout::FunctionPointer(pattern_layouts, ret_layout)) => {
let mut pattern_layouts_vec = Vec::with_capacity_in(pattern_layouts.len(), env.arena);
pattern_layouts_vec.extend_from_slice(pattern_layouts);
build_specialized_proc(
env.arena,
pattern_symbols,
pattern_layouts_vec,
None,
ret_layout.clone(),
)
}
Ok(Layout::Closure(pattern_layouts, closure_layout, ret_layout)) => {
let mut pattern_layouts_vec = Vec::with_capacity_in(pattern_layouts.len(), env.arena);
pattern_layouts_vec.extend_from_slice(pattern_layouts);
build_specialized_proc(
env.arena,
pattern_symbols,
pattern_layouts_vec,
Some(closure_layout),
ret_layout.clone(),
)
}
_ => {
match env.subs.get_without_compacting(fn_var).content {
Content::Structure(FlatType::Func(pattern_vars, closure_var, ret_var)) => {
build_specialized_proc(
let closure_layout = ClosureLayout::from_var(env.arena, env.subs, closure_var)?;
build_specialized_proc_adapter(
env,
layout_cache,
pattern_symbols,
&pattern_vars,
Some(closure_var),
closure_layout,
ret_var,
)
}
@ -1487,63 +1512,129 @@ fn build_specialized_proc_from_var<'a>(
}
_ => {
// a top-level constant 0-argument thunk
build_specialized_proc(env, layout_cache, pattern_symbols, &[], None, fn_var)
build_specialized_proc_adapter(
env,
layout_cache,
pattern_symbols,
&[],
None,
fn_var,
)
}
}
}
}
}
#[allow(clippy::type_complexity)]
fn build_specialized_proc<'a>(
fn build_specialized_proc_adapter<'a>(
env: &mut Env<'a, '_>,
layout_cache: &mut LayoutCache<'a>,
pattern_symbols: &[Symbol],
pattern_vars: &[Variable],
closure_var: Option<Variable>,
opt_closure_layout: Option<ClosureLayout<'a>>,
ret_var: Variable,
) -> Result<(&'a [(Layout<'a>, Symbol)], Layout<'a>, Layout<'a>), LayoutProblem> {
let mut proc_args = Vec::with_capacity_in(pattern_vars.len(), &env.arena);
) -> Result<(&'a [(Layout<'a>, Symbol)], Layout<'a>), LayoutProblem> {
let mut arg_layouts = Vec::with_capacity_in(pattern_vars.len(), &env.arena);
for (arg_var, arg_name) in pattern_vars.iter().zip(pattern_symbols.iter()) {
for arg_var in pattern_vars {
let layout = layout_cache.from_var(&env.arena, *arg_var, env.subs)?;
proc_args.push((layout, *arg_name));
arg_layouts.push(layout);
}
// is the final argument symbol the closure symbol? then add the closure variable to the
// pattern variables
if pattern_symbols.last() == Some(&Symbol::ARG_CLOSURE) {
let layout = layout_cache.from_var(&env.arena, closure_var.unwrap(), env.subs)?;
proc_args.push((layout, Symbol::ARG_CLOSURE));
debug_assert_eq!(
pattern_vars.len() + 1,
pattern_symbols.len(),
"Tried to zip two vecs with different lengths!"
);
} else {
debug_assert_eq!(
pattern_vars.len(),
pattern_symbols.len(),
"Tried to zip two vecs with different lengths!"
);
}
let proc_args = proc_args.into_bump_slice();
let closes_over = match closure_var {
Some(cvar) => match layout_cache.from_var(&env.arena, cvar, env.subs) {
Ok(layout) => layout,
Err(LayoutProblem::UnresolvedTypeVar) => Layout::Struct(&[]),
Err(err) => panic!("TODO handle invalid function {:?}", err),
},
None => Layout::Struct(&[]),
};
let ret_layout = layout_cache
.from_var(&env.arena, ret_var, env.subs)
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err));
Ok((proc_args, closes_over, ret_layout))
build_specialized_proc(
env.arena,
pattern_symbols,
arg_layouts,
opt_closure_layout,
ret_layout,
)
}
#[allow(clippy::type_complexity)]
fn build_specialized_proc<'a>(
arena: &'a Bump,
pattern_symbols: &[Symbol],
pattern_layouts: Vec<Layout<'a>>,
opt_closure_layout: Option<ClosureLayout<'a>>,
ret_layout: Layout<'a>,
) -> Result<(&'a [(Layout<'a>, Symbol)], Layout<'a>), LayoutProblem> {
let mut proc_args = Vec::with_capacity_in(pattern_layouts.len(), arena);
let pattern_layouts_len = pattern_layouts.len();
for (arg_layout, arg_name) in pattern_layouts.into_iter().zip(pattern_symbols.iter()) {
proc_args.push((arg_layout, *arg_name));
}
// Given
//
// foo =
// x = 42
//
// f = \{} -> x
//
// We desugar that into
//
// f = \{}, x -> x
//
// foo =
// x = 42
//
// f_closure = { ptr: f, closure: x }
//
// then
match opt_closure_layout {
Some(layout) if pattern_symbols.last() == Some(&Symbol::ARG_CLOSURE) => {
// here we define the lifted (now top-level) f function. Its final argument is `Symbol::ARG_CLOSURE`,
// it stores the closure structure (just an integer in this case)
proc_args.push((layout.as_layout(), Symbol::ARG_CLOSURE));
debug_assert_eq!(
pattern_layouts_len + 1,
pattern_symbols.len(),
"Tried to zip two vecs with different lengths!"
);
}
Some(layout) => {
// else if there is a closure layout, we're building the `f_closure` value
// that means we're really creating a ( function_ptr, closure_data ) pair
let closure_data_layout = layout.as_layout();
let function_ptr_layout = Layout::FunctionPointer(
arena.alloc([Layout::Struct(&[]), closure_data_layout.clone()]),
arena.alloc(ret_layout),
);
let closure_layout =
Layout::Struct(arena.alloc([function_ptr_layout, closure_data_layout]));
return Ok((&[], closure_layout));
}
None => {
// else we're making a normal function, no closure problems to worry about
// we'll just assert some things
// make sure there is not arg_closure argument without a closure layout
debug_assert!(pattern_symbols.last() != Some(&Symbol::ARG_CLOSURE));
// since this is not a closure, the number of arguments should match between symbols
// and layout
debug_assert_eq!(
pattern_layouts_len,
pattern_symbols.len(),
"Tried to zip two vecs with different lengths!"
);
}
}
let proc_args = proc_args.into_bump_slice();
Ok((proc_args, ret_layout))
}
fn specialize<'a>(

View file

@ -64,6 +64,54 @@ impl<'a> ClosureLayout<'a> {
}
}
pub fn from_var(
arena: &'a Bump,
subs: &Subs,
closure_var: Variable,
) -> Result<Option<Self>, LayoutProblem> {
let mut tags = std::vec::Vec::new();
match roc_types::pretty_print::chase_ext_tag_union(subs, closure_var, &mut tags) {
Ok(()) | Err((_, Content::FlexVar(_))) if !tags.is_empty() => {
// this is a closure
let variant = union_sorted_tags_help(arena, tags, None, subs);
use UnionVariant::*;
match variant {
Never | Unit => {
// a max closure size of 0 means this is a standart top-level function
Ok(None)
}
BoolUnion { .. } => {
let closure_layout = ClosureLayout::from_bool(arena);
Ok(Some(closure_layout))
}
ByteUnion(_) => {
let closure_layout = ClosureLayout::from_byte(arena);
Ok(Some(closure_layout))
}
Unwrapped(layouts) => {
let closure_layout =
ClosureLayout::from_unwrapped(layouts.into_bump_slice());
Ok(Some(closure_layout))
}
Wrapped(_tags) => {
// Wrapped(Vec<'a, (TagName, &'a [Layout<'a>])>),
todo!("can't specialize multi-size closures yet")
}
}
}
Ok(()) | Err((_, Content::FlexVar(_))) => {
// a max closure size of 0 means this is a standart top-level function
Ok(None)
}
_ => panic!("called ClosureLayout.from_var on invalid input"),
}
}
pub fn extend_function_layout(
arena: &'a Bump,
argument_layouts: &'a [Layout<'a>],
@ -567,52 +615,12 @@ fn layout_from_flat_type<'a>(
let ret = Layout::from_var(env, ret_var)?;
let mut tags = std::vec::Vec::new();
match roc_types::pretty_print::chase_ext_tag_union(env.subs, closure_var, &mut tags) {
Ok(()) | Err((_, Content::FlexVar(_))) if !tags.is_empty() => {
// this is a closure
let variant = union_sorted_tags_help(env.arena, tags, None, env.subs);
let fn_args = fn_args.into_bump_slice();
let ret = arena.alloc(ret);
use UnionVariant::*;
match variant {
Never | Unit => {
// a max closure size of 0 means this is a standart top-level function
Ok(Layout::FunctionPointer(fn_args, ret))
}
BoolUnion { .. } => {
let closure_layout = ClosureLayout::from_bool(env.arena);
Ok(Layout::Closure(fn_args, closure_layout, ret))
}
ByteUnion(_) => {
let closure_layout = ClosureLayout::from_byte(env.arena);
Ok(Layout::Closure(fn_args, closure_layout, ret))
}
Unwrapped(layouts) => {
let closure_layout =
ClosureLayout::from_unwrapped(layouts.into_bump_slice());
Ok(Layout::Closure(fn_args, closure_layout, ret))
}
Wrapped(_tags) => {
// Wrapped(Vec<'a, (TagName, &'a [Layout<'a>])>),
todo!("can't specialize multi-size closures yet")
}
}
}
Ok(()) | Err((_, Content::FlexVar(_))) => {
// a max closure size of 0 means this is a standart top-level function
Ok(Layout::FunctionPointer(
fn_args.into_bump_slice(),
arena.alloc(ret),
))
}
Err(_) => todo!(),
match ClosureLayout::from_var(env.arena, env.subs, closure_var)? {
Some(closure_layout) => Ok(Layout::Closure(fn_args, closure_layout, ret)),
None => Ok(Layout::FunctionPointer(fn_args, ret)),
}
}
Record(fields, ext_var) => {
@ -641,7 +649,10 @@ fn layout_from_flat_type<'a>(
use roc_types::types::RecordField::*;
match field {
Optional(_) => {
// optional values are not available at this point
// when an optional field reaches this stage, the field was truly
// optional, and not unified to be demanded or required
// therefore, there is no such field on the record, and we ignore this
// field from now on.
continue;
}
Required(var) => var,

View file

@ -2013,6 +2013,8 @@ mod test_mono {
compiles_to_ir(
indoc!(
r#"
app Test provides [ main ] imports []
swap : Int, Int, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
@ -2026,6 +2028,7 @@ mod test_mono {
_ ->
[]
main =
swap 0 0 [0x1]
"#
),

View file

@ -1121,7 +1121,7 @@ fn adjust_rank_content(
rank
}
Func(arg_vars, _closure_var, ret_var) => {
Func(arg_vars, closure_var, ret_var) => {
let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, ret_var);
// TODO investigate further.
@ -1129,13 +1129,15 @@ fn adjust_rank_content(
// My theory is that because the closure_var contains variables already
// contained in the signature only, it does not need to be part of the rank
// calculuation
// rank = rank.max(adjust_rank(
// subs,
// young_mark,
// visit_mark,
// group_rank,
// closure_var,
// ));
if true {
rank = rank.max(adjust_rank(
subs,
young_mark,
visit_mark,
group_rank,
closure_var,
));
}
for var in arg_vars {
rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var));
@ -1207,14 +1209,19 @@ fn adjust_rank_content(
}
}
Alias(_, args, _) => {
Alias(_, args, real_var) => {
let mut rank = Rank::toplevel();
// from elm-compiler: THEORY: anything in the real_var would be Rank::toplevel()
for (_, var) in args {
rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var));
}
// from elm-compiler: THEORY: anything in the real_var would be Rank::toplevel()
// this theory is not true in Roc! aliases of function types capture the closure var
rank = rank.max(adjust_rank(
subs, young_mark, visit_mark, group_rank, real_var,
));
rank
}
}

View file

@ -1423,6 +1423,7 @@ mod solve_uniq_expr {
}
#[test]
#[ignore]
fn quicksort() {
// theory: partition is handled before swap, so swap is not known, and therefore not taken
// out of its closure
@ -2838,6 +2839,7 @@ mod solve_uniq_expr {
}
#[test]
#[ignore]
fn astar_full_code() {
// theory: things are canonicalized in an order that leaves too much captured
with_larger_debug_stack(|| {

View file

@ -163,14 +163,13 @@ impl fmt::Debug for Type {
for (index, arg) in args.iter().enumerate() {
if index > 0 {
", ".fmt(f)?;
write!(f, ", ")?;
}
arg.fmt(f)?;
write!(f, "{:?}", arg)?;
}
write!(f, " -")?;
closure.fmt(f)?;
write!(f, " |{:?}|", closure)?;
write!(f, " -> ")?;
ret.fmt(f)?;

View file

@ -179,6 +179,10 @@ fn unify_alias(
problems.extend(merge(subs, &ctx, other_content.clone()));
if problems.is_empty() {
problems.extend(unify_pool(subs, pool, real_var, *other_real_var));
}
problems
} else {
mismatch!()