[Python-checkins] bpo-44731: Simplify the union type implementation (GH-27318)

pablogsal webhook-mailer at python.org
Sat Jul 24 09:34:57 EDT 2021


https://github.com/python/cpython/commit/08284231275ac9cc60ae27eab2338805919d8881
commit: 08284231275ac9cc60ae27eab2338805919d8881
branch: main
author: Serhiy Storchaka <storchaka at gmail.com>
committer: pablogsal <Pablogsal at gmail.com>
date: 2021-07-24T14:34:48+01:00
summary:

bpo-44731: Simplify the union type implementation (GH-27318)

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.

files:
M Lib/test/test_types.py
M Lib/typing.py
M Objects/unionobject.c

diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py
index d149e6b359b72..3f491ee5108ed 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 198837cc7203f..3403f5516e80d 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -383,6 +383,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 ea4aed86f839e..ad34dcad1725f 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;
     }
 



More information about the Python-checkins mailing list