mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 22:34:45 +00:00
First pass at pattern exhaustivess checking
This commit is contained in:
parent
5926ac2f01
commit
58c09aeaba
3 changed files with 526 additions and 29 deletions
|
@ -335,7 +335,13 @@ fn pattern_to_when<'a>(
|
||||||
(env.fresh_symbol(), body)
|
(env.fresh_symbol(), body)
|
||||||
}
|
}
|
||||||
|
|
||||||
AppliedTag {..} | RecordDestructure {..} | Shadowed(_, _) | UnsupportedPattern(_) => {
|
Shadowed(_, _) | UnsupportedPattern(_) => {
|
||||||
|
// create the runtime error here, instead of delegating to When.
|
||||||
|
// UnsupportedPattern should then never occcur in When
|
||||||
|
panic!("TODO generate runtime error here");
|
||||||
|
}
|
||||||
|
|
||||||
|
AppliedTag {..} | RecordDestructure {..} => {
|
||||||
let symbol = env.fresh_symbol();
|
let symbol = env.fresh_symbol();
|
||||||
|
|
||||||
let wrapped_body = When {
|
let wrapped_body = When {
|
||||||
|
@ -920,6 +926,13 @@ fn from_can_when<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
let loc_branches: std::vec::Vec<_> = branches.iter().map(|v| v.0.clone()).collect();
|
||||||
|
|
||||||
|
match crate::pattern::check(Region::zero(), &loc_branches) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(errors) => panic!("Errors in patterns: {:?}", errors),
|
||||||
|
}
|
||||||
|
|
||||||
// This is a when-expression with 3+ branches.
|
// This is a when-expression with 3+ branches.
|
||||||
let arena = env.arena;
|
let arena = env.arena;
|
||||||
let cond = from_can(env, loc_cond.value, procs, None);
|
let cond = from_can(env, loc_cond.value, procs, None);
|
||||||
|
@ -949,10 +962,28 @@ fn from_can_when<'a>(
|
||||||
let mut jumpable_branches = Vec::with_capacity_in(branches.len(), arena);
|
let mut jumpable_branches = Vec::with_capacity_in(branches.len(), arena);
|
||||||
let mut opt_default_branch = None;
|
let mut opt_default_branch = None;
|
||||||
|
|
||||||
for (loc_when_pat, loc_expr) in branches {
|
let mut is_last = true;
|
||||||
|
for (loc_when_pat, loc_expr) in branches.into_iter().rev() {
|
||||||
let mono_expr = from_can(env, loc_expr.value, procs, None);
|
let mono_expr = from_can(env, loc_expr.value, procs, None);
|
||||||
let when_pat = from_can_pattern(env, loc_when_pat.value);
|
let when_pat = from_can_pattern(env, loc_when_pat.value);
|
||||||
|
|
||||||
|
if is_last {
|
||||||
|
opt_default_branch = match &when_pat {
|
||||||
|
Identifier(symbol) => {
|
||||||
|
// TODO does this evaluate `cond` twice?
|
||||||
|
Some(arena.alloc(Expr::Store(
|
||||||
|
arena.alloc([(*symbol, layout.clone(), cond.clone())]),
|
||||||
|
arena.alloc(mono_expr.clone()),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
Shadowed(_region, _ident) => {
|
||||||
|
panic!("TODO make runtime exception out of the branch");
|
||||||
|
}
|
||||||
|
_ => Some(arena.alloc(mono_expr.clone())),
|
||||||
|
};
|
||||||
|
is_last = false;
|
||||||
|
}
|
||||||
|
|
||||||
match &when_pat {
|
match &when_pat {
|
||||||
IntLiteral(int) => {
|
IntLiteral(int) => {
|
||||||
// Switch only compares the condition to the
|
// Switch only compares the condition to the
|
||||||
|
@ -961,35 +992,15 @@ fn from_can_when<'a>(
|
||||||
jumpable_branches.push((*int as u64, mono_expr));
|
jumpable_branches.push((*int as u64, mono_expr));
|
||||||
}
|
}
|
||||||
BitLiteral(v) => jumpable_branches.push((*v as u64, mono_expr)),
|
BitLiteral(v) => jumpable_branches.push((*v as u64, mono_expr)),
|
||||||
EnumLiteral(v) => jumpable_branches.push((*v as u64, mono_expr)),
|
EnumLiteral { tag_id , .. } => jumpable_branches.push((*tag_id as u64, mono_expr)),
|
||||||
Identifier(symbol) => {
|
Identifier(_) => {
|
||||||
// Since this is an ident, it must be
|
// store is handled above
|
||||||
// the last pattern in the `when`.
|
|
||||||
// We can safely treat this like an `_`
|
|
||||||
// except that we need to wrap this branch
|
|
||||||
// in a `Store` so the identifier is in scope!
|
|
||||||
|
|
||||||
// TODO does this evaluate `cond` twice?
|
|
||||||
let mono_with_store = Expr::Store(
|
|
||||||
arena.alloc([(*symbol, layout.clone(), cond.clone())]),
|
|
||||||
arena.alloc(mono_expr),
|
|
||||||
);
|
|
||||||
|
|
||||||
opt_default_branch = Some(arena.alloc(mono_with_store));
|
|
||||||
}
|
|
||||||
Underscore => {
|
|
||||||
// We should always have exactly one default branch!
|
|
||||||
debug_assert!(opt_default_branch.is_none());
|
|
||||||
|
|
||||||
opt_default_branch = Some(arena.alloc(mono_expr));
|
|
||||||
}
|
}
|
||||||
|
Underscore => {}
|
||||||
Shadowed(_, _) => {
|
Shadowed(_, _) => {
|
||||||
panic!("TODO runtime error for shadowing in a pattern");
|
panic!("TODO runtime error for shadowing in a pattern");
|
||||||
}
|
}
|
||||||
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
UnsupportedPattern(_region) => unreachable!("When accepts all patterns"),
|
||||||
UnsupportedPattern(_region) => {
|
|
||||||
panic!("TODO runtime error for unsupported pattern");
|
|
||||||
}
|
|
||||||
AppliedTag { .. }
|
AppliedTag { .. }
|
||||||
| StrLiteral(_)
|
| StrLiteral(_)
|
||||||
| RecordDestructure(_, _)
|
| RecordDestructure(_, _)
|
||||||
|
@ -1189,7 +1200,7 @@ pub enum Pattern<'a> {
|
||||||
layout: Layout<'a>,
|
layout: Layout<'a>,
|
||||||
},
|
},
|
||||||
BitLiteral(bool),
|
BitLiteral(bool),
|
||||||
EnumLiteral(u8),
|
EnumLiteral {tag_id: u8, enum_size: u8 },
|
||||||
IntLiteral(i64),
|
IntLiteral(i64),
|
||||||
FloatLiteral(f64),
|
FloatLiteral(f64),
|
||||||
StrLiteral(Box<str>),
|
StrLiteral(Box<str>),
|
||||||
|
@ -1238,7 +1249,7 @@ fn from_can_pattern<'a>(
|
||||||
Pattern::BitLiteral(tag_name == top)
|
Pattern::BitLiteral(tag_name == top)
|
||||||
}
|
}
|
||||||
Ok(Layout::Builtin(Builtin::Byte(conversion))) => match conversion.get(&tag_name) {
|
Ok(Layout::Builtin(Builtin::Byte(conversion))) => match conversion.get(&tag_name) {
|
||||||
Some(index) => Pattern::EnumLiteral(*index),
|
Some(index) => Pattern::EnumLiteral{ tag_id : *index, enum_size: conversion.len() as u8 },
|
||||||
None => unreachable!("Tag must be in its own type"),
|
None => unreachable!("Tag must be in its own type"),
|
||||||
},
|
},
|
||||||
Ok(layout) => {
|
Ok(layout) => {
|
||||||
|
|
|
@ -12,4 +12,8 @@
|
||||||
#![allow(clippy::large_enum_variant)]
|
#![allow(clippy::large_enum_variant)]
|
||||||
pub mod expr;
|
pub mod expr;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
|
|
||||||
|
// Temporary, while we can build up test cases and optimize the exhaustiveness checking.
|
||||||
|
// For now, following this warning's advice will lead to nasty type inference errors.
|
||||||
|
#[allow(clippy::ptr_arg)]
|
||||||
pub mod pattern;
|
pub mod pattern;
|
||||||
|
|
482
compiler/mono/src/pattern.rs
Normal file
482
compiler/mono/src/pattern.rs
Normal file
|
@ -0,0 +1,482 @@
|
||||||
|
use roc_collections::all::MutMap;
|
||||||
|
use roc_module::ident::TagName;
|
||||||
|
use roc_region::all::{Located, Region};
|
||||||
|
|
||||||
|
use self::Pattern::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Union {
|
||||||
|
alternatives: Vec<Ctor>,
|
||||||
|
num_alts: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Ctor {
|
||||||
|
name: TagName,
|
||||||
|
arity: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Pattern {
|
||||||
|
Anything,
|
||||||
|
Literal(Literal),
|
||||||
|
Ctor(Union, TagName, std::vec::Vec<Pattern>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Literal {
|
||||||
|
Num(i64),
|
||||||
|
Int(i64),
|
||||||
|
Float(f64),
|
||||||
|
Str(Box<str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn simplify(pattern: &roc_can::pattern::Pattern) -> Pattern {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
|
simplify_help(pattern, &mut errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn simplify_help(pattern: &roc_can::pattern::Pattern, errors: &mut Vec<Error>) -> Pattern {
|
||||||
|
use roc_can::pattern::Pattern::*;
|
||||||
|
|
||||||
|
match pattern {
|
||||||
|
IntLiteral(v) => Literal(Literal::Int(*v)),
|
||||||
|
NumLiteral(_, v) => Literal(Literal::Int(*v)),
|
||||||
|
FloatLiteral(v) => Literal(Literal::Float(*v)),
|
||||||
|
StrLiteral(v) => Literal(Literal::Str(v.clone())),
|
||||||
|
|
||||||
|
Underscore => Anything,
|
||||||
|
Identifier(_) => Anything,
|
||||||
|
RecordDestructure { .. } => {
|
||||||
|
// TODO we must check the guard conditions!
|
||||||
|
Anything
|
||||||
|
}
|
||||||
|
|
||||||
|
Shadowed(_region, _ident) => {
|
||||||
|
// Treat as an Anything
|
||||||
|
// code-gen will make a runtime error out of the branch
|
||||||
|
Anything
|
||||||
|
}
|
||||||
|
UnsupportedPattern(_region) => {
|
||||||
|
// Treat as an Anything
|
||||||
|
// code-gen will make a runtime error out of the branch
|
||||||
|
Anything
|
||||||
|
}
|
||||||
|
|
||||||
|
AppliedTag {
|
||||||
|
tag_name,
|
||||||
|
arguments,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let union = Union {
|
||||||
|
alternatives: Vec::new(),
|
||||||
|
num_alts: 0,
|
||||||
|
};
|
||||||
|
let simplified_args: std::vec::Vec<_> = arguments
|
||||||
|
.iter()
|
||||||
|
.map(|v| simplify_help(&v.1.value, errors))
|
||||||
|
.collect();
|
||||||
|
Ctor(union, tag_name.clone(), simplified_args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Error {
|
||||||
|
Incomplete(Region, Context, Vec<Pattern>),
|
||||||
|
Redundant(Region, Region, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Context {
|
||||||
|
BadArg,
|
||||||
|
BadDestruct,
|
||||||
|
BadCase,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check
|
||||||
|
|
||||||
|
pub fn check(
|
||||||
|
region: Region,
|
||||||
|
patterns: &[Located<roc_can::pattern::Pattern>],
|
||||||
|
) -> Result<(), Vec<Error>> {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
check_patterns(region, Context::BadArg, patterns, &mut errors);
|
||||||
|
|
||||||
|
if errors.is_empty() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn check(module: roc_can::module::ModuleOutput) -> Result<(), Vec<Error>> {
|
||||||
|
// let mut errors = Vec::new();
|
||||||
|
// check_declarations(&module.declarations, &mut errors);
|
||||||
|
//
|
||||||
|
// if errors.is_empty() {
|
||||||
|
// Ok(())
|
||||||
|
// } else {
|
||||||
|
// Err(errors)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// CHECK DECLS
|
||||||
|
//
|
||||||
|
// fn check_declarations(decls: &[roc_can::def::Declaration], errors: &mut Vec<Error>) {
|
||||||
|
// use roc_can::def::Declaration;
|
||||||
|
//
|
||||||
|
// for decl in decls {
|
||||||
|
// Declaration::Declare(def) => check_def(def, errors),
|
||||||
|
// Declaration::DeclareRef(defs) => {
|
||||||
|
// for def in defs {
|
||||||
|
// check_def(def, errors);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// Declaration::InvalidCycle(_) => {}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fn check_def(def: &roc_can::def::Def, errors: &mut Vec<Error>) {
|
||||||
|
// check_patttern
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn check_patterns(
|
||||||
|
region: Region,
|
||||||
|
context: Context,
|
||||||
|
patterns: &[Located<roc_can::pattern::Pattern>],
|
||||||
|
errors: &mut Vec<Error>,
|
||||||
|
) {
|
||||||
|
match to_nonredundant_rows(region, patterns) {
|
||||||
|
Err(err) => errors.push(err),
|
||||||
|
Ok(matrix) => {
|
||||||
|
let bad_patterns = is_exhaustive(&matrix, 1);
|
||||||
|
if !bad_patterns.is_empty() {
|
||||||
|
// TODO i suspect this is like a concat in in practice? code below can panic
|
||||||
|
let heads = bad_patterns.into_iter().map(|mut v| v.remove(0)).collect();
|
||||||
|
errors.push(Error::Incomplete(region, context, heads));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// EXHAUSTIVE PATTERNS
|
||||||
|
|
||||||
|
/// INVARIANTS:
|
||||||
|
///
|
||||||
|
/// The initial rows "matrix" are all of length 1
|
||||||
|
/// The initial count of items per row "n" is also 1
|
||||||
|
/// The resulting rows are examples of missing patterns
|
||||||
|
fn is_exhaustive(matrix: &PatternMatrix, n: usize) -> PatternMatrix {
|
||||||
|
if matrix.is_empty() {
|
||||||
|
vec![std::iter::repeat(Anything).take(n).collect()]
|
||||||
|
} else if n == 0 {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
let ctors = collect_ctors(matrix);
|
||||||
|
let num_seen = ctors.len();
|
||||||
|
|
||||||
|
if num_seen == 0 {
|
||||||
|
let new_matrix = matrix
|
||||||
|
.iter()
|
||||||
|
.filter_map(specialize_row_by_anything)
|
||||||
|
.collect();
|
||||||
|
let mut rest = is_exhaustive(&new_matrix, n - 1);
|
||||||
|
|
||||||
|
for row in rest.iter_mut() {
|
||||||
|
row.push(Anything);
|
||||||
|
}
|
||||||
|
|
||||||
|
rest
|
||||||
|
} else {
|
||||||
|
let alts = ctors.iter().next().unwrap().1;
|
||||||
|
|
||||||
|
let alt_list = &alts.alternatives;
|
||||||
|
let num_alts = alts.num_alts;
|
||||||
|
|
||||||
|
if num_seen < num_alts {
|
||||||
|
let new_matrix = matrix
|
||||||
|
.iter()
|
||||||
|
.filter_map(specialize_row_by_anything)
|
||||||
|
.collect();
|
||||||
|
let rest: Vec<Vec<Pattern>> = is_exhaustive(&new_matrix, n - 1);
|
||||||
|
|
||||||
|
let last: _ = alt_list
|
||||||
|
.iter()
|
||||||
|
.filter_map(|r| is_missing(alts.clone(), ctors.clone(), r));
|
||||||
|
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
for last_option in last {
|
||||||
|
for mut row in rest.clone() {
|
||||||
|
row.push(last_option.clone());
|
||||||
|
|
||||||
|
result.push(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
let is_alt_exhaustive = |Ctor { name, arity }| {
|
||||||
|
let new_matrix = matrix
|
||||||
|
.iter()
|
||||||
|
.filter_map(|r| specialize_row_by_ctor(&name, arity, r))
|
||||||
|
.collect();
|
||||||
|
let rest: Vec<Vec<Pattern>> = is_exhaustive(&new_matrix, arity + n - 1);
|
||||||
|
|
||||||
|
let mut result = Vec::with_capacity(rest.len());
|
||||||
|
for row in rest {
|
||||||
|
result.push(recover_ctor(alts.clone(), name.clone(), arity, row));
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
};
|
||||||
|
|
||||||
|
alt_list
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(is_alt_exhaustive)
|
||||||
|
.flatten()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_missing<T>(union: Union, ctors: MutMap<TagName, T>, ctor: &Ctor) -> Option<Pattern> {
|
||||||
|
let Ctor { name, arity, .. } = ctor;
|
||||||
|
|
||||||
|
if ctors.contains_key(&name) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let anythings = std::iter::repeat(Anything).take(*arity).collect();
|
||||||
|
Some(Pattern::Ctor(union, name.clone(), anythings))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recover_ctor(
|
||||||
|
union: Union,
|
||||||
|
tag_name: TagName,
|
||||||
|
arity: usize,
|
||||||
|
mut patterns: Vec<Pattern>,
|
||||||
|
) -> Vec<Pattern> {
|
||||||
|
// TODO ensure that this behaves the same as haskell's splitAt
|
||||||
|
let mut rest = patterns.split_off(arity);
|
||||||
|
let args = patterns;
|
||||||
|
|
||||||
|
rest.push(Ctor(union, tag_name, args));
|
||||||
|
|
||||||
|
rest
|
||||||
|
}
|
||||||
|
|
||||||
|
/// REDUNDANT PATTERNS
|
||||||
|
|
||||||
|
/// INVARIANT: Produces a list of rows where (forall row. length row == 1)
|
||||||
|
fn to_nonredundant_rows(
|
||||||
|
overall_region: Region,
|
||||||
|
patterns: &[Located<roc_can::pattern::Pattern>],
|
||||||
|
) -> Result<Vec<Vec<Pattern>>, Error> {
|
||||||
|
let mut checked_rows = Vec::with_capacity(patterns.len());
|
||||||
|
|
||||||
|
for loc_pat in patterns {
|
||||||
|
let region = loc_pat.region;
|
||||||
|
|
||||||
|
let next_row = vec![simplify(&loc_pat.value)];
|
||||||
|
|
||||||
|
if is_useful(&checked_rows, &next_row) {
|
||||||
|
checked_rows.push(next_row);
|
||||||
|
} else {
|
||||||
|
return Err(Error::Redundant(
|
||||||
|
overall_region,
|
||||||
|
region,
|
||||||
|
checked_rows.len() + 1,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(checked_rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a new row "vector" is useful given previous rows "matrix"
|
||||||
|
fn is_useful(matrix: &PatternMatrix, vector: &Row) -> bool {
|
||||||
|
if matrix.is_empty() {
|
||||||
|
// No rows are the same as the new vector! The vector is useful!
|
||||||
|
true
|
||||||
|
} else if vector.is_empty() {
|
||||||
|
// There is nothing left in the new vector, but we still have
|
||||||
|
// rows that match the same things. This is not a useful vector!
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
let mut vector = vector.clone();
|
||||||
|
let first_pattern = vector.remove(0);
|
||||||
|
let patterns = vector;
|
||||||
|
|
||||||
|
match first_pattern {
|
||||||
|
// keep checking rows that start with this Ctor or Anything
|
||||||
|
Ctor(_, name, args) => {
|
||||||
|
let new_matrix: Vec<_> = matrix
|
||||||
|
.iter()
|
||||||
|
.filter_map(|r| specialize_row_by_ctor(&name, args.len(), r))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut new_row = Vec::new();
|
||||||
|
new_row.extend(args);
|
||||||
|
new_row.extend(patterns);
|
||||||
|
|
||||||
|
is_useful(&new_matrix, &new_row)
|
||||||
|
}
|
||||||
|
|
||||||
|
Anything => {
|
||||||
|
// check if all alts appear in matrix
|
||||||
|
match is_complete(matrix) {
|
||||||
|
Complete::No => {
|
||||||
|
// This Anything is useful because some Ctors are missing.
|
||||||
|
// But what if a previous row has an Anything?
|
||||||
|
// If so, this one is not useful.
|
||||||
|
let new_matrix: Vec<_> = matrix
|
||||||
|
.iter()
|
||||||
|
.filter_map(|r| specialize_row_by_anything(r))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
is_useful(&new_matrix, &patterns)
|
||||||
|
}
|
||||||
|
Complete::Yes(alts) => {
|
||||||
|
// All Ctors are covered, so this Anything is not needed for any
|
||||||
|
// of those. But what if some of those Ctors have subpatterns
|
||||||
|
// that make them less general? If so, this actually is useful!
|
||||||
|
let is_useful_alt = |Ctor { name, arity, .. }| {
|
||||||
|
let new_matrix = matrix
|
||||||
|
.iter()
|
||||||
|
.filter_map(|r| specialize_row_by_ctor(&name, arity, r))
|
||||||
|
.collect();
|
||||||
|
let mut new_row: Vec<Pattern> =
|
||||||
|
std::iter::repeat(Anything).take(arity).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
new_row.extend(patterns.clone());
|
||||||
|
|
||||||
|
is_useful(&new_matrix, &new_row)
|
||||||
|
};
|
||||||
|
|
||||||
|
alts.iter().cloned().any(is_useful_alt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Literal(literal) => {
|
||||||
|
// keep checking rows that start with this Literal or Anything
|
||||||
|
let new_matrix = matrix
|
||||||
|
.iter()
|
||||||
|
.filter_map(|r| specialize_row_by_literal(&literal, r))
|
||||||
|
.collect();
|
||||||
|
is_useful(&new_matrix, &patterns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// INVARIANT: (length row == N) ==> (length result == arity + N - 1)
|
||||||
|
fn specialize_row_by_ctor(tag_name: &TagName, arity: usize, row: &Row) -> Option<Row> {
|
||||||
|
let mut row = row.clone();
|
||||||
|
|
||||||
|
let head = row.pop();
|
||||||
|
let patterns = row;
|
||||||
|
|
||||||
|
match head {
|
||||||
|
Some(Ctor(_,name, args)) =>
|
||||||
|
if &name == tag_name {
|
||||||
|
// TODO order!
|
||||||
|
let mut new_patterns = Vec::new();
|
||||||
|
new_patterns.extend(args);
|
||||||
|
new_patterns.extend(patterns);
|
||||||
|
Some(new_patterns)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Some(Anything) => {
|
||||||
|
// TODO order!
|
||||||
|
let new_patterns =
|
||||||
|
std::iter::repeat(Anything).take(arity).chain(patterns).collect();
|
||||||
|
Some(new_patterns)
|
||||||
|
}
|
||||||
|
Some(Literal(_)) => panic!( "Compiler bug! After type checking, constructors and literal should never align in pattern match exhaustiveness checks."),
|
||||||
|
None => panic!("Compiler error! Empty matrices should not get specialized."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// INVARIANT: (length row == N) ==> (length result == N-1)
|
||||||
|
fn specialize_row_by_literal(literal: &Literal, row: &Row) -> Option<Row> {
|
||||||
|
let mut row = row.clone();
|
||||||
|
|
||||||
|
let head = row.pop();
|
||||||
|
let patterns = row;
|
||||||
|
|
||||||
|
match head {
|
||||||
|
Some(Literal(lit)) => if &lit == literal { Some(patterns) } else{ None } ,
|
||||||
|
Some(Anything) => Some(patterns),
|
||||||
|
|
||||||
|
Some(Ctor(_,_,_)) => panic!( "Compiler bug! After type checking, constructors and literals should never align in pattern match exhaustiveness checks."),
|
||||||
|
|
||||||
|
None => panic!("Compiler error! Empty matrices should not get specialized."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// INVARIANT: (length row == N) ==> (length result == N-1)
|
||||||
|
fn specialize_row_by_anything(row: &Row) -> Option<Row> {
|
||||||
|
let mut row = row.clone();
|
||||||
|
|
||||||
|
match row.pop() {
|
||||||
|
Some(Anything) => Some(row),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ALL CONSTRUCTORS ARE PRESENT?
|
||||||
|
|
||||||
|
pub enum Complete {
|
||||||
|
Yes(Vec<Ctor>),
|
||||||
|
No,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_complete(matrix: &PatternMatrix) -> Complete {
|
||||||
|
let ctors = collect_ctors(matrix);
|
||||||
|
|
||||||
|
let mut it = ctors.values();
|
||||||
|
|
||||||
|
match it.next() {
|
||||||
|
None => Complete::No,
|
||||||
|
Some(Union {
|
||||||
|
alternatives,
|
||||||
|
num_alts,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
if ctors.len() == *num_alts {
|
||||||
|
Complete::Yes(alternatives.to_vec())
|
||||||
|
} else {
|
||||||
|
Complete::No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// COLLECT CTORS
|
||||||
|
|
||||||
|
type RefPatternMatrix = [Vec<Pattern>];
|
||||||
|
type PatternMatrix = Vec<Vec<Pattern>>;
|
||||||
|
type Row = Vec<Pattern>;
|
||||||
|
|
||||||
|
fn collect_ctors(matrix: &RefPatternMatrix) -> MutMap<TagName, Union> {
|
||||||
|
let mut ctors = MutMap::default();
|
||||||
|
|
||||||
|
for row in matrix {
|
||||||
|
if let Some(Ctor(union, name, _)) = row.get(0) {
|
||||||
|
ctors.insert(name.clone(), union.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctors
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue