From 7da94d878ce7cef3855cb775ab660cd32f1cd8a8 Mon Sep 17 00:00:00 2001 From: Moreal Date: Sun, 5 Dec 2021 17:06:56 +0900 Subject: [PATCH] Update `types` module to CPython 3.10.0 --- Lib/test/test_types.py | 386 ++++++++++++++++++++++++++++++++++++++++- Lib/types.py | 18 +- 2 files changed, 398 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 32dc2fb29..61d9505ac 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1,7 +1,10 @@ # Python test set -- part 6, built-in types -from test.support import run_with_locale +from test.support import run_with_locale, cpython_only import collections.abc +from collections import namedtuple +import copy +import gc import inspect import pickle import locale @@ -9,6 +12,20 @@ import sys import types import unittest.mock import weakref +import typing + + +T = typing.TypeVar("T") + +class Example: + pass + +class Forward: ... + +def clear_typing_caches(): + for f in typing._cleanups: + f() + class TypesTests(unittest.TestCase): @@ -608,6 +625,355 @@ class TypesTests(unittest.TestCase): self.assertIsInstance(int.from_bytes, types.BuiltinMethodType) self.assertIsInstance(int.__new__, types.BuiltinMethodType) + def test_ellipsis_type(self): + self.assertIsInstance(Ellipsis, types.EllipsisType) + + def test_notimplemented_type(self): + self.assertIsInstance(NotImplemented, types.NotImplementedType) + + def test_none_type(self): + self.assertIsInstance(None, types.NoneType) + + +class UnionTests(unittest.TestCase): + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_or_types_operator(self): + self.assertEqual(int | str, typing.Union[int, str]) + self.assertNotEqual(int | list, typing.Union[int, str]) + self.assertEqual(str | int, typing.Union[int, str]) + self.assertEqual(int | None, typing.Union[int, None]) + self.assertEqual(None | int, typing.Union[int, None]) + self.assertEqual(int | type(None), int | None) + self.assertEqual(type(None) | int, None | int) + self.assertEqual(int | str | list, typing.Union[int, str, list]) + self.assertEqual(int | (str | list), typing.Union[int, str, list]) + self.assertEqual(str | (int | list), typing.Union[int, str, list]) + self.assertEqual(typing.List | typing.Tuple, typing.Union[typing.List, typing.Tuple]) + self.assertEqual(typing.List[int] | typing.Tuple[int], typing.Union[typing.List[int], typing.Tuple[int]]) + self.assertEqual(typing.List[int] | None, typing.Union[typing.List[int], None]) + self.assertEqual(None | typing.List[int], typing.Union[None, typing.List[int]]) + self.assertEqual(str | float | int | complex | int, (int | str) | (float | complex)) + self.assertEqual(typing.Union[str, int, typing.List[int]], str | int | typing.List[int]) + self.assertIs(int | int, int) + self.assertEqual( + BaseException | + bool | + bytes | + complex | + float | + int | + list | + map | + set, + typing.Union[ + BaseException, + bool, + bytes, + complex, + float, + int, + list, + map, + set, + ]) + with self.assertRaises(TypeError): + int | 3 + with self.assertRaises(TypeError): + 3 | int + with self.assertRaises(TypeError): + Example() | int + x = int | str + self.assertEqual(x, int | str) + self.assertEqual(x, str | int) + self.assertNotEqual(x, {}) # should not raise exception + with self.assertRaises(TypeError): + x < x + with self.assertRaises(TypeError): + x <= x + y = typing.Union[str, int] + with self.assertRaises(TypeError): + x < y + y = int | bool + with self.assertRaises(TypeError): + x < y + # Check that we don't crash if typing.Union does not have a tuple in __args__ + y = typing.Union[str, int] + y.__args__ = [str, int] + self.assertEqual(x, y) + + def test_hash(self): + self.assertEqual(hash(int | str), hash(str | int)) + self.assertEqual(hash(int | str), hash(typing.Union[int, str])) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_instancecheck(self): + x = int | str + self.assertIsInstance(1, x) + self.assertIsInstance(True, x) + self.assertIsInstance('a', x) + self.assertNotIsInstance(None, x) + self.assertTrue(issubclass(int, x)) + self.assertTrue(issubclass(bool, x)) + self.assertTrue(issubclass(str, x)) + self.assertFalse(issubclass(type(None), x)) + x = int | None + self.assertIsInstance(None, x) + self.assertTrue(issubclass(type(None), x)) + x = int | collections.abc.Mapping + self.assertIsInstance({}, x) + self.assertTrue(issubclass(dict, x)) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_bad_instancecheck(self): + class BadMeta(type): + def __instancecheck__(cls, inst): + 1/0 + x = int | BadMeta('A', (), {}) + self.assertTrue(isinstance(1, x)) + self.assertRaises(ZeroDivisionError, isinstance, [], x) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_bad_subclasscheck(self): + class BadMeta(type): + def __subclasscheck__(cls, sub): + 1/0 + x = int | BadMeta('A', (), {}) + self.assertTrue(issubclass(int, x)) + self.assertRaises(ZeroDivisionError, issubclass, list, x) + + def test_or_type_operator_with_TypeVar(self): + TV = typing.TypeVar('T') + assert TV | str == typing.Union[TV, str] + assert str | TV == typing.Union[str, TV] + self.assertIs((int | TV)[int], int) + self.assertIs((TV | int)[int], int) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_union_args(self): + def check(arg, expected): + clear_typing_caches() + self.assertEqual(arg.__args__, expected) + + check(int | str, (int, str)) + check((int | str) | list, (int, str, list)) + check(int | (str | list), (int, str, list)) + check((int | str) | int, (int, str)) + check(int | (str | int), (int, str)) + check((int | str) | (str | int), (int, str)) + check(typing.Union[int, str] | list, (int, str, list)) + check(int | typing.Union[str, list], (int, str, list)) + check((int | str) | (list | int), (int, str, list)) + check((int | str) | typing.Union[list, int], (int, str, list)) + check(typing.Union[int, str] | (list | int), (int, str, list)) + check((str | int) | (int | list), (str, int, list)) + check((str | int) | typing.Union[int, list], (str, int, list)) + check(typing.Union[str, int] | (int | list), (str, int, list)) + check(int | type(None), (int, type(None))) + check(type(None) | int, (type(None), int)) + + args = (int, list[int], typing.List[int], + typing.Tuple[int, int], typing.Callable[[int], int], + typing.Hashable, typing.TypeVar('T')) + for x in args: + with self.subTest(x): + check(x | None, (x, type(None))) + check(None | x, (type(None), x)) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_union_parameter_chaining(self): + T = typing.TypeVar("T") + S = typing.TypeVar("S") + + self.assertEqual((float | list[T])[int], float | list[int]) + self.assertEqual(list[int | list[T]].__parameters__, (T,)) + self.assertEqual(list[int | list[T]][str], list[int | list[str]]) + self.assertEqual((list[T] | list[S]).__parameters__, (T, S)) + self.assertEqual((list[T] | list[S])[int, T], list[int] | list[T]) + self.assertEqual((list[T] | list[S])[int, int], list[int]) + + def test_union_parameter_substitution(self): + def eq(actual, expected, typed=True): + self.assertEqual(actual, expected) + if typed: + self.assertIs(type(actual), type(expected)) + + T = typing.TypeVar('T') + S = typing.TypeVar('S') + NT = typing.NewType('NT', str) + x = int | T | bytes + + eq(x[str], int | str | bytes, typed=False) + eq(x[list[int]], int | list[int] | bytes, typed=False) + eq(x[typing.List], int | typing.List | bytes) + eq(x[typing.List[int]], int | typing.List[int] | bytes) + eq(x[typing.Hashable], int | typing.Hashable | bytes) + eq(x[collections.abc.Hashable], + int | collections.abc.Hashable | bytes, typed=False) + eq(x[typing.Callable[[int], str]], + int | typing.Callable[[int], str] | bytes) + eq(x[collections.abc.Callable[[int], str]], + int | collections.abc.Callable[[int], str] | bytes, typed=False) + eq(x[typing.Tuple[int, str]], int | typing.Tuple[int, str] | bytes) + eq(x[typing.Literal['none']], int | typing.Literal['none'] | bytes) + eq(x[str | list], int | str | list | bytes, typed=False) + eq(x[typing.Union[str, list]], typing.Union[int, str, list, bytes]) + eq(x[str | int], int | str | bytes, typed=False) + eq(x[typing.Union[str, int]], typing.Union[int, str, bytes]) + eq(x[NT], int | NT | bytes) + eq(x[S], int | S | bytes) + + def test_union_pickle(self): + orig = list[T] | int + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + s = pickle.dumps(orig, proto) + loaded = pickle.loads(s) + self.assertEqual(loaded, orig) + self.assertEqual(loaded.__args__, orig.__args__) + self.assertEqual(loaded.__parameters__, orig.__parameters__) + + def test_union_copy(self): + orig = list[T] | int + for copied in (copy.copy(orig), copy.deepcopy(orig)): + self.assertEqual(copied, orig) + self.assertEqual(copied.__args__, orig.__args__) + self.assertEqual(copied.__parameters__, orig.__parameters__) + + def test_union_parameter_substitution_errors(self): + T = typing.TypeVar("T") + x = int | T + with self.assertRaises(TypeError): + x[42] + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_or_type_operator_with_forward(self): + T = typing.TypeVar('T') + ForwardAfter = T | 'Forward' + ForwardBefore = 'Forward' | T + def forward_after(x: ForwardAfter[int]) -> None: ... + def forward_before(x: ForwardBefore[int]) -> None: ... + assert typing.get_args(typing.get_type_hints(forward_after)['x']) == (int, Forward) + assert typing.get_args(typing.get_type_hints(forward_before)['x']) == (int, Forward) + + def test_or_type_operator_with_Protocol(self): + class Proto(typing.Protocol): + def meth(self) -> int: + ... + assert Proto | str == typing.Union[Proto, str] + + def test_or_type_operator_with_Alias(self): + assert list | str == typing.Union[list, str] + assert typing.List | str == typing.Union[typing.List, str] + + def test_or_type_operator_with_NamedTuple(self): + NT=namedtuple('A', ['B', 'C', 'D']) + assert NT | str == typing.Union[NT,str] + + def test_or_type_operator_with_TypedDict(self): + class Point2D(typing.TypedDict): + x: int + y: int + label: str + assert Point2D | str == typing.Union[Point2D, str] + + def test_or_type_operator_with_NewType(self): + UserId = typing.NewType('UserId', int) + assert UserId | str == typing.Union[UserId, str] + + def test_or_type_operator_with_IO(self): + assert typing.IO | str == typing.Union[typing.IO, str] + + def test_or_type_operator_with_SpecialForm(self): + assert typing.Any | str == typing.Union[typing.Any, str] + assert typing.NoReturn | str == typing.Union[typing.NoReturn, str] + assert typing.Optional[int] | str == typing.Union[typing.Optional[int], str] + assert typing.Optional[int] | str == typing.Union[int, str, None] + assert typing.Union[int, bool] | str == typing.Union[int, bool, str] + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_or_type_repr(self): + assert repr(int | str) == "int | str" + assert repr((int | str) | list) == "int | str | list" + assert repr(int | (str | list)) == "int | str | list" + assert repr(int | None) == "int | None" + assert repr(int | type(None)) == "int | None" + assert repr(int | typing.GenericAlias(list, int)) == "int | list[int]" + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_or_type_operator_with_genericalias(self): + a = list[int] + b = list[str] + c = dict[float, str] + class SubClass(types.GenericAlias): ... + d = SubClass(list, float) + # equivalence with typing.Union + self.assertEqual(a | b | c | d, typing.Union[a, b, c, d]) + # de-duplicate + self.assertEqual(a | c | b | b | a | c | d | d, a | b | c | d) + # order shouldn't matter + self.assertEqual(a | b | d, b | a | d) + self.assertEqual(repr(a | b | c | d), + "list[int] | list[str] | dict[float, str] | list[float]") + + class BadType(type): + def __eq__(self, other): + return 1 / 0 + + bt = BadType('bt', (), {}) + # Comparison should fail and errors should propagate out for bad types. + with self.assertRaises(ZeroDivisionError): + list[int] | list[bt] + + union_ga = (int | list[str], int | collections.abc.Callable[..., str], + int | d) + # Raise error when isinstance(type, type | genericalias) + for type_ in union_ga: + with self.subTest(f"check isinstance/issubclass is invalid for {type_}"): + with self.assertRaises(TypeError): + isinstance(1, type_) + with self.assertRaises(TypeError): + issubclass(int, type_) + + def test_or_type_operator_with_bad_module(self): + class BadMeta(type): + __qualname__ = 'TypeVar' + @property + def __module__(self): + 1 / 0 + TypeVar = BadMeta('TypeVar', (), {}) + _SpecialForm = BadMeta('_SpecialForm', (), {}) + # Crashes in Issue44483 + with self.assertRaises((TypeError, ZeroDivisionError)): + str | TypeVar() + with self.assertRaises((TypeError, ZeroDivisionError)): + str | _SpecialForm() + + @cpython_only + def test_or_type_operator_reference_cycle(self): + if not hasattr(sys, 'gettotalrefcount'): + self.skipTest('Cannot get total reference count.') + gc.collect() + before = sys.gettotalrefcount() + for _ in range(30): + T = typing.TypeVar('T') + U = int | list[T] + T.blah = U + del T + del U + gc.collect() + leeway = 15 + self.assertLessEqual(sys.gettotalrefcount() - before, leeway, + msg='Check for union reference leak.') + class MappingProxyTests(unittest.TestCase): mappingproxy = types.MappingProxyType @@ -1202,6 +1568,24 @@ class ClassCreationTests(unittest.TestCase): N(5) self.assertEqual(str(cm.exception), expected_message) + def test_metaclass_new_error(self): + # bpo-44232: The C function type_new() must properly report the + # exception when a metaclass constructor raises an exception and the + # winner class is not the metaclass. + class ModelBase(type): + def __new__(cls, name, bases, attrs): + super_new = super().__new__ + new_class = super_new(cls, name, bases, {}) + if name != "Model": + raise RuntimeWarning(f"{name=}") + return new_class + + class Model(metaclass=ModelBase): + pass + + with self.assertRaises(RuntimeWarning): + type("SouthPonies", (Model,), {}) + class SimpleNamespaceTests(unittest.TestCase): diff --git a/Lib/types.py b/Lib/types.py index b6f59460b..b4406978d 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -155,7 +155,12 @@ class DynamicClassAttribute: class's __getattr__ method; this is done by raising AttributeError. This allows one to have properties active on an instance, and have virtual - attributes on the class with the same name (see Enum for an example). + attributes on the class with the same name. (Enum used this between Python + versions 3.4 - 3.9 .) + + Subclass from this to use a different method of accessing virtual atributes + and still be treated properly by the inspect module. (Enum uses this since + Python 3.10 .) """ def __init__(self, fget=None, fset=None, fdel=None, doc=None): @@ -277,9 +282,9 @@ def coroutine(func): @functools.wraps(func) def wrapped(*args, **kwargs): coro = func(*args, **kwargs) - if (coro.__class__ is CoroutineType or - # XXX RUSTPYTHON TODO: iterable coroutine - False and + if (# XXX RUSTPYTHON TODO: iterable coroutine + False and + coro.__class__ is CoroutineType or coro.__class__ is GeneratorType and coro.gi_code.co_flags & 0x100): # 'coro' is a native coroutine object or an iterable coroutine return coro @@ -295,8 +300,11 @@ def coroutine(func): return wrapped - GenericAlias = type(list[int]) +# UnionType = type(int | str) +EllipsisType = type(Ellipsis) +NoneType = type(None) +NotImplementedType = type(NotImplemented) __all__ = [n for n in globals() if n[:1] != '_']