Skip to content

Narrow tuple out of union for irrefutable sequence patterns#3428

Open
lordhaa123 wants to merge 1 commit into
facebook:mainfrom
lordhaa123:fix-3147-sequence-pattern-narrow-union
Open

Narrow tuple out of union for irrefutable sequence patterns#3428
lordhaa123 wants to merge 1 commit into
facebook:mainfrom
lordhaa123:fix-3147-sequence-pattern-narrow-union

Conversation

@lordhaa123
Copy link
Copy Markdown
Contributor

Summary

  • Fixes Sequence pattern in match/case does not narrow tuple out of a union #3147 — sequence patterns like case (_, _): failed to narrow the matched tuple type out of a union in later cases, causing spurious bad-argument-type errors on assert_never exhaustiveness checks.
  • Mirrors the existing MatchClass fix: when every sub-pattern is irrefutable (wildcards, bare names, *rest), strip the spurious Placeholder narrow ops that and_all appends, so negation of the scope-level And(IsSequence, LenEq/LenGte) narrow actually eliminates the matched sequence type from subsequent cases. Refutable sub-patterns (e.g. case (1.0, 2.0)) are left alone — the case can still fail when the structural narrow holds.

The commit message has the full root-cause analysis.

Test plan

  • New test test_match_sequence_pattern_narrows_tuple_out_of_union — verbatim issue repro, asserts zero errors.
  • New test test_match_sequence_star_pattern_narrows — confirms [*_] narrows correctly.
  • New test test_match_sequence_refutable_subpattern_no_strip — guards against over-stripping: case (1.0, 2.0) followed by _ must leave float | tuple[float, float] in the fall-through.
  • cargo test --package pyrefly --lib pattern_match — 64 passed.
  • cargo test --package pyrefly --lib test::narrow — 192 passed.
  • cargo test --package pyrefly --lib flow_branching — 151 passed.
  • python3 test.py --no-test --no-conformance --no-jsonschema — clean.

🤖 Generated with Claude Code

The `match`/`case` matcher was failing to narrow sequence types out of a
union when the sequence pattern had only irrefutable sub-patterns. For
`match value:` with subject `float | tuple[float, float]` and case
`(_, _)`, the fall-through cases still saw `tuple[float, float]`, so a
final `assert_never(unreachable)` would wrongly fire.

The sequence-pattern handler inserts a scope-level `And(IsSequence,
LenEq(N))` narrow on the subject, then iterates sub-patterns calling
`NarrowOps::and_all`. For wildcard sub-patterns each call appends a
spurious `AtomicNarrowOp::Placeholder`, leaving
`And(IsSequence, LenEq, Placeholder, Placeholder)`. Negating yields
`Or(IsNotSequence, LenNotEq, Placeholder, Placeholder)` — and
`Placeholder` is a no-op narrow, so the Or's join with `unions()`
re-introduces the matched tuple type and defeats negative narrowing.

This mirrors the same fix already in place for `Pattern::MatchClass`:
when every sub-pattern is irrefutable (wildcards, bare names, `*rest`),
the structural narrow alone fully describes what the pattern proves, so
the Placeholders are spurious and should be stripped. For refutable
sub-patterns (e.g. `case (1.0, 2.0)`) we leave them — the case can fail
even when the structural narrow holds, so we must not remove length-2
sequences from later cases.

Fixes facebook#3147.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@meta-cla meta-cla Bot added the cla signed label May 16, 2026
@stroxler stroxler self-assigned this May 19, 2026
@meta-codesync
Copy link
Copy Markdown
Contributor

meta-codesync Bot commented May 19, 2026

@stroxler has imported this pull request. If you are a Meta employee, you can view this in D105722881.

@github-actions
Copy link
Copy Markdown

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.

Sequence pattern in match/case does not narrow tuple out of a union

2 participants