Add pattern match exhaustiveness checking

Implements the exhaustiveness algorithm to detect non-exhaustive pattern matches:
- Detects missing Bool patterns (true/false)
- Detects missing Option patterns (Some/None)
- Detects missing Result patterns (Ok/Err)
- Recognizes wildcards and variable patterns as catch-alls
- Warns about redundant patterns after catch-all patterns
- Integrates with the type checker to report errors

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 04:19:56 -05:00
parent d37f0fb096
commit db516f5cff
3 changed files with 544 additions and 0 deletions

View File

@@ -2,6 +2,7 @@
mod ast;
mod diagnostics;
mod exhaustiveness;
mod interpreter;
mod lexer;
mod modules;
@@ -931,4 +932,114 @@ c")"#;
assert_eq!(diag.title, "Purity Violation");
}
}
// Exhaustiveness checking tests
mod exhaustiveness_tests {
use super::*;
#[test]
fn test_exhaustive_bool_match() {
let source = r#"
fn check(b: Bool): Int = match b {
true => 1,
false => 0
}
let result = check(true)
"#;
let result = eval(source);
assert!(result.is_ok(), "Expected success but got: {:?}", result);
}
#[test]
fn test_non_exhaustive_bool_match() {
let source = r#"
fn check(b: Bool): Int = match b {
true => 1
}
let result = check(true)
"#;
let result = eval(source);
assert!(result.is_err());
assert!(result.unwrap_err().contains("Non-exhaustive"));
}
#[test]
fn test_exhaustive_option_match() {
let source = r#"
fn unwrap_or(opt: Option<Int>, default: Int): Int = match opt {
Some(x) => x,
None => default
}
let result = unwrap_or(Some(42), 0)
"#;
let result = eval(source);
assert!(result.is_ok(), "Expected success but got: {:?}", result);
}
#[test]
fn test_non_exhaustive_option_missing_none() {
let source = r#"
fn get_value(opt: Option<Int>): Int = match opt {
Some(x) => x
}
let result = get_value(Some(1))
"#;
let result = eval(source);
assert!(result.is_err());
assert!(result.unwrap_err().contains("Non-exhaustive"));
}
#[test]
fn test_wildcard_is_exhaustive() {
let source = r#"
fn classify(n: Int): String = match n {
0 => "zero",
1 => "one",
_ => "many"
}
let result = classify(5)
"#;
let result = eval(source);
assert!(result.is_ok(), "Expected success but got: {:?}", result);
}
#[test]
fn test_variable_pattern_is_exhaustive() {
let source = r#"
fn identity(n: Int): Int = match n {
x => x
}
let result = identity(42)
"#;
let result = eval(source);
assert!(result.is_ok(), "Expected success but got: {:?}", result);
}
#[test]
fn test_redundant_arm_warning() {
let source = r#"
fn test_fn(n: Int): Int = match n {
_ => 1,
0 => 2
}
let result = test_fn(0)
"#;
let result = eval(source);
assert!(result.is_err());
assert!(result.unwrap_err().contains("Redundant"));
}
#[test]
fn test_exhaustive_result_match() {
let source = r#"
fn handle_result(r: Result<Int, String>): Int = match r {
Ok(n) => n,
Err(_) => 0
}
let result = handle_result(Ok(42))
"#;
let result = eval(source);
assert!(result.is_ok(), "Expected success but got: {:?}", result);
}
}
}