From 45700fb757591a672e9d25b8252971c2a2caeaf2 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 16 Dec 2017 11:25:56 +0200 Subject: [PATCH] Add tests for using PEP560 with classes implemented in C. (#4883) Based on tests from #4878 --- Lib/test/test_genericclass.py | 18 ++++++++ Modules/_testcapimodule.c | 85 +++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/Lib/test/test_genericclass.py b/Lib/test/test_genericclass.py index 214527b01f..2057fc000c 100644 --- a/Lib/test/test_genericclass.py +++ b/Lib/test/test_genericclass.py @@ -1,4 +1,5 @@ import unittest +from test import support class TestMROEntry(unittest.TestCase): @@ -248,5 +249,22 @@ class TestClassGetitem(unittest.TestCase): self.assertEqual(C[int], 'from metaclass') +@support.cpython_only +class CAPITest(unittest.TestCase): + + def test_c_class(self): + from _testcapi import Generic, GenericAlias + self.assertIsInstance(Generic.__class_getitem__(Generic, int), GenericAlias) + + IntGeneric = Generic[int] + self.assertIs(type(IntGeneric), GenericAlias) + self.assertEqual(IntGeneric.__mro_entries__(()), (int,)) + class C(IntGeneric): + pass + self.assertEqual(C.__bases__, (int,)) + self.assertEqual(C.__orig_bases__, (IntGeneric,)) + self.assertEqual(C.__mro__, (C, int, object)) + + if __name__ == "__main__": unittest.main() diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 4bb3e82d1d..47b50640f5 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -5053,6 +5053,81 @@ recurse_infinitely_error_init(PyObject *self, PyObject *args, PyObject *kwds) } +/* Test PEP 560 */ + +typedef struct { + PyObject_HEAD + PyObject *item; +} PyGenericAliasObject; + +static void +generic_alias_dealloc(PyGenericAliasObject *self) +{ + Py_CLEAR(self->item); +} + +static PyObject * +generic_alias_mro_entries(PyGenericAliasObject *self, PyObject *bases) +{ + return PyTuple_Pack(1, self->item); +} + +static PyMethodDef generic_alias_methods[] = { + {"__mro_entries__", (PyCFunction) generic_alias_mro_entries, METH_O, NULL}, + {NULL} /* sentinel */ +}; + +PyTypeObject GenericAlias_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "GenericAlias", + sizeof(PyGenericAliasObject), + 0, + .tp_dealloc = (destructor)generic_alias_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_methods = generic_alias_methods, +}; + +static PyObject * +generic_alias_new(PyObject *item) +{ + PyGenericAliasObject *o = PyObject_New(PyGenericAliasObject, &GenericAlias_Type); + if (o == NULL) { + return NULL; + } + Py_INCREF(item); + o->item = item; + return (PyObject*) o; +} + +typedef struct { + PyObject_HEAD +} PyGenericObject; + +static PyObject * +generic_class_getitem(PyObject *self, PyObject *args) +{ + PyObject *type, *item; + if (!PyArg_UnpackTuple(args, "__class_getitem__", 2, 2, &type, &item)) { + return NULL; + } + return generic_alias_new(item); +} + +static PyMethodDef generic_methods[] = { + {"__class_getitem__", generic_class_getitem, METH_VARARGS|METH_STATIC, NULL}, + {NULL} /* sentinel */ +}; + +PyTypeObject Generic_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "Generic", + sizeof(PyGenericObject), + 0, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_methods = generic_methods, +}; + + static struct PyModuleDef _testcapimodule = { PyModuleDef_HEAD_INIT, "_testcapi", @@ -5094,6 +5169,16 @@ PyInit__testcapi(void) Py_INCREF(&awaitType); PyModule_AddObject(m, "awaitType", (PyObject *)&awaitType); + if (PyType_Ready(&GenericAlias_Type) < 0) + return NULL; + Py_INCREF(&GenericAlias_Type); + PyModule_AddObject(m, "GenericAlias", (PyObject *)&GenericAlias_Type); + + if (PyType_Ready(&Generic_Type) < 0) + return NULL; + Py_INCREF(&Generic_Type); + PyModule_AddObject(m, "Generic", (PyObject *)&Generic_Type); + PyRecursingInfinitelyError_Type.tp_base = (PyTypeObject *)PyExc_Exception; if (PyType_Ready(&PyRecursingInfinitelyError_Type) < 0) { return NULL; -- 2.40.0