From 2d24aba77bc9624563e6eb4543051a1a8b682a23 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 30 Jul 2024 14:39:10 +0200 Subject: [PATCH] gh-121654: Add PyType_Freeze() function --- Doc/c-api/type.rst | 11 +++++ Doc/data/stable_abi.dat | 1 + Doc/whatsnew/3.14.rst | 3 ++ Include/object.h | 4 ++ Lib/test/test_capi/test_type.py | 27 +++++++++++ Lib/test/test_stable_abi_ctypes.py | 1 + ...-07-30-14-40-08.gh-issue-121654.tgGeAl.rst | 2 + Misc/stable_abi.toml | 2 + Modules/_testcapimodule.c | 14 ++++++ Objects/typeobject.c | 47 ++++++++++++++----- PC/python3dll.c | 1 + 11 files changed, 100 insertions(+), 13 deletions(-) create mode 100644 Lib/test/test_capi/test_type.py create mode 100644 Misc/NEWS.d/next/C_API/2024-07-30-14-40-08.gh-issue-121654.tgGeAl.rst diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index 0cae5c09505ebe..42bc09ae4877e7 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -381,6 +381,17 @@ The following functions and structs are used to create :c:member:`~PyTypeObject.tp_new` is deprecated and in Python 3.14+ it will be no longer allowed. +.. c:function:: int PyType_Freeze(PyTypeObject *type) + + Make a type immutable: set the :c:macro:`Py_TPFLAGS_IMMUTABLETYPE` flag. + + Base classes must be immutable. + + On success, return ``0``. + On error, set an exception and return ``-1``. + + .. versionadded:: 3.14 + .. raw:: html diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 592e3465824893..e1079bff6673ea 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -676,6 +676,7 @@ func,PyTuple_Size,3.2,, data,PyTuple_Type,3.2,, type,PyTypeObject,3.2,,opaque func,PyType_ClearCache,3.2,, +func,PyType_Freeze,3.14,, func,PyType_FromMetaclass,3.12,, func,PyType_FromModuleAndSpec,3.10,, func,PyType_FromSpec,3.2,, diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 34434e4fb52873..eff33b48822aa1 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -472,6 +472,9 @@ New Features an interned string and deallocate it during module shutdown. (Contribued by Eddie Elizondo in :gh:`113601`.) +* Add :c:func:`PyType_Freeze` function to make a type immutable. + (Contributed by Victor Stinner in :gh:`121654`.) + Porting to Python 3.14 ---------------------- diff --git a/Include/object.h b/Include/object.h index abfdb6ce24df21..50910222ea1740 100644 --- a/Include/object.h +++ b/Include/object.h @@ -786,6 +786,10 @@ static inline int PyType_CheckExact(PyObject *op) { PyAPI_FUNC(PyObject *) PyType_GetModuleByDef(PyTypeObject *, PyModuleDef *); #endif +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030e0000 +PyAPI_FUNC(int) PyType_Freeze(PyTypeObject *type); +#endif + #ifdef __cplusplus } #endif diff --git a/Lib/test/test_capi/test_type.py b/Lib/test/test_capi/test_type.py new file mode 100644 index 00000000000000..1ed346e5ad4745 --- /dev/null +++ b/Lib/test/test_capi/test_type.py @@ -0,0 +1,27 @@ +from test.support import import_helper +import unittest + +_testcapi = import_helper.import_module('_testcapi') + + +class TypeTests(unittest.TestCase): + def test_freeze(self): + # test PyType_Freeze() + type_freeze = _testcapi.type_freeze + + class MyType: + pass + MyType.attr = "mutable" + + type_freeze(MyType) + with self.assertRaises(TypeError): + MyType.attr = "immutable" + + # PyType_Freeze() requires base classes to be immutable + class Mutable: + "mutable base class" + pass + class MyType2(Mutable): + pass + with self.assertRaises(TypeError): + type_freeze(MyType2) diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index fedad17621cb02..c1e098394b8226 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -705,6 +705,7 @@ def test_windows_feature_macros(self): "PyTuple_Size", "PyTuple_Type", "PyType_ClearCache", + "PyType_Freeze", "PyType_FromMetaclass", "PyType_FromModuleAndSpec", "PyType_FromSpec", diff --git a/Misc/NEWS.d/next/C_API/2024-07-30-14-40-08.gh-issue-121654.tgGeAl.rst b/Misc/NEWS.d/next/C_API/2024-07-30-14-40-08.gh-issue-121654.tgGeAl.rst new file mode 100644 index 00000000000000..134d36c281ab21 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2024-07-30-14-40-08.gh-issue-121654.tgGeAl.rst @@ -0,0 +1,2 @@ +Add :c:func:`PyType_Freeze` function to make a type immutable. Patch by +Victor Stinner. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index c38671e389ac5e..8637fb0656225e 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2510,3 +2510,5 @@ added = '3.14' [function.PyIter_NextItem] added = '3.14' +[function.PyType_Freeze] + added = '3.14' diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 981efb9629031b..98b2f1c688ca67 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3329,6 +3329,19 @@ test_critical_sections(PyObject *module, PyObject *Py_UNUSED(args)) Py_RETURN_NONE; } +static PyObject * +type_freeze(PyObject *module, PyObject *args) +{ + PyTypeObject *type; + if (!PyArg_ParseTuple(args, "O!", &PyType_Type, &type)) { + return NULL; + } + if (PyType_Freeze(type) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + static PyMethodDef TestMethods[] = { {"set_errno", set_errno, METH_VARARGS}, {"test_config", test_config, METH_NOARGS}, @@ -3469,6 +3482,7 @@ static PyMethodDef TestMethods[] = { {"test_weakref_capi", test_weakref_capi, METH_NOARGS}, {"function_set_warning", function_set_warning, METH_NOARGS}, {"test_critical_sections", test_critical_sections, METH_NOARGS}, + {"type_freeze", type_freeze, METH_VARARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index f74d51222b7a65..65a6b0b6d30d99 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4642,6 +4642,26 @@ check_basicsize_includes_size_and_offsets(PyTypeObject* type) return 1; } +static int +check_immutable_bases(const char *type_name, PyObject *bases) +{ + for (Py_ssize_t i=0; iflags & Py_TPFLAGS_IMMUTABLETYPE) { - for (int i=0; iname, b - ); - goto finally; - } + if (check_immutable_bases(spec->name, bases) < 0) { + goto finally; } } @@ -11178,6 +11187,18 @@ add_operators(PyTypeObject *type, PyTypeObject *def) } +int +PyType_Freeze(PyTypeObject *type) +{ + PyObject *bases = type->tp_bases; + if (check_immutable_bases(type->tp_name, bases) < 0) { + return -1; + } + type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE; + return 0; +} + + /* Cooperative 'super' */ typedef struct { diff --git a/PC/python3dll.c b/PC/python3dll.c index 78bcef155f51d5..6b459caaafb290 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -637,6 +637,7 @@ EXPORT_FUNC(PyTuple_Pack) EXPORT_FUNC(PyTuple_SetItem) EXPORT_FUNC(PyTuple_Size) EXPORT_FUNC(PyType_ClearCache) +EXPORT_FUNC(PyType_Freeze) EXPORT_FUNC(PyType_FromMetaclass) EXPORT_FUNC(PyType_FromModuleAndSpec) EXPORT_FUNC(PyType_FromSpec)