Merge pull request #450 from rtfeldman/gen-optional-field

Gen optional fields
This commit is contained in:
Richard Feldman 2020-08-13 00:40:25 -04:00 committed by GitHub
commit 27b2d10b2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 471 additions and 98 deletions

View file

@ -274,7 +274,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
FunctionCall {
call_type: ByName(name),
layout,
full_layout,
args,
..
} => {
@ -287,7 +287,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
call_with_args(
env,
layout_ids,
layout,
&full_layout,
*name,
parent,
arg_tuples.into_bump_slice(),
@ -296,7 +296,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
FunctionCall {
call_type: ByPointer(name),
layout: _,
args,
..
} => {

View file

@ -399,4 +399,110 @@ mod gen_records {
(i64, i64)
);
}
#[test]
fn optional_field_when_use_default() {
assert_evals_to!(
indoc!(
r#"
f = \r ->
when r is
{ x: Blue, y ? 3 } -> y
{ x: Red, y ? 5 } -> y
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]
fn optional_field_when_no_use_default() {
assert_evals_to!(
indoc!(
r#"
f = \r ->
{ x ? 10, y } = r
x + y
f { x: 4, y: 9 }
"#
),
13,
i64
);
}
#[test]
fn optional_field_let_use_default() {
assert_evals_to!(
indoc!(
r#"
f = \r ->
{ x ? 10, y } = r
x + y
f { y: 9 }
"#
),
19,
i64
);
}
#[test]
fn optional_field_let_no_use_default() {
assert_evals_to!(
indoc!(
r#"
f = \r ->
{ x ? 10, y } = r
x + y
f { x: 4, y: 9 }
"#
),
13,
i64
);
}
#[test]
fn optional_field_function_use_default() {
assert_evals_to!(
indoc!(
r#"
f = \{ x ? 10, y } -> x + y
f { y: 9 }
"#
),
19,
i64
);
}
#[test]
fn optional_field_function_no_use_default() {
assert_evals_to!(
indoc!(
r#"
f = \{ x ? 10, y } -> x + y
f { x: 4, y: 9 }
"#
),
13,
i64
);
}
}

View file

@ -409,7 +409,7 @@ fn test_at_path<'a>(selected_path: &Path, branch: &Branch<'a>, all_tests: &mut V
arguments.push((Pattern::Underscore, destruct.layout.clone()));
}
DestructType::Optional(_expr) => {
todo!("test_at_type for optional destruct");
arguments.push((Pattern::Underscore, destruct.layout.clone()));
}
}
}
@ -541,9 +541,7 @@ fn to_relevant_branch_help<'a>(
let pattern = match destruct.typ {
DestructType::Guard(guard) => guard.clone(),
DestructType::Required => Pattern::Underscore,
DestructType::Optional(_expr) => {
todo!("TODO decision tree for optional field branch");
}
DestructType::Optional(_expr) => Pattern::Underscore,
};
(

View file

@ -467,7 +467,8 @@ pub enum Expr<'a> {
FunctionPointer(Symbol, Layout<'a>),
FunctionCall {
call_type: CallType,
layout: Layout<'a>,
full_layout: Layout<'a>,
ret_layout: Layout<'a>,
arg_layouts: &'a [Layout<'a>],
args: &'a [Symbol],
},
@ -1409,7 +1410,8 @@ pub fn with_hole<'a>(
for (label, layout) in sorted_fields.into_iter() {
field_layouts.push(layout);
let field = fields.remove(&label).unwrap();
match fields.remove(&label) {
Some(field) => {
if let roc_can::expr::Expr::Var(symbol) = field.loc_expr.value {
field_symbols.push(symbol);
can_fields.push(None);
@ -1418,6 +1420,12 @@ pub fn with_hole<'a>(
can_fields.push(Some(field));
}
}
None => {
// this field was optional, but not given
continue;
}
}
}
// creating a record from the var will unpack it if it's just a single field.
let layout = layout_cache
@ -1704,12 +1712,23 @@ pub fn with_hole<'a>(
let mut index = None;
let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
for (current, (label, field_layout)) in sorted_fields.into_iter().enumerate() {
let mut current = 0;
for (label, opt_field_layout) in sorted_fields.into_iter() {
match opt_field_layout {
Err(_) => {
// this was an optional field, and now does not exist!
// do not increment `current`!
}
Ok(field_layout) => {
field_layouts.push(field_layout);
if label == field {
index = Some(current);
}
current += 1;
}
}
}
let record_symbol = if let roc_can::expr::Expr::Var(symbol) = loc_expr.value {
@ -1831,13 +1850,13 @@ pub fn with_hole<'a>(
arg_symbols.push(env.unique_symbol());
}
let layout = layout_cache
let full_layout = layout_cache
.from_var(env.arena, fn_var, env.subs)
.unwrap_or_else(|err| {
panic!("TODO turn fn_var into a RuntimeError {:?}", err)
});
let arg_layouts = match layout {
let arg_layouts = match full_layout {
Layout::FunctionPointer(args, _) => args,
_ => unreachable!("function has layout that is not function pointer"),
};
@ -1854,7 +1873,8 @@ pub fn with_hole<'a>(
assigned,
Expr::FunctionCall {
call_type: CallType::ByPointer(function_symbol),
layout,
full_layout,
ret_layout: ret_layout.clone(),
args: arg_symbols,
arg_layouts,
},
@ -2136,8 +2156,13 @@ pub fn from_can<'a>(
// convert the continuation
let mut stmt = from_can(env, cont.value, procs, layout_cache);
if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value {
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
.unwrap()
} else {
let outer_symbol = env.unique_symbol();
stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
stmt =
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
.unwrap();
// convert the def body, store in outer_symbol
@ -2151,6 +2176,7 @@ pub fn from_can<'a>(
)
}
}
}
_ => {
let symbol = env.unique_symbol();
@ -2534,7 +2560,8 @@ fn substitute_in_expr<'a>(
call_type,
args,
arg_layouts,
layout,
ret_layout,
full_layout,
} => {
let opt_call_type = match call_type {
CallType::ByName(s) => substitute(subs, *s).map(CallType::ByName),
@ -2562,7 +2589,8 @@ fn substitute_in_expr<'a>(
call_type,
args,
arg_layouts: *arg_layouts,
layout: layout.clone(),
ret_layout: ret_layout.clone(),
full_layout: full_layout.clone(),
})
} else {
None
@ -2822,8 +2850,15 @@ fn store_record_destruct<'a>(
env.arena.alloc(stmt),
);
}
DestructType::Optional(_expr) => {
todo!("TODO monomorphize optional field destructure's default expr");
DestructType::Optional(expr) => {
stmt = with_hole(
env,
expr.clone(),
procs,
layout_cache,
destruct.symbol,
env.arena.alloc(stmt),
);
}
DestructType::Guard(guard_pattern) => match &guard_pattern {
Identifier(symbol) => {
@ -2909,24 +2944,30 @@ fn call_by_name<'a>(
}
}
let full_layout = layout.clone();
// TODO does this work?
let empty = &[] as &[_];
let (arg_layouts, layout) = if let Layout::FunctionPointer(args, rlayout) = layout {
let (arg_layouts, ret_layout) = if let Layout::FunctionPointer(args, rlayout) = layout {
(args, rlayout)
} else {
(empty, &layout)
};
// If we've already specialized this one, no further work is needed.
if procs.specialized.contains_key(&(proc_name, layout.clone())) {
if procs
.specialized
.contains_key(&(proc_name, full_layout.clone()))
{
let call = Expr::FunctionCall {
call_type: CallType::ByName(proc_name),
layout: layout.clone(),
ret_layout: ret_layout.clone(),
full_layout: full_layout.clone(),
arg_layouts,
args: field_symbols,
};
let mut result = Stmt::Let(assigned, call, layout.clone(), hole);
let mut result = Stmt::Let(assigned, call, ret_layout.clone(), hole);
for ((_, loc_arg), symbol) in
loc_args.into_iter().rev().zip(field_symbols.iter().rev())
@ -2967,16 +3008,22 @@ fn call_by_name<'a>(
match &mut procs.pending_specializations {
Some(pending_specializations) => {
// register the pending specialization, so this gets code genned later
add_pending(pending_specializations, proc_name, layout.clone(), pending);
add_pending(
pending_specializations,
proc_name,
full_layout.clone(),
pending,
);
let call = Expr::FunctionCall {
call_type: CallType::ByName(proc_name),
layout: layout.clone(),
ret_layout: ret_layout.clone(),
full_layout: full_layout.clone(),
arg_layouts,
args: field_symbols,
};
let mut result = Stmt::Let(assigned, call, layout.clone(), hole);
let mut result = Stmt::Let(assigned, call, ret_layout.clone(), hole);
for ((_, loc_arg), symbol) in
loc_args.into_iter().rev().zip(field_symbols.iter().rev())
@ -3010,7 +3057,7 @@ fn call_by_name<'a>(
// (We had a bug around this before this system existed!)
procs
.specialized
.insert((proc_name, layout.clone()), InProgress);
.insert((proc_name, full_layout.clone()), InProgress);
match specialize(
env,
@ -3023,17 +3070,18 @@ fn call_by_name<'a>(
Ok(proc) => {
procs
.specialized
.insert((proc_name, layout.clone()), Done(proc));
.insert((proc_name, full_layout.clone()), Done(proc));
let call = Expr::FunctionCall {
call_type: CallType::ByName(proc_name),
layout: layout.clone(),
ret_layout: ret_layout.clone(),
full_layout: full_layout.clone(),
arg_layouts,
args: field_symbols,
};
let mut result =
Stmt::Let(assigned, call, layout.clone(), hole);
Stmt::Let(assigned, call, ret_layout.clone(), hole);
for ((_, loc_arg), symbol) in loc_args
.into_iter()
@ -3325,6 +3373,7 @@ pub fn from_can_pattern<'a>(
destructs,
..
} => {
// sorted fields based on the destruct
let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena);
let mut destructs = destructs.clone();
destructs.sort_by(|a, b| a.value.label.cmp(&b.value.label));
@ -3332,12 +3381,24 @@ pub fn from_can_pattern<'a>(
let mut it = destructs.iter();
let mut opt_destruct = it.next();
// sorted fields based on the type
let sorted_fields = crate::layout::sort_record_fields(env.arena, *whole_var, env.subs);
let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
for (label, field_layout) in sorted_fields.into_iter() {
if let Some(destruct) = opt_destruct {
// next we step through both sequences of fields. The outer loop is the sequence based
// on the type, since not all fields need to actually be destructured in the source
// language.
//
// However in mono patterns, we do destruct all patterns (but use Underscore) when
// in the source the field is not matche in the source language.
//
// Optional fields somewhat complicate the matter here
for (label, opt_field_layout) in sorted_fields.into_iter() {
match opt_field_layout {
Ok(field_layout) => {
match opt_destruct {
Some(destruct) => {
if destruct.value.label == label {
opt_destruct = it.next();
@ -3356,6 +3417,47 @@ pub fn from_can_pattern<'a>(
typ: DestructType::Guard(Pattern::Underscore),
});
}
}
None => {
// the remainder of the fields (from the type) is not matched on in
// this pattern; to fill it out, we put underscores
mono_destructs.push(RecordDestruct {
label: label.clone(),
symbol: env.unique_symbol(),
layout: field_layout.clone(),
typ: DestructType::Guard(Pattern::Underscore),
});
}
}
field_layouts.push(field_layout);
}
Err(field_layout) => {
// this field was optional, and now does not exist
// if it was actually matched on, we need to evaluate the default
match opt_destruct {
Some(destruct) => {
if destruct.value.label == label {
opt_destruct = it.next();
mono_destructs.push(RecordDestruct {
label: destruct.value.label.clone(),
symbol: destruct.value.symbol,
layout: field_layout,
typ: match &destruct.value.typ {
roc_can::pattern::DestructType::Optional(
_,
loc_expr,
) => {
// if we reach this stage, the optional field is not present
// so use the default
DestructType::Optional(loc_expr.value.clone())
}
_ => unreachable!(
"only optional destructs can be optional fields"
),
},
});
} else {
// insert underscore pattern
mono_destructs.push(RecordDestruct {
@ -3365,7 +3467,14 @@ pub fn from_can_pattern<'a>(
typ: DestructType::Guard(Pattern::Underscore),
});
}
field_layouts.push(field_layout);
}
None => {
// this field does not exist, and was not request in the pattern match
continue;
}
}
}
}
}
Pattern::RecordDestructure(
@ -3388,8 +3497,10 @@ fn from_can_record_destruct<'a>(
layout: field_layout,
typ: match &can_rd.typ {
roc_can::pattern::DestructType::Required => DestructType::Required,
roc_can::pattern::DestructType::Optional(_, loc_expr) => {
DestructType::Optional(loc_expr.value.clone())
roc_can::pattern::DestructType::Optional(_, _) => {
// if we reach this stage, the optional field is present
// DestructType::Optional(loc_expr.value.clone())
DestructType::Required
}
roc_can::pattern::DestructType::Guard(_, loc_pattern) => {
DestructType::Guard(from_can_pattern(env, layout_cache, &loc_pattern.value))

View file

@ -366,7 +366,17 @@ fn layout_from_flat_type<'a>(
for (_, field) in sorted_fields {
use LayoutProblem::*;
let field_var = field.into_inner();
let field_var = {
use roc_types::types::RecordField::*;
match field {
Optional(_) => {
// optional values are not available at this point
continue;
}
Required(var) => var,
Demanded(var) => var,
}
};
let field_content = subs.get_without_compacting(field_var).content;
match Layout::new(arena, field_content, subs) {
@ -414,7 +424,7 @@ pub fn sort_record_fields<'a>(
arena: &'a Bump,
var: Variable,
subs: &Subs,
) -> Vec<'a, (Lowercase, Layout<'a>)> {
) -> Vec<'a, (Lowercase, Result<Layout<'a>, Layout<'a>>)> {
let mut fields_map = MutMap::default();
match roc_types::pretty_print::chase_ext_record(subs, var, &mut fields_map) {
@ -422,13 +432,24 @@ pub fn sort_record_fields<'a>(
// Sort the fields by label
let mut sorted_fields = Vec::with_capacity_in(fields_map.len(), arena);
use roc_types::types::RecordField;
for (label, field) in fields_map {
let var = field.into_inner();
let var = match field {
RecordField::Demanded(v) => v,
RecordField::Required(v) => v,
RecordField::Optional(v) => {
let layout =
Layout::from_var(arena, v, subs).expect("invalid layout from var");
sorted_fields.push((label, Err(layout)));
continue;
}
};
let layout = Layout::from_var(arena, var, subs).expect("invalid layout from var");
// Drop any zero-sized fields like {}
if !layout.is_zero_sized() {
sorted_fields.push((label, layout));
sorted_fields.push((label, Ok(layout)));
}
}

View file

@ -526,15 +526,15 @@ mod test_mono {
"#,
indoc!(
r#"
procedure List.5 (#Attr.2, #Attr.3):
let Test.9 = lowlevel ListAppend #Attr.2 #Attr.3;
ret Test.9;
procedure Test.0 (Test.2):
let Test.8 = 42i64;
let Test.7 = CallByName List.5 Test.2 Test.8;
ret Test.7;
procedure List.5 (#Attr.2, #Attr.3):
let Test.9 = lowlevel ListAppend #Attr.2 #Attr.3;
ret Test.9;
let Test.5 = 1i64;
let Test.6 = 2i64;
let Test.4 = Array [Test.5, Test.6];
@ -580,12 +580,16 @@ mod test_mono {
"#,
indoc!(
r#"
procedure Num.14 (#Attr.2, #Attr.3):
let Test.13 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.13;
procedure List.7 (#Attr.2):
let Test.9 = lowlevel ListLen #Attr.2;
ret Test.9;
procedure Num.14 (#Attr.2, #Attr.3):
let Test.11 = lowlevel NumAdd #Attr.2 #Attr.3;
procedure List.7 (#Attr.2):
let Test.11 = lowlevel ListLen #Attr.2;
ret Test.11;
let Test.8 = 1f64;
@ -901,6 +905,12 @@ mod test_mono {
),
indoc!(
r#"
procedure Test.1 (Test.3):
let Test.9 = 0i64;
let Test.10 = 0i64;
let Test.8 = CallByName List.4 Test.3 Test.9 Test.10;
ret Test.8;
procedure List.4 (#Attr.2, #Attr.3, #Attr.4):
let Test.14 = lowlevel ListLen #Attr.2;
let Test.12 = lowlevel NumLt #Attr.3 Test.14;
@ -910,12 +920,6 @@ mod test_mono {
else
ret #Attr.2;
procedure Test.1 (Test.3):
let Test.9 = 0i64;
let Test.10 = 0i64;
let Test.8 = CallByName List.4 Test.3 Test.9 Test.10;
ret Test.8;
let Test.5 = 1i64;
let Test.6 = 2i64;
let Test.7 = 3i64;
@ -928,6 +932,140 @@ mod test_mono {
)
}
#[test]
fn record_optional_field_let_no_use_default() {
compiles_to_ir(
indoc!(
r#"
f = \r ->
{ x ? 10, y } = r
x + y
f { x: 4, y: 9 }
"#
),
indoc!(
r#"
procedure Num.14 (#Attr.2, #Attr.3):
let Test.10 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.10;
procedure Test.0 (Test.2):
let Test.3 = Index 0 Test.2;
let Test.4 = Index 1 Test.2;
let Test.9 = CallByName Num.14 Test.3 Test.4;
ret Test.9;
let Test.7 = 4i64;
let Test.8 = 9i64;
let Test.6 = Struct {Test.7, Test.8};
let Test.5 = CallByName Test.0 Test.6;
ret Test.5;
"#
),
)
}
#[test]
fn record_optional_field_let_use_default() {
compiles_to_ir(
indoc!(
r#"
f = \r ->
{ x ? 10, y } = r
x + y
f { y: 9 }
"#
),
indoc!(
r#"
procedure Num.14 (#Attr.2, #Attr.3):
let Test.9 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.9;
procedure Test.0 (Test.2):
let Test.3 = 10i64;
let Test.4 = Index 1 Test.2;
let Test.8 = CallByName Num.14 Test.3 Test.4;
ret Test.8;
let Test.7 = 9i64;
let Test.6 = Struct {Test.7};
let Test.5 = CallByName Test.0 Test.6;
ret Test.5;
"#
),
)
}
#[test]
fn record_optional_field_function_no_use_default() {
compiles_to_ir(
indoc!(
r#"
f = \{ x ? 10, y } -> x + y
f { x: 4, y: 9 }
"#
),
indoc!(
r#"
procedure Num.14 (#Attr.2, #Attr.3):
let Test.10 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.10;
procedure Test.0 (Test.4):
let Test.2 = Index 0 Test.4;
let Test.3 = Index 1 Test.4;
let Test.9 = CallByName Num.14 Test.2 Test.3;
ret Test.9;
let Test.7 = 4i64;
let Test.8 = 9i64;
let Test.6 = Struct {Test.7, Test.8};
let Test.5 = CallByName Test.0 Test.6;
ret Test.5;
"#
),
)
}
#[test]
fn record_optional_field_function_use_default() {
compiles_to_ir(
indoc!(
r#"
f = \{ x ? 10, y } -> x + y
f { y: 9 }
"#
),
indoc!(
r#"
procedure Num.14 (#Attr.2, #Attr.3):
let Test.9 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.9;
procedure Test.0 (Test.4):
let Test.2 = 10i64;
let Test.3 = Index 1 Test.4;
let Test.8 = CallByName Num.14 Test.2 Test.3;
ret Test.8;
let Test.7 = 9i64;
let Test.6 = Struct {Test.7};
let Test.5 = CallByName Test.0 Test.6;
ret Test.5;
"#
),
)
}
#[allow(dead_code)]
fn quicksort_help() {
crate::helpers::with_larger_debug_stack(|| {
@ -1094,14 +1232,14 @@ mod test_mono {
let Test.10 = CallByName Num.16 Test.2 Test.3;
jump Test.20 Test.9 Test.10;
procedure Num.15 (#Attr.2, #Attr.3):
let Test.14 = lowlevel NumSub #Attr.2 #Attr.3;
ret Test.14;
procedure Num.16 (#Attr.2, #Attr.3):
let Test.11 = lowlevel NumMul #Attr.2 #Attr.3;
ret Test.11;
procedure Num.15 (#Attr.2, #Attr.3):
let Test.14 = lowlevel NumSub #Attr.2 #Attr.3;
ret Test.14;
let Test.5 = 10i64;
let Test.6 = 1i64;
let Test.4 = CallByName Test.0 Test.5 Test.6;