| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
When comparing a generic Callable parameter with a generic function using ==, mypy incorrectly concluded the types could never overlap and marked the if-body as unreachable. Fix by extending shallow_erase_type_for_equality to handle CallableType and using erased current_type in the equality overlap checks. Fixes #21182 Signed-off-by: bahtya <bahtyar153@qq.com>
for more information, see https://pre-commit.ci
|
Diff from mypy_primer, showing the effect of this PR on open source code: rotki (https://github.com/rotki/rotki)
- rotkehlchen/tests/integration/test_premium.py:844: error: Statement is unreachable [unreachable]
+ rotkehlchen/tests/integration/test_premium.py:845: error: Statement is unreachable [unreachable]
- rotkehlchen/tests/integration/test_premium.py:858: error: Statement is unreachable [unreachable]
+ rotkehlchen/tests/integration/test_premium.py:859: error: Statement is unreachable [unreachable]
|
Sorry, something went wrong.
There was a problem hiding this comment.
Hm having to erase current type points to a bug in is_overlapping_types
Sorry, something went wrong.
|
Thanks for the review! You're right that needing to erase the current type is a signal of an underlying issue in is_overlapping_types. Looking at the bug: when comparing Callable[[T], S] with def identity(x: T) -> T, the overlap check receives the original TypeVar-bound callable on the left, and is_overlapping_types doesn't account for the fact that TypeVar bounds can unify across both sides. The erasing is a workaround to avoid a false non-overlap, but ideally is_overlapping_types should treat TypeVar-bound callables as potentially overlapping with any compatible generic callable. Would you prefer I investigate fixing is_overlapping_types directly? I could open a separate issue/PR for that, and keep this PR as the targeted regression fix in the meantime. |
Sorry, something went wrong.
|
The mypyc-compiled test failures (testAllBase64Features_librt with binascii.Error: Incorrect padding) also appear on #21190 and other recent PRs — this is a pre-existing flaky test unrelated to this change. |
Sorry, something went wrong.
|
Update on the mypyc-compiled test failures: I've confirmed these are pre-existing and unrelated to this PR:
No action needed on this PR for those failures. |
Sorry, something went wrong.
|
Hi @hauntsaninja — pinging on this since I haven't heard back after your is_overlapping_types comment. To recap my earlier response: I agree the type erasure points to a deeper issue in is_overlapping_types. I'm happy to either:
On the CI failures: the 3 failing mypyc-compiled checks (py313, py314, py314t) are pre-existing and unrelated to this change — they're caused by a behavior change in Python 3.13.13+/3.14.4+ in binascii.a2b_base64, tracked in issue #21120. The same failures appear on other recent PRs. What would you like me to do next? |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Problem
Fixes #21182
When comparing a generic Callable parameter with a generic function using ==, mypy incorrectly reported "Statement is unreachable" with --warn-unreachable:
This is a regression — the code was accepted in earlier mypy versions.
Root Cause
In conditional_types with from_equality=True, mypy uses shallow_erase_type_for_equality to erase generic type parameters from the proposed type before checking overlap. However:
shallow_erase_type_for_equality only handled Instance types, not CallableType. So generic callables like def [T] (x: T) -> T were never erased.
Only the proposed type was erased, not the current type. When is_overlapping_types was called with an unerased generic callable as current_type (e.g., the identity function with .variables=(T,)), is_callable_compatible tried to unify its type variables against the other callable's free type variables and failed, concluding the types don't overlap.
At runtime, generic type parameters are erased, so identity and cmp_property can absolutely be equal — one could be passed as the other.
Solution
Two changes:
1. Extend shallow_erase_type_for_equality to CallableType (mypy/erasetype.py)
2. Erase both sides in the overlap check (mypy/checker.py)
Testing