exhaustiveness for record guards

This commit is contained in:
Folkert 2020-03-19 22:34:08 +01:00
parent 0985037754
commit 82655556ab
4 changed files with 165 additions and 52 deletions

View file

@ -77,7 +77,6 @@ mod test_gen {
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr // Populate Procs and Subs, and get the low-level Expr from the canonical Expr
let mono_expr = Expr::new(&arena, &mut subs, loc_expr.value, &mut procs, home, &mut ident_ids, POINTER_SIZE); let mono_expr = Expr::new(&arena, &mut subs, loc_expr.value, &mut procs, home, &mut ident_ids, POINTER_SIZE);
// Put this module's ident_ids back in the interns // Put this module's ident_ids back in the interns
env.interns.all_ident_ids.insert(home, ident_ids); env.interns.all_ident_ids.insert(home, ident_ids);
@ -1345,6 +1344,44 @@ mod test_gen {
5, 5,
i64 i64
); );
assert_evals_to!(
indoc!(
r#"
when { x: 0x2, y: 3.14 } is
{ x: var } -> var + 3
"#
),
5,
i64
);
assert_evals_to!(
indoc!(
r#"
{ x } = { x: 0x2, y: 3.14 }
x
"#
),
2,
i64
);
}
#[test]
fn record_guard_pattern() {
assert_evals_to!(
indoc!(
r#"
when { x: 0x2, y: 3.14 } is
{ x: 0x4 } -> 5
{ x } -> x + 3
"#
),
5,
i64
);
} }
// #[test] // #[test]

View file

@ -7,6 +7,7 @@ use roc_module::symbol::Symbol;
use crate::layout::Builtin; use crate::layout::Builtin;
use crate::layout::Layout; use crate::layout::Layout;
use crate::pattern::{Ctor, Union};
/// COMPILE CASES /// COMPILE CASES
@ -245,11 +246,33 @@ fn test_at_path<'a>(selected_path: &Path, branch: Branch<'a>) -> Option<Test<'a>
{ {
None => None, None => None,
Some((_, pattern)) => match pattern { Some((_, pattern)) => match pattern {
Identifier(_) Identifier(_) | Underscore | Shadowed(_, _) | UnsupportedPattern(_) => None,
| RecordDestructure(_, _)
| Underscore RecordDestructure(destructs, _) => {
| Shadowed(_, _) let union = Union {
| UnsupportedPattern(_) => None, alternatives: vec![Ctor {
name: TagName::Global("#Record".into()),
arity: destructs.len(),
}],
};
let mut arguments = std::vec::Vec::new();
for destruct in destructs {
if let Some(guard) = &destruct.guard {
arguments.push((guard.clone(), destruct.layout.clone()));
} else {
arguments.push((Pattern::Underscore, destruct.layout.clone()));
}
}
Some(IsCtor {
tag_id: 0,
tag_name: TagName::Global("#Record".into()),
union,
arguments,
})
}
AppliedTag { AppliedTag {
tag_name, tag_name,
@ -302,11 +325,42 @@ fn to_relevant_branch<'a>(test: &Test<'a>, path: &Path, branch: Branch<'a>) -> O
found_pattern: pattern, found_pattern: pattern,
end, end,
} => match pattern { } => match pattern {
RecordDestructure(_, _) Identifier(_) | Underscore | Shadowed(_, _) | UnsupportedPattern(_) => Some(branch),
| Identifier(_)
| Underscore RecordDestructure(destructs, _) => match test {
| Shadowed(_, _) IsCtor {
| UnsupportedPattern(_) => Some(branch), tag_name: test_name,
..
} => {
debug_assert!(test_name == &TagName::Global("#Record".into()));
let sub_positions =
destructs.into_iter().enumerate().map(|(index, destruct)| {
let pattern = if let Some(guard) = destruct.guard {
guard.clone()
} else {
Pattern::Underscore
};
(
Path::Index {
index: index as u64,
path: Box::new(path.clone()),
},
pattern,
)
});
start.extend(sub_positions);
start.extend(end);
Some(Branch {
goal: branch.goal,
patterns: start,
})
}
_ => None,
},
AppliedTag { AppliedTag {
union, union,
tag_name, tag_name,
@ -349,22 +403,6 @@ fn to_relevant_branch<'a>(test: &Test<'a>, path: &Path, branch: Branch<'a>) -> O
} }
_ => None, _ => None,
} }
/*
*
Can.PCtor _ _ (Can.Union _ _ numAlts _) name _ ctorArgs ->
case test of
IsCtor _ testName _ _ _ | name == testName ->
Just $ Branch goal $
case map dearg ctorArgs of
[arg] | numAlts == 1 ->
start ++ [(Unbox path, arg)] ++ end
args ->
start ++ subPositions path args ++ end
_ ->
Nothing
*/
} }
StrLiteral(string) => match test { StrLiteral(string) => match test {
IsStr(test_str) if string == *test_str => { IsStr(test_str) if string == *test_str => {
@ -682,24 +720,17 @@ fn path_to_expr<'a>(
symbol: Symbol, symbol: Symbol,
path: &Path, path: &Path,
is_unwrapped: bool, is_unwrapped: bool,
field_layouts: Layout<'a>, field_layouts: &'a [Layout<'a>],
) -> Expr<'a> { ) -> Expr<'a> {
match path { match path {
Path::Unbox(ref path) => path_to_expr(env, symbol, path, true, field_layouts), Path::Unbox(ref path) => path_to_expr(env, symbol, path, true, field_layouts),
// TODO make this work with AccessAtIndex.
// that already works for structs, but not for basic types for some reason
// Expr::AccessAtIndex {
// index: 0,
// field_layouts: env.arena.alloc([field_layouts]),
// expr: env.arena.alloc(Expr::Load(symbol)),
// },
Path::Empty => Expr::Load(symbol), Path::Empty => Expr::Load(symbol),
// TODO path contains a nested path. Traverse all the way // TODO path contains a nested path. Traverse all the way
Path::Index { index, .. } => Expr::AccessAtIndex { Path::Index { index, .. } => Expr::AccessAtIndex {
index: *index, index: *index,
field_layouts: env.arena.alloc([Layout::Builtin(Builtin::Byte)]), field_layouts,
expr: env.arena.alloc(Expr::Load(symbol)), expr: env.arena.alloc(Expr::Load(symbol)),
is_unwrapped, is_unwrapped,
}, },
@ -739,15 +770,17 @@ fn decide_to_branching<'a>(
arguments, arguments,
.. ..
} => { } => {
// the IsCtor check should never be generated for tag unions of size 1
// (e.g. record pattern guard matches)
debug_assert!(union.alternatives.len() > 1);
let lhs = Expr::Int(tag_id as i64); let lhs = Expr::Int(tag_id as i64);
let mut field_layouts = let mut field_layouts =
bumpalo::collections::Vec::with_capacity_in(arguments.len(), env.arena); bumpalo::collections::Vec::with_capacity_in(arguments.len(), env.arena);
if union.alternatives.len() > 1 { // add the tag discriminant
// the tag discriminant field_layouts.push(Layout::Builtin(Builtin::Int64));
field_layouts.push(Layout::Builtin(Builtin::Int64));
}
for (_, layout) in arguments { for (_, layout) in arguments {
field_layouts.push(layout); field_layouts.push(layout);
@ -777,7 +810,7 @@ fn decide_to_branching<'a>(
cond_symbol, cond_symbol,
&path, &path,
false, false,
Layout::Builtin(Builtin::Int64), env.arena.alloc([Layout::Builtin(Builtin::Int64)]),
); );
let cond = env.arena.alloc(Expr::CallByName( let cond = env.arena.alloc(Expr::CallByName(
@ -800,7 +833,7 @@ fn decide_to_branching<'a>(
cond_symbol, cond_symbol,
&path, &path,
false, false,
Layout::Builtin(Builtin::Float64), env.arena.alloc([Layout::Builtin(Builtin::Float64)]),
); );
let cond = env.arena.alloc(Expr::CallByName( let cond = env.arena.alloc(Expr::CallByName(
@ -825,7 +858,7 @@ fn decide_to_branching<'a>(
cond_symbol, cond_symbol,
&path, &path,
false, false,
Layout::Builtin(Builtin::Byte), env.arena.alloc([Layout::Builtin(Builtin::Byte)]),
); );
let cond = env.arena.alloc(Expr::CallByName( let cond = env.arena.alloc(Expr::CallByName(
@ -842,8 +875,6 @@ fn decide_to_branching<'a>(
} }
} }
let cond = tests.remove(0);
let pass = env.arena.alloc(decide_to_branching( let pass = env.arena.alloc(decide_to_branching(
env, env,
cond_symbol, cond_symbol,
@ -862,6 +893,10 @@ fn decide_to_branching<'a>(
jumps, jumps,
)); ));
// TODO take the boolean and of all the tests
debug_assert!(tests.len() == 1);
let cond = tests.remove(0);
let cond_layout = Layout::Builtin(Builtin::Bool); let cond_layout = Layout::Builtin(Builtin::Bool);
Expr::Cond { Expr::Cond {
@ -882,7 +917,7 @@ fn decide_to_branching<'a>(
cond_symbol, cond_symbol,
&path, &path,
false, false,
cond_layout.clone(), env.arena.alloc([cond_layout.clone()]),
)); ));
let default_branch = env.arena.alloc(decide_to_branching( let default_branch = env.arena.alloc(decide_to_branching(
@ -935,6 +970,15 @@ fn decide_to_branching<'a>(
/// Decision trees may have some redundancies, so we convert them to a Decider /// Decision trees may have some redundancies, so we convert them to a Decider
/// which has special constructs to avoid code duplication when possible. /// which has special constructs to avoid code duplication when possible.
/// If a test always succeeds, we don't need to branch on it
/// this saves on work and jumps
fn test_always_succeeds(test: &Test) -> bool {
match test {
Test::IsCtor { union, .. } => union.alternatives.len() == 1,
_ => false,
}
}
fn tree_to_decider(tree: DecisionTree) -> Decider<u64> { fn tree_to_decider(tree: DecisionTree) -> Decider<u64> {
use Decider::*; use Decider::*;
use DecisionTree::*; use DecisionTree::*;
@ -958,7 +1002,11 @@ fn tree_to_decider(tree: DecisionTree) -> Decider<u64> {
let (_, failure_tree) = edges.remove(1); let (_, failure_tree) = edges.remove(1);
let (test, success_tree) = edges.remove(0); let (test, success_tree) = edges.remove(0);
to_chain(path, test, success_tree, failure_tree) if test_always_succeeds(&test) {
tree_to_decider(success_tree)
} else {
to_chain(path, test, success_tree, failure_tree)
}
} }
_ => { _ => {
@ -983,7 +1031,11 @@ fn tree_to_decider(tree: DecisionTree) -> Decider<u64> {
let failure_tree = *last; let failure_tree = *last;
let (test, success_tree) = edges.remove(0); let (test, success_tree) = edges.remove(0);
to_chain(path, test, success_tree, failure_tree) if test_always_succeeds(&test) {
tree_to_decider(success_tree)
} else {
to_chain(path, test, success_tree, failure_tree)
}
} }
_ => { _ => {

View file

@ -943,7 +943,7 @@ fn store_record_destruct<'a>(
stored.push((*symbol, destruct.layout.clone(), load)); stored.push((*symbol, destruct.layout.clone(), load));
} }
Pattern::Underscore => { Pattern::Underscore => {
// important that this is special-cased: mono record patterns will extract all the // important that this is special-cased to do nothing: mono record patterns will extract all the
// fields, but those not bound in the source code are guarded with the underscore // fields, but those not bound in the source code are guarded with the underscore
// pattern. So given some record `{ x : a, y : b }`, a match // pattern. So given some record `{ x : a, y : b }`, a match
// //
@ -993,6 +993,15 @@ fn from_can_when<'a>(
let mono_pattern = from_can_pattern(env, &loc_when_pattern.value); let mono_pattern = from_can_pattern(env, &loc_when_pattern.value);
// record pattern matches can have 1 branch and typecheck, but may still not be exhaustive
match crate::pattern::check(
Region::zero(),
&[Located::at(loc_when_pattern.region, mono_pattern.clone())],
) {
Ok(_) => {}
Err(errors) => panic!("Errors in patterns: {:?}", errors),
}
let cond_layout = Layout::from_var(env.arena, cond_var, env.subs, env.pointer_size) let cond_layout = Layout::from_var(env.arena, cond_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err)); .unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err));
let cond_symbol = env.fresh_symbol(); let cond_symbol = env.fresh_symbol();

View file

@ -72,9 +72,24 @@ fn simplify<'a>(pattern: &crate::expr::Pattern<'a>) -> Pattern {
Underscore => Anything, Underscore => Anything,
Identifier(_) => Anything, Identifier(_) => Anything,
RecordDestructure { .. } => { RecordDestructure(destructures, _) => {
// TODO we must check the guard conditions! let union = Union {
Anything alternatives: vec![Ctor {
name: TagName::Global("#Record".into()),
arity: destructures.len(),
}],
};
let mut patterns = std::vec::Vec::with_capacity(destructures.len());
for destruct in destructures {
match &destruct.guard {
None => patterns.push(Anything),
Some(guard) => patterns.push(simplify(guard)),
}
}
Ctor(union, TagName::Global("#Record".into()), patterns)
} }
Shadowed(_region, _ident) => { Shadowed(_region, _ident) => {