Skip to content

fix match-case: false positive [unbound-name] #2932#2963

Open
asukaminato0721 wants to merge 2 commits intofacebook:mainfrom
asukaminato0721:2932
Open

fix match-case: false positive [unbound-name] #2932#2963
asukaminato0721 wants to merge 2 commits intofacebook:mainfrom
asukaminato0721:2932

Conversation

@asukaminato0721
Copy link
Copy Markdown
Contributor

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

@github-actions

This comment has been minimized.

@asukaminato0721 asukaminato0721 marked this pull request as ready for review March 30, 2026 12:32
Copilot AI review requested due to automatic review settings March 30, 2026 12:32
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread pyrefly/lib/binding/pattern.rs Outdated
Comment on lines +505 to +506
let case_is_irrefutable =
pattern_is_syntactically_exhaustive_for_subject(&subject_expr, &pattern);
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
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);

Copilot uses AI. Check for mistakes.
Comment thread pyrefly/lib/binding/function.rs Outdated
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) {
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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)
{

Copilot uses AI. Check for mistakes.
Comment thread pyrefly/lib/binding/pattern.rs Outdated
Comment on lines 505 to 507
let case_is_irrefutable =
pattern_is_syntactically_exhaustive_for_subject(&subject_expr, &pattern);
if case_is_irrefutable {
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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 {

Copilot uses AI. Check for mistakes.
@NathanTempest
Copy link
Copy Markdown
Contributor

@asukaminato0721 Great fix! The approach with pattern_is_syntactically_exhaustive_for_subject() is exactly right.

One note on Copilot's suggestion about guard.is_none() I checked and the guard field is already available and is an Option<Expr> type. You can see it's already being used in pattern.rs:

if let Some(mut guard) = guard {
    self.ensure_expr(&mut guard, &mut Usage::Narrowing(None));
    // ...
}

So implementing the fix is just a one-liner:

let case_is_irrefutable = guard.is_none()
    && pattern_is_syntactically_exhaustive_for_subject(&subject_expr, &pattern);

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.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 7, 2026

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

match-case: false positive [unbound-name]

3 participants