python / mypy

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

PEP 604 unions don't work with generic type aliases #12211

Closed Eldar1205 closed 1 year ago

Eldar1205 commented 2 years ago

Bug Report

Python 3.10 new Union syntax doesn't work in a scenario old syntax did when involved with generic type aliases.

To Reproduce

Create the following definitions:

_ResponseType = TypeVar("_ResponseType")

SyncFunc: TypeAlias = Callable[..., _ResponseType]
Coro: TypeAlias = Callable[..., Coroutine[Any, Any, _ResponseType]]

SyncFuncOrCoro: TypeAlias = Coro[_ResponseType] | SyncFunc[_ResponseType]

Expected Behavior

Should work without errors, like it does when using old Union syntax:

_ResponseType = TypeVar("_ResponseType")

SyncFunc: TypeAlias = Callable[..., _ResponseType]
Coro: TypeAlias = Callable[..., Coroutine[Any, Any, _ResponseType]]

SyncFuncOrCoro: TypeAlias = Union[Coro[_ResponseType], SyncFunc[_ResponseType]]

Actual Behavior

I receive mypy error "Type application is only supported for generic classes".

Your Environment

I'm using pretty strict mypy settings:

[mypy] follow_imports = normal ignore_errors = false implicit_reexport = false warn_redundant_casts = True warn_unused_ignores = True disallow_any_generics = True disallow_untyped_defs = True check_untyped_defs = True allow_redefinition = false local_partial_types = True strict_optional = true strict_equality = true warn_unused_configs = true warn_unreachable = true warn_no_return = true no_implicit_optional = true

Eldar1205 commented 2 years ago

Just encountered another example, seems like a bug with generic type aliases using the new Union syntax, try to replace this with new syntax:

_FieldValueType = TypeVar("_FieldValueType")
FieldExtractionResult: TypeAlias = Union[InventoryEntityField[_FieldValueType], _FieldValueType, InventoryEntityFieldStatus]
ktbarrett commented 2 years ago

generic type aliases

There is no such things AFAIK. PEP 613 does not mention anything about generic type aliases (a major oversight IMO). Despite that the official Python documentation on the typing module specifically mentions "generic type aliases". Find on that phrase for an example that does not work in mypy.

@hauntsaninja Are there any PEPs underway to support generic type aliases officially? I think I prefer the syntax:

Coro: TypeAlias[_ResponseType] = Callable[..., Coroutine[Any, Any, _ResponseType]]
hauntsaninja commented 2 years ago

Generic type aliases are a thing that mypy claims to support, although I'm not sure if that's standard amongst type checkers: https://mypy.readthedocs.io/en/stable/generics.html#generic-type-aliases Edit: from a quick test, looks like pyright supports this as well

Syntax is the alias is generic over whatever unbound type variables are in the RHS.

ktbarrett commented 2 years ago

That looks like a slightly longer version of what's in the typing documentation. It also fails in mypy.

hauntsaninja commented 2 years ago

Okay, I think I found the section on typing.readthedocs.io you're referring to: https://typing.readthedocs.io/en/latest/source/libraries.html#type-aliases

The generic example there seems to work for me? https://mypy-play.net/?mypy=latest&python=3.10&gist=f8a6936bd13e7f1ac594cbdc6202f1eb

hauntsaninja commented 2 years ago

Maybe I'm confused about something :-)

Anyway, to the concrete suggestion of having to explicitly list the type vars generic type aliases are generic over — I think it's fine as is? Not sure what the extra repetition gets us. The other note is that TypeAlias is not subscriptable in Python 3.10, so the suggested syntax would be unfortunate in that regard. Maybe worth emailing typing-sig.

There is a sort of separate discussion of "we should have one, true reference that spells out all the rules you need to make a standards-compliant (tm) type checker", where we can codify everything that all type checkers agree on. I think the only thing decided in that space is that PEP 484 is not a living document, so if we were to write such a thing it would have to live elsewhere. Currently a lot of the discussion for standardising semantics happens ad hoc on typing-sig or random issues. typing.readthedocs.io could be that, but it also wants to be other things — we'll see what ends up happening :-)

(Getting a little off-topic, but did want to explicitly acknowledge that OP's bug is a bug and not a very fun bug either)

ktbarrett commented 2 years ago

No it's me who is confused. I swear I put the example from docs.python.org/ and it was failing, even as I started playing around with it. But I just tried again and it apparently works now... Every time I try to contribute I swear this program gaslights tf out of me. I'm done.

hauntsaninja commented 2 years ago

(For what it's worth, I really appreciate your contributions! You're consistently one of the most helpful people who hangs out around here :-) )

erictraut commented 2 years ago

Generic type aliases are a thing that mypy claims to support, although I'm not sure if that's standard amongst type checkers.

Generic type aliases are (to the best of my knowledge) supported by all Python type checkers in a consistent manner. PEP 484 talks about type aliases. While it doesn't specifically say that type aliases can be generic, it provides samples that make it pretty clear that they are supported and how they should work.

Eldar1205 commented 2 years ago

How about the fact the explicit Union[] syntax works with mypy? The 3.10 syntax should be equivalent, so isn't it right to say that if mypy supports the pre-3.10 syntax it should support the 3.10 one as well so it's not considered a regression?

JelleZijlstra commented 2 years ago

Yes, the original report is a bug we should fix. The discussion got sidetracked.

KotlinIsland commented 2 years ago

A minified example could be:

from typing import _T as T
Nullable = T | None

playground

ilevkivskyi commented 1 year ago

cc @JukkaL it looks like this is still broken.

erictraut commented 1 year ago

It appears that the original bug has been fixed. I cannot repro it with mypy 1.5.

@KotlinIsland, your code sample is different from OP's. Your sample doesn't use a TypeAlias annotation, and it results in a runtime exception (for which mypy correctly generates an error).

I think this issue can be closed.

hauntsaninja commented 1 year ago

Fixed in #14181