lang: Treat optional wildcards as exhaustive
b22713583a0fb93a7392f9bbc246652c7ef3f029dff2aed467bd7b55e926ccee
Optional match coverage only marked an unguarded else arm as a catch-all. A wildcard pattern was lowered as a catch-all but still failed semantic exhaustiveness checks. Use the computed catch-all status for both else and wildcard arms when validating optional matches. Assisted-by: Codex:gpt-5.6-sol
1 parent
c6107b5e
lib/std/lang/resolver.rad
+6 -9
| 4456 | 4456 | ||
| 4457 | 4457 | for prongNode in prongs { |
|
| 4458 | 4458 | let case ast::NodeValue::MatchProng(prong) = prongNode.value |
|
| 4459 | 4459 | else panic "resolveMatchOptional: expected match prong"; |
|
| 4460 | 4460 | ||
| 4461 | - | // For optionals, a binding prong only covers the value case, not `nil`. |
|
| 4462 | - | // Only `else` without a guard is a true catch-all. |
|
| 4463 | - | if prong.arm == ast::ProngArm::Else and prong.guard == nil { |
|
| 4464 | - | if catchAll { |
|
| 4465 | - | throw emitError(self, prongNode, ErrorKind::DuplicateCatchAll); |
|
| 4466 | - | } |
|
| 4467 | - | set catchAll = true; |
|
| 4468 | - | } |
|
| 4469 | - | ||
| 4470 | 4461 | let mut isCatchAll = false; |
|
| 4471 | 4462 | if prong.guard == nil { |
|
| 4472 | 4463 | match prong.arm { |
|
| 4473 | 4464 | case ast::ProngArm::Else => set isCatchAll = true, |
|
| 4474 | 4465 | case ast::ProngArm::Case(patterns) => set isCatchAll = hasWildcardPattern(patterns), |
|
| 4475 | 4466 | case ast::ProngArm::Binding(_) => { |
|
| 4476 | 4467 | // For optionals, a binding does *not* always match. |
|
| 4477 | 4468 | } |
|
| 4478 | 4469 | } |
|
| 4479 | 4470 | } |
|
| 4471 | + | if isCatchAll { |
|
| 4472 | + | if catchAll { |
|
| 4473 | + | throw emitError(self, prongNode, ErrorKind::DuplicateCatchAll); |
|
| 4474 | + | } |
|
| 4475 | + | set catchAll = true; |
|
| 4476 | + | } |
|
| 4480 | 4477 | setProngCatchAll(self, prongNode, isCatchAll); |
|
| 4481 | 4478 | set matchType = try visitMatchProng(self, prongNode, prong, subjectTy, matchType, matchBy); |
|
| 4482 | 4479 | ||
| 4483 | 4480 | // Track coverage. Guarded prongs don't count as covering a case. |
|
| 4484 | 4481 | if prong.guard == nil { |
test/tests/match.optional.wildcard.rad
added
+15 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! An unguarded wildcard covers both cases of an optional value. |
|
| 3 | + | ||
| 4 | + | fn classify(value: ?i32) -> i32 { |
|
| 5 | + | match value { |
|
| 6 | + | case _ => return 7, |
|
| 7 | + | } |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | @default fn main() -> i32 { |
|
| 11 | + | if classify(nil) + classify(1) == 14 { |
|
| 12 | + | return 0; |
|
| 13 | + | } |
|
| 14 | + | return 1; |
|
| 15 | + | } |