Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 37 additions & 6 deletions Doc/c-api/type.rst
Original file line number Diff line number Diff line change
Expand Up @@ -563,10 +563,10 @@ but need extra remarks for use as slots:
:c:member:`Slot ID <PySlot.sl_id>` for the name of the type,
used to set :c:member:`PyTypeObject.tp_name`.

This slot (or :c:func:`PyType_Spec.name`) is required to create a type.
This slot (or :c:member:`PyType_Spec.name`) is required to create a type.

This may not be used in :c:member:`PyType_Spec.slots`.
Use :c:func:`PyType_Spec.name` instead.
Use :c:member:`PyType_Spec.name` instead.

.. impl-detail::

Expand All @@ -585,7 +585,7 @@ but need extra remarks for use as slots:
The value must be positive.

This may not be used in :c:member:`PyType_Spec.slots`.
Use :c:func:`PyType_Spec.basicsize` instead.
Use :c:member:`PyType_Spec.basicsize` instead.

This slot may not be used with :c:func:`PyType_GetSlot`.
Use :c:member:`PyTypeObject.tp_basicsize` instead if needed, but be aware
Expand Down Expand Up @@ -616,7 +616,7 @@ but need extra remarks for use as slots:
:c:macro:`!Py_tp_extra_basicsize` is an error.

This may not be used in :c:member:`PyType_Spec.slots`.
Use negative :c:func:`PyType_Spec.basicsize` instead.
Use negative :c:member:`PyType_Spec.basicsize` instead.

This slot may not be used with :c:func:`PyType_GetSlot`.

Expand Down Expand Up @@ -648,7 +648,7 @@ but need extra remarks for use as slots:
- With the :c:macro:`Py_TPFLAGS_ITEMS_AT_END` flag.

This may not be used in :c:member:`PyType_Spec.slots`.
Use :c:func:`PyType_Spec.itemsize` instead.
Use :c:member:`PyType_Spec.itemsize` instead.

This slot may not be used with :c:func:`PyType_GetSlot`.

Expand All @@ -663,13 +663,44 @@ but need extra remarks for use as slots:
:c:func:`PyType_FromSpecWithBases` sets it automatically.

This may not be used in :c:member:`PyType_Spec.slots`.
Use negative :c:func:`PyType_Spec.basicsize` instead.
Use negative :c:member:`PyType_Spec.basicsize` instead.

This slot may not be used with :c:func:`PyType_GetSlot`.
Use :c:func:`PyType_GetFlags` instead.

.. versionadded:: 3.15

.. c:macro:: Py_tp_bases

:c:member:`Slot ID <PySlot.sl_id>` for type flags, used to set
:c:member:`PyTypeObject.tp_bases`.

The slot can be set to a tuple of type objects which the newly created
type should inherit from, like the "positional arguments" of
a Python :ref:`class definition <class>`.

Alternately, the slot can be set to a single type object to specify
a single base.
The effect is the same as specifying a one-element tuple.

.. versionchanged:: 3.15

Previously, :c:macro:`!Py_tp_bases` required a tuple of types.

.. c:macro:: Py_tp_base

Equivalent to :c:macro:`Py_tp_bases` (with ``s`` at the end).
If both are specified, :c:macro:`!Py_tp_bases` takes priority and
this slot is ignored.

.. versionchanged:: 3.15

Previously, :c:macro:`!Py_tp_base` required a single type, not a tuple.

.. soft-deprecated:: 3.15

When not targetting older Python versions, pefer :c:macro:`!Py_tp_bases`.

The following slots do not correspond to public fields in the
underlying structures:

Expand Down
11 changes: 3 additions & 8 deletions Doc/c-api/typeobj.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1936,12 +1936,12 @@ and :c:data:`PyType_Type` effectively act as defaults.)

.. c:member:: PyTypeObject* PyTypeObject.tp_base

.. corresponding-type-slot:: Py_tp_base

An optional pointer to a base type from which type properties are inherited. At
this level, only single inheritance is supported; multiple inheritance require
dynamically creating a type object by calling the metatype.

For the corresponding slot ID, see :c:macro:`Py_tp_base`.

.. note::

.. from Modules/xxmodule.c
Expand Down Expand Up @@ -2253,17 +2253,12 @@ and :c:data:`PyType_Type` effectively act as defaults.)

.. c:member:: PyObject* PyTypeObject.tp_bases

.. corresponding-type-slot:: Py_tp_bases

Tuple of base types.

This field should be set to ``NULL`` and treated as read-only.
Python will fill it in when the type is :c:func:`initialized <PyType_Ready>`.

For dynamically created classes, the :c:data:`Py_tp_bases`
:c:type:`slot <PyType_Slot>` can be used instead of the *bases* argument
of :c:func:`PyType_FromSpecWithBases`.
The argument form is preferred.
For the corresponding slot ID, see :c:macro:`Py_tp_bases`.

.. warning::

Expand Down
4 changes: 4 additions & 0 deletions Doc/tools/removed-ids.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ reference/expressions.html: grammar-token-python-grammar-enclosure
reference/expressions.html: grammar-token-python-grammar-list_display
reference/expressions.html: grammar-token-python-grammar-parenth_form
reference/expressions.html: grammar-token-python-grammar-set_display

# Moved to a different page
c-api/typeobj.html: c.Py_tp_base
c-api/typeobj.html: c.Py_tp_bases
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2476,6 +2476,12 @@ New features
* :c:func:`PyModule_FromDefAndSpec2`
* :c:func:`PyModule_ExecDef`


The slots :c:macro:`Py_tp_bases` and :c:macro:`Py_tp_base` are now
equivalent: they can be set either to a single type or a tuple of types.
The :c:macro:`Py_tp_bases` slot is preferred; the other is ignored if both
are specified.

(Contributed by Petr Viktorin in :gh:`149044`.)

* Add :c:func:`PyUnstable_ThreadState_SetStackProtection` and
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_capi/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,7 @@ def test_tp_bases_slot(self):
def test_tp_bases_slot_none(self):
self.assertRaisesRegex(
TypeError,
"metaclass conflict",
"bases must be types",
_testcapi.create_heapctype_with_none_bases_slot
)

Expand Down
35 changes: 35 additions & 0 deletions Lib/test/test_capi/test_slots.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,38 @@ def test_repeat_error(self):
_testlimitedcapi.module_from_slots("repeat_exec", FakeSpec())
with self.assertRaisesRegex(SystemError, "multiple"):
_testlimitedcapi.module_from_slots("repeat_gil", FakeSpec())

def test_bases_slots(self):
create = _testlimitedcapi.type_from_base_slots

# Py_tp_bases overrides Py_tp_base
cls = create(base=int, bases=float)
self.assertEqual(cls.mro(), [cls, float, object])

# type is equivalent to one-element tuple
cls = create(base=None, bases=int)
self.assertEqual(cls.mro(), [cls, int, object])

cls = create(base=None, bases=(int,))
self.assertEqual(cls.mro(), [cls, int, object])

cls = create(base=int)
self.assertEqual(cls.mro(), [cls, int, object])

cls = create(base=(int,))
self.assertEqual(cls.mro(), [cls, int, object])

# Tuple of bases works
class Custom:
pass
cls = create(bases=int)
sub = create(base=float, bases=(Custom, cls, int))
self.assertEqual(sub.mro(), [sub, Custom, cls, int, object])

# Reasonable error message for non-types
with self.assertRaisesRegex(TypeError,
"bases must be types; got 'NoneType'"):
create(base=None)
with self.assertRaisesRegex(TypeError,
"bases must be types; got 'str'"):
create(bases="a string")
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Improved error message when specifying non-type base classes in
:c:macro:`Py_tp_bases`, :c:macro:`Py_tp_base`, and *bases* argument to
:c:func:`PyType_FromMetaclass` and other ``PyType_From*`` functions.
43 changes: 43 additions & 0 deletions Modules/_testlimitedcapi/slots.c
Original file line number Diff line number Diff line change
Expand Up @@ -607,13 +607,56 @@ module_from_null_slot(PyObject* Py_UNUSED(module), PyObject *args)
}, spec);
}



static PyObject *
type_from_base_slots(
PyObject *self, PyObject *args, PyObject *kwargs)
{
PyObject *base = NULL;
PyObject *bases = NULL;
if (!PyArg_ParseTupleAndKeywords(
args, kwargs, "|OO",
(char*[]){"base", "bases", NULL},
&base, &bases))
{
return NULL;
}

PySlot empty_slots[] = {
PySlot_END
};

PySlot base_slots[] = {
PySlot_DATA(Py_tp_base, base),
PySlot_END
};

PySlot bases_slots[] = {
PySlot_DATA(Py_tp_bases, bases),
PySlot_END
};

PySlot slots[] = {
PySlot_STATIC_DATA(Py_tp_name, "_testcapi.HeapCTypeWithBases"),
PySlot_UINT64(Py_tp_flags, Py_TPFLAGS_BASETYPE),
PySlot_DATA(Py_slot_subslots, base ? base_slots: empty_slots),
PySlot_DATA(Py_slot_subslots, bases ? bases_slots: empty_slots),
PySlot_END
};

return PyType_FromSlots(slots);
}

static PyMethodDef _TestMethods[] = {
{"type_from_slots", type_from_slots, METH_VARARGS},
{"module_from_gil_slot", module_from_gil_slot, METH_VARARGS},
{"type_from_null_slot", type_from_null_slot, METH_VARARGS},
{"type_from_null_spec_slot", type_from_null_spec_slot, METH_VARARGS},
{"module_from_slots", module_from_slots, METH_VARARGS},
{"module_from_null_slot", module_from_null_slot, METH_VARARGS},
{"type_from_base_slots", _PyCFunction_CAST(type_from_base_slots),
METH_VARARGS | METH_KEYWORDS},
{NULL},
};
static PyMethodDef *TestMethods = _TestMethods;
Expand Down
18 changes: 10 additions & 8 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3712,9 +3712,9 @@ find_best_base(PyObject *bases)
for (i = 0; i < n; i++) {
PyObject *base_proto = PyTuple_GET_ITEM(bases, i);
if (!PyType_Check(base_proto)) {
PyErr_SetString(
PyErr_Format(
PyExc_TypeError,
"bases must be types");
"bases must be types; got '%T'", base_proto);
return NULL;
}
PyTypeObject *base_i = (PyTypeObject *)base_proto;
Expand Down Expand Up @@ -4162,8 +4162,9 @@ _PyType_CalculateMetaclass(PyTypeObject *metatype, PyObject *bases)
for (i = 0; i < nbases; i++) {
tmp = PyTuple_GET_ITEM(bases, i);
tmptype = Py_TYPE(tmp);
if (PyType_IsSubtype(winner, tmptype))
if (PyType_IsSubtype(winner, tmptype)) {
continue;
}
if (PyType_IsSubtype(tmptype, winner)) {
winner = tmptype;
continue;
Expand Down Expand Up @@ -5524,6 +5525,12 @@ type_from_slots_or_spec(
}
}

/* Calculate best base, and check that all bases are type objects */
PyTypeObject *base = find_best_base(bases); // borrowed ref
if (base == NULL) {
goto finally;
}

/* Calculate the metaclass */

if (!metaclass) {
Expand All @@ -5546,11 +5553,6 @@ type_from_slots_or_spec(
goto finally;
}

/* Calculate best base, and check that all bases are type objects */
PyTypeObject *base = find_best_base(bases); // borrowed ref
if (base == NULL) {
goto finally;
}
// find_best_base() should check Py_TPFLAGS_BASETYPE & raise a proper
// exception, here we just check its work
assert(_PyType_HasFeature(base, Py_TPFLAGS_BASETYPE));
Expand Down
Loading