Skip to content

false negative: int and bool don't implement .hex() #18359

Closed
@jorenham

Description

@jorenham

Bug Report

builtins.float and builtins.int shouldn't be assignable to a Protocol implementing hex: () -> str, because that would be type-unsafe:

>>> int().hex()
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
    int().hex()
    ^^^^^^^^^
AttributeError: 'int' object has no attribute 'hex'
>>> bool().hex()
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    bool().hex()
    ^^^^^^^^^^
AttributeError: 'bool' object has no attribute 'hex'

Mypy accepts this regardless.

To Reproduce

from typing import Protocol

class CanHex(Protocol):
    def hex(self, /) -> str: ...

True negative:

float_has_hex: CanHex = 3.14   # accepted

True positives:

object_doesnt_have_hex: CanHex = object()  # rejected: [assignment]
complex_doesnt_have_hex: CanHex = 1j  # rejected: [assignment]

False negatives:

int_doesnt_have_hex: CanHex = 666  # accepted?
bool_clearly_also_doesnt_have_hex: CanHex = False  # accepted??

https://mypy-play.net/?mypy=latest&python=3.12&gist=8ac210b6e647983b5c165af053ccd7d1

This isn't limited to .hex(), but applies to all methods that are implemented by builtins.float but not by builtins.int.


I consider this a high-priority issue, because of the following reasons:

  1. It's type-unsafe, even in the most explicit of situations: def tohex(x: CanHex): return x.hex()
  2. This issue, in combination with float and complex forcibly change invariant types to covariant #18257, makes it impossible to distinguish between int and float input using static typing.

The second reason in particular is a big problem for NumPy, because it forces us to annotate e.g. np.dtype(complex) as np.dtype[np.complex128 | np.float64 | np.int_ | np.bool], and np.array(3.14) as np.ndarray[tuple[()], np.dtype[np.float64 | np.int_ | np.bool]].
So I'm sure you can see how this is confusing for users, especially if you consider that usually tends to propagate to other dtype- and array-types.

We can't use the "Any trick" either, because that'd be problematic for functions that overload on dtype, of which there are many.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrong

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions