diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-03-15-43-34.gh-issue-149180.RGWxs3.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-03-15-43-34.gh-issue-149180.RGWxs3.rst new file mode 100644 index 00000000000..cdb2ec13025 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-03-15-43-34.gh-issue-149180.RGWxs3.rst @@ -0,0 +1,3 @@ +During :c:func:`PyType_Ready`, ``tp_as_number``, ``tp_as_sequence``, and +``tp_as_mapping`` are now guaranteed to be non-NULL for all ready types. +Types that don't define their own struct share a common empty one. diff --git a/Objects/abstract.c b/Objects/abstract.c index 0bbf60840a3..a52d6fd347d 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -1343,7 +1343,7 @@ PyNumber_InPlaceMultiply(PyObject *v, PyObject *w) if (f != NULL) return sequence_repeat(f, v, w); } - else if (mw != NULL) { + if (mw != NULL) { /* Note that the right hand operand should not be * mutated in this case so sq_inplace_repeat is not * used. */ diff --git a/Objects/typeobject.c b/Objects/typeobject.c index fb3c7101410..6387fab9b18 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -896,6 +896,10 @@ _PyType_CheckConsistency(PyTypeObject *type) CHECK(PyDict_Contains(lookup_tp_dict(type), &_Py_ID(__new__)) == 0); } + CHECK(type->tp_as_number != NULL); + CHECK(type->tp_as_sequence != NULL); + CHECK(type->tp_as_mapping != NULL); + return 1; #undef CHECK } @@ -8718,7 +8722,7 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base) if (type->tp_as_number != NULL && base->tp_as_number != NULL) { basebase = base->tp_base; - if (basebase->tp_as_number == NULL) + if (basebase == NULL || basebase->tp_as_number == NULL) basebase = NULL; COPYNUM(nb_add); COPYNUM(nb_subtract); @@ -8768,7 +8772,7 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base) if (type->tp_as_sequence != NULL && base->tp_as_sequence != NULL) { basebase = base->tp_base; - if (basebase->tp_as_sequence == NULL) + if (basebase == NULL || basebase->tp_as_sequence == NULL) basebase = NULL; COPYSEQ(sq_length); COPYSEQ(sq_concat); @@ -8782,7 +8786,7 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base) if (type->tp_as_mapping != NULL && base->tp_as_mapping != NULL) { basebase = base->tp_base; - if (basebase->tp_as_mapping == NULL) + if (basebase == NULL || basebase->tp_as_mapping == NULL) basebase = NULL; COPYMAP(mp_length); COPYMAP(mp_subscript); @@ -9155,6 +9159,10 @@ type_ready_mro(PyTypeObject *type, int initial) } +static PyNumberMethods _Py_empty_number_methods = {0}; +static PySequenceMethods _Py_empty_sequence_methods = {0}; +static PyMappingMethods _Py_empty_mapping_methods = {0}; + // For static types, inherit tp_as_xxx structures from the base class // if it's NULL. // @@ -9216,6 +9224,19 @@ type_ready_inherit(PyTypeObject *type) type_ready_inherit_as_structs(type, base); } + // Ensure tp_as_number, tp_as_sequence, and tp_as_mapping are never + // NULL after PyType_Ready. Types that don't provide their own struct + // share a common empty one. + if (type->tp_as_number == NULL) { + type->tp_as_number = &_Py_empty_number_methods; + } + if (type->tp_as_sequence == NULL) { + type->tp_as_sequence = &_Py_empty_sequence_methods; + } + if (type->tp_as_mapping == NULL) { + type->tp_as_mapping = &_Py_empty_mapping_methods; + } + /* Sanity check for tp_free. */ if (_PyType_IS_GC(type) && (type->tp_flags & Py_TPFLAGS_BASETYPE) && (type->tp_free == NULL || type->tp_free == PyObject_Free))