From 5129959bb2436d400115b1474397d9a16dba23f6 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 20 Oct 2025 12:35:57 -0700 Subject: [PATCH 1/2] gh-140348: Fix using | on unusual objects plus Unions --- Lib/test/test_typing.py | 9 +++++++++ .../2025-10-20-12-33-49.gh-issue-140348.SAKnQZ.rst | 3 +++ Objects/unionobject.c | 7 +------ 3 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-10-20-12-33-49.gh-issue-140348.SAKnQZ.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index db0501d70e3442..5306c133fe7088 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2283,6 +2283,15 @@ class Ints(enum.IntEnum): self.assertEqual(Union[Literal[1], Literal[Ints.B], Literal[True]].__args__, (Literal[1], Literal[Ints.B], Literal[True])) + def test_allow_non_types_in_or(self): + # gh-140348: Test that using | with a Union object allows things that are + # not allowed by is_unionable(). + U1 = Union[int, str] + self.assertEqual(U1 | float, Union[int, str, float]) + self.assertEqual(U1 | "float", Union[int, str, "float"]) + self.assertEqual(float | U1, Union[float, int, str]) + self.assertEqual("float" | U1, Union["float", int, str]) + class TupleTests(BaseTestCase): diff --git a/Misc/NEWS.d/next/Library/2025-10-20-12-33-49.gh-issue-140348.SAKnQZ.rst b/Misc/NEWS.d/next/Library/2025-10-20-12-33-49.gh-issue-140348.SAKnQZ.rst new file mode 100644 index 00000000000000..16d5b2a8bf03d0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-20-12-33-49.gh-issue-140348.SAKnQZ.rst @@ -0,0 +1,3 @@ +Fix regression in Python 3.14.0 where using the ``|`` operator on a +:class:`typing.Union` object combined with an object that is not a type +would raise an error. diff --git a/Objects/unionobject.c b/Objects/unionobject.c index c4ece0fe09f018..e5342dd5fafc6a 100644 --- a/Objects/unionobject.c +++ b/Objects/unionobject.c @@ -256,13 +256,8 @@ is_unionable(PyObject *obj) PyObject * _Py_union_type_or(PyObject* self, PyObject* other) { - if (!is_unionable(self) || !is_unionable(other)) { - Py_RETURN_NOTIMPLEMENTED; - } - unionbuilder ub; - // unchecked because we already checked is_unionable() - if (!unionbuilder_init(&ub, false)) { + if (!unionbuilder_init(&ub, true)) { return NULL; } if (!unionbuilder_add_single(&ub, self) || From b5bd4800db535e6b3b780dcee9fec3e53b37074a Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 21 Oct 2025 08:19:28 -0700 Subject: [PATCH 2/2] Lower-impact solution --- Objects/unionobject.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Objects/unionobject.c b/Objects/unionobject.c index e5342dd5fafc6a..a47d6193d70889 100644 --- a/Objects/unionobject.c +++ b/Objects/unionobject.c @@ -256,8 +256,13 @@ is_unionable(PyObject *obj) PyObject * _Py_union_type_or(PyObject* self, PyObject* other) { + if (!is_unionable(self) || !is_unionable(other)) { + Py_RETURN_NOTIMPLEMENTED; + } + unionbuilder ub; - if (!unionbuilder_init(&ub, true)) { + // unchecked because we already checked is_unionable() + if (!unionbuilder_init(&ub, false)) { return NULL; } if (!unionbuilder_add_single(&ub, self) || @@ -388,8 +393,23 @@ static PyGetSetDef union_properties[] = { {0} }; +static PyObject * +union_nb_or(PyObject *a, PyObject *b) +{ + unionbuilder ub; + if (!unionbuilder_init(&ub, true)) { + return NULL; + } + if (!unionbuilder_add_single(&ub, a) || + !unionbuilder_add_single(&ub, b)) { + unionbuilder_finalize(&ub); + return NULL; + } + return make_union(&ub); +} + static PyNumberMethods union_as_number = { - .nb_or = _Py_union_type_or, // Add __or__ function + .nb_or = union_nb_or, // Add __or__ function }; static const char* const cls_attrs[] = {