Repository navigation
Propagate conditional termination through nested flow merges - #5196
TimonGaertner wants to merge 1 commit into
Conversation
|
Hi @TimonGaertner! Thank you for your pull request and welcome to our community. Action RequiredIn order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you. ProcessIn order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA. Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
4731bef to
9106850
Compare
9106850 to
53d2f1b
Compare
|
This pull request has been imported. If you are a Meta employee, you can view this in D126795894. (Because this pull request was imported automatically, there will not be any future comments.) |
53d2f1b to
3c48a66
Compare
rchen152
left a comment
There was a problem hiding this comment.
Thanks! I think this is likely good to merge, pending a mypy primer run to evaluate the impact on real-world code, but the merge conflicts prevent our auto-import hooks from working. Could you rebase?
This comment has been minimized.
This comment has been minimized.
A branch that does not terminate syntactically may still end in a statement whose type is only known to be `Never` at solve time, such as a `NoReturn` call or the fall-through of a type-exhaustive `match`. The merge of such a branch already defers the "may be uninitialized" decision to solve time via a termination key, but `merge_flow` reset that key on the merged flow, so the information was lost as soon as the construct was nested inside another branch. The enclosing merge then saw a live branch that does not define the variable and reported a false `unbound-name`. Carry a termination key on the merged flow when every live branch has one. A single key is reused directly; several are joined by a `Binding::Phi` under the new `Key::FlowTermination`, with each key as its own termination key so the join is `Never` exactly when all branches are. Loops are excluded since the pre-loop path stays live. Fixes meta#5069
3c48a66 to
fee49cd
Compare
|
@rchen152 Thanks! Rebased onto current main, and it's mergeable again. The only conflict was in |
rchen152
left a comment
There was a problem hiding this comment.
Review automatically exported from Phabricator review in Meta.
|
Thanks again for the fix! |
|
Diff from mypy_primer, showing the effect of this PR on open source code: ============================================================
SUMMARY
============================================================
Total: +6 new errors, -18 fixed errors
By preset: +0/-16 (default), +1/-16 (strict)
Projects with changes (11):
mongo-python-driver: +0 -2
egglog-python: +0 -1
pytest: +0 -2
jax: +1 -0
manticore: +0 -1
pandera: +0 -1
stone: +1 -1
bokeh: +0 -1
hydpy: +0 -1
pytensor: +0 -3
core: +4 -5
============================================================
FULL DIFF DETAILS
------------------------------------------------------------
mongo-python-driver (https://github.com/mongodb/mongo-python-driver)
- ERROR pymongo/asynchronous/_kms_connect.py:87:19-39: Expected a callable, got `None` [not-callable]
- ERROR pymongo/synchronous/_kms_connect.py:87:19-39: Expected a callable, got `None` [not-callable]
egglog-python (https://github.com/egraphs-good/egglog-python)
- ERROR python/egglog/egraph.py:2817:24-35: Argument `BaseExpr | Fact` is not assignable to parameter `expr` with type `BaseExpr` in function `expr_action` [bad-argument-type]
pytest (https://github.com/pytest-dev/pytest)
- ERROR src/_pytest/doctest.py:585:33-39: `module` may be uninitialized [unbound-name]
- ERROR src/_pytest/unittest.py:296:57-64: `excinfo` may be uninitialized [unbound-name]
jax (https://github.com/google/jax)
+ ERROR jax/_src/pjit.py:1957:3-36: Unused `# pyrefly: ignore` comment for code(s): unbound-name [unused-ignore]
manticore (https://github.com/trailofbits/manticore)
- ERROR manticore/core/smtlib/visitors.py:644:16-21: The type of the returned value is partially unknown; it contains an implicit `Any` [partially-unknown-type]
pandera (https://github.com/pandera-dev/pandera)
- ERROR tests/pyspark/test_pyspark_container.py:657:32-39: `read_df` may be uninitialized [unbound-name]
stone (https://github.com/dropbox/stone)
- ERROR stone/compiler.py:125:28-48: Implicit conversion of `Unknown | None` to `bool` is not allowed [implicit-bool]
+ ERROR stone/compiler.py:125:28-48: Implicit conversion of `None` to `bool` is not allowed [implicit-bool]
bokeh (https://github.com/bokeh/bokeh)
- ERROR src/bokeh/command/bootstrap.py:117:8-11: `ret` may be uninitialized [unbound-name]
hydpy (https://github.com/hydpy-dev/hydpy)
- ERROR hydpy/core/filetools.py:609:16-26: Returned type `str | None` is not assignable to declared return type `str` [bad-return]
pytensor (https://github.com/pymc-devs/pytensor)
- ERROR pytensor/compile/executor.py:712:12-19: `outputs` may be uninitialized [unbound-name]
- ERROR pytensor/scan/op.py:1794:49-53: `t_fn` may be uninitialized [unbound-name]
- ERROR pytensor/scan/op.py:1796:44-51: `n_steps` may be uninitialized [unbound-name]
core (https://github.com/home-assistant/core)
- ERROR homeassistant/components/notion/coordinator.py:162:29-36: `bridges` may be uninitialized [unbound-name]
- ERROR homeassistant/components/notion/coordinator.py:163:31-40: `listeners` may be uninitialized [unbound-name]
- ERROR homeassistant/components/notion/coordinator.py:164:29-36: `sensors` may be uninitialized [unbound-name]
- ERROR homeassistant/components/notion/coordinator.py:165:38-54: `user_preferences` may be uninitialized [unbound-name]
+ ERROR homeassistant/components/notion/coordinator.py:162:29-45: The type of this argument is unknown because it comes from an error [unknown-from-error]
+ ERROR homeassistant/components/notion/coordinator.py:163:31-49: The type of this argument is unknown because it comes from an error [unknown-from-error]
+ ERROR homeassistant/components/notion/coordinator.py:164:29-45: The type of this argument is unknown because it comes from an error [unknown-from-error]
+ ERROR homeassistant/components/notion/coordinator.py:165:38-63: The type of this argument is unknown because it comes from an error [unknown-from-error]
- ERROR homeassistant/util/loop.py:105:19-36: `integration_frame` may be uninitialized [unbound-name] |
|
This pull request has been merged in 0ad7d1d. |
Summary
When a branch does not terminate syntactically but ends in a statement whose type may be
Never(aNoReturncall, or the fall-through of a type-exhaustivematch), the merge of that branch already defers the "may be uninitialized" decision to solve time via a termination key. However,merge_flowalways reset the merged flow's termination key toNone, so this information was lost as soon as the construct was nested inside another branch. The enclosing merge then saw a live branch that does not define the variable and reported a falseunbound-name:The same happened for an
if/elsewhere both arms call aNoReturnfunction, nested inside another branch.This PR makes
merge_flowcarry a termination key when every live branch has one. With a single live branch the key is reused as-is; with several, aBinding::Phiunder a newKey::FlowTerminationjoins them, using each key as its own termination key so the result isNeverexactly when all branches are. Loops are excluded, since the pre-loop path always stays live. Merges where all branches terminate syntactically are unchanged.Fixes #5069
Test Plan
Added tests in
flow_branching.rsandpattern_match.rscovering:matchinside anifbranch (no error)matchinside amatcharm, name bound in sibling arm (no error, the issue's original repro)if/elsewhere both arms callNoReturn(no error)if/elsewhere one arm is notNoReturn(still an error)match(still an error)NoReturninside aforloop body (still an error)The three "no error" tests fail without the fix. Ran
python3 test.py.Note: the related symptom from the issue (no
unreachablewarning after an exhaustivematch) is not addressed here. The reachability gate inbindings.rsdeliberately skips exhaustive paths so that theassert_neveridiom is not flagged, so that would be a separate design decision.