Skip to content

Commit

Permalink
pythongh-121654: Add PyType_Freeze() function
Browse files Browse the repository at this point in the history
  • Loading branch information
vstinner committed Aug 26, 2024
1 parent a1ddaae commit 2d24aba
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 13 deletions.
11 changes: 11 additions & 0 deletions Doc/c-api/type.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
<!-- Keep old URL fragments working (see gh-97908) -->
Expand Down
1 change: 1 addition & 0 deletions Doc/data/stable_abi.dat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------------

Expand Down
4 changes: 4 additions & 0 deletions Include/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 27 additions & 0 deletions Lib/test/test_capi/test_type.py
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions Lib/test/test_stable_abi_ctypes.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :c:func:`PyType_Freeze` function to make a type immutable. Patch by
Victor Stinner.
2 changes: 2 additions & 0 deletions Misc/stable_abi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2510,3 +2510,5 @@
added = '3.14'
[function.PyIter_NextItem]
added = '3.14'
[function.PyType_Freeze]
added = '3.14'
14 changes: 14 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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 */
};

Expand Down
47 changes: 34 additions & 13 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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; 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;
}

static PyObject *
_PyType_FromMetaclass_impl(
PyTypeObject *metaclass, PyObject *module,
Expand Down Expand Up @@ -4798,19 +4818,8 @@ _PyType_FromMetaclass_impl(
* 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) {
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;
}
if (check_immutable_bases(spec->name, bases) < 0) {
goto finally;
}
}

Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions PC/python3dll.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 2d24aba

Please sign in to comment.