mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 00:24:34 +00:00
guard exhaustiveness
This commit is contained in:
parent
a16d48a6a9
commit
1f3b8f7d68
4 changed files with 104 additions and 43 deletions
|
@ -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() {
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue