FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Fix narrowing for unions by hauntsaninja · Pull Request #20728 · python/mypy · GitHub

Repository navigation

Fix narrowing for unions - #20728

Merged
hauntsaninja merged 2 commits into
python:masterfrom
hauntsaninja:narrow81
Feb 6, 2026
Merged

hauntsaninja merged 2 commits into
python:masterfrom
hauntsaninja:narrow81

Conversation

hauntsaninja commented Feb 3, 2026 •
edited
Loading

Copy link
Copy Markdown
Collaborator

Previously we considered the else branch unreachable in the testNarrowingAnyUnion test case. It's also nice that the new code is more obviously correct

Fixes #20330

This will help with landing #20727 as well

hauntsaninja changed the title Fix narrowing for unions with Any Fix narrowing for unions Feb 3, 2026
Previously we considered the else branch in the test case here
unreachable

This comment has been minimized.

Copy link
Copy Markdown
Collaborator Author

mesonbuild is desirable. the line is already type ignored because it is unsafe, now we get a better error message
starlette is a nice improvement, i will add a test for it
homeassistant huawei_lte is great, it makes mypy understand reachable code is reachable
homeassistant entity_registry is unfortunate. we infer dict[literal[...], any] where we used to infer dict[str, any]

[builtins fixtures/tuple.pyi]


[case testTypeIsGeneric]

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This is #20330 . The regression meant we only accepted negative

if is_async_callable(f):
reveal_type(f) # N: Revealed type is "(def (*Any, **Any) -> typing.Awaitable[Any]) | (def () -> typing.Awaitable[builtins.int])"
else:
reveal_type(f) # N: Revealed type is "def () -> builtins.int | typing.Awaitable[builtins.int]"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This is essentially the starlette code. On master the else branch narrowing doesn't happen.

# ...but not regular subtyping relationships
if isinstance(x, FloatLike):
reveal_type(x) # N: Revealed type is "__main__.FloatLike | __main__.IntLike"
reveal_type(x) # N: Revealed type is "__main__.FloatLike"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Not fully clear what this was testing. The change here is just that we simplify the union. I think the real thing this wants to test is that the else branch is unreachable, so I added that

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Yes, I think the else part is the important one. Not simplifying the union is an implementation detail, the two types are equivalent.

github-actions Bot commented Feb 3, 2026

Copy link
Copy Markdown
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

starlette (https://github.com/encode/starlette)
- starlette/middleware/errors.py:178: error: Argument 1 to "run_in_threadpool" has incompatible type "Callable[[Request[State], Exception], Response | Awaitable[Response]] | Callable[[WebSocket, Exception], Awaitable[None]]"; expected "Callable[[Request[State], Exception], Response]"  [arg-type]
+ starlette/middleware/errors.py:178: error: Argument 1 to "run_in_threadpool" has incompatible type "Callable[[Request[State], Exception], Response | Awaitable[Response]]"; expected "Callable[[Request[State], Exception], Response]"  [arg-type]
- starlette/_exception_handler.py:61: error: Argument 1 to "run_in_threadpool" has incompatible type "Callable[[Request[State], Exception], Response | Awaitable[Response]] | Callable[[WebSocket, Exception], Awaitable[None]]"; expected "Callable[[Request[State] | WebSocket, Exception], Any | None]"  [arg-type]
+ starlette/_exception_handler.py:61: error: Argument 2 to "run_in_threadpool" has incompatible type "Request[State] | WebSocket"; expected "Request[State]"  [arg-type]

core (https://github.com/home-assistant/core)
+ homeassistant/components/config/entity_registry.py:231: error: Invalid index type "Literal['aliases']" for "dict[Literal['device_class', 'disabled_by', 'hidden_by', 'icon', 'name', 'new_entity_id', 'area_id'], Any]"; expected type "Literal['device_class', 'disabled_by', 'hidden_by', 'icon', 'name', 'new_entity_id', 'area_id']"  [index]
+ homeassistant/components/config/entity_registry.py:235: error: Invalid index type "Literal['labels']" for "dict[Literal['device_class', 'disabled_by', 'hidden_by', 'icon', 'name', 'new_entity_id', 'area_id'], Any]"; expected type "Literal['device_class', 'disabled_by', 'hidden_by', 'icon', 'name', 'new_entity_id', 'area_id']"  [index]
+ homeassistant/components/config/entity_registry.py:260: error: Invalid index type "Literal['categories']" for "dict[Literal['device_class', 'disabled_by', 'hidden_by', 'icon', 'name', 'new_entity_id', 'area_id'], Any]"; expected type "Literal['device_class', 'disabled_by', 'hidden_by', 'icon', 'name', 'new_entity_id', 'area_id']"  [index]
- homeassistant/components/huawei_lte/__init__.py:192: error: Right operand of "and" is never evaluated  [unreachable]

meson (https://github.com/mesonbuild/meson)
+ mesonbuild/modules/rust.py:189: error: Unused "type: ignore" comment  [unused-ignore]
+ mesonbuild/modules/rust.py:189:39: error: TypedDict "ExecutableKeywordArguments" has no key "prelink"  [typeddict-item]
+ mesonbuild/modules/rust.py:189:39: note: Error code "typeddict-item" not covered by "type: ignore" comment
+ mesonbuild/modules/rust.py:189:39: error: TypedDict "ExecutableKeywordArguments" has no key "rust_abi"  [typeddict-item]
+ mesonbuild/modules/rust.py:189:39: error: TypedDict "ExecutableKeywordArguments" has no key "version"  [typeddict-item]
+ mesonbuild/modules/rust.py:189:39: error: TypedDict "ExecutableKeywordArguments" has no key "soversion"  [typeddict-item]
+ mesonbuild/modules/rust.py:189:39: error: TypedDict "ExecutableKeywordArguments" has no key "darwin_versions"  [typeddict-item]
+ mesonbuild/modules/rust.py:189:39: error: TypedDict "ExecutableKeywordArguments" has no key "shortname"  [typeddict-item]
+ mesonbuild/modules/rust.py:189:39: error: TypedDict "ExecutableKeywordArguments" has no key "pic"  [typeddict-item]

Copy link
Copy Markdown
Collaborator Author

Planning on merging this soon, since it is a prerequisite for some changes that need to be landed to unblock a release

ilevkivskyi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

LG.

hauntsaninja merged commit 79e9c78 into python:master Feb 6, 2026
23 checks passed
hauntsaninja deleted the narrow81 branch February 6, 2026 21:36
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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[1.18 regression] Negative type narrowing of TypeIs of Union with TypeVar not working

2 participants


Back | FazBrowse Home | New Git URL