Skip to content

Commit 524a7f7

Browse files
encukoubarneygale
andauthored
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 <[email protected]>
1 parent 423d7fa commit 524a7f7

File tree

6 files changed

+106
-12
lines changed

6 files changed

+106
-12
lines changed

Doc/c-api/type.rst

+24-2
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,13 @@ The following functions and structs are used to create
256256
The metaclass *metaclass* is used to construct the resulting type object.
257257
When *metaclass* is ``NULL``, the metaclass is derived from *bases*
258258
(or *Py_tp_base[s]* slots if *bases* is ``NULL``, see below).
259-
Note that metaclasses that override
260-
:c:member:`~PyTypeObject.tp_new` are not supported.
259+
260+
Metaclasses that override :c:member:`~PyTypeObject.tp_new` are not
261+
supported.
262+
(For backwards compatibility, other ``PyType_From*`` functions allow
263+
such metaclasses. They ignore ``tp_new``, which may result in incomplete
264+
initialization. This is deprecated and in Python 3.14+ such metaclasses will
265+
not be supported.)
261266
262267
The *bases* argument can be used to specify base classes; it can either
263268
be only one class or a tuple of classes.
@@ -305,6 +310,11 @@ The following functions and structs are used to create
305310
The function now finds and uses a metaclass corresponding to the provided
306311
base classes. Previously, only :class:`type` instances were returned.
307312
313+
The :c:member:`~PyTypeObject.tp_new` of the metaclass is *ignored*.
314+
which may result in incomplete initialization.
315+
Creating classes whose metaclass overrides
316+
:c:member:`~PyTypeObject.tp_new` is deprecated and in Python 3.14+ it
317+
will be no longer allowed.
308318
309319
.. c:function:: PyObject* PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
310320
@@ -317,6 +327,12 @@ The following functions and structs are used to create
317327
The function now finds and uses a metaclass corresponding to the provided
318328
base classes. Previously, only :class:`type` instances were returned.
319329
330+
The :c:member:`~PyTypeObject.tp_new` of the metaclass is *ignored*.
331+
which may result in incomplete initialization.
332+
Creating classes whose metaclass overrides
333+
:c:member:`~PyTypeObject.tp_new` is deprecated and in Python 3.14+ it
334+
will be no longer allowed.
335+
320336
.. c:function:: PyObject* PyType_FromSpec(PyType_Spec *spec)
321337
322338
Equivalent to ``PyType_FromMetaclass(NULL, NULL, spec, NULL)``.
@@ -327,6 +343,12 @@ The following functions and structs are used to create
327343
base classes provided in *Py_tp_base[s]* slots.
328344
Previously, only :class:`type` instances were returned.
329345
346+
The :c:member:`~PyTypeObject.tp_new` of the metaclass is *ignored*.
347+
which may result in incomplete initialization.
348+
Creating classes whose metaclass overrides
349+
:c:member:`~PyTypeObject.tp_new` is deprecated and in Python 3.14+ it
350+
will be no longer allowed.
351+
330352
.. c:type:: PyType_Spec
331353
332354
Structure defining a type's behavior.

Doc/whatsnew/3.12.rst

+20
Original file line numberDiff line numberDiff line change
@@ -1320,6 +1320,21 @@ Porting to Python 3.12
13201320
available on debug builds. If you happen to be using it then you'll
13211321
need to start using ``_Py_GetGlobalRefTotal()``.
13221322

1323+
* The following functions now select an appropriate metaclass for the newly
1324+
created type:
1325+
1326+
* :c:func:`PyType_FromSpec`
1327+
* :c:func:`PyType_FromSpecWithBases`
1328+
* :c:func:`PyType_FromModuleAndSpec`
1329+
1330+
Creating classes whose metaclass overrides :c:member:`~PyTypeObject.tp_new`
1331+
is deprecated, and in Python 3.14+ it will be disallowed.
1332+
Note that these functions ignore ``tp_new`` of the metaclass, possibly
1333+
allowing incomplete initialization.
1334+
1335+
Note that :c:func:`PyType_FromMetaclass` (added in Python 3.12)
1336+
already disallows creating classes whose metaclass overrides ``tp_new``.
1337+
13231338
Deprecated
13241339
----------
13251340

@@ -1396,6 +1411,11 @@ Deprecated
13961411
* ``_PyErr_ChainExceptions`` is deprecated. Use ``_PyErr_ChainExceptions1``
13971412
instead. (Contributed by Irit Katriel in :gh:`102192`.)
13981413

1414+
* Using :c:func:`PyType_FromSpec`, :c:func:`PyType_FromSpecWithBases`
1415+
or :c:func:`PyType_FromModuleAndSpec` to create a class whose metaclass
1416+
overrides :c:member:`~PyTypeObject.tp_new` is deprecated.
1417+
Call the metaclass instead.
1418+
13991419
Removed
14001420
-------
14011421

Lib/test/test_capi/test_misc.py

+14
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,20 @@ def test_heaptype_with_custom_metaclass(self):
681681
with self.assertRaisesRegex(TypeError, msg):
682682
t = _testcapi.pytype_fromspec_meta(_testcapi.HeapCTypeMetaclassCustomNew)
683683

684+
def test_heaptype_with_custom_metaclass_deprecation(self):
685+
# gh-103968: a metaclass with custom tp_new is deprecated, but still
686+
# allowed for functions that existed in 3.11
687+
# (PyType_FromSpecWithBases is used here).
688+
class Base(metaclass=_testcapi.HeapCTypeMetaclassCustomNew):
689+
pass
690+
691+
with warnings_helper.check_warnings(
692+
('.*custom tp_new.*in Python 3.14.*', DeprecationWarning),
693+
):
694+
sub = _testcapi.make_type_with_base(Base)
695+
self.assertTrue(issubclass(sub, Base))
696+
self.assertIsInstance(sub, _testcapi.HeapCTypeMetaclassCustomNew)
697+
684698
def test_multiple_inheritance_ctypes_with_weakref_or_dict(self):
685699

686700
with self.assertRaises(TypeError):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:c:func:`PyType_FromSpec` and its variants now allow creating classes whose
2+
metaclass overrides :c:member:`~PyTypeObject.tp_new`. The ``tp_new`` is
3+
ignored. This behavior is deprecated and will be disallowed in 3.14+. The
4+
new :c:func:`PyType_FromMetaclass` already disallows it.

Modules/_testcapi/heaptype.c

+15-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ static PyObject *pytype_fromspec_meta(PyObject* self, PyObject *meta)
2222
"_testcapi.HeapCTypeViaMetaclass",
2323
sizeof(PyObject),
2424
0,
25-
Py_TPFLAGS_DEFAULT,
25+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
2626
HeapCTypeViaMetaclass_slots
2727
};
2828

@@ -385,6 +385,19 @@ make_immutable_type_with_base(PyObject *self, PyObject *base)
385385
return PyType_FromSpecWithBases(&ImmutableSubclass_spec, base);
386386
}
387387

388+
static PyObject *
389+
make_type_with_base(PyObject *self, PyObject *base)
390+
{
391+
assert(PyType_Check(base));
392+
PyType_Spec ImmutableSubclass_spec = {
393+
.name = "_testcapi.Subclass",
394+
.basicsize = (int)((PyTypeObject*)base)->tp_basicsize,
395+
.slots = empty_type_slots,
396+
.flags = Py_TPFLAGS_DEFAULT,
397+
};
398+
return PyType_FromSpecWithBases(&ImmutableSubclass_spec, base);
399+
}
400+
388401

389402
static PyMethodDef TestMethods[] = {
390403
{"pytype_fromspec_meta", pytype_fromspec_meta, METH_O},
@@ -397,6 +410,7 @@ static PyMethodDef TestMethods[] = {
397410
test_from_spec_invalid_metatype_inheritance,
398411
METH_NOARGS},
399412
{"make_immutable_type_with_base", make_immutable_type_with_base, METH_O},
413+
{"make_type_with_base", make_type_with_base, METH_O},
400414
{NULL},
401415
};
402416

Objects/typeobject.c

+29-9
Original file line numberDiff line numberDiff line change
@@ -3950,9 +3950,10 @@ check_basicsize_includes_size_and_offsets(PyTypeObject* type)
39503950
return 1;
39513951
}
39523952

3953-
PyObject *
3954-
PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
3955-
PyType_Spec *spec, PyObject *bases_in)
3953+
static PyObject *
3954+
_PyType_FromMetaclass_impl(
3955+
PyTypeObject *metaclass, PyObject *module,
3956+
PyType_Spec *spec, PyObject *bases_in, int _allow_tp_new)
39563957
{
39573958
/* Invariant: A non-NULL value in one of these means this function holds
39583959
* a strong reference or owns allocated memory.
@@ -4127,9 +4128,21 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
41274128
goto finally;
41284129
}
41294130
if (metaclass->tp_new != PyType_Type.tp_new) {
4130-
PyErr_SetString(PyExc_TypeError,
4131-
"Metaclasses with custom tp_new are not supported.");
4132-
goto finally;
4131+
if (_allow_tp_new) {
4132+
if (PyErr_WarnFormat(
4133+
PyExc_DeprecationWarning, 1,
4134+
"Using PyType_Spec with metaclasses that have custom "
4135+
"tp_new is deprecated and will no longer be allowed in "
4136+
"Python 3.14.") < 0) {
4137+
goto finally;
4138+
}
4139+
}
4140+
else {
4141+
PyErr_SetString(
4142+
PyExc_TypeError,
4143+
"Metaclasses with custom tp_new are not supported.");
4144+
goto finally;
4145+
}
41334146
}
41344147

41354148
/* Calculate best base, and check that all bases are type objects */
@@ -4316,22 +4329,29 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
43164329
return (PyObject*)res;
43174330
}
43184331

4332+
PyObject *
4333+
PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
4334+
PyType_Spec *spec, PyObject *bases_in)
4335+
{
4336+
return _PyType_FromMetaclass_impl(metaclass, module, spec, bases_in, 0);
4337+
}
4338+
43194339
PyObject *
43204340
PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
43214341
{
4322-
return PyType_FromMetaclass(NULL, module, spec, bases);
4342+
return _PyType_FromMetaclass_impl(NULL, module, spec, bases, 1);
43234343
}
43244344

43254345
PyObject *
43264346
PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
43274347
{
4328-
return PyType_FromMetaclass(NULL, NULL, spec, bases);
4348+
return _PyType_FromMetaclass_impl(NULL, NULL, spec, bases, 1);
43294349
}
43304350

43314351
PyObject *
43324352
PyType_FromSpec(PyType_Spec *spec)
43334353
{
4334-
return PyType_FromMetaclass(NULL, NULL, spec, NULL);
4354+
return _PyType_FromMetaclass_impl(NULL, NULL, spec, NULL, 1);
43354355
}
43364356

43374357
PyObject *

0 commit comments

Comments
 (0)