]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.12] gh-103968: PyType_FromMetaclass: Allow metaclasses with tp_new=NULL (GH-105386...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Mon, 12 Jun 2023 16:24:21 +0000 (09:24 -0700)
committerGitHub <noreply@github.com>
Mon, 12 Jun 2023 16:24:21 +0000 (16:24 +0000)
gh-103968: PyType_FromMetaclass: Allow metaclasses with tp_new=NULL (GH-105386)
(cherry picked from commit 2b90796be6959d5ef46b38c434a514fce25be971)

Co-authored-by: Petr Viktorin <encukou@gmail.com>
Doc/c-api/type.rst
Lib/test/test_capi/test_misc.py
Misc/NEWS.d/next/C API/2023-06-06-14-14-41.gh-issue-103968.BTO6II.rst [new file with mode: 0644]
Modules/_testcapi/heaptype.c
Objects/typeobject.c

index bf261b9814456ea9a001b3e27efbf330a8bcc578..c99c7ef93a45df4d770558617c880d73560c42b4 100644 (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).
 
    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
index e1b55cffe8ff526b58b7cc76529155ef17883e1d..9cd1554614695e49a33bd2655d04790cde68aa08 100644 (file)
@@ -672,31 +672,60 @@ class CAPITest(unittest.TestCase):
         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 (file)
index 0000000..b73103c
--- /dev/null
@@ -0,0 +1,2 @@
+:c:func:`PyType_FromMetaclass` now allows metaclasses with ``tp_new``
+set to ``NULL``.
index 565ab570a42bde7fa7997d27177b5da75cf0ffc2..c124871e43343139551f14ede5a0fc45730e6494 100644 (file)
@@ -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) {
index b6771d3004df91c9aa4af5e84a7a116cd947999c..bf33bde25710120a666ab06d68d19c6dc5c4f96f 100644 (file)
@@ -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,