Skip to content

Commit ca5a4cf

Browse files
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 <storchaka@gmail.com>
1 parent 9356d1e commit ca5a4cf

File tree

3 files changed

+26
-94
lines changed

3 files changed

+26
-94
lines changed

Lib/test/test_types.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -776,31 +776,32 @@ def test_union_parameter_chaining(self):
776776
self.assertEqual((list[T] | list[S])[int, int], list[int])
777777

778778
def test_union_parameter_substitution(self):
779-
def eq(actual, expected):
779+
def eq(actual, expected, typed=True):
780780
self.assertEqual(actual, expected)
781-
self.assertIs(type(actual), type(expected))
781+
if typed:
782+
self.assertIs(type(actual), type(expected))
782783

783784
T = typing.TypeVar('T')
784785
S = typing.TypeVar('S')
785786
NT = typing.NewType('NT', str)
786787
x = int | T | bytes
787788

788-
eq(x[str], int | str | bytes)
789-
eq(x[list[int]], int | list[int] | bytes)
789+
eq(x[str], int | str | bytes, typed=False)
790+
eq(x[list[int]], int | list[int] | bytes, typed=False)
790791
eq(x[typing.List], int | typing.List | bytes)
791792
eq(x[typing.List[int]], int | typing.List[int] | bytes)
792793
eq(x[typing.Hashable], int | typing.Hashable | bytes)
793794
eq(x[collections.abc.Hashable],
794-
int | collections.abc.Hashable | bytes)
795+
int | collections.abc.Hashable | bytes, typed=False)
795796
eq(x[typing.Callable[[int], str]],
796797
int | typing.Callable[[int], str] | bytes)
797798
eq(x[collections.abc.Callable[[int], str]],
798-
int | collections.abc.Callable[[int], str] | bytes)
799+
int | collections.abc.Callable[[int], str] | bytes, typed=False)
799800
eq(x[typing.Tuple[int, str]], int | typing.Tuple[int, str] | bytes)
800801
eq(x[typing.Literal['none']], int | typing.Literal['none'] | bytes)
801-
eq(x[str | list], int | str | list | bytes)
802+
eq(x[str | list], int | str | list | bytes, typed=False)
802803
eq(x[typing.Union[str, list]], typing.Union[int, str, list, bytes])
803-
eq(x[str | int], int | str | bytes)
804+
eq(x[str | int], int | str | bytes, typed=False)
804805
eq(x[typing.Union[str, int]], typing.Union[int, str, bytes])
805806
eq(x[NT], int | NT | bytes)
806807
eq(x[S], int | S | bytes)
@@ -829,9 +830,9 @@ def test_union_from_args(self):
829830
with self.assertRaisesRegex(ValueError, r"args must be not empty"):
830831
types.Union._from_args(())
831832

832-
alias = types.Union._from_args((int, str, T))
833+
alias = types.Union._from_args((int, list[T], None))
833834

834-
self.assertEqual(alias.__args__, (int, str, T))
835+
self.assertEqual(alias.__args__, (int, list[T], type(None)))
835836
self.assertEqual(alias.__parameters__, (T,))
836837

837838
result = types.Union._from_args((int,))
@@ -894,7 +895,6 @@ def test_or_type_repr(self):
894895
assert repr(int | None) == "int | None"
895896
assert repr(int | type(None)) == "int | None"
896897
assert repr(int | typing.GenericAlias(list, int)) == "int | list[int]"
897-
assert repr(int | typing.TypeVar('T')) == "int | ~T"
898898

899899
def test_or_type_operator_with_genericalias(self):
900900
a = list[int]
@@ -939,9 +939,9 @@ def __module__(self):
939939
TypeVar = BadMeta('TypeVar', (), {})
940940
_SpecialForm = BadMeta('_SpecialForm', (), {})
941941
# Crashes in Issue44483
942-
with self.assertRaises(ZeroDivisionError):
942+
with self.assertRaises((TypeError, ZeroDivisionError)):
943943
str | TypeVar()
944-
with self.assertRaises(ZeroDivisionError):
944+
with self.assertRaises((TypeError, ZeroDivisionError)):
945945
str | _SpecialForm()
946946

947947
@cpython_only

Lib/typing.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,12 @@ def __reduce__(self):
375375
def __call__(self, *args, **kwds):
376376
raise TypeError(f"Cannot instantiate {self!r}")
377377

378+
def __or__(self, other):
379+
return Union[self, other]
380+
381+
def __ror__(self, other):
382+
return Union[other, self]
383+
378384
def __instancecheck__(self, obj):
379385
raise TypeError(f"{self} cannot be used with isinstance()")
380386

Objects/unionobject.c

Lines changed: 7 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -115,31 +115,6 @@ union_subclasscheck(PyObject *self, PyObject *instance)
115115
Py_RETURN_FALSE;
116116
}
117117

118-
static int
119-
is_typing_module(PyObject *obj)
120-
{
121-
_Py_IDENTIFIER(__module__);
122-
PyObject *module;
123-
if (_PyObject_LookupAttrId(obj, &PyId___module__, &module) < 0) {
124-
return -1;
125-
}
126-
int is_typing = (module != NULL &&
127-
PyUnicode_Check(module) &&
128-
_PyUnicode_EqualToASCIIString(module, "typing"));
129-
Py_XDECREF(module);
130-
return is_typing;
131-
}
132-
133-
static int
134-
is_typing_name(PyObject *obj, const char *name)
135-
{
136-
PyTypeObject *type = Py_TYPE(obj);
137-
if (strcmp(type->tp_name, name) != 0) {
138-
return 0;
139-
}
140-
return is_typing_module((PyObject *)type);
141-
}
142-
143118
static PyObject *
144119
union_richcompare(PyObject *a, PyObject *b, int op)
145120
{
@@ -251,52 +226,13 @@ dedup_and_flatten_args(PyObject* args)
251226
return new_args;
252227
}
253228

254-
static int
255-
is_typevar(PyObject *obj)
256-
{
257-
return is_typing_name(obj, "TypeVar");
258-
}
259-
260-
static int
261-
is_special_form(PyObject *obj)
262-
{
263-
return is_typing_name(obj, "_SpecialForm");
264-
}
265-
266-
static int
267-
is_new_type(PyObject *obj)
268-
{
269-
PyTypeObject *type = Py_TYPE(obj);
270-
if (type != &PyFunction_Type) {
271-
return 0;
272-
}
273-
return is_typing_module(obj);
274-
}
275-
276-
// Emulates short-circuiting behavior of the ``||`` operator
277-
// while also checking negative values.
278-
#define CHECK_RES(res) { \
279-
int result = res; \
280-
if (result) { \
281-
return result; \
282-
} \
283-
}
284-
285-
// Returns 1 on true, 0 on false, and -1 on error.
286229
static int
287230
is_unionable(PyObject *obj)
288231
{
289-
if (obj == Py_None ||
232+
return (obj == Py_None ||
290233
PyType_Check(obj) ||
291234
_PyGenericAlias_Check(obj) ||
292-
_PyUnion_Check(obj))
293-
{
294-
return 1;
295-
}
296-
CHECK_RES(is_typevar(obj));
297-
CHECK_RES(is_new_type(obj));
298-
CHECK_RES(is_special_form(obj));
299-
return 0;
235+
_PyUnion_Check(obj));
300236
}
301237

302238
static int
@@ -305,12 +241,9 @@ is_args_unionable(PyObject *args)
305241
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
306242
for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
307243
PyObject *arg = PyTuple_GET_ITEM(args, iarg);
308-
int is_arg_unionable = is_unionable(arg);
309-
if (is_arg_unionable <= 0) {
310-
if (is_arg_unionable == 0) {
311-
PyErr_Format(PyExc_TypeError,
312-
"Each union argument must be a type, got %.100R", arg);
313-
}
244+
if (!is_unionable(arg)) {
245+
PyErr_Format(PyExc_TypeError,
246+
"Each union argument must be a type, got %.100R", arg);
314247
return 0;
315248
}
316249
}
@@ -320,14 +253,7 @@ is_args_unionable(PyObject *args)
320253
PyObject *
321254
_Py_union_type_or(PyObject* self, PyObject* other)
322255
{
323-
int r = is_unionable(self);
324-
if (r > 0) {
325-
r = is_unionable(other);
326-
}
327-
if (r < 0) {
328-
return NULL;
329-
}
330-
if (!r) {
256+
if (!is_unionable(self) || !is_unionable(other)) {
331257
Py_RETURN_NOTIMPLEMENTED;
332258
}
333259

@@ -465,7 +391,7 @@ union_from_args(PyObject *cls, PyObject *args)
465391
return NULL;
466392
}
467393

468-
if (is_args_unionable(args) <= 0) {
394+
if (!is_args_unionable(args)) {
469395
return NULL;
470396
}
471397

0 commit comments

Comments
 (0)