From 3bc72c66b8089a0e38a89e38e7c9afd6d6a558f0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 6 Feb 2022 13:58:33 +0200 Subject: [PATCH] bpo-46644: No longer accept arbitrary callables as type arguments in generics --- Lib/test/test_typing.py | 19 ++-- Lib/typing.py | 95 +++++++++---------- .../2022-02-06-13-56-58.bpo-46644.rWSd0I.rst | 2 + 3 files changed, 59 insertions(+), 57 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-02-06-13-56-58.bpo-46644.rWSd0I.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 85f74064458f2d..597a746718be62 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -261,6 +261,12 @@ class UnionTests(BaseTestCase): def test_basics(self): u = Union[int, float] self.assertNotEqual(u, Union) + with self.assertRaises(TypeError): + Union[int, 42] + with self.assertRaises(TypeError): + Union[int, chr] + with self.assertRaises(TypeError): + Union[int, ...] def test_subclass_error(self): with self.assertRaises(TypeError): @@ -386,10 +392,6 @@ def test_no_eval_union(self): def f(x: u): ... self.assertIs(get_type_hints(f)['x'], u) - def test_function_repr_union(self): - def fun() -> int: ... - self.assertEqual(repr(Union[fun, int]), 'typing.Union[fun, int]') - def test_union_str_pattern(self): # Shouldn't crash; see http://bugs.python.org/issue25390 A = Union[str, Pattern] @@ -402,11 +404,6 @@ def test_etree(self): Union[Element, str] # Shouldn't crash - def Elem(*args): - return Element(*args) - - Union[Elem, str] # Nor should this - class TupleTests(BaseTestCase): @@ -2499,6 +2496,8 @@ class ClassVarTests(BaseTestCase): def test_basics(self): with self.assertRaises(TypeError): ClassVar[1] + with self.assertRaises(TypeError): + ClassVar[chr] with self.assertRaises(TypeError): ClassVar[int, str] with self.assertRaises(TypeError): @@ -2539,6 +2538,8 @@ def test_basics(self): Final[int] # OK with self.assertRaises(TypeError): Final[1] + with self.assertRaises(TypeError): + Final[chr] with self.assertRaises(TypeError): Final[int, str] with self.assertRaises(TypeError): diff --git a/Lib/typing.py b/Lib/typing.py index 0cf9755022e9b8..ee75954eae01c9 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -178,11 +178,10 @@ def _type_check(arg, msg, is_argument=True, module=None, *, allow_special_forms= return arg if isinstance(arg, _SpecialForm) or arg in (Generic, Protocol): raise TypeError(f"Plain {arg} is not valid as type argument") - if isinstance(arg, (type, TypeVar, ForwardRef, types.UnionType, ParamSpec)): + if isinstance(arg, (type, TypeVar, ForwardRef, types.UnionType, ParamSpec, + _GenericAlias, _SpecialGenericAlias, NewType)): return arg - if not callable(arg): - raise TypeError(f"{msg} Got {arg!r:.100}.") - return arg + raise TypeError(f"{msg} Got {arg!r:.100}.") def _is_param_expr(arg): @@ -2065,6 +2064,50 @@ class Other(Leaf): # Error reported by type checker return f +class NewType: + """NewType creates simple unique types with almost zero + runtime overhead. NewType(name, tp) is considered a subtype of tp + by static type checkers. At runtime, NewType(name, tp) returns + a dummy callable that simply returns its argument. Usage:: + + UserId = NewType('UserId', int) + + def name_by_id(user_id: UserId) -> str: + ... + + UserId('user') # Fails type check + + name_by_id(42) # Fails type check + name_by_id(UserId(42)) # OK + + num = UserId(5) + 1 # type: int + """ + + __call__ = _idfunc + + def __init__(self, name, tp): + self.__qualname__ = name + if '.' in name: + name = name.rpartition('.')[-1] + self.__name__ = name + self.__supertype__ = tp + def_mod = _caller() + if def_mod != 'typing': + self.__module__ = def_mod + + def __repr__(self): + return f'{self.__module__}.{self.__qualname__}' + + def __reduce__(self): + return self.__qualname__ + + def __or__(self, other): + return Union[self, other] + + def __ror__(self, other): + return Union[other, self] + + # Some unconstrained type variables. These are used by the container types. # (These are not for export.) T = TypeVar('T') # Any type. @@ -2438,50 +2481,6 @@ class body be required. TypedDict.__mro_entries__ = lambda bases: (_TypedDict,) -class NewType: - """NewType creates simple unique types with almost zero - runtime overhead. NewType(name, tp) is considered a subtype of tp - by static type checkers. At runtime, NewType(name, tp) returns - a dummy callable that simply returns its argument. Usage:: - - UserId = NewType('UserId', int) - - def name_by_id(user_id: UserId) -> str: - ... - - UserId('user') # Fails type check - - name_by_id(42) # Fails type check - name_by_id(UserId(42)) # OK - - num = UserId(5) + 1 # type: int - """ - - __call__ = _idfunc - - def __init__(self, name, tp): - self.__qualname__ = name - if '.' in name: - name = name.rpartition('.')[-1] - self.__name__ = name - self.__supertype__ = tp - def_mod = _caller() - if def_mod != 'typing': - self.__module__ = def_mod - - def __repr__(self): - return f'{self.__module__}.{self.__qualname__}' - - def __reduce__(self): - return self.__qualname__ - - def __or__(self, other): - return Union[self, other] - - def __ror__(self, other): - return Union[other, self] - - # Python-version-specific alias (Python 2: unicode; Python 3: str) Text = str diff --git a/Misc/NEWS.d/next/Library/2022-02-06-13-56-58.bpo-46644.rWSd0I.rst b/Misc/NEWS.d/next/Library/2022-02-06-13-56-58.bpo-46644.rWSd0I.rst new file mode 100644 index 00000000000000..4943e0c94a0a6b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-02-06-13-56-58.bpo-46644.rWSd0I.rst @@ -0,0 +1,2 @@ +No longer accept arbitrary callables as type arguments in generics. E.g. +``List[chr]`` is now an error. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy