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]

Reply via email to