Fix false positive unbound-name with NoReturn in except blocks#2486
Closed
Adist319 wants to merge 2 commits intofacebook:mainfrom
Closed
Fix false positive unbound-name with NoReturn in except blocks#2486Adist319 wants to merge 2 commits intofacebook:mainfrom
Adist319 wants to merge 2 commits intofacebook:mainfrom
Conversation
When merging FlowStyles where branches had MaybeInitialized (deferred NoReturn/Never checks), the merge incorrectly converted them to PossiblyUninitialized, discarding the termination keys needed for solve-time verification. This caused false positive `unbound-name` errors when a variable was assigned in a try block with a NoReturn function in the except handler, nested inside an if statement. The fix preserves termination keys by combining them when merging MaybeInitialized styles, allowing the solver to correctly verify that all NoReturn paths terminate. Uninitialized/PossiblyUninitialized arms are matched before MaybeInitialized catch-all to prevent masking valid uninitialized paths. Fixes facebook#2406
|
Diff from mypy_primer, showing the effect of this PR on open source code: stone (https://github.com/dropbox/stone)
- ERROR stone/cli.py:343:9-12: `api` may be uninitialized [unbound-name]
- ERROR stone/cli.py:360:16-19: `api` may be uninitialized [unbound-name]
mkosi (https://github.com/systemd/mkosi)
- ERROR mkosi/config.py:4955:36-42: `result` may be uninitialized [unbound-name]
- ERROR mkosi/config.py:4966:68-74: `result` may be uninitialized [unbound-name]
- ERROR mkosi/distribution/opensuse.py:143:46-52: `subdir` may be uninitialized [unbound-name]
- ERROR mkosi/distribution/opensuse.py:155:54-60: `subdir` may be uninitialized [unbound-name]
- ERROR mkosi/distribution/opensuse.py:164:46-52: `subdir` may be uninitialized [unbound-name]
- ERROR mkosi/distribution/opensuse.py:171:46-52: `subdir` may be uninitialized [unbound-name]
mongo-python-driver (https://github.com/mongodb/mongo-python-driver)
- ERROR bson/__init__.py:369:16-21: `value` may be uninitialized [unbound-name]
- ERROR bson/__init__.py:578:30-35: `value` may be uninitialized [unbound-name]
bokeh (https://github.com/bokeh/bokeh)
- ERROR src/bokeh/core/serialization.py:631:16-22: `buffer` may be uninitialized [unbound-name]
beartype (https://github.com/beartype/beartype)
- ERROR beartype/_check/convert/_reduce/_pep/redpep484612646.py:642:49-64: `typearg_to_hint` may be uninitialized [unbound-name]
mypy (https://github.com/python/mypy)
- ERROR mypy/main.py:1563:12-19: `targets` may be uninitialized [unbound-name]
prefect (https://github.com/PrefectHQ/prefect)
- ERROR src/prefect/cli/_cyclopts/cloud.py:280:8-31: `prompt_switch_workspace` may be uninitialized [unbound-name]
- ERROR src/prefect/cli/_cyclopts/cloud.py:436:21-31: `workspaces` may be uninitialized [unbound-name]
- ERROR src/prefect/cli/_cyclopts/work_queue.py:596:23-27: `runs` may be uninitialized [unbound-name]
- ERROR src/prefect/cli/_cyclopts/work_queue.py:608:8-12: `runs` may be uninitialized [unbound-name]
- ERROR src/prefect/cli/cloud/__init__.py:232:8-31: `prompt_switch_workspace` may be uninitialized [unbound-name]
- ERROR src/prefect/cli/cloud/__init__.py:365:21-31: `workspaces` may be uninitialized [unbound-name]
- ERROR src/prefect/cli/work_queue.py:529:23-27: `runs` may be uninitialized [unbound-name]
- ERROR src/prefect/cli/work_queue.py:541:8-12: `runs` may be uninitialized [unbound-name]
- ERROR src/prefect/events/cli/automations.py:434:19-23: `data` may be uninitialized [unbound-name]
- ERROR src/prefect/events/cli/automations.py:434:52-56: `data` may be uninitialized [unbound-name]
- ERROR src/prefect/events/cli/automations.py:435:28-32: `data` may be uninitialized [unbound-name]
- ERROR src/prefect/events/cli/automations.py:436:21-25: `data` may be uninitialized [unbound-name]
- ERROR src/prefect/events/cli/automations.py:437:28-32: `data` may be uninitialized [unbound-name]
- ERROR src/prefect/events/cli/automations.py:439:29-33: `data` may be uninitialized [unbound-name]
- ERROR src/prefect/events/cli/automations.py:533:52-56: `data` may be uninitialized [unbound-name]
pywin32 (https://github.com/mhammond/pywin32)
- ERROR com/win32com/client/makepy.py:439:16-20: `args` may be uninitialized [unbound-name]
- ERROR com/win32comext/adsi/demos/scp.py:485:8-17: `log_level` may be uninitialized [unbound-name]
- ERROR win32/scripts/setup_d.py:89:32-35: `key` may be uninitialized [unbound-name]
|
|
@yangdanny97 has imported this pull request. If you are a Meta employee, you can view this in D94091327. |
stroxler
approved these changes
Feb 24, 2026
Contributor
stroxler
left a comment
There was a problem hiding this comment.
Review automatically exported from Phabricator review in Meta.
|
@yangdanny97 merged this pull request in f3fffb3. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Describe the Bug
When a variable is assigned in a
tryblock and aNoReturnfunction is called in theexceptblock, pyrefly incorrectly reports`node` may be uninitialized [unbound-name]when the try/except is nested inside anifstatement. The simple (non-nested) case already works correctly.Pyright handles this correctly. Since
foo()returnsNoReturn, the except paths always terminate, sonodeis guaranteed to be initialized atprint(node).Root Cause
In
FlowStyle::merged(), when merging flow styles where any branch hadMaybeInitialized(the deferred NoReturn/Never check style), the code immediately converted them toPossiblyUninitialized, discarding the termination keys needed for solve-time verification.Fix
MaybeInitializedstyles, combine their termination keys instead of discarding themMaybeInitializedwith a fully initialized style, preserve theMaybeInitializedkeysUninitialized/PossiblyUninitializedarms match beforeMaybeInitializedcatch-all to prevent masking valid uninitialized pathsTest Plan
test_noreturn_try_except_simple— simple try/except with NoReturn (passes before and after)test_noreturn_try_except_if_nested— exact reproduction from Erroneousunbound-namewhen usingNoReturn#2406 (failed before, passes after)flow_branchingtests passflow_looping,never,uninit, andscopetests passFixes #2406.