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..5865e0b6 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -4418,5 +4418,71 @@ 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,) + 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 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) + + typing._GenericAlias.__getitem__ = _generic_alias_getitem_with_defaults + + _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): + 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 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) + + typing.Generic.__class_getitem__ = classmethod(_generic_class_getitem_with_defaults) + ForwardRef = typing.ForwardRef Annotated = typing.Annotated