summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPetr Viktorin <encukou@gmail.com>2023-06-12 15:45:49 (GMT)
committerGitHub <noreply@github.com>2023-06-12 15:45:49 (GMT)
commit2b90796be6959d5ef46b38c434a514fce25be971 (patch)
tree6b4f0c83e850c385c50b9ba79cd4522ac76a5350
parent58f0bda34126ed790000451874b96140737f40ed (diff)
downloadcpython-2b90796be6959d5ef46b38c434a514fce25be971.zip
cpython-2b90796be6959d5ef46b38c434a514fce25be971.tar.gz
cpython-2b90796be6959d5ef46b38c434a514fce25be971.tar.bz2
gh-103968: PyType_FromMetaclass: Allow metaclasses with tp_new=NULL (GH-105386)
-rw-r--r--Doc/c-api/type.rst2
-rw-r--r--Lib/test/test_capi/test_misc.py43
-rw-r--r--Misc/NEWS.d/next/C API/2023-06-06-14-14-41.gh-issue-103968.BTO6II.rst2
-rw-r--r--Modules/_testcapi/heaptype.c13
-rw-r--r--Objects/typeobject.c2
5 files changed, 53 insertions, 9 deletions
diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst
index bf261b9..c99c7ef 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 3fcb694..04a0f8f 100644
--- a/Lib/test/test_capi/test_misc.py
+++ b/Lib/test/test_capi/test_misc.py
@@ -671,31 +671,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
index 0000000..b73103c
--- /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 565ab57..c124871 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 0a57991..1e0d79b 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,