gh-95388: Deprecate creating immutable types with mutable bases (GH-95533)

This commit is contained in:
Petr Viktorin 2022-08-04 16:13:45 +02:00 committed by GitHub
parent 000c3874bf
commit a613fedd6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 80 additions and 0 deletions

View File

@ -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 <Py_TPFLAGS_IMMUTABLETYPE>` 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 <Py_TPFLAGS_IMMUTABLETYPE>` with mutable
bases is deprecated and will be disabled in Python 3.14.
Removed
-------

View File

@ -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')

View File

@ -0,0 +1,2 @@
Creating :c:data:`immutable types <Py_TPFLAGS_IMMUTABLETYPE>` with mutable
bases is deprecated and is planned to be disabled in Python 3.14.

View File

@ -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},
};

View File

@ -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; 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)) {
if (PyErr_WarnFormat(
PyExc_DeprecationWarning,
0,
"Creating immutable type %s from mutable base %s is "
"deprecated, and slated to be disallowed in Python 3.14.",
spec->name,
b->tp_name))
{
goto finally;
}
}
}
}
/* Calculate the metaclass */
if (!metaclass) {