From ca5a4cf8266cf789eae379fe224458e94dd41b30 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Sat, 24 Jul 2021 22:49:25 +0800 Subject: [PATCH] bpo-44731: Simplify the union type implementation (GH-27318) (GH-27334) Remove direct support of typing types in the C code because they are already supported by defining methods __or__ and __ror__ in the Python code. Co-authored-by: Serhiy Storchaka --- Lib/test/test_types.py | 26 ++++++------- Lib/typing.py | 6 +++ Objects/unionobject.c | 88 ++++-------------------------------------- 3 files changed, 26 insertions(+), 94 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index d149e6b359b728..3f491ee5108edb 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -776,31 +776,32 @@ def test_union_parameter_chaining(self): self.assertEqual((list[T] | list[S])[int, int], list[int]) def test_union_parameter_substitution(self): - def eq(actual, expected): + def eq(actual, expected, typed=True): self.assertEqual(actual, expected) - self.assertIs(type(actual), type(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) - eq(x[list[int]], int | list[int] | 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) + 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) + 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) + 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) + 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) @@ -829,9 +830,9 @@ def test_union_from_args(self): with self.assertRaisesRegex(ValueError, r"args must be not empty"): types.Union._from_args(()) - alias = types.Union._from_args((int, str, T)) + alias = types.Union._from_args((int, list[T], None)) - self.assertEqual(alias.__args__, (int, str, T)) + self.assertEqual(alias.__args__, (int, list[T], type(None))) self.assertEqual(alias.__parameters__, (T,)) result = types.Union._from_args((int,)) @@ -894,7 +895,6 @@ def test_or_type_repr(self): assert repr(int | None) == "int | None" assert repr(int | type(None)) == "int | None" assert repr(int | typing.GenericAlias(list, int)) == "int | list[int]" - assert repr(int | typing.TypeVar('T')) == "int | ~T" def test_or_type_operator_with_genericalias(self): a = list[int] @@ -939,9 +939,9 @@ def __module__(self): TypeVar = BadMeta('TypeVar', (), {}) _SpecialForm = BadMeta('_SpecialForm', (), {}) # Crashes in Issue44483 - with self.assertRaises(ZeroDivisionError): + with self.assertRaises((TypeError, ZeroDivisionError)): str | TypeVar() - with self.assertRaises(ZeroDivisionError): + with self.assertRaises((TypeError, ZeroDivisionError)): str | _SpecialForm() @cpython_only diff --git a/Lib/typing.py b/Lib/typing.py index e2fb7d1eba281b..0570b4e763396e 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -375,6 +375,12 @@ def __reduce__(self): def __call__(self, *args, **kwds): raise TypeError(f"Cannot instantiate {self!r}") + def __or__(self, other): + return Union[self, other] + + def __ror__(self, other): + return Union[other, self] + def __instancecheck__(self, obj): raise TypeError(f"{self} cannot be used with isinstance()") diff --git a/Objects/unionobject.c b/Objects/unionobject.c index 54cdedae4d92c9..9804d87c66ccd2 100644 --- a/Objects/unionobject.c +++ b/Objects/unionobject.c @@ -115,31 +115,6 @@ union_subclasscheck(PyObject *self, PyObject *instance) Py_RETURN_FALSE; } -static int -is_typing_module(PyObject *obj) -{ - _Py_IDENTIFIER(__module__); - PyObject *module; - if (_PyObject_LookupAttrId(obj, &PyId___module__, &module) < 0) { - return -1; - } - int is_typing = (module != NULL && - PyUnicode_Check(module) && - _PyUnicode_EqualToASCIIString(module, "typing")); - Py_XDECREF(module); - return is_typing; -} - -static int -is_typing_name(PyObject *obj, const char *name) -{ - PyTypeObject *type = Py_TYPE(obj); - if (strcmp(type->tp_name, name) != 0) { - return 0; - } - return is_typing_module((PyObject *)type); -} - static PyObject * union_richcompare(PyObject *a, PyObject *b, int op) { @@ -251,52 +226,13 @@ dedup_and_flatten_args(PyObject* args) return new_args; } -static int -is_typevar(PyObject *obj) -{ - return is_typing_name(obj, "TypeVar"); -} - -static int -is_special_form(PyObject *obj) -{ - return is_typing_name(obj, "_SpecialForm"); -} - -static int -is_new_type(PyObject *obj) -{ - PyTypeObject *type = Py_TYPE(obj); - if (type != &PyFunction_Type) { - return 0; - } - return is_typing_module(obj); -} - -// Emulates short-circuiting behavior of the ``||`` operator -// while also checking negative values. -#define CHECK_RES(res) { \ - int result = res; \ - if (result) { \ - return result; \ - } \ -} - -// Returns 1 on true, 0 on false, and -1 on error. static int is_unionable(PyObject *obj) { - if (obj == Py_None || + return (obj == Py_None || PyType_Check(obj) || _PyGenericAlias_Check(obj) || - _PyUnion_Check(obj)) - { - return 1; - } - CHECK_RES(is_typevar(obj)); - CHECK_RES(is_new_type(obj)); - CHECK_RES(is_special_form(obj)); - return 0; + _PyUnion_Check(obj)); } static int @@ -305,12 +241,9 @@ is_args_unionable(PyObject *args) Py_ssize_t nargs = PyTuple_GET_SIZE(args); for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) { PyObject *arg = PyTuple_GET_ITEM(args, iarg); - int is_arg_unionable = is_unionable(arg); - if (is_arg_unionable <= 0) { - if (is_arg_unionable == 0) { - PyErr_Format(PyExc_TypeError, - "Each union argument must be a type, got %.100R", arg); - } + if (!is_unionable(arg)) { + PyErr_Format(PyExc_TypeError, + "Each union argument must be a type, got %.100R", arg); return 0; } } @@ -320,14 +253,7 @@ is_args_unionable(PyObject *args) PyObject * _Py_union_type_or(PyObject* self, PyObject* other) { - int r = is_unionable(self); - if (r > 0) { - r = is_unionable(other); - } - if (r < 0) { - return NULL; - } - if (!r) { + if (!is_unionable(self) || !is_unionable(other)) { Py_RETURN_NOTIMPLEMENTED; } @@ -465,7 +391,7 @@ union_from_args(PyObject *cls, PyObject *args) return NULL; } - if (is_args_unionable(args) <= 0) { + if (!is_args_unionable(args)) { return NULL; }