mirror of https://github.com/python/cpython
gh-92261: Disallow iteration of Union (and other special forms) (GH-92262)
This commit is contained in:
parent
788ef54bc9
commit
4739997e14
|
@ -487,5 +487,25 @@ class BaseTest(unittest.TestCase):
|
|||
del iter_x
|
||||
|
||||
|
||||
class TypeIterationTests(unittest.TestCase):
|
||||
_UNITERABLE_TYPES = (list, tuple)
|
||||
|
||||
def test_cannot_iterate(self):
|
||||
for test_type in self._UNITERABLE_TYPES:
|
||||
with self.subTest(type=test_type):
|
||||
expected_error_regex = "object is not iterable"
|
||||
with self.assertRaisesRegex(TypeError, expected_error_regex):
|
||||
iter(test_type)
|
||||
with self.assertRaisesRegex(TypeError, expected_error_regex):
|
||||
list(test_type)
|
||||
with self.assertRaisesRegex(TypeError, expected_error_regex):
|
||||
for _ in test_type:
|
||||
pass
|
||||
|
||||
def test_is_not_instance_of_iterable(self):
|
||||
for type_to_test in self._UNITERABLE_TYPES:
|
||||
self.assertNotIsInstance(type_to_test, Iterable)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
@ -7348,6 +7348,37 @@ class AllTests(BaseTestCase):
|
|||
self.assertSetEqual(computed_all, actual_all)
|
||||
|
||||
|
||||
class TypeIterationTests(BaseTestCase):
|
||||
_UNITERABLE_TYPES = (
|
||||
Any,
|
||||
Union,
|
||||
Union[str, int],
|
||||
Union[str, T],
|
||||
List,
|
||||
Tuple,
|
||||
Callable,
|
||||
Callable[..., T],
|
||||
Callable[[T], str],
|
||||
Annotated,
|
||||
Annotated[T, ''],
|
||||
)
|
||||
|
||||
def test_cannot_iterate(self):
|
||||
expected_error_regex = "object is not iterable"
|
||||
for test_type in self._UNITERABLE_TYPES:
|
||||
with self.subTest(type=test_type):
|
||||
with self.assertRaisesRegex(TypeError, expected_error_regex):
|
||||
iter(test_type)
|
||||
with self.assertRaisesRegex(TypeError, expected_error_regex):
|
||||
list(test_type)
|
||||
with self.assertRaisesRegex(TypeError, expected_error_regex):
|
||||
for _ in test_type:
|
||||
pass
|
||||
|
||||
def test_is_not_instance_of_iterable(self):
|
||||
for type_to_test in self._UNITERABLE_TYPES:
|
||||
self.assertNotIsInstance(type_to_test, collections.abc.Iterable)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -405,9 +405,24 @@ class _Immutable:
|
|||
return self
|
||||
|
||||
|
||||
class _NotIterable:
|
||||
"""Mixin to prevent iteration, without being compatible with Iterable.
|
||||
|
||||
That is, we could do:
|
||||
def __iter__(self): raise TypeError()
|
||||
But this would make users of this mixin duck type-compatible with
|
||||
collections.abc.Iterable - isinstance(foo, Iterable) would be True.
|
||||
|
||||
Luckily, we can instead prevent iteration by setting __iter__ to None, which
|
||||
is treated specially.
|
||||
"""
|
||||
|
||||
__iter__ = None
|
||||
|
||||
|
||||
# Internal indicator of special typing constructs.
|
||||
# See __doc__ instance attribute for specific docs.
|
||||
class _SpecialForm(_Final, _root=True):
|
||||
class _SpecialForm(_Final, _NotIterable, _root=True):
|
||||
__slots__ = ('_name', '__doc__', '_getitem')
|
||||
|
||||
def __init__(self, getitem):
|
||||
|
@ -1498,7 +1513,7 @@ class _GenericAlias(_BaseGenericAlias, _root=True):
|
|||
# 1 for List and 2 for Dict. It may be -1 if variable number of
|
||||
# parameters are accepted (needs custom __getitem__).
|
||||
|
||||
class _SpecialGenericAlias(_BaseGenericAlias, _root=True):
|
||||
class _SpecialGenericAlias(_NotIterable, _BaseGenericAlias, _root=True):
|
||||
def __init__(self, origin, nparams, *, inst=True, name=None):
|
||||
if name is None:
|
||||
name = origin.__name__
|
||||
|
@ -1541,7 +1556,7 @@ class _SpecialGenericAlias(_BaseGenericAlias, _root=True):
|
|||
def __ror__(self, left):
|
||||
return Union[left, self]
|
||||
|
||||
class _CallableGenericAlias(_GenericAlias, _root=True):
|
||||
class _CallableGenericAlias(_NotIterable, _GenericAlias, _root=True):
|
||||
def __repr__(self):
|
||||
assert self._name == 'Callable'
|
||||
args = self.__args__
|
||||
|
@ -1606,7 +1621,7 @@ class _TupleType(_SpecialGenericAlias, _root=True):
|
|||
return self.copy_with(params)
|
||||
|
||||
|
||||
class _UnionGenericAlias(_GenericAlias, _root=True):
|
||||
class _UnionGenericAlias(_NotIterable, _GenericAlias, _root=True):
|
||||
def copy_with(self, params):
|
||||
return Union[params]
|
||||
|
||||
|
@ -2046,7 +2061,7 @@ class Protocol(Generic, metaclass=_ProtocolMeta):
|
|||
cls.__init__ = _no_init_or_replace_init
|
||||
|
||||
|
||||
class _AnnotatedAlias(_GenericAlias, _root=True):
|
||||
class _AnnotatedAlias(_NotIterable, _GenericAlias, _root=True):
|
||||
"""Runtime representation of an annotated type.
|
||||
|
||||
At its core 'Annotated[t, dec1, dec2, ...]' is an alias for the type 't'
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Fix hang when trying to iterate over a ``typing.Union``.
|
Loading…
Reference in New Issue