diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 67396f8e022..be0599763e2 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -205,6 +205,10 @@ Pending Removal in Python 3.14 (Contributed by Jason R. Coombs and Hugo van Kemenade in :gh:`93963`.) +* Creating :c:data:`immutable types ` with mutable + bases using the C API. + + Pending Removal in Future Versions ---------------------------------- @@ -458,6 +462,9 @@ Deprecated :c:type:`PyConfig` instead. (Contributed by Victor Stinner in :gh:`77782`.) +* Creating :c:data:`immutable types ` with mutable + bases is deprecated and will be disabled in Python 3.14. + Removed ------- diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index 013229a6cdc..c7435578974 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -15,6 +15,7 @@ import textwrap import threading import time import unittest +import warnings import weakref from test import support from test.support import MISSING_C_DOCSTRINGS @@ -644,6 +645,34 @@ class CAPITest(unittest.TestCase): with self.assertRaises(SystemError): _testcapi.create_type_from_repeated_slots(variant) + def test_immutable_type_with_mutable_base(self): + # Add deprecation warning here so it's removed in 3.14 + warnings._deprecated( + 'creating immutable classes with mutable bases', remove=(3, 14)) + + class MutableBase: + def meth(self): + return 'original' + + with self.assertWarns(DeprecationWarning): + ImmutableSubclass = _testcapi.make_immutable_type_with_base( + MutableBase) + instance = ImmutableSubclass() + + self.assertEqual(instance.meth(), 'original') + + # Cannot override the static type's method + with self.assertRaisesRegex( + TypeError, + "cannot set 'meth' attribute of immutable type"): + ImmutableSubclass.meth = lambda self: 'overridden' + self.assertEqual(instance.meth(), 'original') + + # Can change the method on the mutable base + MutableBase.meth = lambda self: 'changed' + self.assertEqual(instance.meth(), 'changed') + + def test_pynumber_tobase(self): from _testcapi import pynumber_tobase self.assertEqual(pynumber_tobase(123, 2), '0b1111011') diff --git a/Misc/NEWS.d/next/C API/2022-07-29-10-41-59.gh-issue-95388.aiRSgr.rst b/Misc/NEWS.d/next/C API/2022-07-29-10-41-59.gh-issue-95388.aiRSgr.rst new file mode 100644 index 00000000000..c389d13db6a --- /dev/null +++ b/Misc/NEWS.d/next/C API/2022-07-29-10-41-59.gh-issue-95388.aiRSgr.rst @@ -0,0 +1,2 @@ +Creating :c:data:`immutable types ` with mutable +bases is deprecated and is planned to be disabled in Python 3.14. diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c index 12889e825d5..514541ce348 100644 --- a/Modules/_testcapi/heaptype.c +++ b/Modules/_testcapi/heaptype.c @@ -365,6 +365,21 @@ create_type_from_repeated_slots(PyObject *self, PyObject *variant_obj) } + +static PyObject * +make_immutable_type_with_base(PyObject *self, PyObject *base) +{ + assert(PyType_Check(base)); + PyType_Spec ImmutableSubclass_spec = { + .name = "ImmutableSubclass", + .basicsize = (int)((PyTypeObject*)base)->tp_basicsize, + .slots = empty_type_slots, + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE, + }; + return PyType_FromSpecWithBases(&ImmutableSubclass_spec, base); +} + + static PyMethodDef TestMethods[] = { {"pytype_fromspec_meta", pytype_fromspec_meta, METH_O}, {"test_type_from_ephemeral_spec", test_type_from_ephemeral_spec, METH_NOARGS}, @@ -375,6 +390,7 @@ static PyMethodDef TestMethods[] = { {"test_from_spec_invalid_metatype_inheritance", test_from_spec_invalid_metatype_inheritance, METH_NOARGS}, + {"make_immutable_type_with_base", make_immutable_type_with_base, METH_O}, {NULL}, }; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 1980fcbf932..0801d9f3d5f 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -3676,6 +3676,32 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module, goto finally; } + /* If this is an immutable type, check if all bases are also immutable, + * and (for now) fire a deprecation warning if not. + * (This isn't necessary for static types: those can't have heap bases, + * and only heap types can be mutable.) + */ + if (spec->flags & Py_TPFLAGS_IMMUTABLETYPE) { + for (int i=0; iname, + b->tp_name)) + { + goto finally; + } + } + } + } + /* Calculate the metaclass */ if (!metaclass) {