8000 gh-121654: Add Py_tp_create_callback slot · vstinner/cpython@444a17c · GitHub
[go: up one dir, main page]

Skip to content

Commit 444a17c

Browse files
committed
pythongh-121654: Add Py_tp_create_callback slot
1 parent 6f4d64b commit 444a17c

File tree

9 files changed

+98
-8
lines changed

9 files changed

+98
-8
lines changed

Doc/c-api/type.rst

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,10 +508,11 @@ The following functions and structs are used to create
508508
* ``Py_nb_add`` to set :c:member:`PyNumberMethods.nb_add`
509509
* ``Py_sq_length`` to set :c:member:`PySequenceMethods.sq_length`
510510
511-
An additional slot is supported that does not correspond to a
511+
Additional slots are supported that don't correspond to a
512512
:c:type:`!PyTypeObject` struct field:
513513
514514
* :c:data:`Py_tp_token`
515+
* :c:data:`Py_tp_create_callback`
515516
516517
The following “offset” fields cannot be set using :c:type:`PyType_Slot`:
517518
@@ -607,3 +608,20 @@ The following functions and structs are used to create
607608
Expands to ``NULL``.
608609
609610
.. versionadded:: 3.14
611+
612+
613+
.. c:macro:: Py_tp_create_callback
614+
615+
A :c:member:`~PyType_Slot.slot` that records a callback to create a type.
616+
617+
Prototype::
618+
619+
int create_callback(PyTypeObject *type)
620+
621+
The callback must return ``0`` on success, or set an exception and return
622+
``-1`` on error.
623+
624+
For example, the callback can be used to customize a type (set attributes)
625+
before it's made immutable by the :c:macro:`Py_TPFLAGS_IMMUTABLETYPE` flag.
626+
627+
.. versionadded:: 3.14

Doc/whatsnew/3.14.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,11 @@ New Features
680680
<https://peps.python.org/pep-0630/#type-checking>`__ mentioned in :pep:`630`
681681
(:gh:`124153`).
682682

683+
* Add :c:macro:`Py_tp_create_callback` slot to customize a class (set
684+
attributes) before it's made immutable by the
685+
:c:macro:`Py_TPFLAGS_IMMUTABLETYPE` flag.
686+
(Contributed by Victor Stinner in :gh:`121654`.)
687+
683688

684689
Porting to Python 3.14
685690
----------------------

Include/typeslots.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,7 @@
9494
/* New in 3.14 */
9595
#define Py_tp_token 83
9696
#endif
97+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030E0000
98+
/* New in 3.14 */
99+
#define Py_tp_create_callback 84
100+
#endif

Lib/test/test_capi/test_misc.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,6 +1221,15 @@ def genf(): yield
12211221
gen = genf()
12221222
self.assertEqual(_testcapi.gen_get_code(gen), gen.gi_code)
12231223

1224+
def test_immutable_type(self):
1225+
immutable = _testcapi.Immutable
1226+
1227+
# Attribute created by the 'Py_tp_create_callback' callback
1228+
self.assertEqual(immutable.attr, "value")
1229+
1230+
with self.assertRaisesRegex(TypeError, "cannot set .* immutable type"):
1231+
setattr(immutable, "attr2", "value2")
1232+
12241233

12251234
@requires_limited_api
12261235
class TestHeapTypeRelative(unittest.TestCase):

Misc/stable_abi.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2536,3 +2536,5 @@
25362536
added = '3.14'
25372537
[const.Py_TP_USE_SPEC]
25382538
added = '3.14'
2539+
[const.Py_tp_create_callback]
2540+
added = '3.14'

Modules/_testcapi/heaptype.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,31 @@ static PyType_Spec HeapCCollection_spec = {
13131313
.slots = HeapCCollection_slots,
13141314
};
13151315

1316+
static int
1317+
immutable_create_callback(PyTypeObject *type)
1318+
{
1319+
PyObject *value = PyUnicode_FromString("value");
1320+
if (value == NULL) {
1321+
return -1;
1322+
}
1323+
1324+
int res = PyObject_SetAttrString((PyObject*)type, "attr", value);
1325+
Py_DECREF(value);
1326+
return res;
1327+
}
1328+
1329+
static PyType_Slot Immutable_slots[] = {
1330+
{Py_tp_create_callback, (void *)immutable_create_callback},
1331+
{0, 0},
1332+
};
1333+
1334+
static PyType_Spec Immutable_spec = {
1335+
.name = "_testcapi.Immutable",
1336+
.basicsize = sizeof(PyObject),
1337+
.flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE),
1338+
.slots = Immutable_slots,
1339+
};
1340+
13161341
int
13171342
_PyTestCapi_Init_Heaptype(PyObject *m) {
13181343
_testcapimodule = PyModule_GetDef(m);
@@ -1415,5 +1440,8 @@ _PyTestCapi_Init_Heaptype(PyObject *m) {
14151440
return -1;
14161441
}
14171442

1443+
PyObject *Immutable = PyType_FromSpec(&Immutable_spec);
1444+
ADD("Immutable", Immutable);
1445+
14181446
return 0;
14191447
}

Objects/typeobject.c

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4926,8 +4926,9 @@ PyType_FromMetaclass(
49264926
res_start = (char*)res;
49274927

49284928
type = &res->ht_type;
4929-
/* The flags must be initialized early, before the GC traverses us */
4930-
type->tp_flags = spec->flags | Py_TPFLAGS_HEAPTYPE;
4929+
/* The flags must be initialized early, before the GC traverses us.
4930+
* Py_TPFLAGS_IMMUTABLETYPE flag is set at the end. */
4931+
type->tp_flags = (spec->flags & ~Py_TPFLAGS_IMMUTABLETYPE) | Py_TPFLAGS_HEAPTYPE;
49314932

49324933
res->ht_module = Py_XNewRef(module);
49334934

@@ -4963,6 +4964,7 @@ PyType_FromMetaclass(
49634964

49644965
/* Copy all the ordinary slots */
49654966

4967+
void *create_callback = NULL;
49664968
for (slot = spec->slots; slot->slot; slot++) {
49674969
switch (slot->slot) {
49684970
case Py_tp_base:
@@ -4993,6 +4995,11 @@ PyType_FromMetaclass(
49934995
res->ht_token = slot->pfunc == Py_TP_USE_SPEC ? spec : slot->pfunc;
49944996
}
49954997
break;
4998+
case Py_tp_create_callback:
4999+
{
5000+
create_callback = slot->pfunc;
5001+
}
5002+
break;
49965003
default:
49975004
{
49985005
/* Copy other slots directly */
@@ -5094,6 +5101,17 @@ PyType_FromMetaclass(
50945101
}
50955102
}
50965103

5104+
if (create_callback != NULL) {
5105+
int (*callback) (PyTypeObject*) = create_callback;
5106+
if (callback(type) < 0) {
5107+
assert(PyErr_Occurred());
5108+
goto finally;
5109+
}
5110+
}
5111+
if (spec->flags & Py_TPFLAGS_IMMUTABLETYPE) {
5112+
type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE;
5113+
}
5114+
50975115
assert(_PyType_CheckConsistency(type));
50985116

50995117
finally:
@@ -5146,22 +5164,24 @@ PyType_GetModuleName(PyTypeObject *type)
51465164
void *
51475165
PyType_GetSlot(PyTypeObject *type, int slot)
51485166
{
5149-
void *parent_slot;
5150-
int slots_len = Py_ARRAY_LENGTH(pyslot_offsets);
5151-
5152-
if (slot <= 0 || slot >= slots_len) {
5167+
size_t slots_len = Py_ARRAY_LENGTH(pyslot_offsets);
5168+
if (slot <= 0 || (size_t)slot >= slots_len) {
51535169
PyErr_BadInternalCall();
51545170
return NULL;
51555171
}
51565172
int slot_offset = pyslot_offsets[slot].slot_offset;
5173+
if (slot_offset == -1) {
5174+
// Special case for Py_tp_create_callback: always NULL
5175+
return NULL;
5176+
}
51575177

51585178
if (slot_offset >= (int)sizeof(PyTypeObject)) {
51595179
if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
51605180
return NULL;
51615181
}
51625182
}
51635183

5164-
parent_slot = *(void**)((char*)type + slot_offset);
5184+
void *parent_slot = *(void**)((char*)type + slot_offset);
51655185
if (parent_slot == NULL) {
51665186
return NULL;
51675187
}

Objects/typeslots.inc

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Objects/typeslots.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ def generate_typeslots(out=sys.stdout):
1717
# The heap type structure (ht_*) is an implementation detail;
1818
# the public slot for it has a familiar `tp_` prefix
1919
member = '{-1, offsetof(PyHeapTypeObject, ht_token)}'
20+
elif member == "tp_create_callback":
21+
# PyType_GetSlot(tp_create_callback) returns NULL
22+
member = '{-1, -1}'
2023
elif member.startswith("tp_"):
2124
member = f'{{-1, offsetof(PyTypeObject, {member})}}'
2225
elif member.startswith("am_"):

0 commit comments

Comments
 (0)
0