Skip to content

Commit

Permalink
bpo-44731: Simplify the union type implementation (GH-27318) (GH-27334)
Browse files Browse the repository at this point in the history
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 <storchaka@gmail.com>
  • Loading branch information
Fidget-Spinner and serhiy-storchaka committed Jul 24, 2021
1 parent 9356d1e commit ca5a4cf
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 94 deletions.
26 changes: 13 additions & 13 deletions Lib/test/test_types.py
Expand Up @@ -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)
Expand Down Expand Up @@ -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,))
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions Lib/typing.py
Expand Up @@ -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()")

Expand Down
88 changes: 7 additions & 81 deletions Objects/unionobject.c
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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
Expand All @@ -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;
}
}
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand Down

0 comments on commit ca5a4cf

Please sign in to comment.