diff --git a/Cargo.lock b/Cargo.lock index 6f5be90118..6409366ecb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3502,6 +3502,16 @@ dependencies = [ name = "roc_error_macros" version = "0.1.0" +[[package]] +name = "roc_exhaustive" +version = "0.1.0" +dependencies = [ + "roc_collections", + "roc_module", + "roc_region", + "roc_std", +] + [[package]] name = "roc_fmt" version = "0.1.0" @@ -3651,6 +3661,7 @@ dependencies = [ "roc_can", "roc_collections", "roc_error_macros", + "roc_exhaustive", "roc_module", "roc_problem", "roc_region", @@ -3772,6 +3783,7 @@ dependencies = [ "roc_can", "roc_collections", "roc_constrain", + "roc_exhaustive", "roc_module", "roc_mono", "roc_parse", diff --git a/Cargo.toml b/Cargo.toml index 874eeb705d..bbca25568c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "compiler/ident", "compiler/region", "compiler/collections", + "compiler/exhaustive", "compiler/module", "compiler/parse", "compiler/can", diff --git a/compiler/exhaustive/Cargo.toml b/compiler/exhaustive/Cargo.toml new file mode 100644 index 0000000000..7fa1a63bda --- /dev/null +++ b/compiler/exhaustive/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "roc_exhaustive" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" + +[dependencies] +roc_collections = { path = "../collections" } +roc_region = { path = "../region" } +roc_module = { path = "../module" } +roc_std = { path = "../../roc_std", default-features = false } diff --git a/compiler/exhaustive/src/lib.rs b/compiler/exhaustive/src/lib.rs new file mode 100644 index 0000000000..c80b9b01d0 --- /dev/null +++ b/compiler/exhaustive/src/lib.rs @@ -0,0 +1,440 @@ +use roc_collections::all::{Index, MutMap}; +use roc_module::ident::{Lowercase, TagIdIntType, TagName}; +use roc_region::all::Region; +use roc_std::RocDec; + +use self::Pattern::*; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Union { + pub alternatives: Vec, + pub render_as: RenderAs, +} + +impl Union { + pub fn newtype_wrapper(tag_name: TagName, arity: usize) -> Self { + let alternatives = vec![Ctor { + name: tag_name, + tag_id: TagId(0), + arity, + }]; + + Union { + alternatives, + render_as: RenderAs::Tag, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum RenderAs { + Tag, + Opaque, + Record(Vec), + Guard, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)] +pub struct TagId(pub TagIdIntType); + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Ctor { + pub name: TagName, + pub tag_id: TagId, + pub arity: usize, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Pattern { + Anything, + Literal(Literal), + Ctor(Union, TagId, std::vec::Vec), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Literal { + Int(i128), + U128(u128), + Bit(bool), + Byte(u8), + Float(u64), + Decimal(RocDec), + Str(Box), +} + +/// Error + +#[derive(Clone, Debug, PartialEq)] +pub enum Error { + Incomplete(Region, Context, Vec), + Redundant { + overall_region: Region, + branch_region: Region, + index: Index, + }, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Context { + BadArg, + BadDestruct, + BadCase, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Guard { + HasGuard, + NoGuard, +} + +/// Check + +pub fn check( + region: Region, + context: Context, + matrix: Vec>, +) -> Result<(), Vec> { + let mut errors = Vec::new(); + 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 + // if this debug_assert! ever fails, the theory is disproven + debug_assert!(bad_patterns.iter().map(|v| v.len()).sum::() == bad_patterns.len()); + let heads = bad_patterns.into_iter().map(|mut v| v.remove(0)).collect(); + errors.push(Error::Incomplete(region, context, heads)); + return Err(errors); + } + Ok(()) +} + +/// 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: &RefPatternMatrix, 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: Vec<_> = matrix + .iter() + .filter_map(|row| specialize_row_by_anything(row)) + .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 = alt_list.len(); + + if num_seen < num_alts { + let new_matrix: Vec<_> = matrix + .iter() + .filter_map(|row| specialize_row_by_anything(row)) + .collect(); + let rest: Vec> = is_exhaustive(&new_matrix, n - 1); + + let last: _ = alt_list + .iter() + .filter_map(|r| is_missing(alts.clone(), &ctors, 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 { arity, tag_id, .. }| { + let new_matrix: Vec<_> = matrix + .iter() + .filter_map(|r| specialize_row_by_ctor(tag_id, 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(), tag_id, 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 { arity, tag_id, .. } = ctor; + + if ctors.contains_key(tag_id) { + None + } else { + let anythings = std::iter::repeat(Anything).take(*arity).collect(); + Some(Pattern::Ctor(union, *tag_id, anythings)) + } +} + +fn recover_ctor( + union: Union, + tag_id: TagId, + arity: usize, + mut patterns: Vec, +) -> Vec { + let mut rest = patterns.split_off(arity); + let args = patterns; + + rest.push(Ctor(union, tag_id, args)); + + rest +} + +/// Check if a new row "vector" is useful given previous rows "matrix" +pub fn is_useful(mut old_matrix: PatternMatrix, mut vector: Row) -> bool { + let mut matrix = Vec::with_capacity(old_matrix.len()); + + // this loop ping-pongs the rows between old_matrix and matrix + 'outer: loop { + match vector.pop() { + _ if old_matrix.is_empty() => { + // No rows are the same as the new vector! The vector is useful! + break true; + } + None => { + // There is nothing left in the new vector, but we still have + // rows that match the same things. This is not a useful vector! + break false; + } + Some(first_pattern) => { + // NOTE: if there are bugs in this code, look at the ordering of the row/matrix + + match first_pattern { + // keep checking rows that start with this Ctor or Anything + Ctor(_, id, args) => { + specialize_row_by_ctor2(id, args.len(), &mut old_matrix, &mut matrix); + + std::mem::swap(&mut old_matrix, &mut matrix); + + vector.extend(args); + } + + Anything => { + // check if all alternatives appear in matrix + match is_complete(&old_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. + for mut row in old_matrix.drain(..) { + if let Some(Anything) = row.pop() { + matrix.push(row); + } + } + + std::mem::swap(&mut old_matrix, &mut matrix); + } + Complete::Yes(alternatives) => { + // 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! + for alternative in alternatives { + let Ctor { arity, tag_id, .. } = alternative; + + let mut old_matrix = old_matrix.clone(); + let mut matrix = vec![]; + specialize_row_by_ctor2( + tag_id, + arity, + &mut old_matrix, + &mut matrix, + ); + + let mut vector = vector.clone(); + vector.extend(std::iter::repeat(Anything).take(arity)); + + if is_useful(matrix, vector) { + break 'outer true; + } + } + + break false; + } + } + } + + Literal(literal) => { + // keep checking rows that start with this Literal or Anything + + for mut row in old_matrix.drain(..) { + let head = row.pop(); + let patterns = row; + + match head { + Some(Literal(lit)) => { + if lit == literal { + matrix.push(patterns); + } else { + // do nothing + } + } + Some(Anything) => matrix.push(patterns), + + Some(Ctor(_, _, _)) => panic!( + r#"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." + ), + } + } + std::mem::swap(&mut old_matrix, &mut matrix); + } + } + } + } + } +} + +/// INVARIANT: (length row == N) ==> (length result == arity + N - 1) +fn specialize_row_by_ctor2( + tag_id: TagId, + arity: usize, + old_matrix: &mut PatternMatrix, + matrix: &mut PatternMatrix, +) { + for mut row in old_matrix.drain(..) { + let head = row.pop(); + let mut patterns = row; + + match head { + Some(Ctor(_, id, args)) => + if id == tag_id { + patterns.extend(args); + matrix.push(patterns); + } else { + // do nothing + } + Some(Anything) => { + // TODO order! + patterns.extend(std::iter::repeat(Anything).take(arity)); + matrix.push(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 == arity + N - 1) +fn specialize_row_by_ctor(tag_id: TagId, arity: usize, row: &RefRow) -> Option { + let mut row = row.to_vec(); + + let head = row.pop(); + let patterns = row; + + match head { + Some(Ctor(_, id, args)) => { + if id == tag_id { + // 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(_)) => unreachable!( + r#"Compiler bug! After type checking, a constructor can never align with a literal: that should be a type error!"# + ), + None => panic!("Compiler error! Empty matrices should not get specialized."), + } +} + +/// INVARIANT: (length row == N) ==> (length result == N-1) +fn specialize_row_by_anything(row: &RefRow) -> Option { + let mut row = row.to_vec(); + + match row.pop() { + Some(Anything) => Some(row), + _ => None, + } +} + +/// ALL CONSTRUCTORS ARE PRESENT? + +pub enum Complete { + Yes(Vec), + No, +} + +fn is_complete(matrix: &RefPatternMatrix) -> Complete { + let ctors = collect_ctors(matrix); + let length = ctors.len(); + let mut it = ctors.into_iter(); + + match it.next() { + None => Complete::No, + Some((_, Union { alternatives, .. })) => { + if length == alternatives.len() { + Complete::Yes(alternatives) + } else { + Complete::No + } + } + } +} + +/// COLLECT CTORS + +type RefPatternMatrix = [Vec]; +type PatternMatrix = Vec>; +type RefRow = [Pattern]; +type Row = Vec; + +fn collect_ctors(matrix: &RefPatternMatrix) -> MutMap { + let mut ctors = MutMap::default(); + + for row in matrix { + if let Some(Ctor(union, id, _)) = row.get(row.len() - 1) { + ctors.insert(*id, union.clone()); + } + } + + ctors +} diff --git a/compiler/module/src/ident.rs b/compiler/module/src/ident.rs index 73a5fc9737..3f3c8d51f1 100644 --- a/compiler/module/src/ident.rs +++ b/compiler/module/src/ident.rs @@ -40,6 +40,8 @@ pub struct Uppercase(IdentStr); #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] pub struct ForeignSymbol(IdentStr); +pub type TagIdIntType = u16; + #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum TagName { /// Global tags have no module, but tend to be short strings (since they're diff --git a/compiler/mono/Cargo.toml b/compiler/mono/Cargo.toml index dd07c5f107..d47714ebdd 100644 --- a/compiler/mono/Cargo.toml +++ b/compiler/mono/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] roc_collections = { path = "../collections" } +roc_exhaustive = { path = "../exhaustive" } roc_region = { path = "../region" } roc_module = { path = "../module" } roc_types = { path = "../types" } diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index cd68b13503..d0b1a1dff7 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -1,10 +1,10 @@ -use crate::exhaustive::{Ctor, RenderAs, TagId, Union}; use crate::ir::{ BranchInfo, DestructType, Env, Expr, JoinPointId, Literal, Param, Pattern, Procs, Stmt, }; use crate::layout::{Builtin, Layout, LayoutCache, TagIdIntType, UnionLayout}; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::{MutMap, MutSet}; +use roc_exhaustive::{Ctor, RenderAs, TagId, Union}; use roc_module::ident::TagName; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; @@ -83,7 +83,7 @@ enum Test<'a> { IsCtor { tag_id: TagIdIntType, tag_name: TagName, - union: crate::exhaustive::Union, + union: roc_exhaustive::Union, arguments: Vec<(Pattern<'a>, Layout<'a>)>, }, IsInt(i128, IntWidth), diff --git a/compiler/mono/src/exhaustive.rs b/compiler/mono/src/exhaustive.rs index c64ee79e06..cafbf8bbf9 100644 --- a/compiler/mono/src/exhaustive.rs +++ b/compiler/mono/src/exhaustive.rs @@ -1,67 +1,12 @@ -use crate::{ir::DestructType, layout::TagIdIntType}; -use roc_collections::all::{Index, MutMap}; -use roc_module::ident::{Lowercase, TagName}; +use crate::ir::DestructType; +use roc_collections::all::Index; +use roc_exhaustive::{ + is_useful, Context, Ctor, Error, Guard, Literal, Pattern, RenderAs, TagId, Union, +}; +use roc_module::ident::{TagIdIntType, TagName}; use roc_region::all::{Loc, Region}; -use roc_std::RocDec; -use self::Pattern::*; - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct Union { - pub alternatives: Vec, - pub render_as: RenderAs, -} - -impl Union { - pub fn newtype_wrapper(tag_name: TagName, arity: usize) -> Self { - let alternatives = vec![Ctor { - name: tag_name, - tag_id: TagId(0), - arity, - }]; - - Union { - alternatives, - render_as: RenderAs::Tag, - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum RenderAs { - Tag, - Opaque, - Record(Vec), - Guard, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)] -pub struct TagId(pub TagIdIntType); - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct Ctor { - pub name: TagName, - pub tag_id: TagId, - pub arity: usize, -} - -#[derive(Clone, Debug, PartialEq)] -pub enum Pattern { - Anything, - Literal(Literal), - Ctor(Union, TagId, std::vec::Vec), -} - -#[derive(Clone, Debug, PartialEq)] -pub enum Literal { - Int(i128), - U128(u128), - Bit(bool), - Byte(u8), - Float(u64), - Decimal(RocDec), - Str(Box), -} +use Pattern::*; fn simplify(pattern: &crate::ir::Pattern) -> Pattern { use crate::ir::Pattern::*; @@ -153,33 +98,6 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern { } } -/// Error - -#[derive(Clone, Debug, PartialEq)] -pub enum Error { - Incomplete(Region, Context, Vec), - Redundant { - overall_region: Region, - branch_region: Region, - index: Index, - }, -} - -#[derive(Clone, Debug, PartialEq)] -pub enum Context { - BadArg, - BadDestruct, - BadCase, -} - -#[derive(Clone, Debug, PartialEq)] -pub enum Guard { - HasGuard, - NoGuard, -} - -/// Check - pub fn check( region: Region, patterns: &[(Loc, Guard)], @@ -204,128 +122,13 @@ pub fn check_patterns<'a>( 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 - // if this debug_assert! ever fails, the theory is disproven - debug_assert!( - bad_patterns.iter().map(|v| v.len()).sum::() == bad_patterns.len() - ); - let heads = bad_patterns.into_iter().map(|mut v| v.remove(0)).collect(); - errors.push(Error::Incomplete(region, context, heads)); + if let Err(err) = roc_exhaustive::check(region, context, matrix) { + *errors = err; } } } } -/// 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 = alt_list.len(); - - 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, 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 { arity, tag_id, .. }| { - let new_matrix = matrix - .iter() - .filter_map(|r| specialize_row_by_ctor(tag_id, 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(), tag_id, 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 { arity, tag_id, .. } = ctor; - - if ctors.contains_key(tag_id) { - None - } else { - let anythings = std::iter::repeat(Anything).take(*arity).collect(); - Some(Pattern::Ctor(union, *tag_id, anythings)) - } -} - -fn recover_ctor( - union: Union, - tag_id: TagId, - arity: usize, - mut patterns: Vec, -) -> Vec { - let mut rest = patterns.split_off(arity); - let args = patterns; - - rest.push(Ctor(union, tag_id, args)); - - rest -} - /// REDUNDANT PATTERNS /// INVARIANT: Produces a list of rows where (forall row. length row == 1) @@ -393,226 +196,3 @@ fn to_nonredundant_rows( Ok(checked_rows) } - -/// Check if a new row "vector" is useful given previous rows "matrix" -fn is_useful(mut old_matrix: PatternMatrix, mut vector: Row) -> bool { - let mut matrix = Vec::with_capacity(old_matrix.len()); - - // this loop ping-pongs the rows between old_matrix and matrix - 'outer: loop { - match vector.pop() { - _ if old_matrix.is_empty() => { - // No rows are the same as the new vector! The vector is useful! - break true; - } - None => { - // There is nothing left in the new vector, but we still have - // rows that match the same things. This is not a useful vector! - break false; - } - Some(first_pattern) => { - // NOTE: if there are bugs in this code, look at the ordering of the row/matrix - - match first_pattern { - // keep checking rows that start with this Ctor or Anything - Ctor(_, id, args) => { - specialize_row_by_ctor2(id, args.len(), &mut old_matrix, &mut matrix); - - std::mem::swap(&mut old_matrix, &mut matrix); - - vector.extend(args); - } - - Anything => { - // check if all alternatives appear in matrix - match is_complete(&old_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. - for mut row in old_matrix.drain(..) { - if let Some(Anything) = row.pop() { - matrix.push(row); - } - } - - std::mem::swap(&mut old_matrix, &mut matrix); - } - Complete::Yes(alternatives) => { - // 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! - for alternative in alternatives { - let Ctor { arity, tag_id, .. } = alternative; - - let mut old_matrix = old_matrix.clone(); - let mut matrix = vec![]; - specialize_row_by_ctor2( - tag_id, - arity, - &mut old_matrix, - &mut matrix, - ); - - let mut vector = vector.clone(); - vector.extend(std::iter::repeat(Anything).take(arity)); - - if is_useful(matrix, vector) { - break 'outer true; - } - } - - break false; - } - } - } - - Literal(literal) => { - // keep checking rows that start with this Literal or Anything - - for mut row in old_matrix.drain(..) { - let head = row.pop(); - let patterns = row; - - match head { - Some(Literal(lit)) => { - if lit == literal { - matrix.push(patterns); - } else { - // do nothing - } - } - Some(Anything) => matrix.push(patterns), - - Some(Ctor(_, _, _)) => panic!( - r#"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." - ), - } - } - std::mem::swap(&mut old_matrix, &mut matrix); - } - } - } - } - } -} - -/// INVARIANT: (length row == N) ==> (length result == arity + N - 1) -fn specialize_row_by_ctor2( - tag_id: TagId, - arity: usize, - old_matrix: &mut PatternMatrix, - matrix: &mut PatternMatrix, -) { - for mut row in old_matrix.drain(..) { - let head = row.pop(); - let mut patterns = row; - - match head { - Some(Ctor(_, id, args)) => - if id == tag_id { - patterns.extend(args); - matrix.push(patterns); - } else { - // do nothing - } - Some(Anything) => { - // TODO order! - patterns.extend(std::iter::repeat(Anything).take(arity)); - matrix.push(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 == arity + N - 1) -fn specialize_row_by_ctor(tag_id: TagId, arity: usize, row: &Row) -> Option { - let mut row = row.clone(); - - let head = row.pop(); - let patterns = row; - - match head { - Some(Ctor(_, id, args)) => { - if id == tag_id { - // 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(_)) => unreachable!( - r#"Compiler bug! After type checking, a constructor can never align with a literal: that should be a type error!"# - ), - 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 length = ctors.len(); - let mut it = ctors.into_iter(); - - match it.next() { - None => Complete::No, - Some((_, Union { alternatives, .. })) => { - if length == alternatives.len() { - Complete::Yes(alternatives) - } 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, id, _)) = row.get(row.len() - 1) { - ctors.insert(*id, union.clone()); - } - } - - ctors -} diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 8375046869..559b9dae1f 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -1,6 +1,5 @@ #![allow(clippy::manual_map)] -use crate::exhaustive::{Ctor, Guard, RenderAs, TagId}; use crate::layout::{ Builtin, ClosureRepresentation, LambdaSet, Layout, LayoutCache, LayoutProblem, RawFunctionLayout, TagIdIntType, UnionLayout, WrappedVariant, @@ -10,6 +9,7 @@ use bumpalo::Bump; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_can::expr::{ClosureData, IntValue}; use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap}; +use roc_exhaustive::{Ctor, Guard, RenderAs, TagId}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; @@ -95,7 +95,7 @@ pub enum OptLevel { #[derive(Clone, Debug, PartialEq)] pub enum MonoProblem { - PatternProblem(crate::exhaustive::Error), + PatternProblem(roc_exhaustive::Error), } #[derive(Debug, Clone, Copy)] @@ -1895,7 +1895,7 @@ fn patterns_to_when<'a>( // see https://github.com/rtfeldman/roc/issues/786 // this must be fixed when moving exhaustiveness checking to the new canonical AST for (pattern_var, pattern) in patterns.into_iter() { - let context = crate::exhaustive::Context::BadArg; + let context = roc_exhaustive::Context::BadArg; let mono_pattern = match from_can_pattern(env, layout_cache, &pattern.value) { Ok((pat, _assignments)) => { // Don't apply any assignments (e.g. to initialize optional variables) yet. @@ -1921,7 +1921,7 @@ fn patterns_to_when<'a>( pattern.region, &[( Loc::at(pattern.region, mono_pattern), - crate::exhaustive::Guard::NoGuard, + roc_exhaustive::Guard::NoGuard, )], context, ) { @@ -3279,12 +3279,12 @@ pub fn with_hole<'a>( } }; - let context = crate::exhaustive::Context::BadDestruct; + let context = roc_exhaustive::Context::BadDestruct; match crate::exhaustive::check( def.loc_pattern.region, &[( Loc::at(def.loc_pattern.region, mono_pattern.clone()), - crate::exhaustive::Guard::NoGuard, + roc_exhaustive::Guard::NoGuard, )], context, ) { @@ -5619,12 +5619,12 @@ pub fn from_can<'a>( hole, ) } else { - let context = crate::exhaustive::Context::BadDestruct; + let context = roc_exhaustive::Context::BadDestruct; match crate::exhaustive::check( def.loc_pattern.region, &[( Loc::at(def.loc_pattern.region, mono_pattern.clone()), - crate::exhaustive::Guard::NoGuard, + roc_exhaustive::Guard::NoGuard, )], context, ) { @@ -5750,11 +5750,11 @@ fn to_opt_branches<'a>( // NOTE exhaustiveness is checked after the construction of all the branches // In contrast to elm (currently), we still do codegen even if a pattern is non-exhaustive. // So we not only report exhaustiveness errors, but also correct them - let context = crate::exhaustive::Context::BadCase; + let context = roc_exhaustive::Context::BadCase; match crate::exhaustive::check(region, &loc_branches, context) { Ok(_) => {} Err(errors) => { - use crate::exhaustive::Error::*; + use roc_exhaustive::Error::*; let mut is_not_exhaustive = false; let mut overlapping_branches = std::vec::Vec::new(); @@ -7657,12 +7657,12 @@ pub enum Pattern<'a> { BitLiteral { value: bool, tag_name: TagName, - union: crate::exhaustive::Union, + union: roc_exhaustive::Union, }, EnumLiteral { tag_id: u8, tag_name: TagName, - union: crate::exhaustive::Union, + union: roc_exhaustive::Union, }, StrLiteral(Box), @@ -7676,7 +7676,7 @@ pub enum Pattern<'a> { tag_id: TagIdIntType, arguments: Vec<'a, (Pattern<'a>, Layout<'a>)>, layout: UnionLayout<'a>, - union: crate::exhaustive::Union, + union: roc_exhaustive::Union, }, OpaqueUnwrap { opaque: Symbol, @@ -7817,8 +7817,8 @@ fn from_can_pattern_help<'a>( arguments, .. } => { - use crate::exhaustive::Union; use crate::layout::UnionVariant::*; + use roc_exhaustive::Union; let res_variant = crate::layout::union_sorted_tags(env.arena, *whole_var, env.subs, env.target_info) @@ -7883,7 +7883,7 @@ fn from_can_pattern_help<'a>( }) } - let union = crate::exhaustive::Union { + let union = roc_exhaustive::Union { render_as: RenderAs::Tag, alternatives: ctors, }; @@ -7974,7 +7974,7 @@ fn from_can_pattern_help<'a>( }) } - let union = crate::exhaustive::Union { + let union = roc_exhaustive::Union { render_as: RenderAs::Tag, alternatives: ctors, }; @@ -8026,7 +8026,7 @@ fn from_can_pattern_help<'a>( }) } - let union = crate::exhaustive::Union { + let union = roc_exhaustive::Union { render_as: RenderAs::Tag, alternatives: ctors, }; @@ -8069,7 +8069,7 @@ fn from_can_pattern_help<'a>( arity: fields.len(), }); - let union = crate::exhaustive::Union { + let union = roc_exhaustive::Union { render_as: RenderAs::Tag, alternatives: ctors, }; @@ -8139,7 +8139,7 @@ fn from_can_pattern_help<'a>( }); } - let union = crate::exhaustive::Union { + let union = roc_exhaustive::Union { render_as: RenderAs::Tag, alternatives: ctors, }; @@ -8194,7 +8194,7 @@ fn from_can_pattern_help<'a>( arity: other_fields.len() - 1, }); - let union = crate::exhaustive::Union { + let union = roc_exhaustive::Union { render_as: RenderAs::Tag, alternatives: ctors, }; diff --git a/reporting/Cargo.toml b/reporting/Cargo.toml index 2add55b827..f2f9bf1c57 100644 --- a/reporting/Cargo.toml +++ b/reporting/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] roc_collections = { path = "../compiler/collections" } +roc_exhaustive = { path = "../compiler/exhaustive" } roc_region = { path = "../compiler/region" } roc_module = { path = "../compiler/module" } roc_parse = { path = "../compiler/parse" } diff --git a/reporting/src/error/mono.rs b/reporting/src/error/mono.rs index beac8a2d20..74e241281b 100644 --- a/reporting/src/error/mono.rs +++ b/reporting/src/error/mono.rs @@ -10,8 +10,8 @@ pub fn mono_problem<'b>( filename: PathBuf, problem: roc_mono::ir::MonoProblem, ) -> Report<'b> { - use roc_mono::exhaustive::Context::*; - use roc_mono::exhaustive::Error::*; + use roc_exhaustive::Context::*; + use roc_exhaustive::Error::*; use roc_mono::ir::MonoProblem::*; match problem { @@ -121,7 +121,7 @@ pub fn mono_problem<'b>( pub fn unhandled_patterns_to_doc_block<'b>( alloc: &'b RocDocAllocator<'b>, - patterns: Vec, + patterns: Vec, ) -> RocDocBuilder<'b> { alloc .vcat(patterns.into_iter().map(|v| pattern_to_doc(alloc, v))) @@ -131,19 +131,19 @@ pub fn unhandled_patterns_to_doc_block<'b>( fn pattern_to_doc<'b>( alloc: &'b RocDocAllocator<'b>, - pattern: roc_mono::exhaustive::Pattern, + pattern: roc_exhaustive::Pattern, ) -> RocDocBuilder<'b> { pattern_to_doc_help(alloc, pattern, false) } fn pattern_to_doc_help<'b>( alloc: &'b RocDocAllocator<'b>, - pattern: roc_mono::exhaustive::Pattern, + pattern: roc_exhaustive::Pattern, in_type_param: bool, ) -> RocDocBuilder<'b> { - use roc_mono::exhaustive::Literal::*; - use roc_mono::exhaustive::Pattern::*; - use roc_mono::exhaustive::RenderAs; + use roc_exhaustive::Literal::*; + use roc_exhaustive::Pattern::*; + use roc_exhaustive::RenderAs; match pattern { Anything => alloc.text("_"),