feat(exhaustiveness): pretty print missing patterns
This commit is contained in:
parent
de2791fe82
commit
f1100e901d
|
@ -942,6 +942,7 @@ impl<'comments> Formatter<'comments> {
|
||||||
if args.is_empty() && with_spread {
|
if args.is_empty() && with_spread {
|
||||||
if is_record {
|
if is_record {
|
||||||
name.append(" { .. }")
|
name.append(" { .. }")
|
||||||
|
// TODO: not possible
|
||||||
} else {
|
} else {
|
||||||
name.append("(..)")
|
name.append("(..)")
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,10 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use itertools::Itertools;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{
|
ast::{
|
||||||
Annotation, CallArg, DataType, Definition, Function, ModuleConstant, ModuleKind, Pattern,
|
Annotation, CallArg, DataType, Definition, Function, ModuleConstant, ModuleKind, Pattern,
|
||||||
RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition,
|
RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition, TypedPattern,
|
||||||
UnqualifiedImport, UntypedArg, UntypedDefinition, Use, Validator, PIPE_VARIABLE,
|
UnqualifiedImport, UntypedArg, UntypedDefinition, Use, Validator, PIPE_VARIABLE,
|
||||||
},
|
},
|
||||||
builtins::{self, function, generic_var, tuple, unbound_var},
|
builtins::{self, function, generic_var, tuple, unbound_var},
|
||||||
|
@ -19,6 +17,7 @@ use crate::{
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
error::{Error, Snippet, Warning},
|
error::{Error, Snippet, Warning},
|
||||||
|
exhaustive::{simplify, Matrix, PatternStack},
|
||||||
hydrator::Hydrator,
|
hydrator::Hydrator,
|
||||||
AccessorsMap, PatternConstructor, RecordAccessor, Type, TypeConstructor, TypeInfo, TypeVar,
|
AccessorsMap, PatternConstructor, RecordAccessor, Type, TypeConstructor, TypeInfo, TypeVar,
|
||||||
ValueConstructor, ValueConstructorVariant,
|
ValueConstructor, ValueConstructorVariant,
|
||||||
|
@ -1439,65 +1438,43 @@ impl<'a> Environment<'a> {
|
||||||
/// only at the top level (without recursing into constructor arguments).
|
/// only at the top level (without recursing into constructor arguments).
|
||||||
pub fn check_exhaustiveness(
|
pub fn check_exhaustiveness(
|
||||||
&mut self,
|
&mut self,
|
||||||
patterns: Vec<Pattern<PatternConstructor, Arc<Type>>>,
|
unchecked_patterns: &[&TypedPattern],
|
||||||
value_typ: Arc<Type>,
|
|
||||||
location: Span,
|
location: Span,
|
||||||
) -> Result<(), Vec<String>> {
|
is_let: bool,
|
||||||
match &*value_typ {
|
) -> Result<(), Error> {
|
||||||
Type::App {
|
let mut matrix = Matrix::new();
|
||||||
name: type_name,
|
|
||||||
module,
|
for unchecked_pattern in unchecked_patterns {
|
||||||
..
|
let pattern = simplify(self, unchecked_pattern)?;
|
||||||
} => {
|
let pattern_stack = PatternStack::from(pattern);
|
||||||
let m = if module.is_empty() || module == self.current_module {
|
|
||||||
None
|
if matrix.is_useful(&pattern_stack) {
|
||||||
|
matrix.push(pattern_stack);
|
||||||
} else {
|
} else {
|
||||||
Some(module.clone())
|
return Err(Error::RedundantMatchClause {
|
||||||
};
|
location: unchecked_pattern.location(),
|
||||||
|
});
|
||||||
if type_name == "List" && module.is_empty() {
|
|
||||||
return self.check_list_pattern_exhaustiveness(patterns);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(constructors) = self.get_constructors_for_type(&m, type_name, location) {
|
|
||||||
let mut unmatched_constructors: HashSet<String> =
|
|
||||||
constructors.iter().cloned().collect();
|
|
||||||
|
|
||||||
for p in &patterns {
|
|
||||||
// ignore Assign patterns
|
|
||||||
let mut pattern = p;
|
|
||||||
while let Pattern::Assign {
|
|
||||||
pattern: assign_pattern,
|
|
||||||
..
|
|
||||||
} = pattern
|
|
||||||
{
|
|
||||||
pattern = assign_pattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
match pattern {
|
|
||||||
// If the pattern is a Discard or Var, all constructors are covered by it
|
|
||||||
Pattern::Discard { .. } => return Ok(()),
|
|
||||||
Pattern::Var { .. } => return Ok(()),
|
|
||||||
// If the pattern is a constructor, remove it from unmatched patterns
|
|
||||||
Pattern::Constructor {
|
|
||||||
constructor: PatternConstructor::Record { name, .. },
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
unmatched_constructors.remove(name);
|
|
||||||
}
|
|
||||||
_ => return Ok(()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !unmatched_constructors.is_empty() {
|
let missing_patterns = matrix.collect_missing_patterns(1).flatten();
|
||||||
return Err(unmatched_constructors.into_iter().sorted().collect());
|
|
||||||
|
for missing in &missing_patterns {
|
||||||
|
dbg!(missing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !missing_patterns.is_empty() {
|
||||||
|
let unmatched = missing_patterns.into_iter().map(|p| p.pretty()).collect();
|
||||||
|
|
||||||
|
return Err(Error::NotExhaustivePatternMatch {
|
||||||
|
location,
|
||||||
|
unmatched,
|
||||||
|
is_let,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => Ok(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_list_pattern_exhaustiveness(
|
pub fn check_list_pattern_exhaustiveness(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use std::{collections::BTreeMap, iter, ops::Deref};
|
use std::{collections::BTreeMap, iter, ops::Deref};
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{self, Span, TypedPattern},
|
ast, builtins,
|
||||||
tipo::{self, environment::Environment, error::Error}, builtins,
|
tipo::{self, environment::Environment, error::Error},
|
||||||
};
|
};
|
||||||
|
|
||||||
const NIL_NAME: &str = "[]";
|
const NIL_NAME: &str = "[]";
|
||||||
|
@ -65,11 +67,7 @@ impl PatternStack {
|
||||||
|
|
||||||
fn chain_tail_into_iter(&self, front: impl Iterator<Item = Pattern>) -> PatternStack {
|
fn chain_tail_into_iter(&self, front: impl Iterator<Item = Pattern>) -> PatternStack {
|
||||||
front
|
front
|
||||||
.chain(
|
.chain(self.iter().skip(1).cloned())
|
||||||
self.iter()
|
|
||||||
.skip(1)
|
|
||||||
.cloned()
|
|
||||||
)
|
|
||||||
.collect::<Vec<Pattern>>()
|
.collect::<Vec<Pattern>>()
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
@ -138,7 +136,7 @@ impl PatternStack {
|
||||||
pub(super) struct Matrix(Vec<PatternStack>);
|
pub(super) struct Matrix(Vec<PatternStack>);
|
||||||
|
|
||||||
impl Matrix {
|
impl Matrix {
|
||||||
fn new() -> Self {
|
pub(super) fn new() -> Self {
|
||||||
Matrix(vec![])
|
Matrix(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,7 +214,7 @@ impl Matrix {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_useful(&self, vector: &PatternStack) -> bool {
|
pub(super) fn is_useful(&self, vector: &PatternStack) -> bool {
|
||||||
// No rows are the same as the new vector! The vector is useful!
|
// No rows are the same as the new vector! The vector is useful!
|
||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -251,15 +249,16 @@ impl Matrix {
|
||||||
|
|
||||||
let new_vector = vector.tail();
|
let new_vector = vector.tail();
|
||||||
|
|
||||||
new_matrix.is_useful( &new_vector)
|
new_matrix.is_useful(&new_vector)
|
||||||
}
|
}
|
||||||
Complete::Yes(alts) => alts.into_iter().any(|alt| {
|
Complete::Yes(alts) => alts.into_iter().any(|alt| {
|
||||||
let tipo::ValueConstructor { variant, .. } = alt;
|
let tipo::ValueConstructor { variant, .. } = alt;
|
||||||
let tipo::ValueConstructorVariant::Record {
|
let (name, arity) = match variant {
|
||||||
name,
|
tipo::ValueConstructorVariant::Record { name, arity, .. } => {
|
||||||
arity,
|
(name, arity)
|
||||||
..
|
}
|
||||||
} = variant else {unreachable!("variant should be a ValueConstructorVariant")};
|
_ => unreachable!("variant should be a ValueConstructorVariant"),
|
||||||
|
};
|
||||||
|
|
||||||
let new_matrix = self.specialize_rows_by_ctor(&name, arity);
|
let new_matrix = self.specialize_rows_by_ctor(&name, arity);
|
||||||
|
|
||||||
|
@ -279,211 +278,23 @@ impl Matrix {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub(super) fn flatten(self) -> Vec<Pattern> {
|
||||||
pub(crate) enum Complete {
|
self.into_iter().fold(vec![], |mut acc, p_stack| {
|
||||||
Yes(Vec<tipo::ValueConstructor>),
|
acc.extend(p_stack.0);
|
||||||
No,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
acc
|
||||||
pub(crate) struct Witness(Vec<TypedPattern>);
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Usefulness {
|
|
||||||
/// If we don't care about witnesses, simply remember if the pattern was useful.
|
|
||||||
NoWitnesses { useful: bool },
|
|
||||||
/// Carries a list of witnesses of non-exhaustiveness. If empty, indicates that the whole
|
|
||||||
/// pattern is unreachable.
|
|
||||||
WithWitnesses(Vec<Witness>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
enum ArmType {
|
|
||||||
FakeExtraWildcard,
|
|
||||||
RealArm,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub(crate) enum Reachability {
|
|
||||||
/// The arm is reachable. This additionally carries a set of or-pattern branches that have been
|
|
||||||
/// found to be unreachable despite the overall arm being reachable. Used only in the presence
|
|
||||||
/// of or-patterns, otherwise it stays empty.
|
|
||||||
Reachable(Vec<Span>),
|
|
||||||
/// The arm is unreachable.
|
|
||||||
Unreachable,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct UsefulnessReport {
|
|
||||||
/// For each arm of the input, whether that arm is reachable after the arms above it.
|
|
||||||
pub(crate) arm_usefulness: Vec<(ast::TypedClause, Reachability)>,
|
|
||||||
/// If the match is exhaustive, this is empty. If not, this contains witnesses for the lack of
|
|
||||||
/// exhaustiveness.
|
|
||||||
pub(crate) non_exhaustiveness_witnesses: Vec<TypedPattern>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub(crate) enum Pattern {
|
|
||||||
Wildcard,
|
|
||||||
Literal(Literal),
|
|
||||||
Constructor(String, Vec<tipo::ValueConstructor>, Vec<Pattern>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub(crate) enum Literal {
|
|
||||||
Int(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_constructors() -> Vec<tipo::ValueConstructor> {
|
|
||||||
let list_parameter = builtins::generic_var(0);
|
|
||||||
let list_type = builtins::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
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn simplify(environment: &mut Environment, value: &ast::TypedPattern) -> Result<Pattern, Error> {
|
|
||||||
match value {
|
|
||||||
ast::Pattern::Int { value, .. } => Ok(Pattern::Literal(Literal::Int(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 {
|
|
||||||
name,
|
|
||||||
arguments,
|
|
||||||
module,
|
|
||||||
location,
|
|
||||||
tipo,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let type_name = match tipo.deref() {
|
|
||||||
tipo::Type::App {
|
|
||||||
name: type_name, ..
|
|
||||||
} => type_name,
|
|
||||||
tipo::Type::Fn { ret, .. } => {
|
|
||||||
let tipo::Type::App {
|
|
||||||
name: type_name, ..
|
|
||||||
} = ret.deref() else {unreachable!("ret should be a Type::App")};
|
|
||||||
|
|
||||||
type_name
|
|
||||||
}
|
|
||||||
_ => unreachable!("tipo should be a Type::App"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let constructors = environment
|
|
||||||
.get_constructors_for_type(module, type_name, *location)?
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let mut alts = Vec::new();
|
|
||||||
|
|
||||||
for constructor in constructors {
|
|
||||||
let value_constructor =
|
|
||||||
environment.get_value_constructor(module.as_ref(), &constructor, *location)?;
|
|
||||||
|
|
||||||
alts.push(value_constructor.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut args = Vec::new();
|
|
||||||
|
|
||||||
for argument in arguments {
|
|
||||||
args.push(simplify(environment, &argument.value)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Pattern::Constructor(name.to_string(), alts, args))
|
|
||||||
}
|
|
||||||
ast::Pattern::Tuple { elems, .. } => {
|
|
||||||
let mut p = Pattern::Constructor(NIL_NAME.to_string(), list_constructors(), vec![]);
|
|
||||||
|
|
||||||
for hd in elems.iter().rev() {
|
|
||||||
p = Pattern::Constructor(CONS_NAME.to_string(), list_constructors(), vec![simplify(environment, hd)?, p]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(p)
|
|
||||||
},
|
|
||||||
ast::Pattern::Var { .. } | ast::Pattern::Discard { .. } => Ok(Pattern::Wildcard),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl iter::FromIterator<PatternStack> for Matrix {
|
|
||||||
fn from_iter<T: IntoIterator<Item = PatternStack>>(iter: T) -> Self {
|
|
||||||
Matrix(iter.into_iter().collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn compute_match_usefulness(
|
|
||||||
environment: &mut Environment,
|
|
||||||
unchecked_patterns: &[&ast::TypedPattern],
|
|
||||||
) -> Result<UsefulnessReport, Error> {
|
|
||||||
let mut matrix = Matrix::new();
|
|
||||||
|
|
||||||
for unchecked_pattern in unchecked_patterns {
|
|
||||||
let pattern = simplify(environment, unchecked_pattern)?;
|
|
||||||
let pattern_stack = PatternStack::from(pattern);
|
|
||||||
|
|
||||||
if matrix.is_useful(&pattern_stack) {
|
|
||||||
matrix.push(pattern_stack);
|
|
||||||
} else {
|
|
||||||
return Err(Error::RedundantMatchClause { location: unchecked_pattern.location() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dbg!(&matrix);
|
|
||||||
|
|
||||||
let bad_patterns = is_exhaustive(matrix, 1);
|
|
||||||
|
|
||||||
dbg!(bad_patterns);
|
|
||||||
|
|
||||||
Ok(UsefulnessReport {
|
|
||||||
arm_usefulness: vec![],
|
|
||||||
non_exhaustiveness_witnesses: vec![],
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// INVARIANTS:
|
// INVARIANTS:
|
||||||
//
|
//
|
||||||
// The initial rows "matrix" are all of length 1
|
// The initial rows "matrix" are all of length 1
|
||||||
// The initial count of items per row "n" is also 1
|
// The initial count of items per row "n" is also 1
|
||||||
// The resulting rows are examples of missing patterns
|
// The resulting rows are examples of missing patterns
|
||||||
//
|
//
|
||||||
fn is_exhaustive(matrix: Matrix, n: usize) -> Matrix {
|
pub(super) fn collect_missing_patterns(self, n: usize) -> Matrix {
|
||||||
if matrix.is_empty() {
|
if self.is_empty() {
|
||||||
return Matrix(vec![vec![Pattern::Wildcard; n].into()]);
|
return Matrix(vec![vec![Pattern::Wildcard; n].into()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -491,13 +302,13 @@ fn is_exhaustive(matrix: Matrix, n: usize) -> Matrix {
|
||||||
return Matrix::new();
|
return Matrix::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
let ctors = matrix.collect_ctors();
|
let ctors = self.collect_ctors();
|
||||||
let num_seen = ctors.len();
|
let num_seen = ctors.len();
|
||||||
|
|
||||||
if num_seen == 0 {
|
if num_seen == 0 {
|
||||||
let new_matrix = matrix.specialize_rows_by_wildcard();
|
let new_matrix = self.specialize_rows_by_wildcard();
|
||||||
|
|
||||||
let new_matrix = is_exhaustive(new_matrix, n - 1);
|
let new_matrix = new_matrix.collect_missing_patterns(n - 1);
|
||||||
|
|
||||||
let new_matrix = new_matrix
|
let new_matrix = new_matrix
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -514,9 +325,9 @@ fn is_exhaustive(matrix: Matrix, n: usize) -> Matrix {
|
||||||
let (_, alts) = ctors.first_key_value().unwrap();
|
let (_, alts) = ctors.first_key_value().unwrap();
|
||||||
|
|
||||||
if num_seen < alts.len() {
|
if num_seen < alts.len() {
|
||||||
let new_matrix = matrix.specialize_rows_by_wildcard();
|
let new_matrix = self.specialize_rows_by_wildcard();
|
||||||
|
|
||||||
let new_matrix = is_exhaustive(new_matrix, n - 1);
|
let new_matrix = new_matrix.collect_missing_patterns(n - 1);
|
||||||
|
|
||||||
let prefix = alts.iter().filter_map(|alt| is_missing(alts, &ctors, alt));
|
let prefix = alts.iter().filter_map(|alt| is_missing(alts, &ctors, alt));
|
||||||
|
|
||||||
|
@ -549,15 +360,14 @@ fn is_exhaustive(matrix: Matrix, n: usize) -> Matrix {
|
||||||
alts.iter()
|
alts.iter()
|
||||||
.map(|ctor| {
|
.map(|ctor| {
|
||||||
let tipo::ValueConstructor { variant, .. } = ctor;
|
let tipo::ValueConstructor { variant, .. } = ctor;
|
||||||
let tipo::ValueConstructorVariant::Record {
|
let (name, arity) = match variant {
|
||||||
name,
|
tipo::ValueConstructorVariant::Record { name, arity, .. } => (name, arity),
|
||||||
arity,
|
_ => unreachable!("variant should be a ValueConstructorVariant"),
|
||||||
..
|
};
|
||||||
} = variant else {unreachable!("variant should be a ValueConstructorVariant")};
|
|
||||||
|
|
||||||
let new_matrix = matrix.specialize_rows_by_ctor(name, *arity);
|
let new_matrix = self.specialize_rows_by_ctor(name, *arity);
|
||||||
|
|
||||||
let new_matrix = is_exhaustive(new_matrix, *arity + n - 1);
|
let new_matrix = new_matrix.collect_missing_patterns(*arity + n - 1);
|
||||||
|
|
||||||
new_matrix
|
new_matrix
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -565,6 +375,255 @@ fn is_exhaustive(matrix: Matrix, n: usize) -> Matrix {
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
.fold(Matrix::new(), |acc, m| acc.concat(m))
|
.fold(Matrix::new(), |acc, m| acc.concat(m))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum Complete {
|
||||||
|
Yes(Vec<tipo::ValueConstructor>),
|
||||||
|
No,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) enum Pattern {
|
||||||
|
Wildcard,
|
||||||
|
Literal(Literal),
|
||||||
|
Constructor(String, Vec<tipo::ValueConstructor>, Vec<Pattern>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub(crate) enum Literal {
|
||||||
|
Int(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
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 == 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.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<tipo::ValueConstructor> {
|
||||||
|
let list_parameter = builtins::generic_var(0);
|
||||||
|
let list_type = builtins::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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn simplify(
|
||||||
|
environment: &mut Environment,
|
||||||
|
value: &ast::TypedPattern,
|
||||||
|
) -> Result<Pattern, Error> {
|
||||||
|
match value {
|
||||||
|
ast::Pattern::Int { value, .. } => Ok(Pattern::Literal(Literal::Int(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 {
|
||||||
|
name,
|
||||||
|
arguments,
|
||||||
|
module,
|
||||||
|
location,
|
||||||
|
tipo,
|
||||||
|
with_spread,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let (type_name, arity) = match tipo.deref() {
|
||||||
|
tipo::Type::App {
|
||||||
|
name: type_name, ..
|
||||||
|
} => (type_name, 0),
|
||||||
|
tipo::Type::Fn { ret, args, .. } => match ret.deref() {
|
||||||
|
tipo::Type::App {
|
||||||
|
name: type_name, ..
|
||||||
|
} => (type_name, args.len()),
|
||||||
|
_ => {
|
||||||
|
unreachable!("ret should be a Type::App")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => unreachable!("tipo should be a Type::App"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let constructors = environment
|
||||||
|
.get_constructors_for_type(module, type_name, *location)?
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let mut alts = Vec::new();
|
||||||
|
|
||||||
|
for constructor in constructors {
|
||||||
|
let value_constructor =
|
||||||
|
environment.get_value_constructor(module.as_ref(), &constructor, *location)?;
|
||||||
|
|
||||||
|
alts.push(value_constructor.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut args = Vec::new();
|
||||||
|
|
||||||
|
for argument in arguments {
|
||||||
|
args.push(simplify(environment, &argument.value)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
if *with_spread {
|
||||||
|
for _ in 0..(arity - arguments.len()) {
|
||||||
|
args.push(Pattern::Wildcard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Pattern::Constructor(name.to_string(), alts, args))
|
||||||
|
}
|
||||||
|
ast::Pattern::Tuple { elems, .. } => {
|
||||||
|
let mut p = Pattern::Constructor(NIL_NAME.to_string(), list_constructors(), vec![]);
|
||||||
|
|
||||||
|
for hd in elems.iter().rev() {
|
||||||
|
p = Pattern::Constructor(
|
||||||
|
CONS_NAME.to_string(),
|
||||||
|
list_constructors(),
|
||||||
|
vec![simplify(environment, hd)?, p],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(p)
|
||||||
|
}
|
||||||
|
ast::Pattern::Var { .. } | ast::Pattern::Discard { .. } => Ok(Pattern::Wildcard),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl iter::FromIterator<PatternStack> for Matrix {
|
||||||
|
fn from_iter<T: IntoIterator<Item = PatternStack>>(iter: T) -> Self {
|
||||||
|
Matrix(iter.into_iter().collect())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recover_ctor(
|
fn recover_ctor(
|
||||||
|
@ -586,11 +645,10 @@ fn is_missing(
|
||||||
ctor: &tipo::ValueConstructor,
|
ctor: &tipo::ValueConstructor,
|
||||||
) -> Option<Pattern> {
|
) -> Option<Pattern> {
|
||||||
let tipo::ValueConstructor { variant, .. } = ctor;
|
let tipo::ValueConstructor { variant, .. } = ctor;
|
||||||
let tipo::ValueConstructorVariant::Record {
|
let (name, arity) = match variant {
|
||||||
name,
|
tipo::ValueConstructorVariant::Record { name, arity, .. } => (name, arity),
|
||||||
arity,
|
_ => unreachable!("variant should be a ValueConstructorVariant"),
|
||||||
..
|
};
|
||||||
} = variant else {unreachable!("variant should be a ValueConstructorVariant")};
|
|
||||||
|
|
||||||
if ctors.contains_key(name) {
|
if ctors.contains_key(name) {
|
||||||
None
|
None
|
||||||
|
@ -602,5 +660,3 @@ fn is_missing(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ use crate::{
|
||||||
use super::{
|
use super::{
|
||||||
environment::{assert_no_labeled_arguments, collapse_links, EntityKind, Environment},
|
environment::{assert_no_labeled_arguments, collapse_links, EntityKind, Environment},
|
||||||
error::{Error, Warning},
|
error::{Error, Warning},
|
||||||
exhaustive::compute_match_usefulness,
|
|
||||||
hydrator::Hydrator,
|
hydrator::Hydrator,
|
||||||
pattern::PatternTyper,
|
pattern::PatternTyper,
|
||||||
pipe::PipeTyper,
|
pipe::PipeTyper,
|
||||||
|
@ -45,12 +44,9 @@ pub(crate) struct ExprTyper<'a, 'b> {
|
||||||
impl<'a, 'b> ExprTyper<'a, 'b> {
|
impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
fn check_when_exhaustiveness(
|
fn check_when_exhaustiveness(
|
||||||
&mut self,
|
&mut self,
|
||||||
subject: &Type,
|
|
||||||
typed_clauses: &[TypedClause],
|
typed_clauses: &[TypedClause],
|
||||||
_location: Span,
|
location: Span,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let _value_typ = collapse_links(Arc::new(subject.clone()));
|
|
||||||
|
|
||||||
// Currently guards in exhaustiveness checking are assumed that they can fail,
|
// Currently guards in exhaustiveness checking are assumed that they can fail,
|
||||||
// so we go through all clauses and pluck out only the patterns
|
// so we go through all clauses and pluck out only the patterns
|
||||||
// for clauses that don't have guards.
|
// for clauses that don't have guards.
|
||||||
|
@ -66,10 +62,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compute_match_usefulness(self.environment, &patterns)?;
|
self.environment
|
||||||
|
.check_exhaustiveness(&patterns, location, false)?;
|
||||||
// self.environment
|
|
||||||
// .check_exhaustiveness(patterns, value_typ, location)
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -919,35 +913,20 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
)?
|
)?
|
||||||
};
|
};
|
||||||
|
|
||||||
// We currently only do limited exhaustiveness checking of custom types
|
|
||||||
// at the top level of patterns.
|
|
||||||
// Do not perform exhaustiveness checking if user explicitly used `assert`.
|
// Do not perform exhaustiveness checking if user explicitly used `assert`.
|
||||||
match kind {
|
match kind {
|
||||||
AssignmentKind::Let => {
|
AssignmentKind::Let => {
|
||||||
if let Err(unmatched) = self.environment.check_exhaustiveness(
|
self.environment
|
||||||
vec![pattern.clone()],
|
.check_exhaustiveness(&[&pattern], location, true)?
|
||||||
collapse_links(value_typ.clone()),
|
|
||||||
location,
|
|
||||||
) {
|
|
||||||
return Err(Error::NotExhaustivePatternMatch {
|
|
||||||
location,
|
|
||||||
unmatched,
|
|
||||||
is_let: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AssignmentKind::Expect => {
|
AssignmentKind::Expect => {
|
||||||
let is_exaustive_pattern = self
|
let is_exaustive_pattern = self
|
||||||
.environment
|
.environment
|
||||||
.check_exhaustiveness(
|
.check_exhaustiveness(&[&pattern], location, false)
|
||||||
vec![pattern.clone()],
|
|
||||||
collapse_links(value_typ.clone()),
|
|
||||||
location,
|
|
||||||
)
|
|
||||||
.is_ok();
|
.is_ok();
|
||||||
|
|
||||||
if !value_is_data && !value_typ.is_list() && is_exaustive_pattern {
|
if !value_is_data && is_exaustive_pattern {
|
||||||
self.environment
|
self.environment
|
||||||
.warnings
|
.warnings
|
||||||
.push(Warning::SingleConstructorExpect {
|
.push(Warning::SingleConstructorExpect {
|
||||||
|
@ -1881,7 +1860,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.check_when_exhaustiveness(&subject_type, &typed_clauses, location)?;
|
self.check_when_exhaustiveness(&typed_clauses, location)?;
|
||||||
|
|
||||||
Ok(TypedExpr::When {
|
Ok(TypedExpr::When {
|
||||||
location,
|
location,
|
||||||
|
|
Loading…
Reference in New Issue