Skip to content

Populate the enclosing Anywhere phi for walrus-in-comprehension targets#3699

Open
mikeleppane wants to merge 1 commit into
facebook:mainfrom
mikeleppane:fix/walrus-comprehension-crash-3670
Open

Populate the enclosing Anywhere phi for walrus-in-comprehension targets#3699
mikeleppane wants to merge 1 commit into
facebook:mainfrom
mikeleppane:fix/walrus-comprehension-crash-3670

Conversation

@mikeleppane
Copy link
Copy Markdown
Contributor

Fixes #3670

What

Pyrefly panicked (Internal error: key lacking binding, key=Key::Anywhere(c)) instead of type-checking. Reachable from valid Python, not just the fuzzed input in the issue:

# before: 💥 panic
# after:  print(c) → error "`c` is uninitialized"; reveal_type(c) → Literal[2]
print(c)
[(c := 1) for _ in []]
[(c := 2) for _ in []]

Why

A name assigned by ≥2 walrus operators inside comprehensions becomes an Anywhere binding — pyrefly's SSA φ (phi) node whose value is the join of all assignment sites. The φ's value is only ever populated by record_bind_in_anywhere, reached through bind_name. But PEP 572 says a comprehension walrus assigns to the enclosing scope, so it routes through define_in_enclosing_non_comprehension_scope, which updated only the enclosing flow and never recorded the φ branch. When a read resolves to that enclosing Anywhere static before the walrus write (a read-before-write, or the issue's malformed for-target that reads the name), the φ node is created with no value → panic at solve time. In ordinary code the redirect's flow write shadows the φ, masking the gap. mypy, pyright, and ty all handle this without crashing (the conformance bar): error on the early read, infer int/Literal[2] after.

How

Make the walrus redirect fully emulate an enclosing-scope assignment — the same φ bookkeeping bind_name does:

  • pyrefly/lib/binding/scope.rsdefine_in_enclosing_non_comprehension_scope now returns Option<NameWriteInfo> (the enclosing scope's static write-info), mirroring define_in_current_flow.
  • pyrefly/lib/binding/bindings.rs — new bind_walrus_target_in_enclosing_scope helper that, when the enclosing name is Anywhere, calls record_bind_in_anywhere (must live here — table/record_bind_in_anywhere are module-private).
  • pyrefly/lib/binding/expr.rs — the Expr::Named walrus arm calls the helper instead of the bare redirect.

Deliberately untouched: the single-walrus / no-Anywhere case stays a no-op (anywhere_range is None); bind_name's type-alias/final/annotation checks already run on the comprehension-scope binding.

Test plan

Test (pyrefly/lib/test/scope.rs) Protects
test_walrus_in_comprehension_anywhere_read_before_write the crash repro: no panic, early-read error, Literal[2] after
test_walrus_in_comprehension_anywhere_in_function function-scope redirect target
test_walrus_in_comprehension_anywhere_nested nested comprehension (redirect skips all comprehension scopes)
test_walrus_in_comprehension_anywhere_genexp generator expression (also a comprehension scope)
test_walrus_in_comprehension_two_walrus_no_early_read regression guard for the previously-masked path
test_walrus_comprehension_fuzz_no_crash raw fuzzer input → parse errors, no panic
  • cargo test -p pyrefly --lib → 5360 passed; test::scope:: → 92 passed (all pre-existing walrus tests unchanged).
  • mypy_primer, 57 projects (pydantic, pandas, pandas-stubs, pandera, sympy, scipy, django-stubs, …): no diff — no regressions.
  • Format + lint clean.

A name assigned by >=2 walrus operators inside comprehensions becomes an
`Anywhere` (SSA phi) binding in the enclosing scope. PEP 572 routes the
write through `define_in_enclosing_non_comprehension_scope`, which updated
only the enclosing flow and never recorded the phi branch that `bind_name`
records for ordinary assignments. A read that resolves to the enclosing
`Anywhere` static before the walrus write (e.g. a read-before-write, or a
fuzzed for-target) then left the phi promised but value-less, panicking with
"key lacking binding" at solve time.

Have the redirect return the enclosing scope's `NameWriteInfo` and record the
`Anywhere` bind, so the walrus fully emulates an enclosing-scope assignment.

Fixes facebook#3670
@meta-cla meta-cla Bot added the cla signed label Jun 5, 2026
@github-actions github-actions Bot added the size/m label Jun 5, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 5, 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.

panic: key lacking binding with fuzzed assignment expressions and comprehensions

1 participant