guard exhaustiveness

This commit is contained in:
Folkert 2020-03-21 23:33:37 +01:00
parent a16d48a6a9
commit 1f3b8f7d68
4 changed files with 104 additions and 43 deletions

View file

@ -1540,20 +1540,20 @@ mod test_gen {
); );
} }
// #[test] #[test]
// fn if_guard_exhaustiveness() { fn if_guard_exhaustiveness() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// when 2 is when 2 is
// _ if False -> 0 _ if False -> 0
// _ -> 42 _ -> 42
// "# "#
// ), ),
// 42, 42,
// i64 i64
// ); );
// } }
// #[test] // #[test]
// fn linked_list_empty() { // fn linked_list_empty() {

View file

@ -444,26 +444,25 @@ fn to_relevant_branch<'a>(
start, start,
found_pattern: (guard, pattern), found_pattern: (guard, pattern),
end, end,
} => match test { } => {
Test::Guarded(None, _guard_expr) => { let actual_test = match test {
// theory: Some(branch) Test::Guarded(Some(box_test), _guard_expr) => box_test,
todo!(); _ => test,
};
if let Some(mut new_branch) =
to_relevant_branch_help(actual_test, path, start, end, branch, guard, pattern)
{
// guards can/should only occur at the top level. When we recurse on these
// branches, the guard is not relevant any more. Not setthing the guard to None
// leads to infinite recursion.
new_branch.patterns.iter_mut().for_each(|(_, guard, _)| {
*guard = None;
});
new_branches.push(new_branch);
} }
Test::Guarded(Some(box_test), _guard_expr) => { }
if let Some(new_branch) =
to_relevant_branch_help(box_test, path, start, end, branch, guard, pattern)
{
new_branches.push(new_branch);
}
}
_ => {
if let Some(new_branch) =
to_relevant_branch_help(test, path, start, end, branch, guard, pattern)
{
new_branches.push(new_branch);
}
}
},
} }
} }
@ -502,7 +501,7 @@ fn to_relevant_branch_help<'a>(
tag_id: *tag_id, tag_id: *tag_id,
path: Box::new(path.clone()), path: Box::new(path.clone()),
}, },
guard.clone(), None,
pattern, pattern,
) )
}); });
@ -548,7 +547,7 @@ fn to_relevant_branch_help<'a>(
tag_id: *tag_id, tag_id: *tag_id,
path: Box::new(path.clone()), path: Box::new(path.clone()),
}, },
guard.clone(), None,
pattern, pattern,
) )
}); });

View file

@ -1,5 +1,5 @@
use crate::layout::{Builtin, Layout}; use crate::layout::{Builtin, Layout};
use crate::pattern::Ctor; use crate::pattern::{Ctor, Guard};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_can; use roc_can;
@ -996,9 +996,18 @@ 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 // record pattern matches can have 1 branch and typecheck, but may still not be exhaustive
let guard = if first.guard.is_some() {
Guard::HasGuard
} else {
Guard::NoGuard
};
match crate::pattern::check( match crate::pattern::check(
Region::zero(), Region::zero(),
&[Located::at(loc_when_pattern.region, mono_pattern.clone())], &[(
Located::at(loc_when_pattern.region, mono_pattern.clone()),
guard,
)],
) { ) {
Ok(_) => {} Ok(_) => {}
Err(errors) => panic!("Errors in patterns: {:?}", errors), Err(errors) => panic!("Errors in patterns: {:?}", errors),
@ -1037,10 +1046,19 @@ fn from_can_when<'a>(
None None
}; };
let guard = if mono_guard.is_some() {
Guard::HasGuard
} else {
Guard::NoGuard
};
for loc_pattern in when_branch.patterns { for loc_pattern in when_branch.patterns {
let mono_pattern = from_can_pattern(env, &loc_pattern.value); let mono_pattern = from_can_pattern(env, &loc_pattern.value);
loc_branches.push(Located::at(loc_pattern.region, mono_pattern.clone())); loc_branches.push((
Located::at(loc_pattern.region, mono_pattern.clone()),
guard.clone(),
));
let mut stores = Vec::with_capacity_in(1, env.arena); let mut stores = Vec::with_capacity_in(1, env.arena);

View file

@ -131,11 +131,17 @@ pub enum Context {
BadCase, BadCase,
} }
#[derive(Clone, Debug, PartialEq)]
pub enum Guard {
HasGuard,
NoGuard,
}
/// Check /// Check
pub fn check<'a>( pub fn check<'a>(
region: Region, region: Region,
patterns: &[Located<crate::expr::Pattern<'a>>], patterns: &[(Located<crate::expr::Pattern<'a>>, Guard)],
) -> Result<(), Vec<Error>> { ) -> Result<(), Vec<Error>> {
let mut errors = Vec::new(); let mut errors = Vec::new();
check_patterns(region, Context::BadArg, patterns, &mut errors); check_patterns(region, Context::BadArg, patterns, &mut errors);
@ -150,7 +156,7 @@ pub fn check<'a>(
pub fn check_patterns<'a>( pub fn check_patterns<'a>(
region: Region, region: Region,
context: Context, context: Context,
patterns: &[Located<crate::expr::Pattern<'a>>], patterns: &[(Located<crate::expr::Pattern<'a>>, Guard)],
errors: &mut Vec<Error>, errors: &mut Vec<Error>,
) { ) {
match to_nonredundant_rows(region, patterns) { match to_nonredundant_rows(region, patterns) {
@ -283,14 +289,52 @@ fn recover_ctor(
/// INVARIANT: Produces a list of rows where (forall row. length row == 1) /// INVARIANT: Produces a list of rows where (forall row. length row == 1)
fn to_nonredundant_rows<'a>( fn to_nonredundant_rows<'a>(
overall_region: Region, overall_region: Region,
patterns: &[Located<crate::expr::Pattern<'a>>], patterns: &[(Located<crate::expr::Pattern<'a>>, Guard)],
) -> Result<Vec<Vec<Pattern>>, Error> { ) -> Result<Vec<Vec<Pattern>>, Error> {
let mut checked_rows = Vec::with_capacity(patterns.len()); let mut checked_rows = Vec::with_capacity(patterns.len());
for loc_pat in patterns { // If any of the branches has a guard, e.g.
//
// when x is
// y if y < 10 -> "foo"
// _ -> "bar"
//
// then we treat it as a pattern match on the pattern and a boolean, wrapped in the #Guard
// constructor. We can use this special constructor name to generate better error messages.
// This transformation of the pattern match only works because we only report exhaustiveness
// errors: the Pattern created in this file is not used for code gen.
//
// when x is
// #Guard y True -> "foo"
// #Guard _ _ -> "bar"
let any_has_guard = patterns.iter().any(|(_, guard)| guard == &Guard::HasGuard);
for (loc_pat, guard) in patterns {
let region = loc_pat.region; let region = loc_pat.region;
let next_row = vec![simplify(&loc_pat.value)]; let next_row = if any_has_guard {
let guard_pattern = match guard {
Guard::HasGuard => Pattern::Literal(Literal::Bit(true)),
Guard::NoGuard => Pattern::Anything,
};
let union = Union {
alternatives: vec![Ctor {
name: TagName::Global("#Guard".into()),
arity: 2,
}],
};
let tag_name = TagName::Global("#Guard".into());
vec![Pattern::Ctor(
union,
tag_name,
vec![simplify(&loc_pat.value), guard_pattern],
)]
} else {
vec![simplify(&loc_pat.value)]
};
if is_useful(&checked_rows, &next_row) { if is_useful(&checked_rows, &next_row) {
checked_rows.push(next_row); checked_rows.push(next_row);