mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 16:44:33 +00:00
implement optional fields in function pattern matches
This commit is contained in:
parent
ab78725944
commit
20ddbeb528
5 changed files with 264 additions and 52 deletions
|
@ -399,4 +399,36 @@ mod gen_records {
|
|||
(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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
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::Required => Pattern::Underscore,
|
||||
DestructType::Optional(_expr) => {
|
||||
todo!("TODO decision tree for optional field branch");
|
||||
// todo!("TODO decision tree for optional field branch");
|
||||
Pattern::Underscore
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1399,7 +1399,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);
|
||||
|
@ -1408,6 +1409,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
|
||||
|
@ -1643,12 +1650,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 {
|
||||
|
@ -2670,8 +2688,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) => {
|
||||
|
@ -3173,6 +3198,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));
|
||||
|
@ -3180,12 +3206,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();
|
||||
|
||||
|
@ -3204,6 +3242,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 {
|
||||
|
@ -3213,7 +3292,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(
|
||||
|
@ -3236,8 +3322,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))
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue