2020-01-22 21:13:06 -05:00
|
|
|
|
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/
|
2020-01-31 18:38:40 -05:00
|
|
|
|
Status: Accepted
|
2020-01-23 20:20:11 -05:00
|
|
|
|
Type: Standards Track
|
2020-01-22 21:13:06 -05:00
|
|
|
|
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.
|
2020-08-07 12:35:20 -04:00
|
|
|
|
We also introduce explicit aliases of the format ``TypeName: TypeAlias = Expression``
|
2020-01-22 21:13:06 -05:00
|
|
|
|
here for the sake of comparison, but the syntax is discussed in further detail
|
|
|
|
|
in later sections.
|
|
|
|
|
|
|
|
|
|
Forward References:
|
|
|
|
|
*******************
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
MyType = "ClassName"
|
|
|
|
|
def foo() -> MyType: ...
|
|
|
|
|
|
2020-08-07 12:35:20 -04:00
|
|
|
|
This code snippet should not error so long as ``ClassName`` is defined
|
2020-01-28 15:58:47 -05:00
|
|
|
|
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
|
2020-08-07 12:35:20 -04:00
|
|
|
|
that (1) ``MyType`` is an unannotated global string, and (2) ``MyType``
|
2020-01-28 15:58:47 -05:00
|
|
|
|
cannot be used as a return annotation because it is not a valid type.
|
2020-01-22 21:13:06 -05:00
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
MyType: TypeAlias = “ClassName”
|
|
|
|
|
def foo() -> MyType: ...
|
|
|
|
|
|
|
|
|
|
Explicit aliases remove ambiguity so neither of the above errors will be
|
2020-08-07 12:35:20 -04:00
|
|
|
|
thrown. Additionally, if something is wrong with ``ClassName``
|
2020-01-22 21:13:06 -05:00
|
|
|
|
(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]
|
|
|
|
|
|
2020-08-07 12:35:20 -04:00
|
|
|
|
A type checker should warn on this code snippet that ``InvalidType`` is not
|
2020-01-22 21:13:06 -05:00
|
|
|
|
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
|
2020-08-07 12:35:20 -04:00
|
|
|
|
errors that (1) ``MyType`` is a global expression missing an annotation,
|
|
|
|
|
and (2) ``MyType`` is not a valid type in all usages of ``MyType``
|
2020-01-22 21:13:06 -05:00
|
|
|
|
across the codebase.
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
MyType1: TypeAlias = InvalidType
|
|
|
|
|
MyType2: TypeAlias = MyGeneric(int)
|
|
|
|
|
|
|
|
|
|
With explicit aliases, the type checker has enough information to error on the
|
2020-08-07 12:35:20 -04:00
|
|
|
|
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``
|
2020-01-22 21:13:06 -05:00
|
|
|
|
across the codebase can be suppressed.
|
|
|
|
|
|
|
|
|
|
Scope Restrictions:
|
|
|
|
|
*******************
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
x = ClassName
|
|
|
|
|
def foo() -> None:
|
|
|
|
|
x = ClassName
|
|
|
|
|
|
2020-08-07 12:35:20 -04:00
|
|
|
|
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
|
2020-01-28 15:58:47 -05:00
|
|
|
|
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.
|
2020-01-22 21:13:06 -05:00
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
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
|
2020-08-07 12:35:20 -04:00
|
|
|
|
both simultaneously, meaning an untyped global expression ``x = int`` will
|
2020-01-22 21:13:06 -05:00
|
|
|
|
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
|
2020-08-07 12:35:20 -04:00
|
|
|
|
what the runtime value of ``MyType`` is.
|
2020-01-22 21:13:06 -05:00
|
|
|
|
|
|
|
|
|
|
2020-08-07 12:35:20 -04:00
|
|
|
|
In comparison, the chosen syntax option ``MyType: TypeAlias = int`` is
|
|
|
|
|
appealing because it still sticks with the ``MyType = int`` assignment
|
2020-01-22 21:13:06 -05:00
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
..
|
|
|
|
|
Local Variables:
|
|
|
|
|
mode: indented-text
|
|
|
|
|
indent-tabs-mode: nil
|
|
|
|
|
sentence-end-double-space: t
|
|
|
|
|
fill-column: 70
|
|
|
|
|
coding: utf-8
|
|
|
|
|
End:
|