https://github.com/python/cpython/commit/3484ef60039be38096ef617ba096970809917d03
commit: 3484ef60039be38096ef617ba096970809917d03
branch: main
author: Jelle Zijlstra <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-02-28T19:52:04-08:00
summary:
gh-145033: Implement PEP 747 (#145034)
files:
A Misc/NEWS.d/next/Library/2026-02-19-20-54-25.gh-issue-145033.X9EBPQ.rst
M Doc/library/typing.rst
M Doc/whatsnew/3.15.rst
M Lib/test/test_typing.py
M Lib/typing.py
diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index ef44701bb251dd..09e9103e1b80d0 100644
--- a/Doc/library/typing.rst
+++ b/Doc/library/typing.rst
@@ -1524,6 +1524,35 @@ These can be used as types in annotations. They all
support subscription using
.. versionadded:: 3.9
+.. data:: TypeForm
+
+ A special form representing the value that results from evaluating a
+ type expression.
+
+ This value encodes the information supplied in the type expression, and
+ it represents the type described by that type expression.
+
+ When used in a type expression, ``TypeForm`` describes a set of type form
+ objects. It accepts a single type argument, which must be a valid type
+ expression. ``TypeForm[T]`` describes the set of all type form objects that
+ represent the type ``T`` or types assignable to ``T``.
+
+ ``TypeForm(obj)`` simply returns ``obj`` unchanged. This is useful for
+ explicitly marking a value as a type form for static type checkers.
+
+ Example::
+
+ from typing import Any, TypeForm
+
+ def cast[T](typ: TypeForm[T], value: Any) -> T: ...
+
+ reveal_type(cast(int, "x")) # Revealed type is "int"
+
+ See :pep:`747` for details.
+
+ .. versionadded:: 3.15
+
+
.. data:: TypeIs
Special typing construct for marking user-defined type predicate functions.
diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst
index 163d50d7e20e20..63ef5f84301794 100644
--- a/Doc/whatsnew/3.15.rst
+++ b/Doc/whatsnew/3.15.rst
@@ -1431,6 +1431,25 @@ threading
typing
------
+* :pep:`747`: Add :data:`~typing.TypeForm`, a new special form for annotating
+ values that are themselves type expressions.
+ ``TypeForm[T]`` means "a type form object describing ``T`` (or a type
+ assignable to ``T``)". At runtime, ``TypeForm(x)`` simply returns ``x``,
+ which allows explicit annotation of type-form values without changing
+ behavior.
+
+ This helps libraries that accept user-provided type expressions
+ (for example ``int``, ``str | None``, :class:`~typing.TypedDict`
+ classes, or ``list[int]``) expose precise signatures:
+
+ .. code-block:: python
+
+ from typing import Any, TypeForm
+
+ def cast[T](typ: TypeForm[T], value: Any) -> T: ...
+
+ (Contributed by Jelle Zijlstra in :gh:`145033`.)
+
* The undocumented keyword argument syntax for creating
:class:`~typing.NamedTuple` classes (for example,
``Point = NamedTuple("Point", x=int, y=int)``) is no longer supported.
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 50938eadc8f9f3..c6f08ff8a052ab 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -42,7 +42,7 @@
from typing import Self, LiteralString
from typing import TypeAlias
from typing import ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs
-from typing import TypeGuard, TypeIs, NoDefault
+from typing import TypeForm, TypeGuard, TypeIs, NoDefault
import abc
import textwrap
import typing
@@ -5890,6 +5890,7 @@ def test_subclass_special_form(self):
Final[int],
Literal[1, 2],
Concatenate[int, ParamSpec("P")],
+ TypeForm[int],
TypeGuard[int],
TypeIs[range],
):
@@ -7358,6 +7359,7 @@ class C(Generic[T]): pass
self.assertEqual(get_args(Required[int]), (int,))
self.assertEqual(get_args(NotRequired[int]), (int,))
self.assertEqual(get_args(TypeAlias), ())
+ self.assertEqual(get_args(TypeForm[int]), (int,))
self.assertEqual(get_args(TypeGuard[int]), (int,))
self.assertEqual(get_args(TypeIs[range]), (range,))
Ts = TypeVarTuple('Ts')
@@ -10646,6 +10648,72 @@ def test_no_isinstance(self):
issubclass(int, TypeIs)
+class TypeFormTests(BaseTestCase):
+ def test_basics(self):
+ TypeForm[int] # OK
+ self.assertEqual(TypeForm[int], TypeForm[int])
+
+ def foo(arg) -> TypeForm[int]: ...
+ self.assertEqual(gth(foo), {'return': TypeForm[int]})
+
+ with self.assertRaises(TypeError):
+ TypeForm[int, str]
+
+ def test_repr(self):
+ self.assertEqual(repr(TypeForm), 'typing.TypeForm')
+ cv = TypeForm[int]
+ self.assertEqual(repr(cv), 'typing.TypeForm[int]')
+ cv = TypeForm[Employee]
+ self.assertEqual(repr(cv), 'typing.TypeForm[%s.Employee]' % __name__)
+ cv = TypeForm[tuple[int]]
+ self.assertEqual(repr(cv), 'typing.TypeForm[tuple[int]]')
+
+ def test_cannot_subclass(self):
+ with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
+ class C(type(TypeForm)):
+ pass
+ with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
+ class D(type(TypeForm[int])):
+ pass
+ with self.assertRaisesRegex(TypeError,
+ r'Cannot subclass typing\.TypeForm'):
+ class E(TypeForm):
+ pass
+ with self.assertRaisesRegex(TypeError,
+ r'Cannot subclass
typing\.TypeForm\[int\]'):
+ class F(TypeForm[int]):
+ pass
+
+ def test_call(self):
+ objs = [
+ 1,
+ "int",
+ int,
+ tuple[int, str],
+ Tuple[int, str],
+ ]
+ for obj in objs:
+ with self.subTest(obj=obj):
+ self.assertIs(TypeForm(obj), obj)
+
+ with self.assertRaises(TypeError):
+ TypeForm()
+ with self.assertRaises(TypeError):
+ TypeForm("too", "many")
+
+ def test_cannot_init_type(self):
+ with self.assertRaises(TypeError):
+ type(TypeForm)()
+ with self.assertRaises(TypeError):
+ type(TypeForm[Optional[int]])()
+
+ def test_no_isinstance(self):
+ with self.assertRaises(TypeError):
+ isinstance(1, TypeForm[int])
+ with self.assertRaises(TypeError):
+ issubclass(int, TypeForm)
+
+
SpecialAttrsP = typing.ParamSpec('SpecialAttrsP')
SpecialAttrsT = typing.TypeVar('SpecialAttrsT', int, float, complex)
@@ -10747,6 +10815,7 @@ def test_special_attrs(self):
typing.Never: 'Never',
typing.Optional: 'Optional',
typing.TypeAlias: 'TypeAlias',
+ typing.TypeForm: 'TypeForm',
typing.TypeGuard: 'TypeGuard',
typing.TypeIs: 'TypeIs',
typing.TypeVar: 'TypeVar',
@@ -10761,6 +10830,7 @@ def test_special_attrs(self):
typing.Literal[1, 2]: 'Literal',
typing.Literal[True, 2]: 'Literal',
typing.Optional[Any]: 'Union',
+ typing.TypeForm[Any]: 'TypeForm',
typing.TypeGuard[Any]: 'TypeGuard',
typing.TypeIs[Any]: 'TypeIs',
typing.Union[Any]: 'Any',
diff --git a/Lib/typing.py b/Lib/typing.py
index 2dfa6d3b1499ca..e78fb8b71a996c 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -155,6 +155,7 @@
'Text',
'TYPE_CHECKING',
'TypeAlias',
+ 'TypeForm',
'TypeGuard',
'TypeIs',
'TypeAliasType',
@@ -588,6 +589,13 @@ def __getitem__(self, parameters):
return self._getitem(self, *parameters)
+class _TypeFormForm(_SpecialForm, _root=True):
+ # TypeForm(X) is equivalent to X but indicates to the type checker
+ # that the object is a TypeForm.
+ def __call__(self, obj, /):
+ return obj
+
+
class _AnyMeta(type):
def __instancecheck__(self, obj):
if self is Any:
@@ -895,6 +903,31 @@ def func1(val: list[object]):
return _GenericAlias(self, (item,))
+@_TypeFormForm
+def TypeForm(self, parameters):
+ """A special form representing the value that results from the evaluation
+ of a type expression.
+
+ This value encodes the information supplied in the type expression, and it
+ represents the type described by that type expression.
+
+ When used in a type expression, TypeForm describes a set of type form
+ objects. It accepts a single type argument, which must be a valid type
+ expression. ``TypeForm[T]`` describes the set of all type form objects that
+ represent the type T or types that are assignable to T.
+
+ Usage::
+
+ def cast[T](typ: TypeForm[T], value: Any) -> T: ...
+
+ reveal_type(cast(int, "x")) # int
+
+ See PEP 747 for more information.
+ """
+ item = _type_check(parameters, f'{self} accepts only single type.')
+ return _GenericAlias(self, (item,))
+
+
@_SpecialForm
def TypeIs(self, parameters):
"""Special typing construct for marking user-defined type predicate
functions.
@@ -1348,10 +1381,11 @@ class _GenericAlias(_BaseGenericAlias, _root=True):
# A = Callable[[], None] # _CallableGenericAlias
# B = Callable[[T], None] # _CallableGenericAlias
# C = B[int] # _CallableGenericAlias
- # * Parameterized `Final`, `ClassVar`, `TypeGuard`, and `TypeIs`:
+ # * Parameterized `Final`, `ClassVar`, `TypeForm`, `TypeGuard`, and
`TypeIs`:
# # All _GenericAlias
# Final[int]
# ClassVar[float]
+ # TypeForm[bytes]
# TypeGuard[bool]
# TypeIs[range]
diff --git
a/Misc/NEWS.d/next/Library/2026-02-19-20-54-25.gh-issue-145033.X9EBPQ.rst
b/Misc/NEWS.d/next/Library/2026-02-19-20-54-25.gh-issue-145033.X9EBPQ.rst
new file mode 100644
index 00000000000000..6f496bb30e1686
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-02-19-20-54-25.gh-issue-145033.X9EBPQ.rst
@@ -0,0 +1,2 @@
+Add :data:`typing.TypeForm`, implementing :pep:`747`. Patch by Jelle
+Zijlstra.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]