gh-103968: PyType_FromMetaclass: Allow metaclasses with tp_new=NULL (GH-105386)

This commit is contained in:
Petr Viktorin 2023-06-12 17:45:49 +02:00 committed by GitHub
parent 58f0bda341
commit 2b90796be6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 53 additions and 9 deletions

View File

@ -258,7 +258,7 @@ The following functions and structs are used to create
(or *Py_tp_base[s]* slots if *bases* is ``NULL``, see below). (or *Py_tp_base[s]* slots if *bases* is ``NULL``, see below).
Metaclasses that override :c:member:`~PyTypeObject.tp_new` are not Metaclasses that override :c:member:`~PyTypeObject.tp_new` are not
supported. supported, except if ``tp_new`` is ``NULL``.
(For backwards compatibility, other ``PyType_From*`` functions allow (For backwards compatibility, other ``PyType_From*`` functions allow
such metaclasses. They ignore ``tp_new``, which may result in incomplete such metaclasses. They ignore ``tp_new``, which may result in incomplete
initialization. This is deprecated and in Python 3.14+ such metaclasses will initialization. This is deprecated and in Python 3.14+ such metaclasses will

View File

@ -671,31 +671,60 @@ class CAPITest(unittest.TestCase):
self.assertEqual(obj.pvalue, 0) self.assertEqual(obj.pvalue, 0)
def test_heaptype_with_custom_metaclass(self): def test_heaptype_with_custom_metaclass(self):
self.assertTrue(issubclass(_testcapi.HeapCTypeMetaclass, type)) metaclass = _testcapi.HeapCTypeMetaclass
self.assertTrue(issubclass(_testcapi.HeapCTypeMetaclassCustomNew, type)) self.assertTrue(issubclass(metaclass, type))
t = _testcapi.pytype_fromspec_meta(_testcapi.HeapCTypeMetaclass) # Class creation from C
t = _testcapi.pytype_fromspec_meta(metaclass)
self.assertIsInstance(t, type) self.assertIsInstance(t, type)
self.assertEqual(t.__name__, "HeapCTypeViaMetaclass") self.assertEqual(t.__name__, "HeapCTypeViaMetaclass")
self.assertIs(type(t), _testcapi.HeapCTypeMetaclass) self.assertIs(type(t), metaclass)
# Class creation from Python
t = metaclass("PyClassViaMetaclass", (), {})
self.assertIsInstance(t, type)
self.assertEqual(t.__name__, "PyClassViaMetaclass")
def test_heaptype_with_custom_metaclass_null_new(self):
metaclass = _testcapi.HeapCTypeMetaclassNullNew
self.assertTrue(issubclass(metaclass, type))
# Class creation from C
t = _testcapi.pytype_fromspec_meta(metaclass)
self.assertIsInstance(t, type)
self.assertEqual(t.__name__, "HeapCTypeViaMetaclass")
self.assertIs(type(t), metaclass)
# Class creation from Python
with self.assertRaisesRegex(TypeError, "cannot create .* instances"):
metaclass("PyClassViaMetaclass", (), {})
def test_heaptype_with_custom_metaclass_custom_new(self):
metaclass = _testcapi.HeapCTypeMetaclassCustomNew
self.assertTrue(issubclass(_testcapi.HeapCTypeMetaclassCustomNew, type))
msg = "Metaclasses with custom tp_new are not supported." msg = "Metaclasses with custom tp_new are not supported."
with self.assertRaisesRegex(TypeError, msg): with self.assertRaisesRegex(TypeError, msg):
t = _testcapi.pytype_fromspec_meta(_testcapi.HeapCTypeMetaclassCustomNew) t = _testcapi.pytype_fromspec_meta(metaclass)
def test_heaptype_with_custom_metaclass_deprecation(self): def test_heaptype_with_custom_metaclass_deprecation(self):
metaclass = _testcapi.HeapCTypeMetaclassCustomNew
# gh-103968: a metaclass with custom tp_new is deprecated, but still # gh-103968: a metaclass with custom tp_new is deprecated, but still
# allowed for functions that existed in 3.11 # allowed for functions that existed in 3.11
# (PyType_FromSpecWithBases is used here). # (PyType_FromSpecWithBases is used here).
class Base(metaclass=_testcapi.HeapCTypeMetaclassCustomNew): class Base(metaclass=metaclass):
pass pass
# Class creation from C
with warnings_helper.check_warnings( with warnings_helper.check_warnings(
('.*custom tp_new.*in Python 3.14.*', DeprecationWarning), ('.*custom tp_new.*in Python 3.14.*', DeprecationWarning),
): ):
sub = _testcapi.make_type_with_base(Base) sub = _testcapi.make_type_with_base(Base)
self.assertTrue(issubclass(sub, Base)) self.assertTrue(issubclass(sub, Base))
self.assertIsInstance(sub, _testcapi.HeapCTypeMetaclassCustomNew) self.assertIsInstance(sub, metaclass)
def test_multiple_inheritance_ctypes_with_weakref_or_dict(self): def test_multiple_inheritance_ctypes_with_weakref_or_dict(self):

View File

@ -0,0 +1,2 @@
:c:func:`PyType_FromMetaclass` now allows metaclasses with ``tp_new``
set to ``NULL``.

View File

@ -744,6 +744,12 @@ static PyType_Spec HeapCTypeMetaclassCustomNew_spec = {
HeapCTypeMetaclassCustomNew_slots HeapCTypeMetaclassCustomNew_slots
}; };
static PyType_Spec HeapCTypeMetaclassNullNew_spec = {
.name = "_testcapi.HeapCTypeMetaclassNullNew",
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
.slots = empty_type_slots
};
typedef struct { typedef struct {
PyObject_HEAD PyObject_HEAD
@ -1231,6 +1237,13 @@ _PyTestCapi_Init_Heaptype(PyObject *m) {
} }
PyModule_AddObject(m, "HeapCTypeMetaclassCustomNew", HeapCTypeMetaclassCustomNew); PyModule_AddObject(m, "HeapCTypeMetaclassCustomNew", HeapCTypeMetaclassCustomNew);
PyObject *HeapCTypeMetaclassNullNew = PyType_FromMetaclass(
&PyType_Type, m, &HeapCTypeMetaclassNullNew_spec, (PyObject *) &PyType_Type);
if (HeapCTypeMetaclassNullNew == NULL) {
return -1;
}
PyModule_AddObject(m, "HeapCTypeMetaclassNullNew", HeapCTypeMetaclassNullNew);
PyObject *HeapCCollection = PyType_FromMetaclass( PyObject *HeapCCollection = PyType_FromMetaclass(
NULL, m, &HeapCCollection_spec, NULL); NULL, m, &HeapCCollection_spec, NULL);
if (HeapCCollection == NULL) { if (HeapCCollection == NULL) {

View File

@ -4236,7 +4236,7 @@ _PyType_FromMetaclass_impl(
metaclass); metaclass);
goto finally; goto finally;
} }
if (metaclass->tp_new != PyType_Type.tp_new) { if (metaclass->tp_new && metaclass->tp_new != PyType_Type.tp_new) {
if (_allow_tp_new) { if (_allow_tp_new) {
if (PyErr_WarnFormat( if (PyErr_WarnFormat(
PyExc_DeprecationWarning, 1, PyExc_DeprecationWarning, 1,