From 4f3c25043d651a13c41cffcee703f7d5cb677cc7 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Thu, 8 Oct 2020 00:44:31 +0300 Subject: [PATCH] bpo-41923: PEP 613: Add TypeAlias to typing module (#22532) This special marker annotation is intended to help in distinguishing proper PEP 484-compliant type aliases from regular top-level variable assignments. --- Doc/library/typing.rst | 13 ++++++ Doc/whatsnew/3.10.rst | 25 +++++++++++- Lib/test/test_typing.py | 40 +++++++++++++++++++ Lib/typing.py | 16 ++++++++ Misc/ACKS | 1 + .../2020-10-03-23-14-50.bpo-41923.Buonw9.rst | 1 + 6 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-10-03-23-14-50.bpo-41923.Buonw9.rst diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index a72632e61b0..f4b2718cdc2 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -34,6 +34,8 @@ In the function ``greeting``, the argument ``name`` is expected to be of type :class:`str` and the return type :class:`str`. Subtypes are accepted as arguments. +.. _type-aliases: + Type aliases ============ @@ -489,6 +491,17 @@ These can be used as types in annotations and do not support ``[]``. .. versionadded:: 3.5.4 .. versionadded:: 3.6.2 +.. data:: TypeAlias + + Special annotation for explicitly declaring a :ref:`type alias `. + For example:: + + from typing import TypeAlias + + Factors: TypeAlias = list[int] + + .. versionadded:: 3.10 + Special forms """"""""""""" diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 2bcdba69957..4ada4be3b66 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -99,8 +99,29 @@ in :issue:`38605`.) * :pep:`618`: The :func:`zip` function now has an optional ``strict`` flag, used to require that all the iterables have an equal length. -PEP604: New Type Operator -------------------------- +PEP 613: TypeAlias Annotation +----------------------------- + +:pep:`484` introduced the concept of type aliases, only requiring them to be +top-level unannotated assignments. This simplicity sometimes made it difficult +for type checkers to distinguish between type aliases and ordinary assignments, +especially when forward references or invalid types were involved. Compare:: + + StrCache = 'Cache[str]' # a type alias + LOG_PREFIX = 'LOG[DEBUG]' # a module constant + +Now the :mod:`typing` module has a special annotation :data:`TypeAlias` to +declare type aliases more explicitly:: + + StrCache: TypeAlias = 'Cache[str]' # a type alias + LOG_PREFIX = 'LOG[DEBUG]' # a module constant + +See :pep:`613` for more details. + +(Contributed by Mikhail Golubev in :issue:`41923`.) + +PEP604: New Type Union Operator +------------------------------- A new type union operator was introduced which enables the syntax ``X | Y``. This provides a cleaner way of expressing 'either type X or type Y' instead of diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 4bef42f4f32..57dd73c529d 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -24,6 +24,7 @@ from typing import NamedTuple, TypedDict from typing import IO, TextIO, BinaryIO from typing import Pattern, Match from typing import Annotated, ForwardRef +from typing import TypeAlias import abc import typing import weakref @@ -4176,6 +4177,45 @@ class AnnotatedTests(BaseTestCase): self.assertEqual(X[int], List[Annotated[int, 5]]) +class TypeAliasTests(BaseTestCase): + def test_canonical_usage_with_variable_annotation(self): + Alias: TypeAlias = Employee + + def test_canonical_usage_with_type_comment(self): + Alias = Employee # type: TypeAlias + + def test_cannot_instantiate(self): + with self.assertRaises(TypeError): + TypeAlias() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(42, TypeAlias) + + def test_no_issubclass(self): + with self.assertRaises(TypeError): + issubclass(Employee, TypeAlias) + + with self.assertRaises(TypeError): + issubclass(TypeAlias, Employee) + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(TypeAlias): + pass + + with self.assertRaises(TypeError): + class C(type(TypeAlias)): + pass + + def test_repr(self): + self.assertEqual(repr(TypeAlias), 'typing.TypeAlias') + + def test_cannot_subscript(self): + with self.assertRaises(TypeError): + TypeAlias[int] + + class AllTests(BaseTestCase): """Tests for __all__.""" diff --git a/Lib/typing.py b/Lib/typing.py index 4cf33c1ae92..0f457ab1f56 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -113,6 +113,7 @@ __all__ = [ 'runtime_checkable', 'Text', 'TYPE_CHECKING', + 'TypeAlias', ] # The pseudo-submodules 're' and 'io' are part of the public @@ -460,6 +461,21 @@ def Literal(self, parameters): return _GenericAlias(self, parameters) +@_SpecialForm +def TypeAlias(self, parameters): + """Special marker indicating that an assignment should + be recognized as a proper type alias definition by type + checkers. + + For example:: + + Predicate: TypeAlias = Callable[..., bool] + + It's invalid when used anywhere except as in the example above. + """ + raise TypeError(f"{self} is not subscriptable") + + class ForwardRef(_Final, _root=True): """Internal wrapper to hold a forward reference.""" diff --git a/Misc/ACKS b/Misc/ACKS index 08449fe0826..7d445c57214 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -611,6 +611,7 @@ Christoph Gohlke Tim Golden Yonatan Goldschmidt Mark Gollahon +Mikhail Golubev Guilherme Gonçalves Tiago Gonçalves Chris Gonnerman diff --git a/Misc/NEWS.d/next/Library/2020-10-03-23-14-50.bpo-41923.Buonw9.rst b/Misc/NEWS.d/next/Library/2020-10-03-23-14-50.bpo-41923.Buonw9.rst new file mode 100644 index 00000000000..dd9a1f709f3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-10-03-23-14-50.bpo-41923.Buonw9.rst @@ -0,0 +1 @@ +Implement :pep:`613`, introducing :data:`typing.TypeAlias` annotation.