python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.56k stars 2.84k forks source link

What if duck types are unions of all the ducklings #11516

Open KotlinIsland opened 3 years ago

KotlinIsland commented 3 years ago

If the duck typed types were actually unions of all the ducklings then it would fix a bunch of edge cases that arise. And would make this feature more easily understood and discovered.

def func1(c: complex):
    reveal_type(c)  # float | int | complex

def func2(f: float):
    reveal_type(f)  # float | int

def func3(b: bytes):
    reveal_type(b)  # bytes | bytearray | memoryview

def func4(u: unicode):  # python 2 moment
    reveal_type(u)  # unicode | str

edge cases:

def func(f: float):
    f.is_integer()  # error: int has no member "is_integer"
def func(f: float):
    if not isinstance(f, float):
        print("hi")  # no 'unreachable code' error
        reveal_type(f)  # int | complex

It would also be useful to have type types for float and bytes, when you want exactly float and don't want any stinking ints.

Related #11511, #11145

CarliJoy commented 2 years ago

See also: https://github.com/python/mypy/issues/12824 and https://github.com/python/mypy/issues/12643

erictraut commented 1 year ago

This feature request appears to be based on a misunderstanding of the Python type system. The type annotation float doesn't simply refer to the class float. It represents a set of types that includes float and all subtypes thereof. The type float and the type float | int refer to the same set of types, so they are equivalent.

KotlinIsland commented 1 year ago

This feature request appears to be based on a misunderstanding of the Python type system.

That's obviously not what I am saying lol. This issue is regarding addressing the problems introduced by the 'ducktyping' functionality defined in pep 484, which states that float/int should lie about their base classes (mypy extends this idea to bytes). The solution being that instead of lying about their bases (extremely confusing), these specific types should just expand to a union of the ducklings.

erictraut commented 1 year ago

I don't understand what you're asking for here. I'm also not sure that you understood my previous point, so let me try again.

In type theory, if you have a class Parent and a class Child that derives from parent, then the types Parent and Parent | Child are 100% equivalent. If a type checker ever treats these as different, then it's a bug. If I understand you correctly, you're saying that float should be "expanded to" float | int, but those two types are 100% equivalent already because int is (by definition in PEP 484) a subtype of float. If mypy ever treats float differently from float | int, then it's a bug.

Are you suggesting that mypy add a new mode where it deviates from the behavior specified in PEP 484 and treats int as though it's not a subtype of float, etc.? That would be incompatible with all existing type stubs, so I don't think it would be very useful.

Or perhaps what you're asking is for new special forms to be added to the type system that represent "a float that is not an int" (float & ~int), etc.? That would require a new specification in the form of a PEP, and the mypy issue tracker wouldn't be the right place to request such a change.

Or perhaps what you're saying is that when mypy prints the type builtins.float in a textual form (e.g. in error messages), you always want it to print builtins.float | builtins.int. That would be sound from a type perspective, but I think most mypy users would find that representation to be redundant, verbose, and even somewhat confusing.

Or perhaps I haven't yet captured what you're proposing. If that's the case, can you provide more specifics?

ikonst commented 1 year ago

Edit: Now I get it. I totally missed the fact the hierarchy is fake for those very specific types. Been living too long on mypy-island.

I think @KotlinIsland wants 1) the fake hierarchy undone, and 2) for float, emit float | int behind the scenes, and 3) some time later introduce typing.FloatForReal that won't have that union-emitting behavior

Probably subject for a typing issue...

KotlinIsland commented 1 year ago

@ikonst yeah, exactly. I plan on implementing this in basedmypy, it will be an interesting experiment.

erictraut commented 1 year ago

@ikonst, what do you mean by "emit float | int behind the scenes"? Will that result in any user-visible behavior change in mypy? If so, how? Is the only visible change that the textual output for reveal_type and error messages becomes more verbose?

KotlinIsland commented 1 year ago

@ikonst, what do you mean by "emit float | int behind the scenes"? Will that result in any user-visible behavior change in mypy? If so, how? Is the only visible change that the textual output for reveal_type and error messages becomes more verbose?

No, annotating something with float will result in the type being float | int. And the promotion/ducktyping will be removed from int.

erictraut commented 1 year ago

Ah, I see what you mean. Thanks for the explanation. Yeah, that's an interesting experiment. Let us know what you find.