Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
66 changes: 66 additions & 0 deletions src/typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading