Skip to content

Commit ec4c463

Browse files
authored
Short term fix for bytes narrowing (#20704)
Fixes #20701 / see discussion there Background: - In #20492 I added code to treat bytes, bytearray and memoryview as having custom `__eq__`, since they can compare equal to values that are of other types. I did this based on primer hits on draft versions of that PR - In #20643 I rewrote the handling of custom equality when narrowing. That fixed many issues, but regressed the equivalent of `testNarrowingOptionalBytes` because we're now more principled about custom `__eq__`. Perhaps interestingly the primer on that PR is small and not controversial, so I didn't feel the need to prioritise unsound things However, we only need one of the operands to have custom equality to model these things as reachable, so I think we can probably just remove `builtins.bytes` from here. This behaviour still isn't ideal. For instance, narrowing here shouldn't depend on whether we promote or not (we shouldn't narrow `v_all` to bytes) The real fix is adding more dedicated special casing so that we have an in-between option between "narrow assuming we can only compare equal to values of the same type" and "assume `__eq__` means we can't conclude anything about the positive case" Other relevant PRs: - #20673 attempts to remove the other custom eq logic for list, dict, set I added in #20492 . It should be close to mergeable - #20660 improves reachability and unfortunately to land it I have to do a rethink for how int, float, complex narrowing works. I actually think the current version has pretty good semantics and the primer is valid (but scary). But I want to think about it more
1 parent bf7fe09 commit ec4c463

File tree

2 files changed

+77
-1
lines changed

2 files changed

+77
-1
lines changed

mypy/checker.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8637,7 +8637,6 @@ def reduce_and_conditional_type_maps(ms: list[TypeMap], *, use_meet: bool) -> Ty
86378637

86388638

86398639
BUILTINS_CUSTOM_EQ_CHECKS: Final = {
8640-
"builtins.bytes",
86418640
"builtins.bytearray",
86428641
"builtins.memoryview",
86438642
"builtins.list",

test-data/unit/check-narrowing.test

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3498,3 +3498,80 @@ def foo(x: str | array) -> str:
34983498
return result
34993499
raise
35003500
[builtins fixtures/tuple.pyi]
3501+
3502+
[case testNarrowingOptionalBytes]
3503+
from __future__ import annotations
3504+
def f(x: bytes | None):
3505+
if x == b"asdf":
3506+
reveal_type(x) # N: Revealed type is "builtins.bytes"
3507+
else:
3508+
reveal_type(x) # N: Revealed type is "builtins.bytes | None"
3509+
[builtins fixtures/primitives.pyi]
3510+
3511+
[case testNarrowingBytesLikeWithPromotion]
3512+
# flags: --strict-equality --warn-unreachable --strict-bytes
3513+
from __future__ import annotations
3514+
3515+
def check_test(x: bytes) -> None: ...
3516+
check_test(bytearray(b"asdf")) # E: Argument 1 to "check_test" has incompatible type "bytearray"; expected "bytes"
3517+
3518+
def main(
3519+
v_bytes: bytes,
3520+
v_bytearray: bytearray,
3521+
v_memoryview: memoryview,
3522+
v_all: bytes | bytearray | memoryview,
3523+
) -> None:
3524+
if v_bytes == v_bytearray:
3525+
reveal_type(v_bytes) # N: Revealed type is "builtins.bytes"
3526+
reveal_type(v_bytearray) # N: Revealed type is "builtins.bytearray"
3527+
if v_bytes == v_memoryview:
3528+
reveal_type(v_bytes) # N: Revealed type is "builtins.bytes"
3529+
reveal_type(v_memoryview) # N: Revealed type is "builtins.memoryview"
3530+
if v_bytearray == v_memoryview:
3531+
reveal_type(v_bytearray) # N: Revealed type is "builtins.bytearray"
3532+
reveal_type(v_memoryview) # N: Revealed type is "builtins.memoryview"
3533+
3534+
if v_all == v_bytes:
3535+
reveal_type(v_all) # N: Revealed type is "builtins.bytes"
3536+
reveal_type(v_bytes) # N: Revealed type is "builtins.bytes"
3537+
if v_all == v_bytearray:
3538+
reveal_type(v_all) # N: Revealed type is "builtins.bytes | builtins.bytearray | builtins.memoryview"
3539+
reveal_type(v_bytearray) # N: Revealed type is "builtins.bytearray"
3540+
if v_all == v_memoryview:
3541+
reveal_type(v_all) # N: Revealed type is "builtins.bytes | builtins.bytearray | builtins.memoryview"
3542+
reveal_type(v_memoryview) # N: Revealed type is "builtins.memoryview"
3543+
[builtins fixtures/primitives.pyi]
3544+
3545+
[case testNarrowingBytesLikeNoPromotion]
3546+
# flags: --strict-equality --warn-unreachable --no-strict-bytes
3547+
from __future__ import annotations
3548+
3549+
def check_test(x: bytes) -> None: ...
3550+
check_test(bytearray(b"asdf"))
3551+
3552+
def main(
3553+
v_bytes: bytes,
3554+
v_bytearray: bytearray,
3555+
v_memoryview: memoryview,
3556+
v_all: bytes | bytearray | memoryview,
3557+
) -> None:
3558+
if v_bytes == v_bytearray:
3559+
reveal_type(v_bytes) # N: Revealed type is "builtins.bytes"
3560+
reveal_type(v_bytearray) # N: Revealed type is "builtins.bytearray"
3561+
if v_bytes == v_memoryview:
3562+
reveal_type(v_bytes) # N: Revealed type is "builtins.bytes"
3563+
reveal_type(v_memoryview) # N: Revealed type is "builtins.memoryview"
3564+
if v_bytearray == v_memoryview:
3565+
reveal_type(v_bytearray) # N: Revealed type is "builtins.bytearray"
3566+
reveal_type(v_memoryview) # N: Revealed type is "builtins.memoryview"
3567+
3568+
if v_all == v_bytes:
3569+
reveal_type(v_all) # N: Revealed type is "builtins.bytes"
3570+
reveal_type(v_bytes) # N: Revealed type is "builtins.bytes"
3571+
if v_all == v_bytearray:
3572+
reveal_type(v_all) # N: Revealed type is "builtins.bytes | builtins.bytearray | builtins.memoryview"
3573+
reveal_type(v_bytearray) # N: Revealed type is "builtins.bytearray"
3574+
if v_all == v_memoryview:
3575+
reveal_type(v_all) # N: Revealed type is "builtins.bytes | builtins.bytearray | builtins.memoryview"
3576+
reveal_type(v_memoryview) # N: Revealed type is "builtins.memoryview"
3577+
[builtins fixtures/primitives.pyi]

0 commit comments

Comments
 (0)