gh-103968: Deprecate creating heap types whose metaclass has custom tp_new. (GH-103972)

(That's a mouthful of an edge case!)

Co-authored-by: Barney Gale <barney.gale@gmail.com>
This commit is contained in:
Petr Viktorin 2023-05-03 15:17:14 +02:00 committed by GitHub
parent 423d7faeb3
commit 524a7f77fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 106 additions and 12 deletions

View File

@ -256,8 +256,13 @@ The following functions and structs are used to create
The metaclass *metaclass* is used to construct the resulting type object.
When *metaclass* is ``NULL``, the metaclass is derived from *bases*
(or *Py_tp_base[s]* slots if *bases* is ``NULL``, see below).
Note that metaclasses that override
:c:member:`~PyTypeObject.tp_new` are not supported.
Metaclasses that override :c:member:`~PyTypeObject.tp_new` are not
supported.
(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
not be supported.)
The *bases* argument can be used to specify base classes; it can either
be only one class or a tuple of classes.
@ -305,6 +310,11 @@ The following functions and structs are used to create
The function now finds and uses a metaclass corresponding to the provided
base classes. Previously, only :class:`type` instances were returned.
The :c:member:`~PyTypeObject.tp_new` of the metaclass is *ignored*.
which may result in incomplete initialization.
Creating classes whose metaclass overrides
:c:member:`~PyTypeObject.tp_new` is deprecated and in Python 3.14+ it
will be no longer allowed.
.. c:function:: PyObject* PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
@ -317,6 +327,12 @@ The following functions and structs are used to create
The function now finds and uses a metaclass corresponding to the provided
base classes. Previously, only :class:`type` instances were returned.
The :c:member:`~PyTypeObject.tp_new` of the metaclass is *ignored*.
which may result in incomplete initialization.
Creating classes whose metaclass overrides
:c:member:`~PyTypeObject.tp_new` is deprecated and in Python 3.14+ it
will be no longer allowed.
.. c:function:: PyObject* PyType_FromSpec(PyType_Spec *spec)
Equivalent to ``PyType_FromMetaclass(NULL, NULL, spec, NULL)``.
@ -327,6 +343,12 @@ The following functions and structs are used to create
base classes provided in *Py_tp_base[s]* slots.
Previously, only :class:`type` instances were returned.
The :c:member:`~PyTypeObject.tp_new` of the metaclass is *ignored*.
which may result in incomplete initialization.
Creating classes whose metaclass overrides
:c:member:`~PyTypeObject.tp_new` is deprecated and in Python 3.14+ it
will be no longer allowed.
.. c:type:: PyType_Spec
Structure defining a type's behavior.

View File

@ -1320,6 +1320,21 @@ Porting to Python 3.12
available on debug builds. If you happen to be using it then you'll
need to start using ``_Py_GetGlobalRefTotal()``.
* The following functions now select an appropriate metaclass for the newly
created type:
* :c:func:`PyType_FromSpec`
* :c:func:`PyType_FromSpecWithBases`
* :c:func:`PyType_FromModuleAndSpec`
Creating classes whose metaclass overrides :c:member:`~PyTypeObject.tp_new`
is deprecated, and in Python 3.14+ it will be disallowed.
Note that these functions ignore ``tp_new`` of the metaclass, possibly
allowing incomplete initialization.
Note that :c:func:`PyType_FromMetaclass` (added in Python 3.12)
already disallows creating classes whose metaclass overrides ``tp_new``.
Deprecated
----------
@ -1396,6 +1411,11 @@ Deprecated
* ``_PyErr_ChainExceptions`` is deprecated. Use ``_PyErr_ChainExceptions1``
instead. (Contributed by Irit Katriel in :gh:`102192`.)
* Using :c:func:`PyType_FromSpec`, :c:func:`PyType_FromSpecWithBases`
or :c:func:`PyType_FromModuleAndSpec` to create a class whose metaclass
overrides :c:member:`~PyTypeObject.tp_new` is deprecated.
Call the metaclass instead.
Removed
-------

View File

@ -681,6 +681,20 @@ class CAPITest(unittest.TestCase):
with self.assertRaisesRegex(TypeError, msg):
t = _testcapi.pytype_fromspec_meta(_testcapi.HeapCTypeMetaclassCustomNew)
def test_heaptype_with_custom_metaclass_deprecation(self):
# 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):
pass
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)
def test_multiple_inheritance_ctypes_with_weakref_or_dict(self):
with self.assertRaises(TypeError):

View File

@ -0,0 +1,4 @@
:c:func:`PyType_FromSpec` and its variants now allow creating classes whose
metaclass overrides :c:member:`~PyTypeObject.tp_new`. The ``tp_new`` is
ignored. This behavior is deprecated and will be disallowed in 3.14+. The
new :c:func:`PyType_FromMetaclass` already disallows it.

View File

@ -22,7 +22,7 @@ static PyObject *pytype_fromspec_meta(PyObject* self, PyObject *meta)
"_testcapi.HeapCTypeViaMetaclass",
sizeof(PyObject),
0,
Py_TPFLAGS_DEFAULT,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
HeapCTypeViaMetaclass_slots
};
@ -385,6 +385,19 @@ make_immutable_type_with_base(PyObject *self, PyObject *base)
return PyType_FromSpecWithBases(&ImmutableSubclass_spec, base);
}
static PyObject *
make_type_with_base(PyObject *self, PyObject *base)
{
assert(PyType_Check(base));
PyType_Spec ImmutableSubclass_spec = {
.name = "_testcapi.Subclass",
.basicsize = (int)((PyTypeObject*)base)->tp_basicsize,
.slots = empty_type_slots,
.flags = Py_TPFLAGS_DEFAULT,
};
return PyType_FromSpecWithBases(&ImmutableSubclass_spec, base);
}
static PyMethodDef TestMethods[] = {
{"pytype_fromspec_meta", pytype_fromspec_meta, METH_O},
@ -397,6 +410,7 @@ static PyMethodDef TestMethods[] = {
test_from_spec_invalid_metatype_inheritance,
METH_NOARGS},
{"make_immutable_type_with_base", make_immutable_type_with_base, METH_O},
{"make_type_with_base", make_type_with_base, METH_O},
{NULL},
};

View File

@ -3950,9 +3950,10 @@ check_basicsize_includes_size_and_offsets(PyTypeObject* type)
return 1;
}
PyObject *
PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
PyType_Spec *spec, PyObject *bases_in)
static PyObject *
_PyType_FromMetaclass_impl(
PyTypeObject *metaclass, PyObject *module,
PyType_Spec *spec, PyObject *bases_in, int _allow_tp_new)
{
/* Invariant: A non-NULL value in one of these means this function holds
* a strong reference or owns allocated memory.
@ -4127,9 +4128,21 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
goto finally;
}
if (metaclass->tp_new != PyType_Type.tp_new) {
PyErr_SetString(PyExc_TypeError,
"Metaclasses with custom tp_new are not supported.");
goto finally;
if (_allow_tp_new) {
if (PyErr_WarnFormat(
PyExc_DeprecationWarning, 1,
"Using PyType_Spec with metaclasses that have custom "
"tp_new is deprecated and will no longer be allowed in "
"Python 3.14.") < 0) {
goto finally;
}
}
else {
PyErr_SetString(
PyExc_TypeError,
"Metaclasses with custom tp_new are not supported.");
goto finally;
}
}
/* Calculate best base, and check that all bases are type objects */
@ -4316,22 +4329,29 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
return (PyObject*)res;
}
PyObject *
PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
PyType_Spec *spec, PyObject *bases_in)
{
return _PyType_FromMetaclass_impl(metaclass, module, spec, bases_in, 0);
}
PyObject *
PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
{
return PyType_FromMetaclass(NULL, module, spec, bases);
return _PyType_FromMetaclass_impl(NULL, module, spec, bases, 1);
}
PyObject *
PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
{
return PyType_FromMetaclass(NULL, NULL, spec, bases);
return _PyType_FromMetaclass_impl(NULL, NULL, spec, bases, 1);
}
PyObject *
PyType_FromSpec(PyType_Spec *spec)
{
return PyType_FromMetaclass(NULL, NULL, spec, NULL);
return _PyType_FromMetaclass_impl(NULL, NULL, spec, NULL, 1);
}
PyObject *