diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py index 2d2adc11e1594b..b311ee66ca1210 100644 --- a/Lib/test/test_genericalias.py +++ b/Lib/test/test_genericalias.py @@ -343,6 +343,8 @@ def test_equality(self): self.assertEqual(list[int], list[int]) self.assertEqual(dict[str, int], dict[str, int]) self.assertEqual((*tuple[int],)[0], (*tuple[int],)[0]) + self.assertNotEqual(tuple[int], (*tuple[int],)[0]) + self.assertNotEqual(tuple[int], Unpack[tuple[int]]) self.assertEqual( tuple[ tuple( # Effectively the same as starring; TODO @@ -394,6 +396,9 @@ def test_pickle(self): self.assertEqual(loaded.__origin__, alias.__origin__) self.assertEqual(loaded.__args__, alias.__args__) self.assertEqual(loaded.__parameters__, alias.__parameters__) + if isinstance(alias, GenericAlias): + self.assertEqual(loaded.__unpacked__, alias.__unpacked__) + def test_copy(self): class X(list): @@ -418,12 +423,28 @@ def __deepcopy__(self, memo): self.assertEqual(copied.__args__, alias.__args__) self.assertEqual(copied.__parameters__, alias.__parameters__) + def test_non_bool_to_third_constructor_argument_raises_typeerror(self): + with self.assertRaises(TypeError): + GenericAlias(tuple, int, 0) + with self.assertRaises(TypeError): + GenericAlias(tuple, int, 'foo') + with self.assertRaises(TypeError): + GenericAlias(tuple, int, list) + def test_unpack(self): - alias = tuple[str, ...] - self.assertIs(alias.__unpacked__, False) - unpacked = (*alias,)[0] + alias1 = tuple[str, ...] + self.assertIs(alias1.__unpacked__, False) + unpacked = (*alias1,)[0] self.assertIs(unpacked.__unpacked__, True) + # The (optional) third positional argument should control unpackedness. + alias2 = GenericAlias(tuple, int) + self.assertIs(alias2.__unpacked__, False) + alias3 = GenericAlias(tuple, int, False) + self.assertIs(alias3.__unpacked__, False) + alias4 = GenericAlias(tuple, int, True) + self.assertIs(alias4.__unpacked__, True) + def test_union(self): a = typing.Union[list[int], list[str]] self.assertEqual(a.__args__, (list[int], list[str])) diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-05-03-20-49-53.gh-issue-87390.QKwANx.rst b/Misc/NEWS.d/next/Core and Builtins/2022-05-03-20-49-53.gh-issue-87390.QKwANx.rst new file mode 100644 index 00000000000000..f051c08f9a0d7d --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-05-03-20-49-53.gh-issue-87390.QKwANx.rst @@ -0,0 +1 @@ +Give ``types.GenericAlias`` constructor an optional third ``bool`` argument that controls whether it represents an unpacked type (e.g. ``*tuple[int, str]``). diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index c6ed1611bd29ec..471d22f8ff0cd6 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -567,6 +567,9 @@ ga_richcompare(PyObject *a, PyObject *b, int op) gaobject *aa = (gaobject *)a; gaobject *bb = (gaobject *)b; + if (aa->starred != bb->starred) { + Py_RETURN_FALSE; + } int eq = PyObject_RichCompareBool(aa->origin, bb->origin, Py_EQ); if (eq < 0) { return NULL; @@ -604,8 +607,13 @@ static PyObject * ga_reduce(PyObject *self, PyObject *Py_UNUSED(ignored)) { gaobject *alias = (gaobject *)self; - return Py_BuildValue("O(OO)", Py_TYPE(alias), - alias->origin, alias->args); + PyObject *starred = PyBool_FromLong(alias->starred); + PyObject *value = Py_BuildValue("O(OOO)", Py_TYPE(alias), + alias->origin, alias->args, starred); + // Avoid double increment of reference count on Py_True/Py_False - once from + // PyBool_FromLong, and once from Py_BuildValue. + Py_CLEAR(starred); + return value; } static PyObject * @@ -685,7 +693,7 @@ static PyGetSetDef ga_properties[] = { * Returns 1 on success, 0 on failure. */ static inline int -setup_ga(gaobject *alias, PyObject *origin, PyObject *args) { +setup_ga(gaobject *alias, PyObject *origin, PyObject *args, bool starred) { if (!PyTuple_Check(args)) { args = PyTuple_Pack(1, args); if (args == NULL) { @@ -700,6 +708,7 @@ setup_ga(gaobject *alias, PyObject *origin, PyObject *args) { alias->origin = origin; alias->args = args; alias->parameters = NULL; + alias->starred = starred; alias->weakreflist = NULL; if (PyVectorcall_Function(origin) != NULL) { @@ -718,16 +727,33 @@ ga_new(PyTypeObject *type, PyObject *args, PyObject *kwds) if (!_PyArg_NoKeywords("GenericAlias", kwds)) { return NULL; } - if (!_PyArg_CheckPositional("GenericAlias", PyTuple_GET_SIZE(args), 2, 2)) { + if (!_PyArg_CheckPositional("GenericAlias", PyTuple_GET_SIZE(args), 2, 3)) { return NULL; } + PyObject *origin = PyTuple_GET_ITEM(args, 0); PyObject *arguments = PyTuple_GET_ITEM(args, 1); + + bool starred; + if (PyTuple_GET_SIZE(args) < 3) { + starred = false; + } else { + PyObject *py_starred = PyTuple_GET_ITEM(args, 2); + if (!PyBool_Check(py_starred)) { + PyErr_SetString(PyExc_TypeError, + "Third argument to constructor of GenericAlias " + "must be a bool (since it's a flag controlling " + "whether the alias is unpacked)"); + return NULL; + } + starred = PyLong_AsLong(py_starred); + } + gaobject *self = (gaobject *)type->tp_alloc(type, 0); if (self == NULL) { return NULL; } - if (!setup_ga(self, origin, arguments)) { + if (!setup_ga(self, origin, arguments, starred)) { Py_DECREF(self); return NULL; } @@ -837,7 +863,7 @@ Py_GenericAlias(PyObject *origin, PyObject *args) if (alias == NULL) { return NULL; } - if (!setup_ga(alias, origin, args)) { + if (!setup_ga(alias, origin, args, false)) { Py_DECREF(alias); return NULL; }