From 0774aecd397de24346911e40838ab7f08d154821 Mon Sep 17 00:00:00 2001 From: Jan-Eric Nitschke <47750513+JanEricNitschke@users.noreply.github.com> Date: Sat, 13 Sep 2025 11:01:23 +0200 Subject: [PATCH 1/8] Fix error message when subclassing TypeVarTuple --- CHANGELOG.md | 1 + src/test_typing_extensions.py | 25 +++++++++++++++++++++++++ src/typing_extensions.py | 4 +++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 733505a5..aef8b964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- Fix error message when trying to subclass `typing_extensions.TypeVarTuple`. - Fix incorrect behaviour on Python 3.9 and Python 3.10 that meant that calling `isinstance` with `typing_extensions.Concatenate[...]` or `typing_extensions.Unpack[...]` as the first argument could have a different diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 88fa699e..82d90e58 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -110,6 +110,10 @@ KT = TypeVar("KT") VT = TypeVar("VT") +CANNOT_SUBCLASS_TYPE = 'Cannot subclass special typing classes' +NOT_A_BASE_TYPE = r"type '(?:typing|typing_extensions).%s' is not an acceptable base type" +CANNOT_SUBCLASS_INSTANCE = 'Cannot subclass an instance of %s' + # Flags used to mark tests that only apply after a specific # version of the typing module. TYPING_3_10_0 = sys.version_info[:3] >= (3, 10, 0) @@ -6792,6 +6796,27 @@ def test_pickle(self): self.assertEqual(z.__name__, typevartuple.__name__) self.assertEqual(z.__default__, typevartuple.__default__) + def test_cannot_subclass(self): + with self.assertRaisesRegex(TypeError, NOT_A_BASE_TYPE % 'TypeVarTuple'): + class C(TypeVarTuple): pass + Ts = TypeVarTuple('Ts') + with self.assertRaisesRegex(TypeError, + CANNOT_SUBCLASS_INSTANCE % 'TypeVarTuple'): + class D(Ts): pass + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): + class E(type(Unpack)): pass + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): + class F(type(*Ts)): pass + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): + class G(type(Unpack[Ts])): pass + with self.assertRaisesRegex(TypeError, + r'Cannot subclass typing\.Unpack'): + class H(Unpack): pass + with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[Ts\]'): + class I(*Ts): pass + with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[Ts\]'): + class J(Unpack[Ts]): pass + class FinalDecoratorTests(BaseTestCase): def test_final_unmodified(self): diff --git a/src/typing_extensions.py b/src/typing_extensions.py index bd67a80a..100d6398 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2637,7 +2637,9 @@ def _typevartuple_prepare_subst(alias, args): return tvt def __init_subclass__(self, *args, **kwds): - raise TypeError("Cannot subclass special typing classes") + raise TypeError( + f"type '{__name__}.TypeVarTuple' is not an acceptable base type" + ) else: # <=3.10 class TypeVarTuple(_DefaultMixin): From d9c83c064a27b73c7039b2a4bc2bcdfc5b75f63d Mon Sep 17 00:00:00 2001 From: Jan-Eric Nitschke <47750513+JanEricNitschke@users.noreply.github.com> Date: Sat, 13 Sep 2025 11:10:05 +0200 Subject: [PATCH 2/8] Silence ruff warning in test --- src/test_typing_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 82d90e58..08e69e58 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -6813,7 +6813,7 @@ class G(type(Unpack[Ts])): pass r'Cannot subclass typing\.Unpack'): class H(Unpack): pass with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[Ts\]'): - class I(*Ts): pass + class I(*Ts): pass # noqa: E742 with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[Ts\]'): class J(Unpack[Ts]): pass From 244ddf7f6705a3179aafafccdeaab6353201e519 Mon Sep 17 00:00:00 2001 From: Jan-Eric Nitschke <47750513+JanEricNitschke@users.noreply.github.com> Date: Sat, 13 Sep 2025 14:51:12 +0200 Subject: [PATCH 3/8] Fix subclassing message for 3.10- and instance subclassing --- src/typing_extensions.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 100d6398..3530c464 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2634,6 +2634,11 @@ def _typevartuple_prepare_subst(alias, args): ) tvt.__typing_prepare_subst__ = _typevartuple_prepare_subst + + def __mro_entries__(self, bases): + raise TypeError("Cannot subclass an instance of TypeVarTuple.") + tvt.__mro_entries__ = __mro_entries__ + return tvt def __init_subclass__(self, *args, **kwds): @@ -2641,6 +2646,7 @@ def __init_subclass__(self, *args, **kwds): f"type '{__name__}.TypeVarTuple' is not an acceptable base type" ) + else: # <=3.10 class TypeVarTuple(_DefaultMixin): """Type variable tuple. @@ -2717,7 +2723,12 @@ def __reduce__(self): def __init_subclass__(self, *args, **kwds): if '_root' not in kwds: - raise TypeError("Cannot subclass special typing classes") + raise TypeError( + f"type '{__name__}.TypeVarTuple' is not an acceptable base type" + ) + + def __mro_entries__(self, bases): + raise TypeError("Cannot subclass an instance of TypeVarTuple.") if hasattr(typing, "reveal_type"): # 3.11+ From 29af0f035514c580571b615a0ee830213ac84736 Mon Sep 17 00:00:00 2001 From: Jan-Eric Nitschke <47750513+JanEricNitschke@users.noreply.github.com> Date: Sat, 13 Sep 2025 16:54:02 +0200 Subject: [PATCH 4/8] Fix __more_entries__ --- src/typing_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 3530c464..4b58a116 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2635,7 +2635,7 @@ def _typevartuple_prepare_subst(alias, args): tvt.__typing_prepare_subst__ = _typevartuple_prepare_subst - def __mro_entries__(self, bases): + def __mro_entries__(bases): raise TypeError("Cannot subclass an instance of TypeVarTuple.") tvt.__mro_entries__ = __mro_entries__ From b15af16765ccb00563a68d0aea19721b32c4baa0 Mon Sep 17 00:00:00 2001 From: Jan-Eric Nitschke <47750513+JanEricNitschke@users.noreply.github.com> Date: Sun, 14 Sep 2025 06:53:22 +0200 Subject: [PATCH 5/8] Fix expected regex for Unpack subclassing --- src/test_typing_extensions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 08e69e58..1c4ba275 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -6810,11 +6810,11 @@ class F(type(*Ts)): pass with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): class G(type(Unpack[Ts])): pass with self.assertRaisesRegex(TypeError, - r'Cannot subclass typing\.Unpack'): + r'Cannot subclass (typing|typing_extensions)\.Unpack'): class H(Unpack): pass - with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[Ts\]'): + with self.assertRaisesRegex(TypeError, r'Cannot subclass (typing|typing_extensions)\.Unpack\[Ts\]'): class I(*Ts): pass # noqa: E742 - with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[Ts\]'): + with self.assertRaisesRegex(TypeError, r'Cannot subclass (typing|typing_extensions)\.Unpack\[Ts\]'): class J(Unpack[Ts]): pass From cd5cd0089742e1997de986d1afdb09517e5e048e Mon Sep 17 00:00:00 2001 From: Jan-Eric Nitschke <47750513+JanEricNitschke@users.noreply.github.com> Date: Sun, 14 Sep 2025 11:52:05 +0200 Subject: [PATCH 6/8] Take Unpack error messages out of scope --- src/test_typing_extensions.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 1c4ba275..8c2fcc2d 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -6809,12 +6809,11 @@ class E(type(Unpack)): pass class F(type(*Ts)): pass with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): class G(type(Unpack[Ts])): pass - with self.assertRaisesRegex(TypeError, - r'Cannot subclass (typing|typing_extensions)\.Unpack'): + with self.assertRaises(TypeError): class H(Unpack): pass - with self.assertRaisesRegex(TypeError, r'Cannot subclass (typing|typing_extensions)\.Unpack\[Ts\]'): + with self.assertRaises(TypeError): class I(*Ts): pass # noqa: E742 - with self.assertRaisesRegex(TypeError, r'Cannot subclass (typing|typing_extensions)\.Unpack\[Ts\]'): + with self.assertRaises(TypeError): class J(Unpack[Ts]): pass From a49d36d9daa948cf08fb9e0c8ef760f4e31826c3 Mon Sep 17 00:00:00 2001 From: Jan-Eric Nitschke <47750513+JanEricNitschke@users.noreply.github.com> Date: Mon, 15 Sep 2025 05:42:04 +0200 Subject: [PATCH 7/8] Fix whitespace Co-authored-by: Jelle Zijlstra --- src/typing_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 4b58a116..c26f96fc 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2637,7 +2637,7 @@ def _typevartuple_prepare_subst(alias, args): def __mro_entries__(bases): raise TypeError("Cannot subclass an instance of TypeVarTuple.") - tvt.__mro_entries__ = __mro_entries__ + tvt.__mro_entries__ = __mro_entries__ return tvt From 91563887d9aa2e2f82f6fb071910d851669d6cc8 Mon Sep 17 00:00:00 2001 From: Jan-Eric Nitschke <47750513+JanEricNitschke@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:26:03 +0200 Subject: [PATCH 8/8] Update changelog entry --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aef8b964..4f40c3d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Unreleased -- Fix error message when trying to subclass `typing_extensions.TypeVarTuple`. +- Update error message when attempting to subclass `typing_extensions.TypeVarTuple` + to align with the message produced by `typing` in Python 3.12+. + Patch by Jan-Eric Nitschke. - Fix incorrect behaviour on Python 3.9 and Python 3.10 that meant that calling `isinstance` with `typing_extensions.Concatenate[...]` or `typing_extensions.Unpack[...]` as the first argument could have a different