From d9f923280f204204f8703756aef4f655b579b4b8 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 16 Jul 2021 12:49:33 +0300 Subject: [PATCH] bpo-44636: Collapse union of equal types (GH-27178) The result of `int | int` is now `int`. Fix comparison of the union type with non-hashable objects. `int | str == {}` no longer raises a TypeError. --- Lib/test/test_types.py | 7 +++++- .../2021-07-16-09-36-12.bpo-44636.ZWebi8.rst | 1 + Objects/unionobject.c | 24 ++++++++++++------- 3 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2021-07-16-09-36-12.bpo-44636.ZWebi8.rst diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index b35555878518d7..d940f1aaaf383c 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -623,7 +623,7 @@ def test_or_types_operator(self): 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.assertEqual(int | int, int) + self.assertIs(int | int, int) self.assertEqual( BaseException | bool | @@ -651,6 +651,8 @@ def test_or_types_operator(self): 3 | int with self.assertRaises(TypeError): Example() | int + x = int | str + self.assertNotEqual(x, {}) with self.assertRaises(TypeError): (int | str) < typing.Union[str, int] with self.assertRaises(TypeError): @@ -704,6 +706,8 @@ 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) def test_union_args(self): self.assertEqual((int | str).__args__, (int, str)) @@ -721,6 +725,7 @@ def test_union_parameter_chaining(self): 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_or_type_operator_with_forward(self): T = typing.TypeVar('T') diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-07-16-09-36-12.bpo-44636.ZWebi8.rst b/Misc/NEWS.d/next/Core and Builtins/2021-07-16-09-36-12.bpo-44636.ZWebi8.rst new file mode 100644 index 00000000000000..1a053ae69e97e0 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-07-16-09-36-12.bpo-44636.ZWebi8.rst @@ -0,0 +1 @@ +Collapse union of equal types. E.g. the result of ``int | int`` is now ``int``. Fix comparison of the union type with non-hashable objects. E.g. ``int | str == {}`` no longer raises a TypeError. diff --git a/Objects/unionobject.c b/Objects/unionobject.c index 85092e667a21d4..dad26c32b29d15 100644 --- a/Objects/unionobject.c +++ b/Objects/unionobject.c @@ -187,9 +187,9 @@ union_richcompare(PyObject *a, PyObject *b, int op) } } } else { - if (PySet_Add(b_set, b) == -1) { - goto exit; - } + Py_DECREF(a_set); + Py_DECREF(b_set); + Py_RETURN_NOTIMPLEMENTED; } result = PyObject_RichCompare(a_set, b_set, op); exit: @@ -551,17 +551,25 @@ _Py_Union(PyObject *args) } } + args = dedup_and_flatten_args(args); + if (args == NULL) { + return NULL; + } + if (PyTuple_GET_SIZE(args) == 1) { + PyObject *result1 = PyTuple_GET_ITEM(args, 0); + Py_INCREF(result1); + Py_DECREF(args); + return result1; + } + result = PyObject_GC_New(unionobject, &_Py_UnionType); if (result == NULL) { + Py_DECREF(args); return NULL; } result->parameters = NULL; - result->args = dedup_and_flatten_args(args); + result->args = args; _PyObject_GC_TRACK(result); - if (result->args == NULL) { - Py_DECREF(result); - return NULL; - } return (PyObject*)result; }