Thanks for additional examples of slots, it helps me to understand the big picture.
So if I understand correctly, PyType_FromModuleAndSpec() and PyType_FromMetaclass() were added instead of adding Py_tp_module and Py_tp_metaclass, because slots are almost always defined at the module level as a static array like static PyType_Slot type_slots[] = {...};. Interesting. I now see better the advantage of nested slots:
static PySlot type_static_slots[] = {
... // values known at build time
};
static int
init_type(PyObject *module)
{
PySlot type_dynamic_slots[] = {
... // values only known at runtime
PySlot_PTR(Py_tp_slots, type_static_slots),
PySlot_END,
};
state->type = PyType_FromSlots(type_dynamic_slots);
...
}
For example, the module isn’t known at build time, but it can only be used in “dynamic slots”.
For PySlot_OPTIONAL, if I understood correctly the use case, the idea is to build an extension with Python 3.16 and new slots which only exists in Python 3.16, but mark these slots as “optional” to be able to use this extension on Python 3.15: so PyType_Slots() know that these unknown slots should be ignored.
If I understand correctly, all slots added after Python 3.15 should be used with PySlot_OPTIONAL. That’s not convenient. I would instead expect PyType_Slots() to ignore all “new” slots by default.
But if we add a new “mandatory” slot in Python 3.16, it should not be silently ignored by Python 3.15 (but fail with an error). In that case, I would prefer to mark such specific slot as PySlot_MANDATORY.
I don’t understand the PySlot_HAS_FALLBACK example with tp_getattr to tp_getattro.
For me, it seems easy to modify PyType_FromSlots() to handle migrations. For example, if tp_getattr and tp_getattro are defined, only use tp_getattro (and ignore tp_getattr). I don’t see how it would be better to implement the migration logic in slots.
I would prefer to remove PySlot_HAS_FALLBACK for now.