mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 08:34: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)
|
(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()));
|
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
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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