Skip to content

Commit

Permalink
[3.12] gh-103968: PyType_FromMetaclass: Allow metaclasses with tp_new…
Browse files Browse the repository at this point in the history
…=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>
  • Loading branch information
miss-islington and encukou committed Jun 12, 2023
1 parent 9e3e5d5 commit 2eed1f5
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 9 deletions.
2 changes: 1 addition & 1 deletion Doc/c-api/type.rst
Expand Up @@ -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).
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
such metaclasses. They ignore ``tp_new``, which may result in incomplete
initialization. This is deprecated and in Python 3.14+ such metaclasses will
Expand Down
43 changes: 36 additions & 7 deletions Lib/test/test_capi/test_misc.py
Expand Up @@ -672,31 +672,60 @@ def test_heaptype_with_setattro(self):
self.assertEqual(obj.pvalue, 0)

def test_heaptype_with_custom_metaclass(self):
self.assertTrue(issubclass(_testcapi.HeapCTypeMetaclass, type))
self.assertTrue(issubclass(_testcapi.HeapCTypeMetaclassCustomNew, type))
metaclass = _testcapi.HeapCTypeMetaclass
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.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."
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):
metaclass = _testcapi.HeapCTypeMetaclassCustomNew

# gh-103968: a metaclass with custom tp_new is deprecated, but still
# allowed for functions that existed in 3.11
# (PyType_FromSpecWithBases is used here).
class Base(metaclass=_testcapi.HeapCTypeMetaclassCustomNew):
class Base(metaclass=metaclass):
pass

# Class creation from C
with warnings_helper.check_warnings(
('.*custom tp_new.*in Python 3.14.*', DeprecationWarning),
):
sub = _testcapi.make_type_with_base(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):

Expand Down
@@ -0,0 +1,2 @@
:c:func:`PyType_FromMetaclass` now allows metaclasses with ``tp_new``
set to ``NULL``.
13 changes: 13 additions & 0 deletions Modules/_testcapi/heaptype.c
Expand Up @@ -744,6 +744,12 @@ static PyType_Spec HeapCTypeMetaclassCustomNew_spec = {
HeapCTypeMetaclassCustomNew_slots
};

static PyType_Spec HeapCTypeMetaclassNullNew_spec = {
.name = "_testcapi.HeapCTypeMetaclassNullNew",
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
.slots = empty_type_slots
};


typedef struct {
PyObject_HEAD
Expand Down Expand Up @@ -1231,6 +1237,13 @@ _PyTestCapi_Init_Heaptype(PyObject *m) {
}
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(
NULL, m, &HeapCCollection_spec, NULL);
if (HeapCCollection == NULL) {
Expand Down
2 changes: 1 addition & 1 deletion Objects/typeobject.c
Expand Up @@ -4236,7 +4236,7 @@ _PyType_FromMetaclass_impl(
metaclass);
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 (PyErr_WarnFormat(
PyExc_DeprecationWarning, 1,
Expand Down

0 comments on commit 2eed1f5

Please sign in to comment.