8000 Backport banning type parameters with defaults after TypeVarTuples by AlexWaygood · Pull Request #389 · python/typing_extensions · GitHub
[go: up one dir, main page]

Skip to content
8000

Backport banning type parameters with defaults after TypeVarTuples #389

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 15, 2024
Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
at runtime rather than `types.NoneType`.
- Fix most tests for `TypeVar`, `ParamSpec` and `TypeVarTuple` on Python
3.13.0b1 and newer.
- It is now disallowed to use a `TypeVar` with a default value after a
`TypeVarTuple` in a type parameter list. This matches the CPython
implementation of PEP 696 on Python 3.13+.
- Fix `Protocol` tests on Python 3.13.0a6 and newer. 3.13.0a6 adds a new
`__static_attributes__` attribute to all classes in Python,
which broke some assumptions made by the implementation of
Expand Down
6 changes: 6 additions & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,12 @@ Special typing primitives
TypeVarTuples now have a ``has_default()`` method, for compatibility
with :py:class:`typing.TypeVarTuple` on Python 3.13+.

.. versionchanged:: 4.12.0

It is now disallowed to use a `TypeVar` with a default value after a
`TypeVarTuple` in a type parameter list. This matches the CPython
implementation of PEP 696 on Python 3.13+.

.. data:: Unpack

See :py:data:`typing.Unpack` and :pep:`646`. In ``typing`` since 3.11.
Expand Down
29 changes: 28 additions & 1 deletion src/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6380,6 +6380,14 @@ class A(Generic[P]): ...
self.assertIs(P_default.__default__, ...)
self.assertTrue(P_default.has_default())

def test_paramspec_none(self):
U = ParamSpec('U')
U_None = ParamSpec('U_None', default=None)
self.assertIs(U.__default__, NoDefault)
self.assertFalse(U.has_default())
self.assertIs(U_None.__default__, None)
self.assertTrue(U_None.has_default())

def test_typevartuple(self):
Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]])
self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]])
Expand All @@ -6394,7 +6402,26 @@ def test_typevartuple(self):
class A(Generic[Unpack[Ts]]): ...
Alias = Optional[Unpack[Ts]]

def test_erroneous_generic(self):
def test_no_default_after_typevar_tuple(self):
T = TypeVar("T", default=int)
Ts = TypeVarTuple("Ts")
Ts_default = TypeVarTuple("Ts_default", default=Unpack[Tuple[str, int]])

with self.assertRaises(TypeError):
class X(Generic[Unpack[Ts], T]): ...

with self.assertRaises(TypeError):
class Y(Generic[Unpack[Ts_default], T]): ...

def test_typevartuple_none(self):
U = TypeVarTuple('U')
U_None = TypeVarTuple('U_None', default=None)
self.assertIs(U.__default__, NoDefault)
self.assertFalse(U.has_default())
self.assertIs(U_None.__default__, None)
self.assertTrue(U_None.has_default())

def test_no_default_after_non_default(self):
DefaultStrT = typing_extensions.TypeVar('DefaultStrT', default=str)
T = TypeVar('T')

Expand Down
43 changes: 36 additions & 7 deletions src/typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2847,6 +2847,21 @@ def _check_generic(cls, parameters, elen):
if not _PEP_696_IMPLEMENTED:
typing._check_generic = _check_generic


_TYPEVARTUPLE_TYPES = {TypeVarTuple, getattr(typing, "TypeVarTuple", None)}


def _is_unpacked_typevartuple(x) -> bool:
if get_origin(x) is not Unpack:
return False
args = get_args(x)
return (
bool(args)
and len(args) == 1
and type(args[0]) in _TYPEVARTUPLE_TYPES
)


# Python 3.11+ _collect_type_vars was renamed to _collect_parameters
if hasattr(typing, '_collect_type_vars'):
def _collect_type_vars(types, typevar_types=None):
Expand All @@ -2860,13 +2875,17 @@ def _collect_type_vars(types, typevar_types=None):
tvars = []
# required TypeVarLike cannot appear after TypeVarLike with default
default_encountered = False
# or after TypeVarTuple
type_var_tuple_encountered = False
for t in types:
if (
isinstance(t, typevar_types) and
t not in tvars and
not _is_unpack(t)
):
if getattr(t, '__default__', NoDefault) is not NoDefault:
if _is_unpacked_typevartuple(t):
type_var_tuple_encountered = True
elif isinstance(t, typevar_types) and t not in tvars:
has_default = getattr(t, '__default__', NoDefault) is not NoDefault
if has_default:
if type_var_tuple_encountered:
raise TypeError('Type parameter with a default'
' follows TypeVarTuple')
default_encountered = True
elif default_encountered:
raise TypeError(f'Type parameter {t!r} without a default'
Expand All @@ -2890,6 +2909,8 @@ def _collect_parameters(args):
parameters = []
# required TypeVarLike cannot appear after TypeVarLike with default
default_encountered = False
# or after TypeVarTuple
type_var_tuple_encountered = False
for t in args:
if isinstance(t, type):
# We don't want __parameters__ descriptor of a bare Python class.
Expand All @@ -2903,14 +2924,22 @@ def _collect_parameters(args):
parameters.append(collected)
elif hasattr(t, '__typing_subst__'):
if t not in parameters:
if getattr(t, '__default__', NoDefault) is not NoDefault:
has_default = getattr(t, '__default__', NoDefault) is not NoDefault

if type_var_tuple_encountered and has_default:
raise TypeError('Type parameter with a default'
' follows TypeVarTuple')

if has_default:
default_encountered = True
elif default_encountered:
raise TypeError(f'Type parameter {t!r} without a default'
' follows type pa 5F5B rameter with a default')

parameters.append(t)
else:
if _is_unpacked_typevartuple(t):
type_var_tuple_encountered = True
for x in getattr(t, '__parameters__', ()):
if x not in parameters:
parameters.append(x)
Expand Down
Loading
0