Skip to content

Commit 81e91c9

Browse files
bpo-46642: Explicitly disallow subclassing of instaces of TypeVar, ParamSpec, etc (GH-31148)
The existing test covering this case passed only incidentally. We explicitly disallow doing this and add a proper error message. Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent 605e9c6 commit 81e91c9

File tree

3 files changed

+81
-39
lines changed

3 files changed

+81
-39
lines changed

Lib/test/test_typing.py

+68-39
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@
5151
c_typing = import_helper.import_fresh_module('typing', fresh=['_typing'])
5252

5353

54+
CANNOT_SUBCLASS_TYPE = 'Cannot subclass special typing classes'
55+
CANNOT_SUBCLASS_INSTANCE = 'Cannot subclass an instance of %s'
56+
57+
5458
class BaseTestCase(TestCase):
5559

5660
def assertIsSubclass(self, cls, class_or_tuple, msg=None):
@@ -170,10 +174,11 @@ def test_not_generic(self):
170174
self.bottom_type[int]
171175

172176
def test_cannot_subclass(self):
173-
with self.assertRaises(TypeError):
177+
with self.assertRaisesRegex(TypeError,
178+
'Cannot subclass ' + re.escape(str(self.bottom_type))):
174179
class A(self.bottom_type):
175180
pass
176-
with self.assertRaises(TypeError):
181+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
177182
class A(type(self.bottom_type)):
178183
pass
179184

@@ -266,10 +271,11 @@ def test_cannot_subscript(self):
266271
Self[int]
267272

268273
def test_cannot_subclass(self):
269-
with self.assertRaises(TypeError):
274+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
270275
class C(type(Self)):
271276
pass
272-
with self.assertRaises(TypeError):
277+
with self.assertRaisesRegex(TypeError,
278+
r'Cannot subclass typing\.Self'):
273279
class C(Self):
274280
pass
275281

@@ -322,10 +328,11 @@ def test_cannot_subscript(self):
322328
LiteralString[int]
323329

324330
def test_cannot_subclass(self):
325-
with self.assertRaises(TypeError):
331+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
326332
class C(type(LiteralString)):
327333
pass
328-
with self.assertRaises(TypeError):
334+
with self.assertRaisesRegex(TypeError,
335+
r'Cannot subclass typing\.LiteralString'):
329336
class C(LiteralString):
330337
pass
331338

@@ -415,15 +422,13 @@ def test_no_redefinition(self):
415422
self.assertNotEqual(TypeVar('T'), TypeVar('T'))
416423
self.assertNotEqual(TypeVar('T', int, str), TypeVar('T', int, str))
417424

418-
def test_cannot_subclass_vars(self):
419-
with self.assertRaises(TypeError):
420-
class V(TypeVar('T')):
421-
pass
422-
423-
def test_cannot_subclass_var_itself(self):
424-
with self.assertRaises(TypeError):
425-
class V(TypeVar):
426-
pass
425+
def test_cannot_subclass(self):
426+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
427+
class V(TypeVar): pass
428+
T = TypeVar("T")
429+
with self.assertRaisesRegex(TypeError,
430+
CANNOT_SUBCLASS_INSTANCE % 'TypeVar'):
431+
class V(T): pass
427432

428433
def test_cannot_instantiate_vars(self):
429434
with self.assertRaises(TypeError):
@@ -1016,15 +1021,14 @@ class A(Generic[Unpack[Ts]]): pass
10161021
self.assertEndsWith(repr(F[float]), 'A[float, *tuple[str, ...]]')
10171022
self.assertEndsWith(repr(F[float, str]), 'A[float, str, *tuple[str, ...]]')
10181023

1019-
def test_cannot_subclass_class(self):
1020-
with self.assertRaises(TypeError):
1024+
def test_cannot_subclass(self):
1025+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
10211026
class C(TypeVarTuple): pass
1022-
1023-
def test_cannot_subclass_instance(self):
10241027
Ts = TypeVarTuple('Ts')
1025-
with self.assertRaises(TypeError):
1028+
with self.assertRaisesRegex(TypeError,
1029+
CANNOT_SUBCLASS_INSTANCE % 'TypeVarTuple'):
10261030
class C(Ts): pass
1027-
with self.assertRaises(TypeError):
1031+
with self.assertRaisesRegex(TypeError, r'Cannot subclass \*Ts'):
10281032
class C(Unpack[Ts]): pass
10291033

10301034
def test_variadic_class_args_are_correct(self):
@@ -1411,13 +1415,15 @@ def test_repr(self):
14111415
self.assertEqual(repr(u), 'typing.Optional[str]')
14121416

14131417
def test_cannot_subclass(self):
1414-
with self.assertRaises(TypeError):
1418+
with self.assertRaisesRegex(TypeError,
1419+
r'Cannot subclass typing\.Union'):
14151420
class C(Union):
14161421
pass
1417-
with self.assertRaises(TypeError):
1422+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
14181423
class C(type(Union)):
14191424
pass
1420-
with self.assertRaises(TypeError):
1425+
with self.assertRaisesRegex(TypeError,
1426+
r'Cannot subclass typing\.Union\[int, str\]'):
14211427
class C(Union[int, str]):
14221428
pass
14231429

@@ -3658,10 +3664,10 @@ def test_repr(self):
36583664
self.assertEqual(repr(cv), 'typing.ClassVar[%s.Employee]' % __name__)
36593665

36603666
def test_cannot_subclass(self):
3661-
with self.assertRaises(TypeError):
3667+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
36623668
class C(type(ClassVar)):
36633669
pass
3664-
with self.assertRaises(TypeError):
3670+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
36653671
class C(type(ClassVar[int])):
36663672
pass
36673673

@@ -3700,10 +3706,10 @@ def test_repr(self):
37003706
self.assertEqual(repr(cv), 'typing.Final[tuple[int]]')
37013707

37023708
def test_cannot_subclass(self):
3703-
with self.assertRaises(TypeError):
3709+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
37043710
class C(type(Final)):
37053711
pass
3706-
with self.assertRaises(TypeError):
3712+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
37073713
class C(type(Final[int])):
37083714
pass
37093715

@@ -6206,16 +6212,18 @@ def test_repr(self):
62066212
self.assertEqual(repr(cv), f'typing.Required[{__name__}.Employee]')
62076213

62086214
def test_cannot_subclass(self):
6209-
with self.assertRaises(TypeError):
6215+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
62106216
class C(type(Required)):
62116217
pass
6212-
with self.assertRaises(TypeError):
6218+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
62136219
class C(type(Required[int])):
62146220
pass
6215-
with self.assertRaises(TypeError):
6221+
with self.assertRaisesRegex(TypeError,
6222+
r'Cannot subclass typing\.Required'):
62166223
class C(Required):
62176224
pass
6218-
with self.assertRaises(TypeError):
6225+
with self.assertRaisesRegex(TypeError,
6226+
r'Cannot subclass typing\.Required\[int\]'):
62196227
class C(Required[int]):
62206228
pass
62216229

@@ -6252,16 +6260,18 @@ def test_repr(self):
62526260
self.assertEqual(repr(cv), f'typing.NotRequired[{__name__}.Employee]')
62536261

62546262
def test_cannot_subclass(self):
6255-
with self.assertRaises(TypeError):
6263+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
62566264
class C(type(NotRequired)):
62576265
pass
6258-
with self.assertRaises(TypeError):
6266+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
62596267
class C(type(NotRequired[int])):
62606268
pass
6261-
with self.assertRaises(TypeError):
6269+
with self.assertRaisesRegex(TypeError,
6270+
r'Cannot subclass typing\.NotRequired'):
62626271
class C(NotRequired):
62636272
pass
6264-
with self.assertRaises(TypeError):
6273+
with self.assertRaisesRegex(TypeError,
6274+
r'Cannot subclass typing\.NotRequired\[int\]'):
62656275
class C(NotRequired[int]):
62666276
pass
62676277

@@ -6677,7 +6687,8 @@ def test_no_issubclass(self):
66776687
issubclass(TypeAlias, Employee)
66786688

66796689
def test_cannot_subclass(self):
6680-
with self.assertRaises(TypeError):
6690+
with self.assertRaisesRegex(TypeError,
6691+
r'Cannot subclass typing\.TypeAlias'):
66816692
class C(TypeAlias):
66826693
pass
66836694

@@ -6879,6 +6890,24 @@ def test_paramspec_gets_copied(self):
68796890
self.assertEqual(C2[Concatenate[str, P2]].__parameters__, (P2,))
68806891
self.assertEqual(C2[Concatenate[T, P2]].__parameters__, (T, P2))
68816892

6893+
def test_cannot_subclass(self):
6894+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
6895+
class C(ParamSpec): pass
6896+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
6897+
class C(ParamSpecArgs): pass
6898+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
6899+
class C(ParamSpecKwargs): pass
6900+
P = ParamSpec('P')
6901+
with self.assertRaisesRegex(TypeError,
6902+
CANNOT_SUBCLASS_INSTANCE % 'ParamSpec'):
6903+
class C(P): pass
6904+
with self.assertRaisesRegex(TypeError,
6905+
CANNOT_SUBCLASS_INSTANCE % 'ParamSpecArgs'):
6906+
class C(P.args): pass
6907+
with self.assertRaisesRegex(TypeError,
6908+
CANNOT_SUBCLASS_INSTANCE % 'ParamSpecKwargs'):
6909+
class C(P.kwargs): pass
6910+
68826911

68836912
class ConcatenateTests(BaseTestCase):
68846913
def test_basics(self):
@@ -6945,10 +6974,10 @@ def test_repr(self):
69456974
self.assertEqual(repr(cv), 'typing.TypeGuard[tuple[int]]')
69466975

69476976
def test_cannot_subclass(self):
6948-
with self.assertRaises(TypeError):
6977+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
69496978
class C(type(TypeGuard)):
69506979
pass
6951-
with self.assertRaises(TypeError):
6980+
with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE):
69526981
class C(type(TypeGuard[int])):
69536982
pass
69546983

Lib/typing.py

+12
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,9 @@ def __repr__(self):
954954
prefix = '~'
955955
return prefix + self.__name__
956956

957+
def __mro_entries__(self, bases):
958+
raise TypeError(f"Cannot subclass an instance of {type(self).__name__}")
959+
957960

958961
class TypeVar(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin,
959962
_root=True):
@@ -1101,6 +1104,9 @@ def __typing_prepare_subst__(self, alias, args):
11011104
*args[alen - right:],
11021105
)
11031106

1107+
def __mro_entries__(self, bases):
1108+
raise TypeError(f"Cannot subclass an instance of {type(self).__name__}")
1109+
11041110

11051111
class ParamSpecArgs(_Final, _Immutable, _root=True):
11061112
"""The args for a ParamSpec object.
@@ -1125,6 +1131,9 @@ def __eq__(self, other):
11251131
return NotImplemented
11261132
return self.__origin__ == other.__origin__
11271133

1134+
def __mro_entries__(self, bases):
1135+
raise TypeError(f"Cannot subclass an instance of {type(self).__name__}")
1136+
11281137

11291138
class ParamSpecKwargs(_Final, _Immutable, _root=True):
11301139
"""The kwargs for a ParamSpec object.
@@ -1149,6 +1158,9 @@ def __eq__(self, other):
11491158
return NotImplemented
11501159
return self.__origin__ == other.__origin__
11511160

1161+
def __mro_entries__(self, bases):
1162+
raise TypeError(f"Cannot subclass an instance of {type(self).__name__}")
1163+
11521164

11531165
class ParamSpec(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin,
11541166
_root=True):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve error message when trying to subclass an instance of :data:`typing.TypeVar`, :data:`typing.ParamSpec`, :data:`typing.TypeVarTuple`, etc. Based on patch by Gregory Beauregard.

0 commit comments

Comments
 (0)