Skip to content

Commit 88c0a42

Browse files
committed
Implement PEP 747
1 parent 852ec18 commit 88c0a42

File tree

5 files changed

+156
-2
lines changed

5 files changed

+156
-2
lines changed

Doc/library/typing.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,6 +1524,35 @@ These can be used as types in annotations. They all support subscription using
15241524
.. versionadded:: 3.9
15251525

15261526

1527+
.. data:: TypeForm
1528+
1529+
A special form representing the value that results from evaluating a
1530+
type expression.
1531+
1532+
This value encodes the information supplied in the type expression, and
1533+
it represents the type described by that type expression.
1534+
1535+
When used in a type expression, ``TypeForm`` describes a set of type form
1536+
objects. It accepts a single type argument, which must be a valid type
1537+
expression. ``TypeForm[T]`` describes the set of all type form objects that
1538+
represent the type ``T`` or types assignable to ``T``.
1539+
1540+
``TypeForm(obj)`` simply returns ``obj`` unchanged. This is useful for
1541+
explicitly marking a value as a type form for static type checkers.
1542+
1543+
Example::
1544+
1545+
from typing import Any, TypeForm
1546+
1547+
def cast[T](typ: TypeForm[T], value: Any) -> T: ...
1548+
1549+
reveal_type(cast(int, "x")) # Revealed type is "int"
1550+
1551+
See :pep:`747` for details.
1552+
1553+
.. versionadded:: 3.15
1554+
1555+
15271556
.. data:: TypeIs
15281557

15291558
Special typing construct for marking user-defined type predicate functions.

Doc/whatsnew/3.15.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1417,6 +1417,25 @@ threading
14171417
typing
14181418
------
14191419

1420+
* :pep:`747`: Add :data:`~typing.TypeForm`, a new special form for annotating
1421+
values that are themselves type expressions.
1422+
``TypeForm[T]`` means "a type form object describing ``T`` (or a type
1423+
assignable to ``T``)". At runtime, ``TypeForm(x)`` simply returns ``x``,
1424+
which allows explicit annotation of type-form values without changing
1425+
behavior.
1426+
1427+
This helps libraries that accept user-provided type expressions
1428+
(for example ``int``, ``str | None``, :class:`~typing.TypedDict`
1429+
classes, or ``list[int]``) expose precise signatures:
1430+
1431+
.. code-block:: python
1432+
1433+
from typing import Any, TypeForm
1434+
1435+
def cast[T](typ: TypeForm[T], value: Any) -> T: ...
1436+
1437+
(Contributed by Jelle Zijlstra in :gh:`145033`.)
1438+
14201439
* The undocumented keyword argument syntax for creating
14211440
:class:`~typing.NamedTuple` classes (for example,
14221441
``Point = NamedTuple("Point", x=int, y=int)``) is no longer supported.

Lib/test/test_typing.py

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
from typing import Self, LiteralString
4343
from typing import TypeAlias
4444
from typing import ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs
45-
from typing import TypeGuard, TypeIs, NoDefault
45+
from typing import TypeForm, TypeGuard, TypeIs, NoDefault
4646
import abc
4747
import textwrap
4848
import typing
@@ -5890,6 +5890,7 @@ def test_subclass_special_form(self):
58905890
Final[int],
58915891
Literal[1, 2],
58925892
Concatenate[int, ParamSpec("P")],
5893+
TypeForm[int],
58935894
TypeGuard[int],
58945895
TypeIs[range],
58955896
):
@@ -7358,6 +7359,7 @@ class C(Generic[T]): pass
73587359
self.assertEqual(get_args(Required[int]), (int,))
73597360
self.assertEqual(get_args(NotRequired[int]), (int,))
73607361
self.assertEqual(get_args(TypeAlias), ())
7362+
self.assertEqual(get_args(TypeForm[int]), (int,))
73617363
self.assertEqual(get_args(TypeGuard[int]), (int,))
73627364
self.assertEqual(get_args(TypeIs[range]), (range,))
73637365
Ts = TypeVarTuple('Ts')
@@ -10646,6 +10648,72 @@ def test_no_isinstance(self):
1064610648
issubclass(int, TypeIs)
1064710649

1064810650

10651+
class TypeFormTests(BaseTestCase):
10652+
def test_basics(self):
10653+
TypeForm[int] # OK
10654+
self.assertEqual(TypeForm[int], TypeForm[int])
10655+
10656+
def foo(arg) -> TypeForm[int]: ...
10657+
self.assertEqual(gth(foo), {'return': TypeForm[int]})
10658+
10659+
with self.assertRaises(TypeError):
10660+
TypeForm[int, str]
10661+
10662+
def test_repr(self):
10663+
self.assertEqual(repr(TypeForm), 'typing.TypeForm')
10664+
cv = TypeForm[int]
10665+
self.assertEqual(repr(cv), 'typing.TypeForm[int]')
10666+
cv = TypeForm[Employee]
10667+
self.assertEqual(repr(cv), 'typing.TypeForm[%s.Employee]' % __name__)
10668+
cv = TypeForm[tuple[int]]
10669+
self.assertEqual(repr(cv), 'typing.TypeForm[tuple[int]]')
10670+
10671+
def test_cannot_subclass(self):
10672+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
10673+
class C(type(TypeForm)):
10674+
pass
10675+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
10676+
class D(type(TypeForm[int])):
10677+
pass
10678+
with self.assertRaisesRegex(TypeError,
10679+
r'Cannot subclass typing\.TypeForm'):
10680+
class E(TypeForm):
10681+
pass
10682+
with self.assertRaisesRegex(TypeError,
10683+
r'Cannot subclass typing\.TypeForm\[int\]'):
10684+
class F(TypeForm[int]):
10685+
pass
10686+
10687+
def test_call(self):
10688+
objs = [
10689+
1,
10690+
"int",
10691+
int,
10692+
tuple[int, str],
10693+
Tuple[int, str],
10694+
]
10695+
for obj in objs:
10696+
with self.subTest(obj=obj):
10697+
self.assertIs(TypeForm(obj), obj)
10698+
10699+
with self.assertRaises(TypeError):
10700+
TypeForm()
10701+
with self.assertRaises(TypeError):
10702+
TypeForm("too", "many")
10703+
10704+
def test_cannot_init_type(self):
10705+
with self.assertRaises(TypeError):
10706+
type(TypeForm)()
10707+
with self.assertRaises(TypeError):
10708+
type(TypeForm[Optional[int]])()
10709+
10710+
def test_no_isinstance(self):
10711+
with self.assertRaises(TypeError):
10712+
isinstance(1, TypeForm[int])
10713+
with self.assertRaises(TypeError):
10714+
issubclass(int, TypeForm)
10715+
10716+
1064910717
SpecialAttrsP = typing.ParamSpec('SpecialAttrsP')
1065010718
SpecialAttrsT = typing.TypeVar('SpecialAttrsT', int, float, complex)
1065110719

@@ -10747,6 +10815,7 @@ def test_special_attrs(self):
1074710815
typing.Never: 'Never',
1074810816
typing.Optional: 'Optional',
1074910817
typing.TypeAlias: 'TypeAlias',
10818+
typing.TypeForm: 'TypeForm',
1075010819
typing.TypeGuard: 'TypeGuard',
1075110820
typing.TypeIs: 'TypeIs',
1075210821
typing.TypeVar: 'TypeVar',
@@ -10761,6 +10830,7 @@ def test_special_attrs(self):
1076110830
typing.Literal[1, 2]: 'Literal',
1076210831
typing.Literal[True, 2]: 'Literal',
1076310832
typing.Optional[Any]: 'Union',
10833+
typing.TypeForm[Any]: 'TypeForm',
1076410834
typing.TypeGuard[Any]: 'TypeGuard',
1076510835
typing.TypeIs[Any]: 'TypeIs',
1076610836
typing.Union[Any]: 'Any',

Lib/typing.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@
155155
'Text',
156156
'TYPE_CHECKING',
157157
'TypeAlias',
158+
'TypeForm',
158159
'TypeGuard',
159160
'TypeIs',
160161
'TypeAliasType',
@@ -588,6 +589,13 @@ def __getitem__(self, parameters):
588589
return self._getitem(self, *parameters)
589590

590591

592+
class _TypeFormForm(_SpecialForm, _root=True):
593+
# TypeForm(X) is equivalent to X but indicates to the type checker
594+
# that the object is a TypeForm.
595+
def __call__(self, obj, /):
596+
return obj
597+
598+
591599
class _AnyMeta(type):
592600
def __instancecheck__(self, obj):
593601
if self is Any:
@@ -895,6 +903,31 @@ def func1(val: list[object]):
895903
return _GenericAlias(self, (item,))
896904

897905

906+
@_TypeFormForm
907+
def TypeForm(self, parameters):
908+
"""A special form representing the value that results from the evaluation
909+
of a type expression.
910+
911+
This value encodes the information supplied in the type expression, and it
912+
represents the type described by that type expression.
913+
914+
When used in a type expression, TypeForm describes a set of type form
915+
objects. It accepts a single type argument, which must be a valid type
916+
expression. ``TypeForm[T]`` describes the set of all type form objects that
917+
represent the type T or types that are assignable to T.
918+
919+
Usage::
920+
921+
def cast[T](typ: TypeForm[T], value: Any) -> T: ...
922+
923+
reveal_type(cast(int, "x")) # int
924+
925+
See PEP 747 for more information.
926+
"""
927+
item = _type_check(parameters, f'{self} accepts only single type.')
928+
return _GenericAlias(self, (item,))
929+
930+
898931
@_SpecialForm
899932
def TypeIs(self, parameters):
900933
"""Special typing construct for marking user-defined type predicate functions.
@@ -1348,10 +1381,11 @@ class _GenericAlias(_BaseGenericAlias, _root=True):
13481381
# A = Callable[[], None] # _CallableGenericAlias
13491382
# B = Callable[[T], None] # _CallableGenericAlias
13501383
# C = B[int] # _CallableGenericAlias
1351-
# * Parameterized `Final`, `ClassVar`, `TypeGuard`, and `TypeIs`:
1384+
# * Parameterized `Final`, `ClassVar`, `TypeForm`, `TypeGuard`, and `TypeIs`:
13521385
# # All _GenericAlias
13531386
# Final[int]
13541387
# ClassVar[float]
1388+
# TypeForm[bytes]
13551389
# TypeGuard[bool]
13561390
# TypeIs[range]
13571391

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :data:`typing.TypeForm`, implementing :pep:`747`. Patch by Jelle
2+
Zijlstra.

0 commit comments

Comments
 (0)