Add correct redundancy checking and tests for list patterns

This commit is contained in:
Ayaz Hafiz 2022-11-01 10:58:41 -05:00
parent 17920911e4
commit 342f9f2e02
No known key found for this signature in database
GPG Key ID: 0E2A37416A25EF58
2 changed files with 186 additions and 17 deletions

View File

@ -349,11 +349,43 @@ pub fn is_useful(mut old_matrix: PatternMatrix, mut vector: Row) -> bool {
// keep checking rows that are supersets of this list pattern, or Anything
List(arity, args) => {
specialize_rows_by_list(arity, &mut old_matrix, &mut matrix);
// Now check, is there any specialized constructor of this list pattern
// that is useful?
let spec_list_ctors = build_list_ctors_covering_patterns(
arity,
filter_matrix_list_ctors(&old_matrix),
);
debug_assert!(!spec_list_ctors.is_empty());
std::mem::swap(&mut old_matrix, &mut matrix);
if spec_list_ctors.len() == 1 {
specialize_matrix_by_list(
spec_list_ctors[0],
&mut old_matrix,
&mut matrix,
);
vector.extend(args);
std::mem::swap(&mut old_matrix, &mut matrix);
vector.extend(args);
} else {
// TODO turn this into an iterative loop over the outer loop rather than bouncing
vector.extend(args.clone());
for list_ctor in spec_list_ctors {
let mut old_matrix = old_matrix.clone();
let mut spec_matrix = Vec::with_capacity(old_matrix.len());
specialize_matrix_by_list(
list_ctor,
&mut old_matrix,
&mut spec_matrix,
);
if is_useful(spec_matrix, vector.clone()) {
return true;
}
}
return false;
}
}
Anything => {
@ -439,10 +471,10 @@ pub fn is_useful(mut old_matrix: PatternMatrix, mut vector: Row) -> bool {
// Largely derived from Rust's list-pattern exhaustiveness checking algorithm: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir_build/thir/pattern/usefulness/index.html
// Dual-licensed under MIT and Apache licenses.
// Thank you, Rust contributors.
fn specialize_rows_by_list(
fn specialize_matrix_by_list(
spec_arity: ListArity,
old_matrix: &mut PatternMatrix,
matrix: &mut PatternMatrix,
spec_matrix: &mut PatternMatrix,
) {
for mut row in old_matrix.drain(..) {
let head = row.pop();
@ -471,13 +503,13 @@ fn specialize_rows_by_list(
let new_pats = (before.iter().chain(extra_wildcards).chain(after)).cloned();
row_patterns.extend(new_pats);
matrix.push(row_patterns);
spec_matrix.push(row_patterns);
}
}
} else {
debug_assert_eq!(this_arity.min_len(), spec_arity.min_len());
row_patterns.extend(args);
matrix.push(row_patterns);
spec_matrix.push(row_patterns);
}
}
}
@ -485,7 +517,7 @@ fn specialize_rows_by_list(
// The specialized fields for a `Anything` pattern with a list constructor is just
// `Anything` repeated for the number of times we want to see the list pattern.
row_patterns.extend(std::iter::repeat(Anything).take(spec_arity.min_len()));
matrix.push(row_patterns);
spec_matrix.push(row_patterns);
}
Some(Ctor(..)) => internal_error!("After type checking, lists and constructors should never align in exhaustiveness checking"),
Some(Literal(..)) => internal_error!("After type checking, lists and literals should never align in exhaustiveness checking"),
@ -691,11 +723,8 @@ fn collect_ctors<'a>(
Anything => CollectedCtors::NonExhaustiveAny,
Pattern::Literal(_) => CollectedCtors::NonExhaustiveAny,
List(_, _) => {
let list_ctors =
build_relevant_list_ctors(matrix.iter().filter_map(|ctor| match ctor.last() {
Some(List(ar, _)) => Some(*ar),
_ => None,
}));
let list_ctors = build_relevant_list_ctors(0, 0, filter_matrix_list_ctors(matrix));
CollectedCtors::NonExhaustiveList(list_ctors)
}
Pattern::Ctor(_, _, _) => {
@ -741,12 +770,16 @@ fn collect_ctors<'a>(
/// Putting that together, we calculate L via
///
/// L = max(max_exact_len + 1, max_prefix_len + max_suffix_len)
pub fn build_relevant_list_ctors(
fn build_relevant_list_ctors(
prefix_len: usize,
suffix_len: usize,
list_pattern_arities: impl IntoIterator<Item = ListArity>,
) -> impl Iterator<Item = ListArity> {
let min_len = prefix_len + suffix_len;
let mut max_exact_len = 0;
let mut max_prefix_len = 0;
let mut max_suffix_len = 0;
let mut max_prefix_len = prefix_len;
let mut max_suffix_len = suffix_len;
for arity in list_pattern_arities {
match arity {
@ -766,8 +799,32 @@ pub fn build_relevant_list_ctors(
};
let l = inf_cover_prefix + inf_cover_suffix;
let exact_size_lists = (0..l) // exclusive
let exact_size_lists = (min_len..l) // exclusive
.map(ListArity::Exact);
exact_size_lists.chain([ListArity::Slice(inf_cover_prefix, inf_cover_suffix)])
}
fn build_list_ctors_covering_patterns(
list_arity: ListArity,
list_pattern_arities: impl IntoIterator<Item = ListArity>,
) -> std::vec::Vec<ListArity> {
match list_arity {
ListArity::Exact(_) => {
// Exact-size lists can only cover themselves..
vec![list_arity]
}
ListArity::Slice(prefix, suffix) => {
build_relevant_list_ctors(prefix, suffix, list_pattern_arities).collect()
}
}
}
fn filter_matrix_list_ctors<'a>(
matrix: &'a RefPatternMatrix,
) -> impl Iterator<Item = ListArity> + 'a {
matrix.iter().filter_map(|ctor| match ctor.last() {
Some(List(ar, _)) => Some(*ar),
_ => None,
})
}

View File

@ -12190,4 +12190,116 @@ All branches in an `if` must have the same type!
I would have to crash if I saw one of those! Add branches for them!
"###
);
test_report!(
list_match_redundant_exact_size,
indoc!(
r#"
l : List [A]
when l is
[] -> ""
[_] -> ""
[_] -> ""
[..] -> ""
"#
),
@r###"
REDUNDANT PATTERN /code/proj/Main.roc
The 3rd pattern is redundant:
6 when l is
7 [] -> ""
8 [_] -> ""
9> [_] -> ""
10 [..] -> ""
Any value of this shape will be handled by a previous pattern, so this
one should be removed.
"###
);
test_report!(
list_match_redundant_any_slice,
indoc!(
r#"
l : List [A]
when l is
[] -> ""
[_, ..] -> ""
[..] -> ""
"#
),
@r###"
REDUNDANT PATTERN /code/proj/Main.roc
The 3rd pattern is redundant:
6 when l is
7 [] -> ""
8 [_, ..] -> ""
9 [..] -> ""
^^^^
Any value of this shape will be handled by a previous pattern, so this
one should be removed.
"###
);
test_report!(
list_match_redundant_suffix_slice_with_sized_prefix,
indoc!(
r#"
l : List [A]
when l is
[] -> ""
[_, ..] -> ""
[.., _] -> ""
"#
),
@r###"
REDUNDANT PATTERN /code/proj/Main.roc
The 3rd pattern is redundant:
6 when l is
7 [] -> ""
8 [_, ..] -> ""
9 [.., _] -> ""
^^^^^^^
Any value of this shape will be handled by a previous pattern, so this
one should be removed.
"###
);
test_report!(
list_match_redundant_based_on_ctors,
indoc!(
r#"
l : List {}
when l is
[{}, .., _] -> ""
[_, .., {}] -> ""
[..] -> ""
"#
),
@r###"
REDUNDANT PATTERN /code/proj/Main.roc
The 2nd pattern is redundant:
6 when l is
7 [{}, .., _] -> ""
8> [_, .., {}] -> ""
9 [..] -> ""
Any value of this shape will be handled by a previous pattern, so this
one should be removed.
"###
);
}