Skip to content

Commit 2eed1f5

Browse files
[3.12] gh-103968: PyType_FromMetaclass: Allow metaclasses with tp_new=NULL (GH-105386) (GH-105697)
gh-103968: PyType_FromMetaclass: Allow metaclasses with tp_new=NULL (GH-105386) (cherry picked from commit 2b90796) Co-authored-by: Petr Viktorin <encukou@gmail.com>
1 parent 9e3e5d5 commit 2eed1f5

File tree

5 files changed

+53
-9
lines changed

5 files changed

+53
-9
lines changed

Doc/c-api/type.rst

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

Lib/test/test_capi/test_misc.py

+36-7
Original file line numberDiff line numberDiff line change
@@ -672,31 +672,60 @@ def test_heaptype_with_setattro(self):
672672
self.assertEqual(obj.pvalue, 0)
673673

674674
def test_heaptype_with_custom_metaclass(self):
675-
self.assertTrue(issubclass(_testcapi.HeapCTypeMetaclass, type))
676-
self.assertTrue(issubclass(_testcapi.HeapCTypeMetaclassCustomNew, type))
675+
metaclass = _testcapi.HeapCTypeMetaclass
676+
self.assertTrue(issubclass(metaclass, type))
677677

678-
t = _testcapi.pytype_fromspec_meta(_testcapi.HeapCTypeMetaclass)
678+
# Class creation from C
679+
t = _testcapi.pytype_fromspec_meta(metaclass)
679680
self.assertIsInstance(t, type)
680681
self.assertEqual(t.__name__, "HeapCTypeViaMetaclass")
681-
self.assertIs(type(t), _testcapi.HeapCTypeMetaclass)
682+
self.assertIs(type(t), metaclass)
683+
684+
# Class creation from Python
685+
t = metaclass("PyClassViaMetaclass", (), {})
686+
self.assertIsInstance(t, type)
687+
self.assertEqual(t.__name__, "PyClassViaMetaclass")
688+
689+
def test_heaptype_with_custom_metaclass_null_new(self):
690+
metaclass = _testcapi.HeapCTypeMetaclassNullNew
691+
692+
self.assertTrue(issubclass(metaclass, type))
693+
694+
# Class creation from C
695+
t = _testcapi.pytype_fromspec_meta(metaclass)
696+
self.assertIsInstance(t, type)
697+
self.assertEqual(t.__name__, "HeapCTypeViaMetaclass")
698+
self.assertIs(type(t), metaclass)
699+
700+
# Class creation from Python
701+
with self.assertRaisesRegex(TypeError, "cannot create .* instances"):
702+
metaclass("PyClassViaMetaclass", (), {})
703+
704+
def test_heaptype_with_custom_metaclass_custom_new(self):
705+
metaclass = _testcapi.HeapCTypeMetaclassCustomNew
706+
707+
self.assertTrue(issubclass(_testcapi.HeapCTypeMetaclassCustomNew, type))
682708

683709
msg = "Metaclasses with custom tp_new are not supported."
684710
with self.assertRaisesRegex(TypeError, msg):
685-
t = _testcapi.pytype_fromspec_meta(_testcapi.HeapCTypeMetaclassCustomNew)
711+
t = _testcapi.pytype_fromspec_meta(metaclass)
686712

687713
def test_heaptype_with_custom_metaclass_deprecation(self):
714+
metaclass = _testcapi.HeapCTypeMetaclassCustomNew
715+
688716
# gh-103968: a metaclass with custom tp_new is deprecated, but still
689717
# allowed for functions that existed in 3.11
690718
# (PyType_FromSpecWithBases is used here).
691-
class Base(metaclass=_testcapi.HeapCTypeMetaclassCustomNew):
719+
class Base(metaclass=metaclass):
692720
pass
693721

722+
# Class creation from C
694723
with warnings_helper.check_warnings(
695724
('.*custom tp_new.*in Python 3.14.*', DeprecationWarning),
696725
):
697726
sub = _testcapi.make_type_with_base(Base)
698727
self.assertTrue(issubclass(sub, Base))
699-
self.assertIsInstance(sub, _testcapi.HeapCTypeMetaclassCustomNew)
728+
self.assertIsInstance(sub, metaclass)
700729

701730
def test_multiple_inheritance_ctypes_with_weakref_or_dict(self):
702731

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

Modules/_testcapi/heaptype.c

+13
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,12 @@ static PyType_Spec HeapCTypeMetaclassCustomNew_spec = {
744744
HeapCTypeMetaclassCustomNew_slots
745745
};
746746

747+
static PyType_Spec HeapCTypeMetaclassNullNew_spec = {
748+
.name = "_testcapi.HeapCTypeMetaclassNullNew",
749+
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
750+
.slots = empty_type_slots
751+
};
752+
747753

748754
typedef struct {
749755
PyObject_HEAD
@@ -1231,6 +1237,13 @@ _PyTestCapi_Init_Heaptype(PyObject *m) {
12311237
}
12321238
PyModule_AddObject(m, "HeapCTypeMetaclassCustomNew", HeapCTypeMetaclassCustomNew);
12331239

1240+
PyObject *HeapCTypeMetaclassNullNew = PyType_FromMetaclass(
1241+
&PyType_Type, m, &HeapCTypeMetaclassNullNew_spec, (PyObject *) &PyType_Type);
1242+
if (HeapCTypeMetaclassNullNew == NULL) {
1243+
return -1;
1244+
}
1245+
PyModule_AddObject(m, "HeapCTypeMetaclassNullNew", HeapCTypeMetaclassNullNew);
1246+
12341247
PyObject *HeapCCollection = PyType_FromMetaclass(
12351248
NULL, m, &HeapCCollection_spec, NULL);
12361249
if (HeapCCollection == NULL) {

Objects/typeobject.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -4236,7 +4236,7 @@ _PyType_FromMetaclass_impl(
42364236
metaclass);
42374237
goto finally;
42384238
}
4239-
if (metaclass->tp_new != PyType_Type.tp_new) {
4239+
if (metaclass->tp_new && metaclass->tp_new != PyType_Type.tp_new) {
42404240
if (_allow_tp_new) {
42414241
if (PyErr_WarnFormat(
42424242
PyExc_DeprecationWarning, 1,

0 commit comments

Comments
 (0)