PEP 613: Explicit Type Aliases (#1278)
This commit is contained in:
parent
00a0215285
commit
60d5d10a1d
|
@ -0,0 +1,223 @@
|
|||
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: Draft
|
||||
Type: Informational
|
||||
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 :code:`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 :code:`ClassName` is defined
|
||||
later on. However, a type checker is forced to throw spurious errors that
|
||||
(1) :code:`MyType` is an unannotated global string, and (2) :code:`MyType`
|
||||
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 :code:`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 :code:`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) :code:`MyType` is a global expression missing an annotation,
|
||||
and (2) :code:`MyType` is not a valid type in all usages of :code:`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 :code:`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 :code:`MyType`
|
||||
across the codebase can be suppressed.
|
||||
|
||||
Scope Restrictions:
|
||||
*******************
|
||||
|
||||
::
|
||||
|
||||
x = ClassName
|
||||
def foo() -> None:
|
||||
x = ClassName
|
||||
|
||||
The outer :code:`x` is a valid type alias, but type checkers must error if the
|
||||
inner :code:`x` is ever used as a type. This is confusing because the alias
|
||||
declaration rule is not explicit, and the error is not 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 :code:`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 :code:`MyType` is.
|
||||
|
||||
|
||||
In comparison, the chosen syntax option :code:`MyType: TypeAlias = int` is
|
||||
appealing because it still sticks with the :code:`MyType = int` assignment
|
||||
syntax, and adds some information for the type checker purely as an annotation.
|
||||
|
||||
|
||||
Open Issues
|
||||
===========
|
||||
|
||||
|
||||
The current implementation utilizes the :code:`TypeAlias` already defined in :code:`typing.pyi`,
|
||||
but for conciseness there are suggestions to standardize this to a new class, :code:`typing.Alias`.
|
||||
|
||||
|
||||
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:
|
Loading…
Reference in New Issue