From 26171c5f72c413b3f7989b7b21eba57e34a54506 Mon Sep 17 00:00:00 2001 From: GruffElixir <136691411+GruffElixir@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:40:40 +0530 Subject: [PATCH 1/3] Support default type parameters in older Python --- src/test_typing_extensions.py | 8 ++++++++ src/typing_extensions.py | 38 +++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 48107c04..3c1278f9 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7815,6 +7815,14 @@ def test_typevar(self): class A(Generic[T]): ... self.assertEqual(Optional[T].__args__, (T, type(None))) + def test_generic_typevar_default(self): + R = TypeVar("R") + T = TypeVar("T", default=int) + + class A(Generic[R, T]): ... + + self.assertEqual(get_args(A[str]), (str, int)) + def test_typevar_none(self): U = typing_extensions.TypeVar('U') U_None = typing_extensions.TypeVar('U_None', default=None) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index ced78373..28a6d717 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -4418,5 +4418,43 @@ def type_repr(value): # These are defined unconditionally because they are used in # typing-extensions itself. Generic = typing.Generic + +# Python 3.9-3.12 do not ask type parameters to fill in their defaults when +# specializing a generic alias. TypeVarLike implementations above expose the +# same substitution hook used by newer versions of typing, so apply it before +# delegating to the standard library alias implementation. +if not _PEP_696_IMPLEMENTED: + _generic_alias_getitem = typing._GenericAlias.__getitem__ + + @typing._tp_cache + def _generic_alias_getitem_with_defaults(self, params): + if not isinstance(params, tuple): + params = (params,) + + for param in self.__parameters__: + prepare = getattr(param, "__typing_prepare_subst__", None) + if prepare is not None: + params = prepare(self, params) + + return _generic_alias_getitem(self, params) + + typing._GenericAlias.__getitem__ = _generic_alias_getitem_with_defaults + + _generic_class_getitem = typing.Generic.__dict__["__class_getitem__"].__func__ + + @typing._tp_cache + def _generic_class_getitem_with_defaults(cls, params): + if not isinstance(params, tuple): + params = (params,) + + for param in getattr(cls, "__parameters__", ()): + prepare = getattr(param, "__typing_prepare_subst__", None) + if prepare is not None: + params = prepare(cls, params) + + return _generic_class_getitem(cls, params) + + typing.Generic.__class_getitem__ = classmethod(_generic_class_getitem_with_defaults) + ForwardRef = typing.ForwardRef Annotated = typing.Annotated From 909d85b46f46a43bacc2a5c57d6a1a4d21c4402f Mon Sep 17 00:00:00 2001 From: GruffElixir <136691411+GruffElixir@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:00:05 +0530 Subject: [PATCH 2/3] Preserve typing conversions during default substitution --- src/typing_extensions.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 28a6d717..2f879de3 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -4430,11 +4430,21 @@ def type_repr(value): def _generic_alias_getitem_with_defaults(self, params): if not isinstance(params, tuple): params = (params,) + params = tuple(typing._type_convert(param) for param in params) for param in self.__parameters__: prepare = getattr(param, "__typing_prepare_subst__", None) - if prepare is not None: - params = prepare(self, params) + if prepare is None: + continue + if type(param).__name__ == "TypeVar": + if not getattr(param, "has_default", lambda: False)(): + continue + elif not ( + sys.version_info < (3, 11) + and type(param).__name__ == "TypeVarTuple" + ): + continue + params = prepare(self, params) return _generic_alias_getitem(self, params) @@ -4446,11 +4456,21 @@ def _generic_alias_getitem_with_defaults(self, params): def _generic_class_getitem_with_defaults(cls, params): if not isinstance(params, tuple): params = (params,) + params = tuple(typing._type_convert(param) for param in params) for param in getattr(cls, "__parameters__", ()): prepare = getattr(param, "__typing_prepare_subst__", None) - if prepare is not None: - params = prepare(cls, params) + if prepare is None: + continue + if type(param).__name__ == "TypeVar": + if not getattr(param, "has_default", lambda: False)(): + continue + elif not ( + sys.version_info < (3, 11) + and type(param).__name__ == "TypeVarTuple" + ): + continue + params = prepare(cls, params) return _generic_class_getitem(cls, params) From 7c49d9b6a436a056156184ce46a9c7c98789b149 Mon Sep 17 00:00:00 2001 From: GruffElixir <136691411+GruffElixir@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:24:10 +0530 Subject: [PATCH 3/3] Support classmethod descriptors on Python 3.12 --- src/typing_extensions.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 2f879de3..5865e0b6 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -4450,7 +4450,15 @@ def _generic_alias_getitem_with_defaults(self, params): typing._GenericAlias.__getitem__ = _generic_alias_getitem_with_defaults - _generic_class_getitem = typing.Generic.__dict__["__class_getitem__"].__func__ + _generic_class_getitem_descriptor = typing.Generic.__dict__["__class_getitem__"] + if hasattr(_generic_class_getitem_descriptor, "__func__"): + _generic_class_getitem = _generic_class_getitem_descriptor.__func__ + else: + # Python 3.12 stores this as a classmethod descriptor rather than a + # classmethod object. Bind it through the subclass so the original + # implementation receives the class being specialized. + def _generic_class_getitem(cls, params): + return _generic_class_getitem_descriptor.__get__(None, cls)(params) @typing._tp_cache def _generic_class_getitem_with_defaults(cls, params):