From a39d11e7182b0a44fe938a62821e5263b82b8b6e Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 23 Jun 2026 12:58:30 +0100 Subject: [PATCH 1/9] `typing_extensions`: re-export `builtins.sentinel` on Python 3.15+ This would make ty's implementation of `builtins.sentinel` a little easier --- stdlib/typing_extensions.pyi | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/stdlib/typing_extensions.pyi b/stdlib/typing_extensions.pyi index fdbba495c579..dafe948d0f00 100644 --- a/stdlib/typing_extensions.pyi +++ b/stdlib/typing_extensions.pyi @@ -680,11 +680,16 @@ else: def type_repr(value: object) -> str: ... # PEP 661 -class Sentinel: - def __init__(self, name: str, repr: str | None = None) -> None: ... - if sys.version_info >= (3, 14): - def __or__(self, other: Any) -> UnionType: ... # other can be any type form legal for unions - def __ror__(self, other: Any) -> UnionType: ... # other can be any type form legal for unions - else: - def __or__(self, other: Any) -> _SpecialForm: ... # other can be any type form legal for unions - def __ror__(self, other: Any) -> _SpecialForm: ... # other can be any type form legal for unions +if sys.version_info >= (3, 15): + from builtins import sentinel as sentinel +else: + class sentinel: + def __init__(self, name: str, repr: str | None = None) -> None: ... + if sys.version_info >= (3, 14): + def __or__(self, other: Any) -> UnionType: ... # other can be any type form legal for unions + def __ror__(self, other: Any) -> UnionType: ... # other can be any type form legal for unions + else: + def __or__(self, other: Any) -> _SpecialForm: ... # other can be any type form legal for unions + def __ror__(self, other: Any) -> _SpecialForm: ... # other can be any type form legal for unions + +Sentinel = sentinel From 71d9e0445bd368ac014f111b11f38a4cf8743074 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 23 Jun 2026 13:02:40 +0100 Subject: [PATCH 2/9] Update typing_extensions.pyi --- stdlib/typing_extensions.pyi | 1 + 1 file changed, 1 insertion(+) diff --git a/stdlib/typing_extensions.pyi b/stdlib/typing_extensions.pyi index dafe948d0f00..b70394e5c483 100644 --- a/stdlib/typing_extensions.pyi +++ b/stdlib/typing_extensions.pyi @@ -143,6 +143,7 @@ __all__ = [ "override", "Protocol", "Sentinel", + "sentinel", "reveal_type", "runtime", "runtime_checkable", From 98734a05c136da2fd828b6796e19492449d2caa1 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 24 Jun 2026 20:50:28 +0100 Subject: [PATCH 3/9] Update typing_extensions version to 4.16.0rc1 --- requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-tests.txt b/requirements-tests.txt index eccf4793fd8b..a624c7f8113a 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -17,7 +17,7 @@ stubdefaulter==0.1.0; python_version < "3.15" termcolor>=2.3 tomli==2.4.1; python_version < "3.11" tomlkit==0.14.0 -typing_extensions>=4.15.0rc1 +typing_extensions>=4.16.0rc1 uv==0.11.15 # Utilities for typeshed infrastructure scripts. From 37430d79470e9a7a44fa3192164aa5a53c70b892 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 24 Jun 2026 20:55:02 +0100 Subject: [PATCH 4/9] Update typing_extensions.pyi --- stdlib/typing_extensions.pyi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stdlib/typing_extensions.pyi b/stdlib/typing_extensions.pyi index b70394e5c483..67212206449d 100644 --- a/stdlib/typing_extensions.pyi +++ b/stdlib/typing_extensions.pyi @@ -685,7 +685,8 @@ if sys.version_info >= (3, 15): from builtins import sentinel as sentinel else: class sentinel: - def __init__(self, name: str, repr: str | None = None) -> None: ... + def __init__(self, name: str, /, repr: str | None = None) -> None: ... + __name__: str if sys.version_info >= (3, 14): def __or__(self, other: Any) -> UnionType: ... # other can be any type form legal for unions def __ror__(self, other: Any) -> UnionType: ... # other can be any type form legal for unions From 2e78c7a5e284606540054d192f585db6ea134bec Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 24 Jun 2026 21:08:18 +0100 Subject: [PATCH 5/9] allow --- stdlib/@tests/stubtest_allowlists/py310.txt | 13 +++++++------ stdlib/@tests/stubtest_allowlists/py311.txt | 5 +++++ stdlib/@tests/stubtest_allowlists/py312.txt | 5 +++++ stdlib/@tests/stubtest_allowlists/py313.txt | 5 +++++ stdlib/@tests/stubtest_allowlists/py314.txt | 5 +++++ stdlib/@tests/stubtest_allowlists/py315.txt | 3 --- 6 files changed, 27 insertions(+), 9 deletions(-) diff --git a/stdlib/@tests/stubtest_allowlists/py310.txt b/stdlib/@tests/stubtest_allowlists/py310.txt index 58e826f7081b..4f1fe3bae1fd 100644 --- a/stdlib/@tests/stubtest_allowlists/py310.txt +++ b/stdlib/@tests/stubtest_allowlists/py310.txt @@ -29,11 +29,6 @@ typing_extensions.NewType.__mro_entries__ importlib.metadata._meta.SimplePath.__truediv__ # Runtime definition of protocol is incorrect -# =================================== -# Pre-existing errors from Python 3.9 -# =================================== - - # ======= # <= 3.10 # ======= @@ -56,9 +51,10 @@ tkinter.Tk.split # Exists at runtime, but missing from stubs typing._SpecialForm.__mro_entries__ # Exists at runtime, but missing from stubs typing_extensions.LiteralString + # Will always raise. Not included to avoid type checkers inferring that # Sentinel instances are callable. -typing_extensions.Sentinel.__call__ +typing_extensions.sentinel.__call__ # ======= @@ -141,6 +137,11 @@ tkinter.simpledialog.[A-Z_]+ tkinter.simpledialog.TclVersion tkinter.simpledialog.TkVersion +# The runtime does some hacks to deprecate passing various parameters +# positionally or by keyword. We just present a signature in the stub +# that describes the way users are supposed to call the constructor. +typing_extensions.sentinel.__init__ + # ============================================================= # Allowlist entries that cannot or should not be fixed; <3.15 diff --git a/stdlib/@tests/stubtest_allowlists/py311.txt b/stdlib/@tests/stubtest_allowlists/py311.txt index 11af276eb139..69f314e576ab 100644 --- a/stdlib/@tests/stubtest_allowlists/py311.txt +++ b/stdlib/@tests/stubtest_allowlists/py311.txt @@ -115,6 +115,11 @@ tkinter.simpledialog.[A-Z_]+ tkinter.simpledialog.TclVersion tkinter.simpledialog.TkVersion +# The runtime does some hacks to deprecate passing various parameters +# positionally or by keyword. We just present a signature in the stub +# that describes the way users are supposed to call the constructor. +typing_extensions.sentinel.__init__ + # ============================================================= # Allowlist entries that cannot or should not be fixed; <3.15 diff --git a/stdlib/@tests/stubtest_allowlists/py312.txt b/stdlib/@tests/stubtest_allowlists/py312.txt index 1e1fdc9cf15b..28f037fe9099 100644 --- a/stdlib/@tests/stubtest_allowlists/py312.txt +++ b/stdlib/@tests/stubtest_allowlists/py312.txt @@ -108,6 +108,11 @@ tkinter.simpledialog.[A-Z_]+ tkinter.simpledialog.TclVersion tkinter.simpledialog.TkVersion +# The runtime does some hacks to deprecate passing various parameters +# positionally or by keyword. We just present a signature in the stub +# that describes the way users are supposed to call the constructor. +typing_extensions.sentinel.__init__ + # ============================================================= # Allowlist entries that cannot or should not be fixed; <3.15 diff --git a/stdlib/@tests/stubtest_allowlists/py313.txt b/stdlib/@tests/stubtest_allowlists/py313.txt index 3929791d4ef4..da06450426d6 100644 --- a/stdlib/@tests/stubtest_allowlists/py313.txt +++ b/stdlib/@tests/stubtest_allowlists/py313.txt @@ -61,6 +61,11 @@ tkinter.simpledialog.[A-Z_]+ tkinter.simpledialog.TclVersion tkinter.simpledialog.TkVersion +# The runtime does some hacks to deprecate passing various parameters +# positionally or by keyword. We just present a signature in the stub +# that describes the way users are supposed to call the constructor. +typing_extensions.sentinel.__init__ + # ============================================================= # Allowlist entries that cannot or should not be fixed; <3.15 diff --git a/stdlib/@tests/stubtest_allowlists/py314.txt b/stdlib/@tests/stubtest_allowlists/py314.txt index 64a6fa7d2752..f9f93ac23bd1 100644 --- a/stdlib/@tests/stubtest_allowlists/py314.txt +++ b/stdlib/@tests/stubtest_allowlists/py314.txt @@ -61,6 +61,11 @@ tkinter.simpledialog.[A-Z_]+ tkinter.simpledialog.TclVersion tkinter.simpledialog.TkVersion +# The runtime does some hacks to deprecate passing various parameters +# positionally or by keyword. We just present a signature in the stub +# that describes the way users are supposed to call the constructor. +typing_extensions.sentinel.__init__ + # ============================================================= # Allowlist entries that cannot or should not be fixed; <3.15 diff --git a/stdlib/@tests/stubtest_allowlists/py315.txt b/stdlib/@tests/stubtest_allowlists/py315.txt index b225bfc4924a..b28bfe571e08 100644 --- a/stdlib/@tests/stubtest_allowlists/py315.txt +++ b/stdlib/@tests/stubtest_allowlists/py315.txt @@ -46,9 +46,6 @@ base64.b64decode urllib.parse.urlunparse urllib.parse.urlunsplit -# Runtime __all__ includes no_type_check_decorator, but the attribute does not exist on Python 3.15. -typing_extensions.__all__ - # Internal implementation details of the sampling profiler. profiling.sampling.binary_collector profiling.sampling.binary_reader From ccbe50d812111ebdd4e84f4c227e09e5d795aff0 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 24 Jun 2026 21:10:11 +0100 Subject: [PATCH 6/9] a surprise, to be sure, but a welcome one --- stdlib/@tests/stubtest_allowlists/py313.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/stdlib/@tests/stubtest_allowlists/py313.txt b/stdlib/@tests/stubtest_allowlists/py313.txt index da06450426d6..b61f60e33987 100644 --- a/stdlib/@tests/stubtest_allowlists/py313.txt +++ b/stdlib/@tests/stubtest_allowlists/py313.txt @@ -113,9 +113,6 @@ multiprocessing.managers._BaseDictProxy.values # To match `dict`, we lie about the runtime, but use overloads to match the correct behavior types.MappingProxyType.get -typing_extensions.Protocol # Super-special typing primitive - - # ============================================================= # Allowlist entries that cannot or should not be fixed; >= 3.12 # ============================================================= From 6b08478bd4dd10424b6546b729636f15b48ffcce Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 24 Jun 2026 21:16:11 +0100 Subject: [PATCH 7/9] Update stdlib/typing_extensions.pyi --- stdlib/typing_extensions.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/typing_extensions.pyi b/stdlib/typing_extensions.pyi index 67212206449d..17f2a4a6f409 100644 --- a/stdlib/typing_extensions.pyi +++ b/stdlib/typing_extensions.pyi @@ -685,7 +685,7 @@ if sys.version_info >= (3, 15): from builtins import sentinel as sentinel else: class sentinel: - def __init__(self, name: str, /, repr: str | None = None) -> None: ... + def __init__(self, name: str, /, *, repr: str | None = None) -> None: ... __name__: str if sys.version_info >= (3, 14): def __or__(self, other: Any) -> UnionType: ... # other can be any type form legal for unions From a7f7ee14de65c93532f80bb0eb2eca62ac6096b2 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 24 Jun 2026 22:42:33 +0100 Subject: [PATCH 8/9] harmonize builtins and typing_extensions, fix various `__(r)or__` methods --- stdlib/builtins.pyi | 14 ++++++++------ stdlib/types.pyi | 6 ++++-- stdlib/typing_extensions.pyi | 12 ++++++++---- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/stdlib/builtins.pyi b/stdlib/builtins.pyi index 774ef031c7ad..de34b0fe7ee8 100644 --- a/stdlib/builtins.pyi +++ b/stdlib/builtins.pyi @@ -33,7 +33,7 @@ from _typeshed import ( from collections.abc import Awaitable, Callable, Iterable, Iterator, MutableSet, Reversible, Set as AbstractSet, Sized from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper from os import PathLike -from types import CellType, CodeType, EllipsisType, GenericAlias, NotImplementedType, TracebackType +from types import CellType, CodeType, EllipsisType, GenericAlias, NotImplementedType, TracebackType, UnionType # mypy crashes if any of {ByteString, Sequence, MutableSequence, Mapping, MutableMapping} # are imported from collections.abc in builtins.pyi @@ -2185,11 +2185,13 @@ if sys.version_info >= (3, 15): class sentinel: __name__: str __module__: str - def __new__(cls, name: str, /, *, repr: str | None = None) -> Self: ... - def __copy__(self, /) -> Self: ... - def __deepcopy__(self, memo: Any, /) -> Self: ... - def __or__(self, other: Any, /) -> Any: ... - def __ror__(self, other: Any, /) -> Any: ... + def __new__(cls, name: str, /, *, repr: str | None = None) -> sentinel: ... + def __copy__(self, /) -> sentinel: ... + def __deepcopy__(self, memo: Any, /) -> sentinel: ... + # `other` can be any legal form for unions. + # `sentinel("X") | sentinel("X")` creates a `sentinel` instance, not a `UnionType` instance. + def __or__(self, other: Any, /) -> UnionType | sentinel: ... + def __ror__(self, other: Any, /) -> UnionType | sentinel: ... @overload def sorted( diff --git a/stdlib/types.pyi b/stdlib/types.pyi index b9771ffc72da..68b6b3fbe41d 100644 --- a/stdlib/types.pyi +++ b/stdlib/types.pyi @@ -703,8 +703,10 @@ class GenericAlias: @property def __typing_unpacked_tuple_args__(self) -> tuple[Any, ...] | None: ... - def __or__(self, value: Any, /) -> UnionType: ... - def __ror__(self, value: Any, /) -> UnionType: ... + # `other` can be any legal form for unions. + # `list[int] | list[int]` creates a `GenericAlias` instance, not a `UnionType` instance + def __or__(self, value: Any, /) -> UnionType | GenericAlias: ... + def __ror__(self, value: Any, /) -> UnionType | GenericAlias: ... # GenericAlias delegates attr access to `__origin__` def __getattr__(self, name: str) -> Any: ... diff --git a/stdlib/typing_extensions.pyi b/stdlib/typing_extensions.pyi index 17f2a4a6f409..49797cc33ee5 100644 --- a/stdlib/typing_extensions.pyi +++ b/stdlib/typing_extensions.pyi @@ -687,11 +687,15 @@ else: class sentinel: def __init__(self, name: str, /, *, repr: str | None = None) -> None: ... __name__: str + __module__: str if sys.version_info >= (3, 14): - def __or__(self, other: Any) -> UnionType: ... # other can be any type form legal for unions - def __ror__(self, other: Any) -> UnionType: ... # other can be any type form legal for unions + # `other`` can be any type form legal for unions. + # `sentinel("X") | sentinel("X")` creates a `sentinel` instance, not a `UnionType` instance + def __or__(self, other: Any) -> UnionType | sentinel: ... + def __ror__(self, other: Any) -> UnionType | sentinel: ... else: - def __or__(self, other: Any) -> _SpecialForm: ... # other can be any type form legal for unions - def __ror__(self, other: Any) -> _SpecialForm: ... # other can be any type form legal for unions + # other can be any type form legal for unions + def __or__(self, other: Any) -> _SpecialForm: ... + def __ror__(self, other: Any) -> _SpecialForm: ... Sentinel = sentinel From 992b1db52195f4d748c886b7d8fed35d41563711 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 24 Jun 2026 22:47:53 +0100 Subject: [PATCH 9/9] Apply suggestions from code review --- stdlib/builtins.pyi | 2 +- stdlib/typing_extensions.pyi | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/builtins.pyi b/stdlib/builtins.pyi index de34b0fe7ee8..fdd554c7fcd7 100644 --- a/stdlib/builtins.pyi +++ b/stdlib/builtins.pyi @@ -2189,7 +2189,7 @@ if sys.version_info >= (3, 15): def __copy__(self, /) -> sentinel: ... def __deepcopy__(self, memo: Any, /) -> sentinel: ... # `other` can be any legal form for unions. - # `sentinel("X") | sentinel("X")` creates a `sentinel` instance, not a `UnionType` instance. + # `x | x` creates a `sentinel` instance if `x` is a sentinel, not a `UnionType` instance. def __or__(self, other: Any, /) -> UnionType | sentinel: ... def __ror__(self, other: Any, /) -> UnionType | sentinel: ... diff --git a/stdlib/typing_extensions.pyi b/stdlib/typing_extensions.pyi index 49797cc33ee5..8f8ec0cfc8e0 100644 --- a/stdlib/typing_extensions.pyi +++ b/stdlib/typing_extensions.pyi @@ -690,7 +690,7 @@ else: __module__: str if sys.version_info >= (3, 14): # `other`` can be any type form legal for unions. - # `sentinel("X") | sentinel("X")` creates a `sentinel` instance, not a `UnionType` instance + # `x | x` creates a `sentinel` instance if `x` is a sentinel, not a `UnionType` instance def __or__(self, other: Any) -> UnionType | sentinel: ... def __ror__(self, other: Any) -> UnionType | sentinel: ... else: