Basic exhaustivness check on list patterns

Before that commit, the type-checker would allow unsafe list patterns
  such as:

  ```
  let [x] = xs

  when xs is {
    [x] -> ...
    [x, ..] ->  ...
  }
  ```

  This is quite unsafe and can lead to confusing situations. Now at
  least the compiler warns about this. It isn't perfect though,
  especially in the presence of clause guards. But that's a start.
This commit is contained in:
KtorZ 2023-02-11 16:20:28 +01:00
parent 21fbd48b8d
commit 3c7663cd3c
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
1 changed files with 73 additions and 0 deletions

View File

@ -1386,6 +1386,10 @@ impl<'a> Environment<'a> {
Some(module.clone()) Some(module.clone())
}; };
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) { if let Ok(constructors) = self.get_constructors_for_type(&m, type_name, location) {
let mut unmatched_constructors: HashSet<String> = let mut unmatched_constructors: HashSet<String> =
constructors.iter().cloned().collect(); constructors.iter().cloned().collect();
@ -1426,6 +1430,75 @@ impl<'a> Environment<'a> {
} }
} }
pub fn check_list_pattern_exhaustiveness(
&mut self,
patterns: Vec<Pattern<PatternConstructor, Arc<Type>>>,
) -> Result<(), Vec<String>> {
let mut cover_empty = false;
let mut cover_tail = false;
let patterns = patterns.iter().map(|p| match p {
Pattern::Assign { pattern, .. } => pattern,
_ => p,
});
// TODO: We could also warn on redundant patterns. As soon as we've matched the entire
// list, any new pattern is redundant. For example:
//
// when xs is {
// [] => ...
// [x, ..] => ...
// [y] => ...
// }
//
// That last pattern is actually redundant / unreachable.
for p in patterns {
match p {
Pattern::Var { .. } => {
cover_empty = true;
cover_tail = true;
}
Pattern::Discard { .. } => {
cover_empty = true;
cover_tail = true;
}
Pattern::List { elements, tail, .. } => {
if elements.is_empty() {
cover_empty = true;
}
match tail {
None => {}
Some(p) => match **p {
Pattern::Discard { .. } => {
cover_tail = true;
}
Pattern::Var { .. } => {
cover_tail = true;
}
_ => {
unreachable!()
}
},
}
}
_ => {}
}
}
if cover_empty && cover_tail {
Ok(())
} else {
let mut missing = vec![];
if !cover_empty {
missing.push("[]".to_owned());
}
if !cover_tail {
missing.push("[_, ..]".to_owned());
}
Err(missing)
}
}
/// Lookup constructors for type in the current scope. /// Lookup constructors for type in the current scope.
/// ///
pub fn get_constructors_for_type( pub fn get_constructors_for_type(