implement optional fields in function pattern matches

This commit is contained in:
Folkert 2020-08-13 00:09:57 +02:00
parent ab78725944
commit 20ddbeb528
5 changed files with 264 additions and 52 deletions

View file

@ -399,4 +399,36 @@ mod gen_records {
(i64, i64) (i64, i64)
); );
} }
#[test]
fn optional_field_use_default() {
assert_evals_to!(
indoc!(
r#"
f = \{ x ? 10, y } -> x + y
f { y: 9 }
"#
),
19,
i64
);
}
#[test]
fn optional_field_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,8 @@ fn test_at_path<'a>(selected_path: &Path, branch: &Branch<'a>, all_tests: &mut V
arguments.push((Pattern::Underscore, destruct.layout.clone())); arguments.push((Pattern::Underscore, destruct.layout.clone()));
} }
DestructType::Optional(_expr) => { DestructType::Optional(_expr) => {
todo!("test_at_type for optional destruct"); // todo!("test_at_type for optional destruct");
arguments.push((Pattern::Underscore, destruct.layout.clone()));
} }
} }
} }
@ -542,7 +543,8 @@ fn to_relevant_branch_help<'a>(
DestructType::Guard(guard) => guard.clone(), DestructType::Guard(guard) => guard.clone(),
DestructType::Required => Pattern::Underscore, DestructType::Required => Pattern::Underscore,
DestructType::Optional(_expr) => { DestructType::Optional(_expr) => {
todo!("TODO decision tree for optional field branch"); // todo!("TODO decision tree for optional field branch");
Pattern::Underscore
} }
}; };

View file

@ -1399,13 +1399,20 @@ pub fn with_hole<'a>(
for (label, layout) in sorted_fields.into_iter() { for (label, layout) in sorted_fields.into_iter() {
field_layouts.push(layout); field_layouts.push(layout);
let field = fields.remove(&label).unwrap(); match fields.remove(&label) {
if let roc_can::expr::Expr::Var(symbol) = field.loc_expr.value { Some(field) => {
field_symbols.push(symbol); if let roc_can::expr::Expr::Var(symbol) = field.loc_expr.value {
can_fields.push(None); field_symbols.push(symbol);
} else { can_fields.push(None);
field_symbols.push(env.unique_symbol()); } else {
can_fields.push(Some(field)); field_symbols.push(env.unique_symbol());
can_fields.push(Some(field));
}
}
None => {
// this field was optional, but not given
continue;
}
} }
} }
@ -1643,11 +1650,22 @@ pub fn with_hole<'a>(
let mut index = None; let mut index = None;
let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena); 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;
field_layouts.push(field_layout); 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 { if label == field {
index = Some(current); index = Some(current);
}
current += 1;
}
} }
} }
@ -2670,8 +2688,15 @@ fn store_record_destruct<'a>(
env.arena.alloc(stmt), env.arena.alloc(stmt),
); );
} }
DestructType::Optional(_expr) => { DestructType::Optional(expr) => {
todo!("TODO monomorphize optional field destructure's default expr"); stmt = with_hole(
env,
expr.clone(),
procs,
layout_cache,
destruct.symbol,
env.arena.alloc(stmt),
);
} }
DestructType::Guard(guard_pattern) => match &guard_pattern { DestructType::Guard(guard_pattern) => match &guard_pattern {
Identifier(symbol) => { Identifier(symbol) => {
@ -3173,6 +3198,7 @@ pub fn from_can_pattern<'a>(
destructs, destructs,
.. ..
} => { } => {
// sorted fields based on the destruct
let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena); let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena);
let mut destructs = destructs.clone(); let mut destructs = destructs.clone();
destructs.sort_by(|a, b| a.value.label.cmp(&b.value.label)); destructs.sort_by(|a, b| a.value.label.cmp(&b.value.label));
@ -3180,40 +3206,100 @@ pub fn from_can_pattern<'a>(
let mut it = destructs.iter(); let mut it = destructs.iter();
let mut opt_destruct = it.next(); 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 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); let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
for (label, field_layout) in sorted_fields.into_iter() { // next we step through both sequences of fields. The outer loop is the sequence based
if let Some(destruct) = opt_destruct { // on the type, since not all fields need to actually be destructured in the source
if destruct.value.label == label { // language.
opt_destruct = it.next(); //
// 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();
mono_destructs.push(from_can_record_destruct( mono_destructs.push(from_can_record_destruct(
env, env,
layout_cache, layout_cache,
&destruct.value, &destruct.value,
field_layout.clone(), field_layout.clone(),
)); ));
} else { } else {
// insert underscore pattern // insert underscore pattern
mono_destructs.push(RecordDestruct { mono_destructs.push(RecordDestruct {
label: label.clone(), label: label.clone(),
symbol: env.unique_symbol(), symbol: env.unique_symbol(),
layout: field_layout.clone(), layout: field_layout.clone(),
typ: DestructType::Guard(Pattern::Underscore), 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 {
label: label.clone(),
symbol: env.unique_symbol(),
layout: field_layout.clone(),
typ: DestructType::Guard(Pattern::Underscore),
});
}
}
None => {
// this field does not exist, and was not request in the pattern match
continue;
}
}
} }
} else {
// insert underscore pattern
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);
} }
Pattern::RecordDestructure( Pattern::RecordDestructure(
@ -3236,8 +3322,10 @@ fn from_can_record_destruct<'a>(
layout: field_layout, layout: field_layout,
typ: match &can_rd.typ { typ: match &can_rd.typ {
roc_can::pattern::DestructType::Required => DestructType::Required, roc_can::pattern::DestructType::Required => DestructType::Required,
roc_can::pattern::DestructType::Optional(_, loc_expr) => { roc_can::pattern::DestructType::Optional(_, _) => {
DestructType::Optional(loc_expr.value.clone()) // 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) => { roc_can::pattern::DestructType::Guard(_, loc_pattern) => {
DestructType::Guard(from_can_pattern(env, layout_cache, &loc_pattern.value)) 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 { for (_, field) in sorted_fields {
use LayoutProblem::*; 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; let field_content = subs.get_without_compacting(field_var).content;
match Layout::new(arena, field_content, subs) { match Layout::new(arena, field_content, subs) {
@ -414,7 +424,7 @@ pub fn sort_record_fields<'a>(
arena: &'a Bump, arena: &'a Bump,
var: Variable, var: Variable,
subs: &Subs, subs: &Subs,
) -> Vec<'a, (Lowercase, Layout<'a>)> { ) -> Vec<'a, (Lowercase, Result<Layout<'a>, Layout<'a>>)> {
let mut fields_map = MutMap::default(); let mut fields_map = MutMap::default();
match roc_types::pretty_print::chase_ext_record(subs, var, &mut fields_map) { 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 // Sort the fields by label
let mut sorted_fields = Vec::with_capacity_in(fields_map.len(), arena); let mut sorted_fields = Vec::with_capacity_in(fields_map.len(), arena);
use roc_types::types::RecordField;
for (label, field) in fields_map { 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"); let layout = Layout::from_var(arena, var, subs).expect("invalid layout from var");
// Drop any zero-sized fields like {} // Drop any zero-sized fields like {}
if !layout.is_zero_sized() { if !layout.is_zero_sized() {
sorted_fields.push((label, layout)); sorted_fields.push((label, Ok(layout)));
} }
} }

View file

@ -1037,4 +1037,73 @@ mod test_mono {
), ),
) )
} }
#[test]
fn record_optional_field_no_use_default() {
compiles_to_ir(
indoc!(
r#"
f = \{ x ? 10, y } -> x + y
f { x: 4, y: 9 }
"#
),
indoc!(
r#"
procedure Test.0 (Test.4):
let Test.2 = Index 0 Test.4;
let Test.3 = Index 1 Test.4;
let Test.11 = CallByName Num.14 Test.2 Test.3;
jump Test.10 Test.11;
joinpoint Test.10 Test.9:
ret Test.9;
procedure Num.14 (#Attr.2, #Attr.3):
let Test.12 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.12;
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_use_default() {
compiles_to_ir(
indoc!(
r#"
f = \{ x ? 10, y } -> x + y
f { y: 9 }
"#
),
indoc!(
r#"
procedure Test.0 (Test.4):
let Test.2 = 10i64;
let Test.3 = Index 1 Test.4;
let Test.10 = CallByName Num.14 Test.2 Test.3;
jump Test.9 Test.10;
joinpoint Test.9 Test.8:
ret Test.8;
procedure Num.14 (#Attr.2, #Attr.3):
let Test.11 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.11;
let Test.7 = 9i64;
let Test.6 = Struct {Test.7};
let Test.5 = CallByName Test.0 Test.6;
ret Test.5;
"#
),
)
}
} }