Skip to content

Commit 2b90796

Browse files
authored
gh-103968: PyType_FromMetaclass: Allow metaclasses with tp_new=NULL (GH-105386)
1 parent 58f0bda commit 2b90796

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
@@ -671,31 +671,60 @@ def test_heaptype_with_setattro(self):
671671
self.assertEqual(obj.pvalue, 0)
672672

673673
def test_heaptype_with_custom_metaclass(self):
674-
self.assertTrue(issubclass(_testcapi.HeapCTypeMetaclass, type))
675-
self.assertTrue(issubclass(_testcapi.HeapCTypeMetaclassCustomNew, type))
674+
metaclass = _testcapi.HeapCTypeMetaclass
675+
self.assertTrue(issubclass(metaclass, type))
676676

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

682708
msg = "Metaclasses with custom tp_new are not supported."
683709
with self.assertRaisesRegex(TypeError, msg):
684-
t = _testcapi.pytype_fromspec_meta(_testcapi.HeapCTypeMetaclassCustomNew)
710+
t = _testcapi.pytype_fromspec_meta(metaclass)
685711

686712
def test_heaptype_with_custom_metaclass_deprecation(self):
713+
metaclass = _testcapi.HeapCTypeMetaclassCustomNew
714+
687715
# gh-103968: a metaclass with custom tp_new is deprecated, but still
688716
# allowed for functions that existed in 3.11
689717
# (PyType_FromSpecWithBases is used here).
690-
class Base(metaclass=_testcapi.HeapCTypeMetaclassCustomNew):
718+
class Base(metaclass=metaclass):
691719
pass
692720

721+
# Class creation from C
693722
with warnings_helper.check_warnings(
694723
('.*custom tp_new.*in Python 3.14.*', DeprecationWarning),
695724
):
696725
sub = _testcapi.make_type_with_base(Base)
697726
self.assertTrue(issubclass(sub, Base))
698-
self.assertIsInstance(sub, _testcapi.HeapCTypeMetaclassCustomNew)
727+
self.assertIsInstance(sub, metaclass)
699728

700729
def test_multiple_inheritance_ctypes_with_weakref_or_dict(self):
701730

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)