diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index dfbe2d9d34c8ff..3894ab43b5d657 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -51,6 +51,10 @@ c_typing = import_helper.import_fresh_module('typing', fresh=['_typing']) +CANNOT_SUBCLASS_TYPE = 'Cannot subclass special typing classes' +CANNOT_SUBCLASS_INSTANCE = 'Cannot subclass an instance of %s' + + class BaseTestCase(TestCase): def assertIsSubclass(self, cls, class_or_tuple, msg=None): @@ -170,10 +174,11 @@ def test_not_generic(self): self.bottom_type[int] def test_cannot_subclass(self): - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, + 'Cannot subclass ' + re.escape(str(self.bottom_type))): class A(self.bottom_type): pass - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): class A(type(self.bottom_type)): pass @@ -266,10 +271,11 @@ def test_cannot_subscript(self): Self[int] def test_cannot_subclass(self): - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): class C(type(Self)): pass - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, + r'Cannot subclass typing\.Self'): class C(Self): pass @@ -322,10 +328,11 @@ def test_cannot_subscript(self): LiteralString[int] def test_cannot_subclass(self): - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): class C(type(LiteralString)): pass - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, + r'Cannot subclass typing\.LiteralString'): class C(LiteralString): pass @@ -415,15 +422,13 @@ def test_no_redefinition(self): self.assertNotEqual(TypeVar('T'), TypeVar('T')) self.assertNotEqual(TypeVar('T', int, str), TypeVar('T', int, str)) - def test_cannot_subclass_vars(self): - with self.assertRaises(TypeError): - class V(TypeVar('T')): - pass - - def test_cannot_subclass_var_itself(self): - with self.assertRaises(TypeError): - class V(TypeVar): - pass + def test_cannot_subclass(self): + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): + class V(TypeVar): pass + T = TypeVar("T") + with self.assertRaisesRegex(TypeError, + CANNOT_SUBCLASS_INSTANCE % 'TypeVar'): + class V(T): pass def test_cannot_instantiate_vars(self): with self.assertRaises(TypeError): @@ -1016,15 +1021,14 @@ class A(Generic[Unpack[Ts]]): pass self.assertEndsWith(repr(F[float]), 'A[float, *tuple[str, ...]]') self.assertEndsWith(repr(F[float, str]), 'A[float, str, *tuple[str, ...]]') - def test_cannot_subclass_class(self): - with self.assertRaises(TypeError): + def test_cannot_subclass(self): + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): class C(TypeVarTuple): pass - - def test_cannot_subclass_instance(self): Ts = TypeVarTuple('Ts') - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, + CANNOT_SUBCLASS_INSTANCE % 'TypeVarTuple'): class C(Ts): pass - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, r'Cannot subclass \*Ts'): class C(Unpack[Ts]): pass def test_variadic_class_args_are_correct(self): @@ -1411,13 +1415,15 @@ def test_repr(self): self.assertEqual(repr(u), 'typing.Optional[str]') def test_cannot_subclass(self): - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, + r'Cannot subclass typing\.Union'): class C(Union): pass - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): class C(type(Union)): pass - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, + r'Cannot subclass typing\.Union\[int, str\]'): class C(Union[int, str]): pass @@ -3658,10 +3664,10 @@ def test_repr(self): self.assertEqual(repr(cv), 'typing.ClassVar[%s.Employee]' % __name__) def test_cannot_subclass(self): - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): class C(type(ClassVar)): pass - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): class C(type(ClassVar[int])): pass @@ -3700,10 +3706,10 @@ def test_repr(self): self.assertEqual(repr(cv), 'typing.Final[tuple[int]]') def test_cannot_subclass(self): - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): class C(type(Final)): pass - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): class C(type(Final[int])): pass @@ -6206,16 +6212,18 @@ def test_repr(self): self.assertEqual(repr(cv), f'typing.Required[{__name__}.Employee]') def test_cannot_subclass(self): - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): class C(type(Required)): pass - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): class C(type(Required[int])): pass - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, + r'Cannot subclass typing\.Required'): class C(Required): pass - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, + r'Cannot subclass typing\.Required\[int\]'): class C(Required[int]): pass @@ -6252,16 +6260,18 @@ def test_repr(self): self.assertEqual(repr(cv), f'typing.NotRequired[{__name__}.Employee]') def test_cannot_subclass(self): - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): class C(type(NotRequired)): pass - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): class C(type(NotRequired[int])): pass - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, + r'Cannot subclass typing\.NotRequired'): class C(NotRequired): pass - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, + r'Cannot subclass typing\.NotRequired\[int\]'): class C(NotRequired[int]): pass @@ -6677,7 +6687,8 @@ def test_no_issubclass(self): issubclass(TypeAlias, Employee) def test_cannot_subclass(self): - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, + r'Cannot subclass typing\.TypeAlias'): class C(TypeAlias): pass @@ -6879,6 +6890,24 @@ def test_paramspec_gets_copied(self): self.assertEqual(C2[Concatenate[str, P2]].__parameters__, (P2,)) self.assertEqual(C2[Concatenate[T, P2]].__parameters__, (T, P2)) + def test_cannot_subclass(self): + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): + class C(ParamSpec): pass + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): + class C(ParamSpecArgs): pass + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): + class C(ParamSpecKwargs): pass + P = ParamSpec('P') + with self.assertRaisesRegex(TypeError, + CANNOT_SUBCLASS_INSTANCE % 'ParamSpec'): + class C(P): pass + with self.assertRaisesRegex(TypeError, + CANNOT_SUBCLASS_INSTANCE % 'ParamSpecArgs'): + class C(P.args): pass + with self.assertRaisesRegex(TypeError, + CANNOT_SUBCLASS_INSTANCE % 'ParamSpecKwargs'): + class C(P.kwargs): pass + class ConcatenateTests(BaseTestCase): def test_basics(self): @@ -6945,10 +6974,10 @@ def test_repr(self): self.assertEqual(repr(cv), 'typing.TypeGuard[tuple[int]]') def test_cannot_subclass(self): - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): class C(type(TypeGuard)): pass - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, CANNOT_SUBCLASS_TYPE): class C(type(TypeGuard[int])): pass diff --git a/Lib/typing.py b/Lib/typing.py index 1ebc3ce1f64bd6..25ae19f71fe94a 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -954,6 +954,9 @@ def __repr__(self): prefix = '~' return prefix + self.__name__ + def __mro_entries__(self, bases): + raise TypeError(f"Cannot subclass an instance of {type(self).__name__}") + class TypeVar(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin, _root=True): @@ -1101,6 +1104,9 @@ def __typing_prepare_subst__(self, alias, args): *args[alen - right:], ) + def __mro_entries__(self, bases): + raise TypeError(f"Cannot subclass an instance of {type(self).__name__}") + class ParamSpecArgs(_Final, _Immutable, _root=True): """The args for a ParamSpec object. @@ -1125,6 +1131,9 @@ def __eq__(self, other): return NotImplemented return self.__origin__ == other.__origin__ + def __mro_entries__(self, bases): + raise TypeError(f"Cannot subclass an instance of {type(self).__name__}") + class ParamSpecKwargs(_Final, _Immutable, _root=True): """The kwargs for a ParamSpec object. @@ -1149,6 +1158,9 @@ def __eq__(self, other): return NotImplemented return self.__origin__ == other.__origin__ + def __mro_entries__(self, bases): + raise TypeError(f"Cannot subclass an instance of {type(self).__name__}") + class ParamSpec(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin, _root=True): diff --git a/Misc/NEWS.d/next/Library/2022-02-05-18-46-54.bpo-46642.YI6nHQ.rst b/Misc/NEWS.d/next/Library/2022-02-05-18-46-54.bpo-46642.YI6nHQ.rst new file mode 100644 index 00000000000000..2d2815c1e4b00d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-02-05-18-46-54.bpo-46642.YI6nHQ.rst @@ -0,0 +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.