mirror of https://github.com/python/cpython
gh-121654: Add PyType_Freeze() function (#122457)
Co-authored-by: Petr Viktorin <encukou@gmail.com>
This commit is contained in:
parent
da8673da36
commit
db96327203
|
@ -413,6 +413,20 @@ The following functions and structs are used to create
|
|||
Creating classes whose metaclass overrides
|
||||
:c:member:`~PyTypeObject.tp_new` is no longer allowed.
|
||||
|
||||
.. c:function:: int PyType_Freeze(PyTypeObject *type)
|
||||
|
||||
Make a type immutable: set the :c:macro:`Py_TPFLAGS_IMMUTABLETYPE` flag.
|
||||
|
||||
All base classes of *type* must be immutable.
|
||||
|
||||
On success, return ``0``.
|
||||
On error, set an exception and return ``-1``.
|
||||
|
||||
The type must not be used before it's made immutable. For example, type
|
||||
instances must not be created before the type is made immutable.
|
||||
|
||||
.. versionadded:: 3.14
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<!-- Keep old URL fragments working (see gh-97908) -->
|
||||
|
|
|
@ -684,6 +684,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,,
|
||||
|
|
|
@ -777,6 +777,9 @@ New features
|
|||
(Contributed by Victor Stinner in :gh:`124502`.)
|
||||
|
||||
|
||||
* Add :c:func:`PyType_Freeze` function to make a type immutable.
|
||||
(Contributed by Victor Stinner in :gh:`121654`.)
|
||||
|
||||
Porting to Python 3.14
|
||||
----------------------
|
||||
|
||||
|
|
|
@ -796,6 +796,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
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
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
|
||||
|
||||
# simple case, no inherante
|
||||
class MyType:
|
||||
pass
|
||||
MyType.attr = "mutable"
|
||||
|
||||
type_freeze(MyType)
|
||||
err_msg = "cannot set 'attr' attribute of immutable type 'MyType'"
|
||||
with self.assertRaisesRegex(TypeError, err_msg):
|
||||
# the class is now immutable
|
||||
MyType.attr = "immutable"
|
||||
|
||||
# test MRO: PyType_Freeze() requires base classes to be immutable
|
||||
class A: pass
|
||||
class B: pass
|
||||
class C(B): pass
|
||||
class D(A, C): pass
|
||||
|
||||
self.assertEqual(D.mro(), [D, A, C, B, object])
|
||||
with self.assertRaises(TypeError):
|
||||
type_freeze(D)
|
||||
|
||||
type_freeze(A)
|
||||
type_freeze(B)
|
||||
type_freeze(C)
|
||||
# all parent classes are now immutable, so D can be made immutable
|
||||
# as well
|
||||
type_freeze(D)
|
||||
|
||||
def test_freeze_meta(self):
|
||||
"""test PyType_Freeze() with overridden MRO"""
|
||||
type_freeze = _testcapi.type_freeze
|
||||
|
||||
class Base:
|
||||
value = 1
|
||||
|
||||
class Meta(type):
|
||||
def mro(cls):
|
||||
return (cls, Base, object)
|
||||
|
||||
class FreezeThis(metaclass=Meta):
|
||||
"""This has `Base` in the MRO, but not tp_bases"""
|
||||
|
||||
self.assertEqual(FreezeThis.value, 1)
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
type_freeze(FreezeThis)
|
||||
|
||||
Base.value = 2
|
||||
self.assertEqual(FreezeThis.value, 2)
|
||||
|
||||
type_freeze(Base)
|
||||
with self.assertRaises(TypeError):
|
||||
Base.value = 3
|
||||
type_freeze(FreezeThis)
|
||||
self.assertEqual(FreezeThis.value, 2)
|
|
@ -713,6 +713,7 @@ SYMBOL_NAMES = (
|
|||
"PyTuple_Size",
|
||||
"PyTuple_Type",
|
||||
"PyType_ClearCache",
|
||||
"PyType_Freeze",
|
||||
"PyType_FromMetaclass",
|
||||
"PyType_FromModuleAndSpec",
|
||||
"PyType_FromSpec",
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Add :c:func:`PyType_Freeze` function to make a type immutable. Patch by
|
||||
Victor Stinner.
|
|
@ -2538,3 +2538,5 @@
|
|||
added = '3.14'
|
||||
[function.PyUnicode_Equal]
|
||||
added = '3.14'
|
||||
[function.PyType_Freeze]
|
||||
added = '3.14'
|
||||
|
|
|
@ -3310,6 +3310,7 @@ test_critical_sections(PyObject *module, PyObject *Py_UNUSED(args))
|
|||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
// Used by `finalize_thread_hang`.
|
||||
#ifdef _POSIX_THREADS
|
||||
static void finalize_thread_hang_cleanup_callback(void *Py_UNUSED(arg)) {
|
||||
|
@ -3339,6 +3340,20 @@ finalize_thread_hang(PyObject *self, PyObject *callback)
|
|||
}
|
||||
|
||||
|
||||
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},
|
||||
|
@ -3479,6 +3494,7 @@ static PyMethodDef TestMethods[] = {
|
|||
{"function_set_warning", function_set_warning, METH_NOARGS},
|
||||
{"test_critical_sections", test_critical_sections, METH_NOARGS},
|
||||
{"finalize_thread_hang", finalize_thread_hang, METH_O, NULL},
|
||||
{"type_freeze", type_freeze, METH_VARARGS},
|
||||
{NULL, NULL} /* sentinel */
|
||||
};
|
||||
|
||||
|
|
|
@ -4637,6 +4637,32 @@ check_basicsize_includes_size_and_offsets(PyTypeObject* type)
|
|||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
check_immutable_bases(const char *type_name, PyObject *bases, int skip_first)
|
||||
{
|
||||
Py_ssize_t i = 0;
|
||||
if (skip_first) {
|
||||
// When testing the MRO, skip the type itself
|
||||
i = 1;
|
||||
}
|
||||
for (; i<PyTuple_GET_SIZE(bases); i++) {
|
||||
PyTypeObject *b = (PyTypeObject*)PyTuple_GET_ITEM(bases, i);
|
||||
if (!b) {
|
||||
return -1;
|
||||
}
|
||||
if (!_PyType_HasFeature(b, Py_TPFLAGS_IMMUTABLETYPE)) {
|
||||
PyErr_Format(
|
||||
PyExc_TypeError,
|
||||
"Creating immutable type %s from mutable base %N",
|
||||
type_name, b
|
||||
);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Set *dest to the offset specified by a special "__*offset__" member.
|
||||
* Return 0 on success, -1 on failure.
|
||||
*/
|
||||
|
@ -4820,20 +4846,9 @@ PyType_FromMetaclass(
|
|||
* and only heap types can be mutable.)
|
||||
*/
|
||||
if (spec->flags & Py_TPFLAGS_IMMUTABLETYPE) {
|
||||
for (int i=0; i<PyTuple_GET_SIZE(bases); i++) {
|
||||
PyTypeObject *b = (PyTypeObject*)PyTuple_GET_ITEM(bases, i);
|
||||
if (!b) {
|
||||
if (check_immutable_bases(spec->name, bases, 0) < 0) {
|
||||
goto finally;
|
||||
}
|
||||
if (!_PyType_HasFeature(b, Py_TPFLAGS_IMMUTABLETYPE)) {
|
||||
PyErr_Format(
|
||||
PyExc_TypeError,
|
||||
"Creating immutable type %s from mutable base %N",
|
||||
spec->name, b
|
||||
);
|
||||
goto finally;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Calculate the metaclass */
|
||||
|
@ -11319,6 +11334,30 @@ add_operators(PyTypeObject *type)
|
|||
}
|
||||
|
||||
|
||||
int
|
||||
PyType_Freeze(PyTypeObject *type)
|
||||
{
|
||||
// gh-121654: Check the __mro__ instead of __bases__
|
||||
PyObject *mro = type_get_mro(type, NULL);
|
||||
if (!PyTuple_Check(mro)) {
|
||||
Py_DECREF(mro);
|
||||
PyErr_SetString(PyExc_TypeError, "unable to get the type MRO");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int check = check_immutable_bases(type->tp_name, mro, 1);
|
||||
Py_DECREF(mro);
|
||||
if (check < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE;
|
||||
PyType_Modified(type);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Cooperative 'super' */
|
||||
|
||||
typedef struct {
|
||||
|
|
|
@ -646,6 +646,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)
|
||||
|
|
Loading…
Reference in New Issue