feat(exhaustiveness): pretty print missing patterns

This commit is contained in:
rvcas 2023-07-28 22:51:57 -04:00 committed by Lucas
parent de2791fe82
commit f1100e901d
4 changed files with 309 additions and 296 deletions

View File

@ -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("(..)")
} }

View File

@ -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,64 +1438,42 @@ 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,
..
} => {
let m = if module.is_empty() || module == self.current_module {
None
} else {
Some(module.clone())
};
if type_name == "List" && module.is_empty() { for unchecked_pattern in unchecked_patterns {
return self.check_list_pattern_exhaustiveness(patterns); let pattern = simplify(self, unchecked_pattern)?;
} let pattern_stack = PatternStack::from(pattern);
if let Ok(constructors) = self.get_constructors_for_type(&m, type_name, location) { if matrix.is_useful(&pattern_stack) {
let mut unmatched_constructors: HashSet<String> = matrix.push(pattern_stack);
constructors.iter().cloned().collect(); } else {
return Err(Error::RedundantMatchClause {
for p in &patterns { location: unchecked_pattern.location(),
// 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() {
return Err(unmatched_constructors.into_iter().sorted().collect());
}
}
Ok(())
} }
_ => Ok(()),
} }
let missing_patterns = matrix.collect_missing_patterns(1).flatten();
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(())
} }
pub fn check_list_pattern_exhaustiveness( pub fn check_list_pattern_exhaustiveness(

View File

@ -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,6 +278,104 @@ impl Matrix {
} }
} }
} }
pub(super) fn flatten(self) -> Vec<Pattern> {
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::<Matrix>();
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);
}
}
// (:)
// <$> Maybe.mapMaybe (isMissing alts ctors) altList
// <*> isExhaustive (Maybe.mapMaybe specializeRowByAnything matrix) (n - 1)
return m;
}
// let
// isAltExhaustive (Can.Ctor name _ arity _) =
// recoverCtor alts name arity <$>
// isExhaustive
// (Maybe.mapMaybe (specializeRowByCtor name arity) matrix)
// (arity + n - 1)
// in
// concatMap isAltExhaustive altList
//
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)] #[derive(Debug)]
@ -287,43 +384,6 @@ pub(crate) enum Complete {
No, No,
} }
#[derive(Debug)]
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)] #[derive(Debug, Clone)]
pub(crate) enum Pattern { pub(crate) enum Pattern {
Wildcard, Wildcard,
@ -336,6 +396,106 @@ pub(crate) enum Literal {
Int(String), 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> { fn list_constructors() -> Vec<tipo::ValueConstructor> {
let list_parameter = builtins::generic_var(0); let list_parameter = builtins::generic_var(0);
let list_type = builtins::list(list_parameter); let list_type = builtins::list(list_parameter);
@ -350,8 +510,8 @@ fn list_constructors() -> Vec<tipo::ValueConstructor> {
field_map: None, field_map: None,
location: ast::Span::empty(), location: ast::Span::empty(),
module: "".to_string(), module: "".to_string(),
constructors_count: 2 constructors_count: 2,
} },
}, },
tipo::ValueConstructor { tipo::ValueConstructor {
public: true, public: true,
@ -362,13 +522,16 @@ fn list_constructors() -> Vec<tipo::ValueConstructor> {
field_map: None, field_map: None,
location: ast::Span::empty(), location: ast::Span::empty(),
module: "".to_string(), module: "".to_string(),
constructors_count: 2 constructors_count: 2,
} },
}, },
] ]
} }
fn simplify(environment: &mut Environment, value: &ast::TypedPattern) -> Result<Pattern, Error> { pub(super) fn simplify(
environment: &mut Environment,
value: &ast::TypedPattern,
) -> Result<Pattern, Error> {
match value { match value {
ast::Pattern::Int { value, .. } => Ok(Pattern::Literal(Literal::Int(value.clone()))), ast::Pattern::Int { value, .. } => Ok(Pattern::Literal(Literal::Int(value.clone()))),
ast::Pattern::Assign { pattern, .. } => simplify(environment, pattern.as_ref()), ast::Pattern::Assign { pattern, .. } => simplify(environment, pattern.as_ref()),
@ -380,30 +543,36 @@ fn simplify(environment: &mut Environment, value: &ast::TypedPattern) -> Result<
}; };
for hd in elements.iter().rev() { for hd in elements.iter().rev() {
p = Pattern::Constructor(CONS_NAME.to_string(), list_constructors(), vec![simplify(environment, hd)?, p]); p = Pattern::Constructor(
CONS_NAME.to_string(),
list_constructors(),
vec![simplify(environment, hd)?, p],
);
} }
Ok(p) Ok(p)
}, }
ast::Pattern::Constructor { ast::Pattern::Constructor {
name, name,
arguments, arguments,
module, module,
location, location,
tipo, tipo,
with_spread,
.. ..
} => { } => {
let type_name = match tipo.deref() { let (type_name, arity) = match tipo.deref() {
tipo::Type::App { tipo::Type::App {
name: type_name, .. name: type_name, ..
} => type_name, } => (type_name, 0),
tipo::Type::Fn { ret, .. } => { tipo::Type::Fn { ret, args, .. } => match ret.deref() {
let tipo::Type::App { tipo::Type::App {
name: type_name, .. name: type_name, ..
} = ret.deref() else {unreachable!("ret should be a Type::App")}; } => (type_name, args.len()),
_ => {
type_name unreachable!("ret should be a Type::App")
} }
},
_ => unreachable!("tipo should be a Type::App"), _ => unreachable!("tipo should be a Type::App"),
}; };
@ -426,17 +595,27 @@ fn simplify(environment: &mut Environment, value: &ast::TypedPattern) -> Result<
args.push(simplify(environment, &argument.value)?); 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)) Ok(Pattern::Constructor(name.to_string(), alts, args))
} }
ast::Pattern::Tuple { elems, .. } => { ast::Pattern::Tuple { elems, .. } => {
let mut p = Pattern::Constructor(NIL_NAME.to_string(), list_constructors(), vec![]); let mut p = Pattern::Constructor(NIL_NAME.to_string(), list_constructors(), vec![]);
for hd in elems.iter().rev() { for hd in elems.iter().rev() {
p = Pattern::Constructor(CONS_NAME.to_string(), list_constructors(), vec![simplify(environment, hd)?, p]); p = Pattern::Constructor(
CONS_NAME.to_string(),
list_constructors(),
vec![simplify(environment, hd)?, p],
);
} }
Ok(p) Ok(p)
}, }
ast::Pattern::Var { .. } | ast::Pattern::Discard { .. } => Ok(Pattern::Wildcard), ast::Pattern::Var { .. } | ast::Pattern::Discard { .. } => Ok(Pattern::Wildcard),
} }
} }
@ -447,126 +626,6 @@ impl iter::FromIterator<PatternStack> for Matrix {
} }
} }
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:
//
// 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: Matrix, n: usize) -> Matrix {
if matrix.is_empty() {
return Matrix(vec![vec![Pattern::Wildcard; n].into()]);
}
if n == 0 {
return Matrix::new();
}
let ctors = matrix.collect_ctors();
let num_seen = ctors.len();
if num_seen == 0 {
let new_matrix = matrix.specialize_rows_by_wildcard();
let new_matrix = is_exhaustive(new_matrix, 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::<Matrix>();
return new_matrix;
}
let (_, alts) = ctors.first_key_value().unwrap();
if num_seen < alts.len() {
let new_matrix = matrix.specialize_rows_by_wildcard();
let new_matrix = is_exhaustive(new_matrix, 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);
}
}
// (:)
// <$> Maybe.mapMaybe (isMissing alts ctors) altList
// <*> isExhaustive (Maybe.mapMaybe specializeRowByAnything matrix) (n - 1)
return m;
}
// let
// isAltExhaustive (Can.Ctor name _ arity _) =
// recoverCtor alts name arity <$>
// isExhaustive
// (Maybe.mapMaybe (specializeRowByCtor name arity) matrix)
// (arity + n - 1)
// in
// concatMap isAltExhaustive altList
//
alts.iter()
.map(|ctor| {
let tipo::ValueConstructor { variant, .. } = ctor;
let tipo::ValueConstructorVariant::Record {
name,
arity,
..
} = variant else {unreachable!("variant should be a ValueConstructorVariant")};
let new_matrix = matrix.specialize_rows_by_ctor(name, *arity);
let new_matrix = is_exhaustive(new_matrix, *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))
}
fn recover_ctor( fn recover_ctor(
alts: Vec<tipo::ValueConstructor>, alts: Vec<tipo::ValueConstructor>,
name: &str, name: &str,
@ -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(
)) ))
} }
} }

View File

@ -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,