diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index fd321a13aa..e9d42fd544 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -335,7 +335,13 @@ fn pattern_to_when<'a>( (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 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. let arena = env.arena; 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 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 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 { IntLiteral(int) => { // Switch only compares the condition to the @@ -961,35 +992,15 @@ fn from_can_when<'a>( jumpable_branches.push((*int as u64, mono_expr)); } BitLiteral(v) => jumpable_branches.push((*v as u64, mono_expr)), - EnumLiteral(v) => jumpable_branches.push((*v as u64, mono_expr)), - Identifier(symbol) => { - // Since this is an ident, it must be - // 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)); + EnumLiteral { tag_id , .. } => jumpable_branches.push((*tag_id as u64, mono_expr)), + Identifier(_) => { + // store is handled above } + Underscore => {} Shadowed(_, _) => { 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) => { - panic!("TODO runtime error for unsupported pattern"); - } + UnsupportedPattern(_region) => unreachable!("When accepts all patterns"), AppliedTag { .. } | StrLiteral(_) | RecordDestructure(_, _) @@ -1189,7 +1200,7 @@ pub enum Pattern<'a> { layout: Layout<'a>, }, BitLiteral(bool), - EnumLiteral(u8), + EnumLiteral {tag_id: u8, enum_size: u8 }, IntLiteral(i64), FloatLiteral(f64), StrLiteral(Box), @@ -1238,7 +1249,7 @@ fn from_can_pattern<'a>( Pattern::BitLiteral(tag_name == top) } 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"), }, Ok(layout) => { diff --git a/compiler/mono/src/lib.rs b/compiler/mono/src/lib.rs index 2082770978..37bd3c4957 100644 --- a/compiler/mono/src/lib.rs +++ b/compiler/mono/src/lib.rs @@ -12,4 +12,8 @@ #![allow(clippy::large_enum_variant)] pub mod expr; 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; diff --git a/compiler/mono/src/pattern.rs b/compiler/mono/src/pattern.rs new file mode 100644 index 0000000000..7068d1e725 --- /dev/null +++ b/compiler/mono/src/pattern.rs @@ -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, + 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), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Literal { + Num(i64), + Int(i64), + Float(f64), + Str(Box), +} + +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) -> 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), + Redundant(Region, Region, usize), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Context { + BadArg, + BadDestruct, + BadCase, +} + +/// Check + +pub fn check( + region: Region, + patterns: &[Located], +) -> Result<(), Vec> { + 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> { +// 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) { +// 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) { +// check_patttern +// +// +// } + +pub fn check_patterns( + region: Region, + context: Context, + patterns: &[Located], + errors: &mut Vec, +) { + 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> = 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> = 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(union: Union, ctors: MutMap, ctor: &Ctor) -> Option { + 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, +) -> Vec { + // 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], +) -> Result>, 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 = + std::iter::repeat(Anything).take(arity).collect::>(); + + 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 { + 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 { + 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 { + let mut row = row.clone(); + + match row.pop() { + Some(Anything) => Some(row), + _ => None, + } +} + +/// ALL CONSTRUCTORS ARE PRESENT? + +pub enum Complete { + Yes(Vec), + 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]; +type PatternMatrix = Vec>; +type Row = Vec; + +fn collect_ctors(matrix: &RefPatternMatrix) -> MutMap { + 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 +}