blob: cc7181d2475caf6268075be2a902407d42d55e04 [file] [log] [blame]
Maggie Moss1b4552c2020-09-09 13:23:24 -07001// types.Union -- used to represent e.g. Union[int, str], int | str
2#include "Python.h"
Miss Islington (bot)08561342021-07-03 06:33:16 -07003#include "pycore_object.h" // _PyObject_GC_TRACK/UNTRACK
Maggie Moss1b4552c2020-09-09 13:23:24 -07004#include "pycore_unionobject.h"
5#include "structmember.h"
6
7
8typedef struct {
9 PyObject_HEAD
10 PyObject *args;
11} unionobject;
12
13static void
14unionobject_dealloc(PyObject *self)
15{
16 unionobject *alias = (unionobject *)self;
17
Miss Islington (bot)08561342021-07-03 06:33:16 -070018 _PyObject_GC_UNTRACK(self);
19
Maggie Moss1b4552c2020-09-09 13:23:24 -070020 Py_XDECREF(alias->args);
Neil Schemenauer0564aaf2020-10-27 11:55:52 -070021 Py_TYPE(self)->tp_free(self);
Maggie Moss1b4552c2020-09-09 13:23:24 -070022}
23
Miss Islington (bot)08561342021-07-03 06:33:16 -070024static int
25union_traverse(PyObject *self, visitproc visit, void *arg)
26{
27 unionobject *alias = (unionobject *)self;
28 Py_VISIT(alias->args);
29 return 0;
30}
31
Maggie Moss1b4552c2020-09-09 13:23:24 -070032static Py_hash_t
33union_hash(PyObject *self)
34{
35 unionobject *alias = (unionobject *)self;
36 Py_hash_t h1 = PyObject_Hash(alias->args);
37 if (h1 == -1) {
38 return -1;
39 }
40 return h1;
41}
42
43static int
44is_generic_alias_in_args(PyObject *args) {
45 Py_ssize_t nargs = PyTuple_GET_SIZE(args);
46 for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
47 PyObject *arg = PyTuple_GET_ITEM(args, iarg);
Ken Jin49cd68f2021-01-03 00:19:15 +080048 if (PyObject_TypeCheck(arg, &Py_GenericAliasType)) {
Maggie Moss1b4552c2020-09-09 13:23:24 -070049 return 0;
50 }
51 }
52 return 1;
53}
54
55static PyObject *
56union_instancecheck(PyObject *self, PyObject *instance)
57{
58 unionobject *alias = (unionobject *) self;
59 Py_ssize_t nargs = PyTuple_GET_SIZE(alias->args);
60 if (!is_generic_alias_in_args(alias->args)) {
61 PyErr_SetString(PyExc_TypeError,
62 "isinstance() argument 2 cannot contain a parameterized generic");
63 return NULL;
64 }
65 for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
66 PyObject *arg = PyTuple_GET_ITEM(alias->args, iarg);
67 if (arg == Py_None) {
68 arg = (PyObject *)&_PyNone_Type;
69 }
70 if (PyType_Check(arg) && PyObject_IsInstance(instance, arg) != 0) {
71 Py_RETURN_TRUE;
72 }
73 }
74 Py_RETURN_FALSE;
75}
76
77static PyObject *
78union_subclasscheck(PyObject *self, PyObject *instance)
79{
80 if (!PyType_Check(instance)) {
81 PyErr_SetString(PyExc_TypeError, "issubclass() arg 1 must be a class");
82 return NULL;
83 }
84 unionobject *alias = (unionobject *)self;
85 if (!is_generic_alias_in_args(alias->args)) {
86 PyErr_SetString(PyExc_TypeError,
87 "issubclass() argument 2 cannot contain a parameterized generic");
88 return NULL;
89 }
90 Py_ssize_t nargs = PyTuple_GET_SIZE(alias->args);
91 for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
92 PyObject *arg = PyTuple_GET_ITEM(alias->args, iarg);
93 if (PyType_Check(arg) && (PyType_IsSubtype((PyTypeObject *)instance, (PyTypeObject *)arg) != 0)) {
94 Py_RETURN_TRUE;
95 }
96 }
97 Py_RETURN_FALSE;
98}
99
100static int
101is_typing_module(PyObject *obj) {
102 PyObject *module = PyObject_GetAttrString(obj, "__module__");
103 if (module == NULL) {
104 return -1;
105 }
106 int is_typing = PyUnicode_Check(module) && _PyUnicode_EqualToASCIIString(module, "typing");
107 Py_DECREF(module);
108 return is_typing;
109}
110
111static int
112is_typing_name(PyObject *obj, char *name)
113{
114 PyTypeObject *type = Py_TYPE(obj);
115 if (strcmp(type->tp_name, name) != 0) {
116 return 0;
117 }
118 return is_typing_module(obj);
119}
120
121static PyObject *
122union_richcompare(PyObject *a, PyObject *b, int op)
123{
124 PyObject *result = NULL;
125 if (op != Py_EQ && op != Py_NE) {
126 result = Py_NotImplemented;
127 Py_INCREF(result);
128 return result;
129 }
130
131 PyTypeObject *type = Py_TYPE(b);
132
133 PyObject* a_set = PySet_New(((unionobject*)a)->args);
134 if (a_set == NULL) {
135 return NULL;
136 }
137 PyObject* b_set = PySet_New(NULL);
138 if (b_set == NULL) {
139 goto exit;
140 }
141
142 // Populate b_set with the data from the right object
143 int is_typing_union = is_typing_name(b, "_UnionGenericAlias");
144 if (is_typing_union < 0) {
145 goto exit;
146 }
147 if (is_typing_union) {
148 PyObject *b_args = PyObject_GetAttrString(b, "__args__");
149 if (b_args == NULL) {
150 goto exit;
151 }
152 if (!PyTuple_CheckExact(b_args)) {
153 Py_DECREF(b_args);
154 PyErr_SetString(PyExc_TypeError, "__args__ argument of typing.Union object is not a tuple");
155 goto exit;
156 }
157 Py_ssize_t b_arg_length = PyTuple_GET_SIZE(b_args);
158 for (Py_ssize_t i = 0; i < b_arg_length; i++) {
159 PyObject* arg = PyTuple_GET_ITEM(b_args, i);
160 if (arg == (PyObject *)&_PyNone_Type) {
161 arg = Py_None;
162 }
163 if (PySet_Add(b_set, arg) == -1) {
164 Py_DECREF(b_args);
165 goto exit;
166 }
167 }
168 Py_DECREF(b_args);
169 } else if (type == &_Py_UnionType) {
170 PyObject* args = ((unionobject*) b)->args;
171 Py_ssize_t arg_length = PyTuple_GET_SIZE(args);
172 for (Py_ssize_t i = 0; i < arg_length; i++) {
173 PyObject* arg = PyTuple_GET_ITEM(args, i);
174 if (PySet_Add(b_set, arg) == -1) {
175 goto exit;
176 }
177 }
178 } else {
179 if (PySet_Add(b_set, b) == -1) {
180 goto exit;
181 }
182 }
183 result = PyObject_RichCompare(a_set, b_set, op);
184exit:
185 Py_XDECREF(a_set);
186 Py_XDECREF(b_set);
187 return result;
188}
189
190static PyObject*
191flatten_args(PyObject* args)
192{
Victor Stinnerd67de0a2020-09-23 23:25:54 +0200193 Py_ssize_t arg_length = PyTuple_GET_SIZE(args);
194 Py_ssize_t total_args = 0;
Maggie Moss1b4552c2020-09-09 13:23:24 -0700195 // Get number of total args once it's flattened.
196 for (Py_ssize_t i = 0; i < arg_length; i++) {
197 PyObject *arg = PyTuple_GET_ITEM(args, i);
198 PyTypeObject* arg_type = Py_TYPE(arg);
199 if (arg_type == &_Py_UnionType) {
200 total_args += PyTuple_GET_SIZE(((unionobject*) arg)->args);
201 } else {
202 total_args++;
203 }
204 }
205 // Create new tuple of flattened args.
206 PyObject *flattened_args = PyTuple_New(total_args);
207 if (flattened_args == NULL) {
208 return NULL;
209 }
210 Py_ssize_t pos = 0;
211 for (Py_ssize_t i = 0; i < arg_length; i++) {
212 PyObject *arg = PyTuple_GET_ITEM(args, i);
213 PyTypeObject* arg_type = Py_TYPE(arg);
214 if (arg_type == &_Py_UnionType) {
215 PyObject* nested_args = ((unionobject*)arg)->args;
Victor Stinnerd73cf7c2020-09-26 12:48:41 +0200216 Py_ssize_t nested_arg_length = PyTuple_GET_SIZE(nested_args);
217 for (Py_ssize_t j = 0; j < nested_arg_length; j++) {
Maggie Moss1b4552c2020-09-09 13:23:24 -0700218 PyObject* nested_arg = PyTuple_GET_ITEM(nested_args, j);
219 Py_INCREF(nested_arg);
220 PyTuple_SET_ITEM(flattened_args, pos, nested_arg);
221 pos++;
222 }
223 } else {
224 Py_INCREF(arg);
225 PyTuple_SET_ITEM(flattened_args, pos, arg);
226 pos++;
227 }
228 }
229 return flattened_args;
230}
231
232static PyObject*
233dedup_and_flatten_args(PyObject* args)
234{
235 args = flatten_args(args);
236 if (args == NULL) {
237 return NULL;
238 }
239 Py_ssize_t arg_length = PyTuple_GET_SIZE(args);
240 PyObject *new_args = PyTuple_New(arg_length);
241 if (new_args == NULL) {
242 return NULL;
243 }
244 // Add unique elements to an array.
Victor Stinnerd73cf7c2020-09-26 12:48:41 +0200245 Py_ssize_t added_items = 0;
Maggie Moss1b4552c2020-09-09 13:23:24 -0700246 for (Py_ssize_t i = 0; i < arg_length; i++) {
247 int is_duplicate = 0;
248 PyObject* i_element = PyTuple_GET_ITEM(args, i);
249 for (Py_ssize_t j = i + 1; j < arg_length; j++) {
250 PyObject* j_element = PyTuple_GET_ITEM(args, j);
kj463c7d32020-12-14 02:38:24 +0800251 int is_ga = PyObject_TypeCheck(i_element, &Py_GenericAliasType) &&
252 PyObject_TypeCheck(j_element, &Py_GenericAliasType);
kj4eb41d02020-11-09 12:00:13 +0800253 // RichCompare to also deduplicate GenericAlias types (slower)
254 is_duplicate = is_ga ? PyObject_RichCompareBool(i_element, j_element, Py_EQ)
255 : i_element == j_element;
256 // Should only happen if RichCompare fails
257 if (is_duplicate < 0) {
258 Py_DECREF(args);
259 Py_DECREF(new_args);
260 return NULL;
Maggie Moss1b4552c2020-09-09 13:23:24 -0700261 }
kj4eb41d02020-11-09 12:00:13 +0800262 if (is_duplicate)
263 break;
Maggie Moss1b4552c2020-09-09 13:23:24 -0700264 }
265 if (!is_duplicate) {
266 Py_INCREF(i_element);
267 PyTuple_SET_ITEM(new_args, added_items, i_element);
268 added_items++;
269 }
270 }
271 Py_DECREF(args);
272 _PyTuple_Resize(&new_args, added_items);
273 return new_args;
274}
275
276static int
277is_typevar(PyObject *obj)
278{
279 return is_typing_name(obj, "TypeVar");
280}
281
282static int
283is_special_form(PyObject *obj)
284{
285 return is_typing_name(obj, "_SpecialForm");
286}
287
288static int
289is_new_type(PyObject *obj)
290{
291 PyTypeObject *type = Py_TYPE(obj);
292 if (type != &PyFunction_Type) {
293 return 0;
294 }
295 return is_typing_module(obj);
296}
297
Miss Islington (bot)7e6cad72021-06-23 02:38:49 -0700298// Emulates short-circuiting behavior of the ``||`` operator
299// while also checking negative values.
300#define CHECK_RES(res) { \
301 int result = res; \
302 if (result) { \
303 return result; \
304 } \
305}
306
307// Returns 1 on true, 0 on false, and -1 on error.
Maggie Moss1b4552c2020-09-09 13:23:24 -0700308static int
309is_unionable(PyObject *obj)
310{
311 if (obj == Py_None) {
312 return 1;
313 }
314 PyTypeObject *type = Py_TYPE(obj);
Miss Islington (bot)7e6cad72021-06-23 02:38:49 -0700315 CHECK_RES(is_typevar(obj));
316 CHECK_RES(is_new_type(obj));
317 CHECK_RES(is_special_form(obj));
Maggie Moss1b4552c2020-09-09 13:23:24 -0700318 return (
Miss Islington (bot)7e6cad72021-06-23 02:38:49 -0700319 // The following checks never fail.
Maggie Moss1b4552c2020-09-09 13:23:24 -0700320 PyType_Check(obj) ||
kj463c7d32020-12-14 02:38:24 +0800321 PyObject_TypeCheck(obj, &Py_GenericAliasType) ||
Maggie Moss1b4552c2020-09-09 13:23:24 -0700322 type == &_Py_UnionType);
323}
324
kj4eb41d02020-11-09 12:00:13 +0800325PyObject *
326_Py_union_type_or(PyObject* self, PyObject* param)
Maggie Moss1b4552c2020-09-09 13:23:24 -0700327{
328 PyObject *tuple = PyTuple_Pack(2, self, param);
329 if (tuple == NULL) {
330 return NULL;
331 }
332 PyObject *new_union = _Py_Union(tuple);
333 Py_DECREF(tuple);
334 return new_union;
335}
336
337static int
338union_repr_item(_PyUnicodeWriter *writer, PyObject *p)
339{
340 _Py_IDENTIFIER(__module__);
341 _Py_IDENTIFIER(__qualname__);
342 _Py_IDENTIFIER(__origin__);
343 _Py_IDENTIFIER(__args__);
344 PyObject *qualname = NULL;
345 PyObject *module = NULL;
Serhiy Storchaka98c44332020-10-10 22:23:42 +0300346 PyObject *tmp;
Maggie Moss1b4552c2020-09-09 13:23:24 -0700347 PyObject *r = NULL;
348 int err;
349
Serhiy Storchaka98c44332020-10-10 22:23:42 +0300350 if (_PyObject_LookupAttrId(p, &PyId___origin__, &tmp) < 0) {
Maggie Moss1b4552c2020-09-09 13:23:24 -0700351 goto exit;
352 }
353
Serhiy Storchaka98c44332020-10-10 22:23:42 +0300354 if (tmp) {
355 Py_DECREF(tmp);
356 if (_PyObject_LookupAttrId(p, &PyId___args__, &tmp) < 0) {
Maggie Moss1b4552c2020-09-09 13:23:24 -0700357 goto exit;
358 }
Serhiy Storchaka98c44332020-10-10 22:23:42 +0300359 if (tmp) {
Maggie Moss1b4552c2020-09-09 13:23:24 -0700360 // It looks like a GenericAlias
Serhiy Storchaka98c44332020-10-10 22:23:42 +0300361 Py_DECREF(tmp);
Maggie Moss1b4552c2020-09-09 13:23:24 -0700362 goto use_repr;
363 }
364 }
365
366 if (_PyObject_LookupAttrId(p, &PyId___qualname__, &qualname) < 0) {
367 goto exit;
368 }
369 if (qualname == NULL) {
370 goto use_repr;
371 }
372 if (_PyObject_LookupAttrId(p, &PyId___module__, &module) < 0) {
373 goto exit;
374 }
375 if (module == NULL || module == Py_None) {
376 goto use_repr;
377 }
378
379 // Looks like a class
380 if (PyUnicode_Check(module) &&
381 _PyUnicode_EqualToASCIIString(module, "builtins"))
382 {
383 // builtins don't need a module name
384 r = PyObject_Str(qualname);
385 goto exit;
386 }
387 else {
388 r = PyUnicode_FromFormat("%S.%S", module, qualname);
389 goto exit;
390 }
391
392use_repr:
393 r = PyObject_Repr(p);
394exit:
395 Py_XDECREF(qualname);
396 Py_XDECREF(module);
397 if (r == NULL) {
398 return -1;
399 }
400 err = _PyUnicodeWriter_WriteStr(writer, r);
401 Py_DECREF(r);
402 return err;
403}
404
405static PyObject *
406union_repr(PyObject *self)
407{
408 unionobject *alias = (unionobject *)self;
409 Py_ssize_t len = PyTuple_GET_SIZE(alias->args);
410
411 _PyUnicodeWriter writer;
412 _PyUnicodeWriter_Init(&writer);
413 for (Py_ssize_t i = 0; i < len; i++) {
414 if (i > 0 && _PyUnicodeWriter_WriteASCIIString(&writer, " | ", 3) < 0) {
415 goto error;
416 }
417 PyObject *p = PyTuple_GET_ITEM(alias->args, i);
418 if (union_repr_item(&writer, p) < 0) {
419 goto error;
420 }
421 }
422 return _PyUnicodeWriter_Finish(&writer);
423error:
424 _PyUnicodeWriter_Dealloc(&writer);
425 return NULL;
426}
427
428static PyMemberDef union_members[] = {
429 {"__args__", T_OBJECT, offsetof(unionobject, args), READONLY},
430 {0}
431};
432
433static PyMethodDef union_methods[] = {
434 {"__instancecheck__", union_instancecheck, METH_O},
435 {"__subclasscheck__", union_subclasscheck, METH_O},
436 {0}};
437
438static PyNumberMethods union_as_number = {
kj4eb41d02020-11-09 12:00:13 +0800439 .nb_or = _Py_union_type_or, // Add __or__ function
Maggie Moss1b4552c2020-09-09 13:23:24 -0700440};
441
442PyTypeObject _Py_UnionType = {
443 PyVarObject_HEAD_INIT(&PyType_Type, 0)
444 .tp_name = "types.Union",
445 .tp_doc = "Represent a PEP 604 union type\n"
446 "\n"
447 "E.g. for int | str",
448 .tp_basicsize = sizeof(unionobject),
449 .tp_dealloc = unionobject_dealloc,
450 .tp_alloc = PyType_GenericAlloc,
Miss Islington (bot)08561342021-07-03 06:33:16 -0700451 .tp_free = PyObject_GC_Del,
452 .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
453 .tp_traverse = union_traverse,
Maggie Moss1b4552c2020-09-09 13:23:24 -0700454 .tp_hash = union_hash,
455 .tp_getattro = PyObject_GenericGetAttr,
456 .tp_members = union_members,
457 .tp_methods = union_methods,
458 .tp_richcompare = union_richcompare,
459 .tp_as_number = &union_as_number,
460 .tp_repr = union_repr,
461};
462
463PyObject *
464_Py_Union(PyObject *args)
465{
466 assert(PyTuple_CheckExact(args));
467
468 unionobject* result = NULL;
469
470 // Check arguments are unionable.
Victor Stinnerd67de0a2020-09-23 23:25:54 +0200471 Py_ssize_t nargs = PyTuple_GET_SIZE(args);
Maggie Moss1b4552c2020-09-09 13:23:24 -0700472 for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
473 PyObject *arg = PyTuple_GET_ITEM(args, iarg);
474 if (arg == NULL) {
475 return NULL;
476 }
477 int is_arg_unionable = is_unionable(arg);
478 if (is_arg_unionable < 0) {
479 return NULL;
480 }
481 if (!is_arg_unionable) {
482 Py_INCREF(Py_NotImplemented);
483 return Py_NotImplemented;
484 }
485 }
486
Miss Islington (bot)08561342021-07-03 06:33:16 -0700487 result = PyObject_GC_New(unionobject, &_Py_UnionType);
Maggie Moss1b4552c2020-09-09 13:23:24 -0700488 if (result == NULL) {
489 return NULL;
490 }
491
492 result->args = dedup_and_flatten_args(args);
493 if (result->args == NULL) {
Miss Islington (bot)08561342021-07-03 06:33:16 -0700494 PyObject_GC_Del(result);
Maggie Moss1b4552c2020-09-09 13:23:24 -0700495 return NULL;
496 }
Miss Islington (bot)08561342021-07-03 06:33:16 -0700497 _PyObject_GC_TRACK(result);
Maggie Moss1b4552c2020-09-09 13:23:24 -0700498 return (PyObject*)result;
499}