mirror of
https://github.com/python/cpython.git
synced 2025-09-26 10:19:53 +00:00
bpo-44490: Add __parameters__ and __getitem__ to types.Union (GH-26980)
Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Co-authored-by: Guido van Rossum <gvanrossum@gmail.com>
This commit is contained in:
parent
8b849ea0f3
commit
c45fa1a5d9
5 changed files with 101 additions and 19 deletions
|
@ -5,6 +5,11 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef Py_LIMITED_API
|
||||||
|
PyAPI_FUNC(PyObject *) _Py_subs_parameters(PyObject *, PyObject *, PyObject *, PyObject *);
|
||||||
|
PyAPI_FUNC(PyObject *) _Py_make_parameters(PyObject *);
|
||||||
|
#endif
|
||||||
|
|
||||||
PyAPI_FUNC(PyObject *) Py_GenericAlias(PyObject *, PyObject *);
|
PyAPI_FUNC(PyObject *) Py_GenericAlias(PyObject *, PyObject *);
|
||||||
PyAPI_DATA(PyTypeObject) Py_GenericAliasType;
|
PyAPI_DATA(PyTypeObject) Py_GenericAliasType;
|
||||||
|
|
||||||
|
|
|
@ -666,6 +666,16 @@ class TypesTests(unittest.TestCase):
|
||||||
assert TV | str == typing.Union[TV, str]
|
assert TV | str == typing.Union[TV, str]
|
||||||
assert str | TV == typing.Union[str, TV]
|
assert str | TV == typing.Union[str, TV]
|
||||||
|
|
||||||
|
def test_union_parameter_chaining(self):
|
||||||
|
T = typing.TypeVar("T")
|
||||||
|
S = typing.TypeVar("S")
|
||||||
|
|
||||||
|
self.assertEqual((float | list[T])[int], float | list[int])
|
||||||
|
self.assertEqual(list[int | list[T]].__parameters__, (T,))
|
||||||
|
self.assertEqual(list[int | list[T]][str], list[int | list[str]])
|
||||||
|
self.assertEqual((list[T] | list[S]).__parameters__, (T, S))
|
||||||
|
self.assertEqual((list[T] | list[S])[int, T], list[int] | list[T])
|
||||||
|
|
||||||
def test_or_type_operator_with_forward(self):
|
def test_or_type_operator_with_forward(self):
|
||||||
T = typing.TypeVar('T')
|
T = typing.TypeVar('T')
|
||||||
ForwardAfter = T | 'Forward'
|
ForwardAfter = T | 'Forward'
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Add ``__parameters__`` attribute and ``__getitem__``
|
||||||
|
operator to ``types.Union``. Patch provided by Yurii Karabas.
|
|
@ -198,8 +198,8 @@ tuple_add(PyObject *self, Py_ssize_t len, PyObject *item)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
PyObject *
|
||||||
make_parameters(PyObject *args)
|
_Py_make_parameters(PyObject *args)
|
||||||
{
|
{
|
||||||
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
|
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
|
||||||
Py_ssize_t len = nargs;
|
Py_ssize_t len = nargs;
|
||||||
|
@ -294,18 +294,10 @@ subs_tvars(PyObject *obj, PyObject *params, PyObject **argitems)
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
PyObject *
|
||||||
ga_getitem(PyObject *self, PyObject *item)
|
_Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObject *item)
|
||||||
{
|
{
|
||||||
gaobject *alias = (gaobject *)self;
|
Py_ssize_t nparams = PyTuple_GET_SIZE(parameters);
|
||||||
// do a lookup for __parameters__ so it gets populated (if not already)
|
|
||||||
if (alias->parameters == NULL) {
|
|
||||||
alias->parameters = make_parameters(alias->args);
|
|
||||||
if (alias->parameters == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Py_ssize_t nparams = PyTuple_GET_SIZE(alias->parameters);
|
|
||||||
if (nparams == 0) {
|
if (nparams == 0) {
|
||||||
return PyErr_Format(PyExc_TypeError,
|
return PyErr_Format(PyExc_TypeError,
|
||||||
"There are no type variables left in %R",
|
"There are no type variables left in %R",
|
||||||
|
@ -320,32 +312,32 @@ ga_getitem(PyObject *self, PyObject *item)
|
||||||
nitems > nparams ? "many" : "few",
|
nitems > nparams ? "many" : "few",
|
||||||
self);
|
self);
|
||||||
}
|
}
|
||||||
/* Replace all type variables (specified by alias->parameters)
|
/* Replace all type variables (specified by parameters)
|
||||||
with corresponding values specified by argitems.
|
with corresponding values specified by argitems.
|
||||||
t = list[T]; t[int] -> newargs = [int]
|
t = list[T]; t[int] -> newargs = [int]
|
||||||
t = dict[str, T]; t[int] -> newargs = [str, int]
|
t = dict[str, T]; t[int] -> newargs = [str, int]
|
||||||
t = dict[T, list[S]]; t[str, int] -> newargs = [str, list[int]]
|
t = dict[T, list[S]]; t[str, int] -> newargs = [str, list[int]]
|
||||||
*/
|
*/
|
||||||
Py_ssize_t nargs = PyTuple_GET_SIZE(alias->args);
|
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
|
||||||
PyObject *newargs = PyTuple_New(nargs);
|
PyObject *newargs = PyTuple_New(nargs);
|
||||||
if (newargs == NULL) {
|
if (newargs == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
|
for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
|
||||||
PyObject *arg = PyTuple_GET_ITEM(alias->args, iarg);
|
PyObject *arg = PyTuple_GET_ITEM(args, iarg);
|
||||||
int typevar = is_typevar(arg);
|
int typevar = is_typevar(arg);
|
||||||
if (typevar < 0) {
|
if (typevar < 0) {
|
||||||
Py_DECREF(newargs);
|
Py_DECREF(newargs);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (typevar) {
|
if (typevar) {
|
||||||
Py_ssize_t iparam = tuple_index(alias->parameters, nparams, arg);
|
Py_ssize_t iparam = tuple_index(parameters, nparams, arg);
|
||||||
assert(iparam >= 0);
|
assert(iparam >= 0);
|
||||||
arg = argitems[iparam];
|
arg = argitems[iparam];
|
||||||
Py_INCREF(arg);
|
Py_INCREF(arg);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
arg = subs_tvars(arg, alias->parameters, argitems);
|
arg = subs_tvars(arg, parameters, argitems);
|
||||||
if (arg == NULL) {
|
if (arg == NULL) {
|
||||||
Py_DECREF(newargs);
|
Py_DECREF(newargs);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -354,6 +346,26 @@ ga_getitem(PyObject *self, PyObject *item)
|
||||||
PyTuple_SET_ITEM(newargs, iarg, arg);
|
PyTuple_SET_ITEM(newargs, iarg, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return newargs;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
ga_getitem(PyObject *self, PyObject *item)
|
||||||
|
{
|
||||||
|
gaobject *alias = (gaobject *)self;
|
||||||
|
// Populate __parameters__ if needed.
|
||||||
|
if (alias->parameters == NULL) {
|
||||||
|
alias->parameters = _Py_make_parameters(alias->args);
|
||||||
|
if (alias->parameters == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *newargs = _Py_subs_parameters(self, alias->args, alias->parameters, item);
|
||||||
|
if (newargs == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
PyObject *res = Py_GenericAlias(alias->origin, newargs);
|
PyObject *res = Py_GenericAlias(alias->origin, newargs);
|
||||||
|
|
||||||
Py_DECREF(newargs);
|
Py_DECREF(newargs);
|
||||||
|
@ -550,7 +562,7 @@ ga_parameters(PyObject *self, void *unused)
|
||||||
{
|
{
|
||||||
gaobject *alias = (gaobject *)self;
|
gaobject *alias = (gaobject *)self;
|
||||||
if (alias->parameters == NULL) {
|
if (alias->parameters == NULL) {
|
||||||
alias->parameters = make_parameters(alias->args);
|
alias->parameters = _Py_make_parameters(alias->args);
|
||||||
if (alias->parameters == NULL) {
|
if (alias->parameters == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
PyObject *args;
|
PyObject *args;
|
||||||
|
PyObject *parameters;
|
||||||
} unionobject;
|
} unionobject;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -18,6 +19,7 @@ unionobject_dealloc(PyObject *self)
|
||||||
_PyObject_GC_UNTRACK(self);
|
_PyObject_GC_UNTRACK(self);
|
||||||
|
|
||||||
Py_XDECREF(alias->args);
|
Py_XDECREF(alias->args);
|
||||||
|
Py_XDECREF(alias->parameters);
|
||||||
Py_TYPE(self)->tp_free(self);
|
Py_TYPE(self)->tp_free(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +28,7 @@ union_traverse(PyObject *self, visitproc visit, void *arg)
|
||||||
{
|
{
|
||||||
unionobject *alias = (unionobject *)self;
|
unionobject *alias = (unionobject *)self;
|
||||||
Py_VISIT(alias->args);
|
Py_VISIT(alias->args);
|
||||||
|
Py_VISIT(alias->parameters);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,6 +438,53 @@ static PyMethodDef union_methods[] = {
|
||||||
{"__subclasscheck__", union_subclasscheck, METH_O},
|
{"__subclasscheck__", union_subclasscheck, METH_O},
|
||||||
{0}};
|
{0}};
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
union_getitem(PyObject *self, PyObject *item)
|
||||||
|
{
|
||||||
|
unionobject *alias = (unionobject *)self;
|
||||||
|
// Populate __parameters__ if needed.
|
||||||
|
if (alias->parameters == NULL) {
|
||||||
|
alias->parameters = _Py_make_parameters(alias->args);
|
||||||
|
if (alias->parameters == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *newargs = _Py_subs_parameters(self, alias->args, alias->parameters, item);
|
||||||
|
if (newargs == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *res = _Py_Union(newargs);
|
||||||
|
|
||||||
|
Py_DECREF(newargs);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyMappingMethods union_as_mapping = {
|
||||||
|
.mp_subscript = union_getitem,
|
||||||
|
};
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
union_parameters(PyObject *self, void *Py_UNUSED(unused))
|
||||||
|
{
|
||||||
|
unionobject *alias = (unionobject *)self;
|
||||||
|
if (alias->parameters == NULL) {
|
||||||
|
alias->parameters = _Py_make_parameters(alias->args);
|
||||||
|
if (alias->parameters == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Py_INCREF(alias->parameters);
|
||||||
|
return alias->parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyGetSetDef union_properties[] = {
|
||||||
|
{"__parameters__", union_parameters, (setter)NULL, "Type variables in the types.Union.", NULL},
|
||||||
|
{0}
|
||||||
|
};
|
||||||
|
|
||||||
static PyNumberMethods union_as_number = {
|
static PyNumberMethods union_as_number = {
|
||||||
.nb_or = _Py_union_type_or, // Add __or__ function
|
.nb_or = _Py_union_type_or, // Add __or__ function
|
||||||
};
|
};
|
||||||
|
@ -456,8 +506,10 @@ PyTypeObject _Py_UnionType = {
|
||||||
.tp_members = union_members,
|
.tp_members = union_members,
|
||||||
.tp_methods = union_methods,
|
.tp_methods = union_methods,
|
||||||
.tp_richcompare = union_richcompare,
|
.tp_richcompare = union_richcompare,
|
||||||
|
.tp_as_mapping = &union_as_mapping,
|
||||||
.tp_as_number = &union_as_number,
|
.tp_as_number = &union_as_number,
|
||||||
.tp_repr = union_repr,
|
.tp_repr = union_repr,
|
||||||
|
.tp_getset = union_properties,
|
||||||
};
|
};
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
|
@ -489,6 +541,7 @@ _Py_Union(PyObject *args)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result->parameters = NULL;
|
||||||
result->args = dedup_and_flatten_args(args);
|
result->args = dedup_and_flatten_args(args);
|
||||||
_PyObject_GC_TRACK(result);
|
_PyObject_GC_TRACK(result);
|
||||||
if (result->args == NULL) {
|
if (result->args == NULL) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue