use crate::{ ast, tipo::{self, environment::Environment, error::Error, Type}, }; use itertools::Itertools; use std::{collections::BTreeMap, iter, ops::Deref}; const NIL_NAME: &str = "[]"; const CONS_NAME: &str = "::"; const TUPLE_NAME: &str = "__Tuple"; #[derive(Debug, Clone)] pub(crate) struct PatternStack(Vec); impl From for PatternStack { fn from(value: Pattern) -> Self { Self(vec![value]) } } impl From> for PatternStack { fn from(value: Vec) -> Self { Self(value) } } impl From for Vec { fn from(value: PatternStack) -> Self { value.0 } } impl PatternStack { fn is_empty(&self) -> bool { self.0.is_empty() } fn insert(&mut self, index: usize, element: Pattern) { self.0.insert(index, element); } pub(super) fn head(&self) -> &Pattern { &self.0[0] } fn tail(&self) -> PatternStack { self.0 .iter() .skip(1) .cloned() .collect::>() .into() } fn iter(&self) -> impl Iterator { self.0.iter() } fn chain_tail_to_iter<'a>(&'a self, front: impl Iterator) -> PatternStack { front .chain(self.iter().skip(1)) .cloned() .collect::>() .into() } fn chain_tail_into_iter(&self, front: impl Iterator) -> PatternStack { front .chain(self.iter().skip(1).cloned()) .collect::>() .into() } // INVARIANT: (length row == N) ==> (length result == arity + N - 1) fn specialize_row_by_ctor(&self, name: &String, arity: usize) -> Option { match self.head() { Pattern::Constructor(p_name, _, p_args) => { if p_name == name && p_args.len() == arity { Some(self.chain_tail_to_iter(p_args.iter())) } else { None } } Pattern::Wildcard => { Some(self.chain_tail_into_iter(vec![Pattern::Wildcard; arity].into_iter())) } Pattern::Literal(_) => unreachable!( "constructors and literals should never align in pattern match exhaustiveness checks." ), } } // INVARIANT: (length row == N) ==> (length result == N-1) fn specialize_row_by_wildcard(&self) -> Option { if self.is_empty() { return None; } match self.head() { Pattern::Constructor(_, _, _) => None, Pattern::Literal(_) => None, Pattern::Wildcard => Some(self.tail()), } } // INVARIANT: (length row == N) ==> (length result == N-1) fn specialize_row_by_literal(&self, literal: &Literal) -> Option { match self.head() { Pattern::Literal(p_literal) => { if p_literal == literal { Some(self.tail()) } else { None } } Pattern::Wildcard => Some(self.tail()), Pattern::Constructor(_, _, _) => unreachable!( "constructors and literals should never align in pattern match exhaustiveness checks." ), } } fn split_at(self, arity: usize) -> (PatternStack, PatternStack) { let mut rest = self.0; let mut args = rest.split_off(arity); std::mem::swap(&mut rest, &mut args); (args.into(), rest.into()) } } #[derive(Debug)] pub(super) struct Matrix(Vec); impl Matrix { pub(super) fn new() -> Self { Matrix(vec![]) } pub(crate) fn is_empty(&self) -> bool { self.0.is_empty() } pub(crate) fn push(&mut self, pattern_stack: PatternStack) { self.0.push(pattern_stack); } /// Iterate over the first component of each row pub(super) fn iter(&self) -> impl Iterator { self.0.iter() } /// Iterate over the first component of each row, mutably pub(super) fn into_iter(self) -> impl Iterator { self.0.into_iter() } pub(super) fn concat(self, other: Matrix) -> Matrix { let mut patterns = self.0; patterns.extend(other.0); Matrix(patterns) } pub(crate) fn is_complete(&self) -> Complete { let ctors = self.collect_ctors(); let num_seen = ctors.len(); if num_seen == 0 { Complete::No } else { let (_, alts) = ctors.first_key_value().to_owned().unwrap(); if num_seen == alts.len() { Complete::Yes(alts.to_vec()) } else { Complete::No } } } pub(crate) fn collect_ctors(&self) -> BTreeMap> { let mut ctors = BTreeMap::new(); for pattern_stack in self.iter() { match pattern_stack.head() { Pattern::Constructor(name, alts, _) => { ctors.insert(name.clone(), alts.clone()); } Pattern::Wildcard | Pattern::Literal(_) => {} } } ctors } fn specialize_rows_by_ctor(&self, name: &String, arity: usize) -> Matrix { self.iter() .filter_map(|p_stack| p_stack.specialize_row_by_ctor(name, arity)) .collect() } fn specialize_rows_by_wildcard(&self) -> Matrix { self.iter() .filter_map(|p_stack| p_stack.specialize_row_by_wildcard()) .collect() } fn specialize_rows_by_literal(&self, literal: &Literal) -> Matrix { self.iter() .filter_map(|p_stack| p_stack.specialize_row_by_literal(literal)) .collect() } pub(super) fn is_useful(&self, vector: &PatternStack) -> bool { // No rows are the same as the new vector! The vector is useful! if self.is_empty() { return true; } // There is nothing left in the new vector, but we still have // rows that match the same things. This is not a useful vector! if vector.is_empty() { return false; } let first_pattern = vector.head(); match first_pattern { Pattern::Constructor(name, _, args) => { let arity = args.len(); let new_matrix = self.specialize_rows_by_ctor(name, arity); let new_vector: PatternStack = vector.chain_tail_to_iter(args.iter()); new_matrix.is_useful(&new_vector) } Pattern::Wildcard => { // check if all alts appear in matrix match self.is_complete() { Complete::No => { // This Wildcard is useful because some Ctors are missing. // But what if a previous row has a Wildcard? // If so, this one is not useful. let new_matrix = self.specialize_rows_by_wildcard(); let new_vector = vector.tail(); new_matrix.is_useful(&new_vector) } Complete::Yes(alts) => alts.into_iter().any(|alt| { let tipo::ValueConstructor { variant, .. } = alt; let (name, arity) = match variant { tipo::ValueConstructorVariant::Record { name, arity, .. } => { (name, arity) } _ => unreachable!("variant should be a ValueConstructorVariant"), }; let new_matrix = self.specialize_rows_by_ctor(&name, arity); let new_vector = vector.chain_tail_into_iter(vec![Pattern::Wildcard; arity].into_iter()); new_matrix.is_useful(&new_vector) }), } } Pattern::Literal(literal) => { let new_matrix: Matrix = self.specialize_rows_by_literal(literal); let new_vector = vector.tail(); new_matrix.is_useful(&new_vector) } } } pub(super) fn flatten(self) -> Vec { self.into_iter().fold(vec![], |mut acc, p_stack| { acc.extend(p_stack.0); acc }) } // 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 // pub(super) fn collect_missing_patterns(self, n: usize) -> Matrix { if self.is_empty() { return Matrix(vec![vec![Pattern::Wildcard; n].into()]); } if n == 0 { return Matrix::new(); } let ctors = self.collect_ctors(); let num_seen = ctors.len(); if num_seen == 0 { let new_matrix = self.specialize_rows_by_wildcard(); let new_matrix = new_matrix.collect_missing_patterns(n - 1); let new_matrix = new_matrix .iter() .map(|p_stack| { let mut new_p_stack = p_stack.clone(); new_p_stack.insert(0, Pattern::Wildcard); new_p_stack }) .collect::(); return new_matrix; } let (_, alts) = ctors.first_key_value().unwrap(); if num_seen < alts.len() { let new_matrix = self.specialize_rows_by_wildcard(); let new_matrix = new_matrix.collect_missing_patterns(n - 1); let prefix = alts.iter().filter_map(|alt| is_missing(alts, &ctors, alt)); let mut m = Matrix::new(); for p_stack in new_matrix.into_iter() { for p in prefix.clone() { let mut p_stack = p_stack.clone(); p_stack.insert(0, p); m.push(p_stack); } } return m; } alts.iter() .map(|ctor| { let tipo::ValueConstructor { variant, .. } = ctor; let (name, arity) = match variant { tipo::ValueConstructorVariant::Record { name, arity, .. } => (name, arity), _ => unreachable!("variant should be a ValueConstructorVariant"), }; let new_matrix = self.specialize_rows_by_ctor(name, *arity); let new_matrix = new_matrix.collect_missing_patterns(*arity + n - 1); new_matrix .into_iter() .map(|p_stack| recover_ctor(alts.clone(), name, *arity, p_stack)) .collect() }) .fold(Matrix::new(), |acc, m| acc.concat(m)) } } #[derive(Debug)] pub(crate) enum Complete { Yes(Vec), No, } #[derive(Debug, Clone, PartialEq)] pub(crate) enum Pattern { Wildcard, Literal(Literal), Constructor(String, Vec, Vec), } #[derive(Debug, Clone, PartialEq)] pub(crate) enum Literal { Int(String), ByteArray(Vec), } impl Pattern { pub(super) fn pretty(self) -> String { match self { Pattern::Wildcard => "_".to_string(), Pattern::Literal(_) => unreachable!("maybe never happens?"), Pattern::Constructor(name, _alts, args) if name.contains(TUPLE_NAME) => { let mut pretty_pattern = "(".to_string(); pretty_pattern.push_str(&args.into_iter().map(Pattern::pretty).join(", ")); pretty_pattern.push(')'); pretty_pattern } Pattern::Constructor(name, _alts, args) if name == CONS_NAME => { let mut pretty_pattern = "[".to_string(); let args = args .into_iter() .enumerate() .filter_map(|(index, p)| { if index == 1 { let tail = pretty_tail(p); if tail == "[]" { None } else { Some(tail) } } else { Some(p.pretty()) } }) .join(", "); pretty_pattern.push_str(&args); pretty_pattern.push(']'); pretty_pattern } Pattern::Constructor(mut name, alts, args) => { let field_map = alts.into_iter().find_map(|alt| { let tipo::ValueConstructor { variant, .. } = alt; match variant { tipo::ValueConstructorVariant::Record { name: r_name, field_map, .. } if r_name == name => field_map, _ => None, } }); if let Some(field_map) = field_map { name.push_str(" { "); let labels = field_map .fields .into_iter() .sorted_by(|(_, (index_a, _)), (_, (index_b, _))| index_a.cmp(index_b)) .map(|(label, _)| label) .zip(args) .map(|(label, arg)| match arg { Pattern::Wildcard => label, rest => format!("{label}: {}", rest.pretty()), }) .join(", "); name.push_str(&labels); name.push_str(" }"); name } else { if !args.is_empty() { name.push('('); name.push_str(&args.into_iter().map(Pattern::pretty).join(", ")); name.push(')'); } name } } } } } fn pretty_tail(tail: Pattern) -> String { match tail { Pattern::Constructor(name, _alts, args) if name == CONS_NAME => { let mut pretty_pattern = "".to_string(); let args = args .into_iter() .enumerate() .map(|(index, p)| { if index == 1 { pretty_tail(p) } else { p.pretty() } }) .join(", "); pretty_pattern.push_str(&args); pretty_pattern } Pattern::Wildcard => "..".to_string(), rest => rest.pretty(), } } fn list_constructors() -> Vec { let list_parameter = Type::generic_var(0); let list_type = Type::list(list_parameter); vec![ tipo::ValueConstructor { public: true, tipo: list_type.clone(), variant: tipo::ValueConstructorVariant::Record { name: CONS_NAME.to_string(), arity: 2, field_map: None, location: ast::Span::empty(), module: "".to_string(), constructors_count: 2, }, }, tipo::ValueConstructor { public: true, tipo: list_type, variant: tipo::ValueConstructorVariant::Record { name: NIL_NAME.to_string(), arity: 0, field_map: None, location: ast::Span::empty(), module: "".to_string(), constructors_count: 2, }, }, ] } #[allow(clippy::result_large_err)] pub(super) fn simplify( environment: &mut Environment, value: &ast::TypedPattern, ) -> Result { match value { ast::Pattern::Int { value, .. } => Ok(Pattern::Literal(Literal::Int(value.clone()))), ast::Pattern::ByteArray { value, .. } => { Ok(Pattern::Literal(Literal::ByteArray(value.clone()))) } ast::Pattern::Assign { pattern, .. } => simplify(environment, pattern.as_ref()), ast::Pattern::List { elements, tail, .. } => { let mut p = if let Some(t) = tail { simplify(environment, t)? } else { Pattern::Constructor(NIL_NAME.to_string(), list_constructors(), vec![]) }; for hd in elements.iter().rev() { p = Pattern::Constructor( CONS_NAME.to_string(), list_constructors(), vec![simplify(environment, hd)?, p], ); } Ok(p) } ast::Pattern::Constructor { arguments, location, tipo, spread_location, constructor: super::PatternConstructor::Record { name, .. }, .. } => { let (module, type_name, arity) = match tipo.deref() { tipo::Type::App { name: type_name, module, .. } => (module, type_name, 0), tipo::Type::Fn { ret, args, .. } => match ret.deref() { tipo::Type::App { name: type_name, module, .. } => (module, type_name, args.len()), _ => { unreachable!("ret should be a Type::App") } }, _ => unreachable!("tipo should be a Type::App"), }; let alts = environment.get_constructors_for_type(module, type_name, *location)?; let mut args = Vec::new(); for argument in arguments { args.push(simplify(environment, &argument.value)?); } if spread_location.is_some() { for _ in 0..(arity - arguments.len()) { args.push(Pattern::Wildcard) } } Ok(Pattern::Constructor(name.to_string(), alts, args)) } ast::Pattern::Pair { fst, snd, location } => simplify( environment, &ast::Pattern::Tuple { elems: vec![*fst.clone(), *snd.clone()], location: *location, }, ), ast::Pattern::Tuple { elems, .. } => { let mut args = vec![]; for elem in elems { args.push(simplify(environment, elem)?); } Ok(Pattern::Constructor( TUPLE_NAME.to_string(), vec![tipo::ValueConstructor { tipo: tipo::Type::Tuple { elems: vec![], alias: None, } .into(), public: true, variant: tipo::ValueConstructorVariant::Record { name: TUPLE_NAME.to_string(), arity: elems.len(), field_map: None, location: ast::Span::empty(), module: "".to_string(), constructors_count: 1, }, }], args, )) } ast::Pattern::Var { .. } | ast::Pattern::Discard { .. } => Ok(Pattern::Wildcard), } } impl iter::FromIterator for Matrix { fn from_iter>(iter: T) -> Self { Matrix(iter.into_iter().collect()) } } fn recover_ctor( alts: Vec, name: &str, arity: usize, patterns: PatternStack, ) -> PatternStack { let (args, mut rest) = patterns.split_at(arity); rest.insert(0, Pattern::Constructor(name.to_string(), alts, args.into())); rest } fn is_missing( alts: &[tipo::ValueConstructor], ctors: &BTreeMap>, ctor: &tipo::ValueConstructor, ) -> Option { let tipo::ValueConstructor { variant, .. } = ctor; let (name, arity) = match variant { tipo::ValueConstructorVariant::Record { name, arity, .. } => (name, arity), _ => unreachable!("variant should be a ValueConstructorVariant"), }; if ctors.contains_key(name) { None } else { Some(Pattern::Constructor( name.clone(), alts.to_vec(), vec![Pattern::Wildcard; *arity], )) } }