Run-time behaviour of `TypeAliasType

Some user feedback:

After having tried to upgrade a 200 kloc code base from typing.TypeAlias to type just to have to revert it all I’m not sure how we can deprecate TypeAlias. Here’s a tagged union type definition, which we use together with pydantic, among other things. We probably have 100 of these in our code base, each with perhaps 2-10 union members.

ChatMessage: TypeAlias = Annotated[
    TextMessage | AudioMessage,
    pydantic.Field(discriminator='message_type')
]

We will use this TypeAlias to annotate e.g. variables:

msg: ChatMessage

To do type narrowing:

if isinstance(obj, ChatMessage):
    ...

And to do runtime parsing using Pydantic:

ta: pydantic.TypeAdapter[ChatMessage] = pydantic.TypeAdapter(ChatMessage)
msg = ta.validate_python({"message_type": "message", ...}) # parse into target type or fail

Issues when switching to type aliases:

  • isinstance doesn’t work.
  • All code that does runtime work on e.g. unions now need to manually flatten unions to remove type aliases (see the end of the post for a helper I wrote).

If we didn’t have TypeAlias anymore we would have to define every type twice, something like:

type ChatMessage = Annotated[
    TextMessage | AudioMessage,
    pydantic.Field(discriminator='message_type')
]
ChatMessageTuple = (TextMessage, AudioMessage)

And, depending on the use case, use one or the other. Examples:

# annotating e.g. variables:
msg: ChatMessage # must use type alias

# type narrowing:
if isinstance(obj, ChatMessageTuple): # must use tuple
   ...

# type-directed parsing (probably doesn't type check):
ta: pydantic.TypeAdapter[ChatMessage] = pydantic.TypeAdapter(ChatMessageTuple)

I suspect that the pydantic.TypeAdapter wouldn’t work at all (without unsafe casts), as it needs to relate a static type (the return type of validate_python()) and a runtime type (the argument of TypeAdapter()), which isn’t possible if the type has been split in two as above, not even in theory.