[Python-checkins] [3.12] gh-103968: PyType_FromMetaclass: Allow metaclasses with tp_new=NULL (GH-105386) (GH-105697)

encukou webhook-mailer at python.org
Mon Jun 12 12:24:28 EDT 2023


https://github.com/python/cpython/commit/2eed1f5868b1c54a5314b64c38010258d27658da
commit: 2eed1f5868b1c54a5314b64c38010258d27658da
branch: 3.12
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: encukou <encukou at gmail.com>
date: 2023-06-12T16:24:21Z
summary:

[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 2b90796be6959d5ef46b38c434a514fce25be971)

Co-authored-by: Petr Viktorin <encukou at gmail.com>

files:
A Misc/NEWS.d/next/C API/2023-06-06-14-14-41.gh-issue-103968.BTO6II.rst
M Doc/c-api/type.rst
M Lib/test/test_capi/test_misc.py
M Modules/_testcapi/heaptype.c
M Objects/typeobject.c

diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst
index bf261b9814456..c99c7ef93a45d 100644
--- a/Doc/c-api/type.rst
+++ b/Doc/c-api/type.rst
@@ -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
diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py
index e1b55cffe8ff5..9cd1554614695 100644
--- a/Lib/test/test_capi/test_misc.py
+++ b/Lib/test/test_capi/test_misc.py
@@ -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):
 
diff --git a/Misc/NEWS.d/next/C API/2023-06-06-14-14-41.gh-issue-103968.BTO6II.rst b/Misc/NEWS.d/next/C API/2023-06-06-14-14-41.gh-issue-103968.BTO6II.rst
new file mode 100644
index 0000000000000..b73103c4e0ad9
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2023-06-06-14-14-41.gh-issue-103968.BTO6II.rst	
@@ -0,0 +1,2 @@
+:c:func:`PyType_FromMetaclass` now allows metaclasses with ``tp_new``
+set to ``NULL``.
diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c
index 565ab570a42bd..c124871e43343 100644
--- a/Modules/_testcapi/heaptype.c
+++ b/Modules/_testcapi/heaptype.c
@@ -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
@@ -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) {
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index b6771d3004df9..bf33bde257101 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -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,



More information about the Python-checkins mailing list