fix match-case: false positive [unbound-name] #2932#2963
fix match-case: false positive [unbound-name] #2932#2963asukaminato0721 wants to merge 2 commits intofacebook:mainfrom
Conversation
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Fixes a false-positive [unbound-name] in match statements by teaching the binder/return analysis to treat tuple-subject sequence catch-alls (e.g. case _, _:) as syntactically exhaustive when the match subject is a tuple expression of compatible shape.
Changes:
- Add subject-aware syntactic exhaustiveness check for sequence patterns over tuple-expression subjects.
- Use the new exhaustiveness predicate in match binding and in function “last expression” / return analysis.
- Add regression tests covering the unbound-name and missing-return scenarios from #2932.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
pyrefly/lib/binding/pattern.rs |
Introduces subject-aware exhaustiveness helper and uses it to decide match exhaustiveness in binding. |
pyrefly/lib/binding/function.rs |
Updates return/control-flow analysis to use the new subject-aware exhaustiveness helper. |
pyrefly/lib/test/pattern_match.rs |
Adds regression tests for tuple-subject catch-all exhaustiveness (binding + return analysis). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let case_is_irrefutable = | ||
| pattern_is_syntactically_exhaustive_for_subject(&subject_expr, &pattern); |
There was a problem hiding this comment.
case_is_irrefutable/exhaustive is set based only on the pattern. If a case has a guard (case _ if cond:), the pattern may be syntactically exhaustive for the subject but the case is not actually irrefutable/exhaustive because the guard can fail and control can fall through. Consider including guard.is_none() in the exhaustiveness condition so guarded cases don’t cause finish_exhaustive_fork() to be used incorrectly (which can hide unbound-name or exhaustiveness warnings).
| let case_is_irrefutable = | |
| pattern_is_syntactically_exhaustive_for_subject(&subject_expr, &pattern); | |
| let case_is_irrefutable = guard.is_none() | |
| && pattern_is_syntactically_exhaustive_for_subject(&subject_expr, &pattern); |
| for case in x.cases.iter() { | ||
| f(sys_info, &case.body, res)?; | ||
| if case.pattern.is_wildcard() || case.pattern.is_irrefutable() { | ||
| if pattern_is_syntactically_exhaustive_for_subject(&x.subject, &case.pattern) { |
There was a problem hiding this comment.
Match exhaustiveness for return/control-flow analysis should not ignore case guards. The loop currently treats a syntactically-exhaustive pattern as making the match exhaustive and breaks early, but case ... if guard: can fail and allow later cases / fall-through. Consider requiring case.guard.is_none() (or incorporating the guard into the exhaustiveness predicate) before setting syntactically_exhaustive = true and breaking.
| if pattern_is_syntactically_exhaustive_for_subject(&x.subject, &case.pattern) { | |
| if case.guard.is_none() | |
| && pattern_is_syntactically_exhaustive_for_subject(&x.subject, &case.pattern) | |
| { |
| let case_is_irrefutable = | ||
| pattern_is_syntactically_exhaustive_for_subject(&subject_expr, &pattern); | ||
| if case_is_irrefutable { |
There was a problem hiding this comment.
The variable name case_is_irrefutable is now derived from pattern_is_syntactically_exhaustive_for_subject(...), which depends on the match subject shape (not just the pattern’s inherent irrefutability). Renaming to something like case_is_syntactically_exhaustive (or similar) would avoid confusion with Pattern::is_irrefutable() semantics.
| let case_is_irrefutable = | |
| pattern_is_syntactically_exhaustive_for_subject(&subject_expr, &pattern); | |
| if case_is_irrefutable { | |
| let case_is_syntactically_exhaustive = | |
| pattern_is_syntactically_exhaustive_for_subject(&subject_expr, &pattern); | |
| if case_is_syntactically_exhaustive { |
|
@asukaminato0721 Great fix! The approach with One note on Copilot's suggestion about if let Some(mut guard) = guard {
self.ensure_expr(&mut guard, &mut Usage::Narrowing(None));
// ...
}So implementing the fix is just a one-liner: This handles edge cases like: case _, _ if some_condition: # Pattern exhaustive, but guard can fail! Without this check, Pyrefly would incorrectly treat guarded catch-all cases as exhaustive. The same change should be applied in function.rs for the return analysis path. |
|
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅ |
Summary
Fixes #2932
teaching the binder to treat tuple-subject sequence patterns like
case _, _:as syntactically exhaustive when the match subject is a tuple expression of compatible shape.Test Plan
add test