I don’t see a problem in the formulation. It says that this “shortcut” is limited to this case only:
def foo(n: float):
pass
foo(1) # OK
No other special treatment of int vs float should be done. In particular:
def foo(n: float):
n.hex() # OK, as n is really a float
if isinstance(n, int):
# should be marked as unreachable
x: float = 1 # Error, 1 is int not float
y: int = 1 # OK
foo(y) # also OK, as a special case
This may be surprising, or inconsistent but is pretty much what the spec say.
So this means if you want to have consistent typing in your own code, you better use float | int if you want to accept both (and float if you only want floats). The special casing of calling float-annotated methods with ints, would then help with backwards compatibility / interfacing with other external libraries in cases such as the following:
def foo(n: float | int):
external_lib.method_only_with_float_annotated_but_handling_int_as_well(n)
x: float | int = 1
foo(x)
What would be helpful though is adding these examples that are not valid to the type spec tests, so that the behavior of the type checkers gets more consistent (and more in line with what the specs actually say). In my opinion, it would also be helpful if type checkers allow to disable this “shortcut”. For example, if my project is not using external libs and I use float | int consistently, I want to have foo(1) being marked as wrong if foo really only allows floats.
Another instance where mixing float and int is confusing, would be in the context of type guards. For example, math.isinf(n) == Truereally means that n is a float now, because an integer can never be infinity. So your typing system should be able to distinguish between float and int.
As a general remark, I would echo Oscar’s opinion that a clear distinction between float and int is essential for everything that seriously cares about correct results (i.e. everything in the scientific python ecosystem).