PEP: 613 Title: Explicit Type Aliases Author: Shannon Zhu
\<<szhu@fb.com>\> Sponsor: Guido van Rossum \<<guido@python.org>\>
Discussions-To:
<https://mail.python.org/archives/list/typing-sig@python.org/thread/MWRJOBEEEMFVXE7CAKO7B4P46IPM4AN3/>
Status: Accepted Type: Standards Track Content-Type: text/x-rst Created:
21-Jan-2020 Post-History: 21-Jan-2020

Abstract
========

Type aliases are user-specified types which may be as complex as any
type hint, and are specified with a simple variable assignment on a
module top level.

This PEP formalizes a way to explicitly declare an assignment as a type
alias.

Motivation
==========

Type aliases are declared as top level variable assignments. In [PEP
484](https://www.python.org/dev/peps/pep-0484/#type-aliases/), the
distinction between a valid type alias and a global variable was
implicitly determined: if a top level assignment is unannotated, and the
assigned value is a valid type, then the name being assigned to is a
valid type alias. Otherwise, that name is simply a global value that
cannot be used as a type hint.

These implicit type alias declaration rules create confusion when type
aliases involve forward references, invalid types, or violate other
restrictions enforced on type alias declaration. Because the distinction
between an unannotated value and a type alias is implicit, ambiguous or
incorrect type alias declarations implicitly default to a valid value
assignment. This creates expressions that are impossible to express as
type aliases and punts error diagnosis of malformed type aliases
downstream.

The following examples each include an illustration of some of the
suboptimal or confusing behaviors resulting from existing implicit alias
declarations. We also introduce explicit aliases of the format
`TypeName: TypeAlias = Expression` here for the sake of comparison, but
the syntax is discussed in further detail in later sections.

Forward References:
-------------------

    MyType = "ClassName"
    def foo() -> MyType: ...

This code snippet should not error so long as `ClassName` is defined
later on. However, a type checker is forced to assume that MyType is a
value assignment rather than a type alias, and therefore may throw
spurious errors that (1) `MyType` is an unannotated global string, and
(2) `MyType` cannot be used as a return annotation because it is not a
valid type.

    MyType: TypeAlias = “ClassName”
    def foo() -> MyType: ...

Explicit aliases remove ambiguity so neither of the above errors will be
thrown. Additionally, if something is wrong with `ClassName` (i.e., it's
not actually defined later), the type checker can throw an error.

Error Messaging:
----------------

    MyType1 = InvalidType
    MyType2 = MyGeneric(int)  # i.e., intention was MyGeneric[int]

A type checker should warn on this code snippet that `InvalidType` is
not a valid type, and therefore cannot be used to annotate an expression
or to construct a type alias. Instead, type checkers are forced to throw
spurious errors that (1) `MyType` is a global expression missing an
annotation, and (2) `MyType` is not a valid type in all usages of
`MyType` across the codebase.

    MyType1: TypeAlias = InvalidType
    MyType2: TypeAlias = MyGeneric(int)

With explicit aliases, the type checker has enough information to error
on the actual definition of the bad type alias, and explain why: that
`MyGeneric(int)` and `InvalidType` are not valid types. When the value
expression is no longer evaluated as a global value, unactionable type
errors on all usages of `MyType` across the codebase can be suppressed.

Scope Restrictions:
-------------------

    x = ClassName
    def foo() -> None:
      x = ClassName

The outer `x` is a valid type alias, but type checkers must error if the
inner `x` is ever used as a type because type aliases cannot be defined
inside a nested scope. This is confusing because the alias declaration
rule is not explicit, and because a type error will not be thrown on the
location of the inner type alias declaration but rather on every one of
its subsequent use cases.

    x: TypeAlias = ClassName
    def foo() -> None:
      x = ClassName
    def bar() -> None:
      x: TypeAlias = ClassName

With explicit aliases, the outer assignment is still a valid type
variable, and the inner assignment can either be a valid local variable
or a clear error, communicating to the author that type aliases cannot
be defined inside a nested scope.

Specification
=============

The explicit alias declaration syntax clearly differentiates between the
three possible kinds of assignments: typed global expressions, untyped
global expressions, and type aliases. This avoids the existence of
assignments that break type checking when an annotation is added, and
avoids classifying the nature of the assignment based on the type of the
value.

Implicit syntax (pre-existing):

    x = 1  # untyped global expression
    x: int = 1  # typed global expression

    x = int  # type alias
    x: Type[int] = int  # typed global expression

Explicit syntax:

    x = 1  # untyped global expression
    x: int = 1  # typed global expression

    x = int  # untyped global expression (see note below)
    x: Type[int] = int  # typed global expression

    x: TypeAlias = int  # type alias
    x: TypeAlias = “MyClass”  # type alias

Note: The examples above illustrate implicit and explicit alias
declarations in isolation. For the sake of backwards compatibility, type
checkers should support both simultaneously, meaning an untyped global
expression `x = int` will still be considered a valid type alias.

Backwards Compatibility
=======================

Explicit aliases provide an alternative way to declare type aliases, but
all pre-existing code and old alias declarations will work as before.

Reference Implementation
========================

The [Pyre](https://pyre-check.org/) type checker supports explicit type
alias declarations.

Rejected Ideas
==============

Some alternative syntaxes were considered for explicit aliases:

    MyType: TypeAlias[int]

This looks a lot like an uninitialized variable.

    MyType = TypeAlias[int]

Along with the option above, this format potentially adds confusion
around what the runtime value of `MyType` is.

In comparison, the chosen syntax option `MyType: TypeAlias = int` is
appealing because it still sticks with the `MyType = int` assignment
syntax, and adds some information for the type checker purely as an
annotation.

Copyright
=========

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.
