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
|
Creating classes whose metaclass overrides
|
||||||
:c:member:`~PyTypeObject.tp_new` is no longer allowed.
|
: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
|
.. raw:: html
|
||||||
|
|
||||||
<!-- Keep old URL fragments working (see gh-97908) -->
|
<!-- Keep old URL fragments working (see gh-97908) -->
|
||||||
|
|
|
@ -684,6 +684,7 @@ func,PyTuple_Size,3.2,,
|
||||||
data,PyTuple_Type,3.2,,
|
data,PyTuple_Type,3.2,,
|
||||||
type,PyTypeObject,3.2,,opaque
|
type,PyTypeObject,3.2,,opaque
|
||||||
func,PyType_ClearCache,3.2,,
|
func,PyType_ClearCache,3.2,,
|
||||||
|
func,PyType_Freeze,3.14,,
|
||||||
func,PyType_FromMetaclass,3.12,,
|
func,PyType_FromMetaclass,3.12,,
|
||||||
func,PyType_FromModuleAndSpec,3.10,,
|
func,PyType_FromModuleAndSpec,3.10,,
|
||||||
func,PyType_FromSpec,3.2,,
|
func,PyType_FromSpec,3.2,,
|
||||||
|
|
|
@ -777,6 +777,9 @@ New features
|
||||||
(Contributed by Victor Stinner in :gh:`124502`.)
|
(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
|
Porting to Python 3.14
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
|
@ -796,6 +796,10 @@ static inline int PyType_CheckExact(PyObject *op) {
|
||||||
PyAPI_FUNC(PyObject *) PyType_GetModuleByDef(PyTypeObject *, PyModuleDef *);
|
PyAPI_FUNC(PyObject *) PyType_GetModuleByDef(PyTypeObject *, PyModuleDef *);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030e0000
|
||||||
|
PyAPI_FUNC(int) PyType_Freeze(PyTypeObject *type);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#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_Size",
|
||||||
"PyTuple_Type",
|
"PyTuple_Type",
|
||||||
"PyType_ClearCache",
|
"PyType_ClearCache",
|
||||||
|
"PyType_Freeze",
|
||||||
"PyType_FromMetaclass",
|
"PyType_FromMetaclass",
|
||||||
"PyType_FromModuleAndSpec",
|
"PyType_FromModuleAndSpec",
|
||||||
"PyType_FromSpec",
|
"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'
|
added = '3.14'
|
||||||
[function.PyUnicode_Equal]
|
[function.PyUnicode_Equal]
|
||||||
added = '3.14'
|
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;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Used by `finalize_thread_hang`.
|
// Used by `finalize_thread_hang`.
|
||||||
#ifdef _POSIX_THREADS
|
#ifdef _POSIX_THREADS
|
||||||
static void finalize_thread_hang_cleanup_callback(void *Py_UNUSED(arg)) {
|
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[] = {
|
static PyMethodDef TestMethods[] = {
|
||||||
{"set_errno", set_errno, METH_VARARGS},
|
{"set_errno", set_errno, METH_VARARGS},
|
||||||
{"test_config", test_config, METH_NOARGS},
|
{"test_config", test_config, METH_NOARGS},
|
||||||
|
@ -3479,6 +3494,7 @@ static PyMethodDef TestMethods[] = {
|
||||||
{"function_set_warning", function_set_warning, METH_NOARGS},
|
{"function_set_warning", function_set_warning, METH_NOARGS},
|
||||||
{"test_critical_sections", test_critical_sections, METH_NOARGS},
|
{"test_critical_sections", test_critical_sections, METH_NOARGS},
|
||||||
{"finalize_thread_hang", finalize_thread_hang, METH_O, NULL},
|
{"finalize_thread_hang", finalize_thread_hang, METH_O, NULL},
|
||||||
|
{"type_freeze", type_freeze, METH_VARARGS},
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4637,6 +4637,32 @@ check_basicsize_includes_size_and_offsets(PyTypeObject* type)
|
||||||
return 1;
|
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.
|
/* Set *dest to the offset specified by a special "__*offset__" member.
|
||||||
* Return 0 on success, -1 on failure.
|
* Return 0 on success, -1 on failure.
|
||||||
*/
|
*/
|
||||||
|
@ -4820,20 +4846,9 @@ PyType_FromMetaclass(
|
||||||
* and only heap types can be mutable.)
|
* and only heap types can be mutable.)
|
||||||
*/
|
*/
|
||||||
if (spec->flags & Py_TPFLAGS_IMMUTABLETYPE) {
|
if (spec->flags & Py_TPFLAGS_IMMUTABLETYPE) {
|
||||||
for (int i=0; i<PyTuple_GET_SIZE(bases); i++) {
|
if (check_immutable_bases(spec->name, bases, 0) < 0) {
|
||||||
PyTypeObject *b = (PyTypeObject*)PyTuple_GET_ITEM(bases, i);
|
|
||||||
if (!b) {
|
|
||||||
goto finally;
|
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 */
|
/* 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' */
|
/* Cooperative 'super' */
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
|
@ -646,6 +646,7 @@ EXPORT_FUNC(PyTuple_Pack)
|
||||||
EXPORT_FUNC(PyTuple_SetItem)
|
EXPORT_FUNC(PyTuple_SetItem)
|
||||||
EXPORT_FUNC(PyTuple_Size)
|
EXPORT_FUNC(PyTuple_Size)
|
||||||
EXPORT_FUNC(PyType_ClearCache)
|
EXPORT_FUNC(PyType_ClearCache)
|
||||||
|
EXPORT_FUNC(PyType_Freeze)
|
||||||
EXPORT_FUNC(PyType_FromMetaclass)
|
EXPORT_FUNC(PyType_FromMetaclass)
|
||||||
EXPORT_FUNC(PyType_FromModuleAndSpec)
|
EXPORT_FUNC(PyType_FromModuleAndSpec)
|
||||||
EXPORT_FUNC(PyType_FromSpec)
|
EXPORT_FUNC(PyType_FromSpec)
|
||||||
|
|
Loading…
Reference in New Issue