From 4bbf3401aee6d7afe7992489f164b3a922f7f8a8 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Sun, 29 Mar 2026 17:04:08 +0530 Subject: [PATCH 1/5] add multiple slotdefs check --- Objects/typeobject.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 3374051c42af8a..83317ba08c2c86 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11768,11 +11768,27 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p, pytype_slotdef **next_p, ((PyWrapperDescrObject *)descr)->d_base->name_strobj == p->name_strobj) { void **tptr; size_t index = (p - slotdefs); - if (slotdefs_name_counts[index] == 1) { - tptr = slotptr(type, p->offset); + if (slotdefs_name_counts[index] > 1) { + /* If multiple slotdefs have the same name, + * if we need to check that only one of them has the slot filled + * and return that, else return NULL. + */ + tptr = NULL; + for (pytype_slotdef *q = slotdefs; q->name_strobj; q++) { + if (q->name_strobj != p->name_strobj) + continue; + void **qptr = slotptr(type, q->offset); + if (qptr == NULL || *qptr == NULL) + continue; + if (tptr != NULL) { + tptr = NULL; + break; + } + tptr = qptr; + } } else { - tptr = NULL; + tptr = slotptr(type, p->offset); } if (tptr == NULL || tptr == ptr) From cb32c2c78ffe967261936b1d4babdf060607feb6 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 11:39:13 +0000 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-03-29-11-39-05.gh-issue-146587.YJicXt.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-03-29-11-39-05.gh-issue-146587.YJicXt.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-29-11-39-05.gh-issue-146587.YJicXt.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-29-11-39-05.gh-issue-146587.YJicXt.rst new file mode 100644 index 00000000000000..a33dee5c875389 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-29-11-39-05.gh-issue-146587.YJicXt.rst @@ -0,0 +1 @@ +Fix type slot assignment incase of multiple slots for same name in type object implementation. Patch by Kumar Aditya. From 895ab33b2912cea85b5a2bb9c98fc9aa66a476a7 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Sun, 29 Mar 2026 17:09:53 +0530 Subject: [PATCH 3/5] add test --- Lib/test/test_descr.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index d6e3719479a214..1d7669e4fa5c96 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -5361,6 +5361,31 @@ def foo(self): with self.assertRaisesRegex(NotImplementedError, "BAR"): B().foo + def test_gh146587(self): + # See https://github.com/python/cpython/issues/146587 + + class A: + def __radd__(self, other): ... + + class B(tuple): ... + + self.assertIsNone(() + A()) + self.assertIsNone(B() + A()) + + from typing import NamedTuple + + class T(NamedTuple): + x: int + + class A: + def __init__(self, *args): + self.lst = list(args) + def __radd__(self, other): + return A(*self.lst, other) + + self.assertEqual(((1,)+A()).lst, [(1,)]) + self.assertEqual((T(x=1)+A()).lst, [T(x=1)]) + class DictProxyTests(unittest.TestCase): def setUp(self): From 13645606e21e520aeaa34b70b2d45677d57c03a9 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Mon, 30 Mar 2026 15:04:10 +0530 Subject: [PATCH 4/5] implement suggestion --- Objects/typeobject.c | 19 +++++++------------ Tools/c-analyzer/cpython/ignored.tsv | 2 +- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 83317ba08c2c86..d8ac662e304603 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11601,7 +11601,7 @@ static pytype_slotdef slotdefs[] = { /* Stores the number of times where slotdefs has elements with same name. This counter precalculated by _PyType_InitSlotDefs() when the main interpreter starts. */ -static uint8_t slotdefs_name_counts[Py_ARRAY_LENGTH(slotdefs)]; +static uint8_t slotdefs_dups[Py_ARRAY_LENGTH(slotdefs)][1 + MAX_EQUIV]; /* Given a type pointer and an offset gotten from a slotdef entry, return a pointer to the actual slot. This is not quite the same as simply adding @@ -11768,15 +11768,10 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p, pytype_slotdef **next_p, ((PyWrapperDescrObject *)descr)->d_base->name_strobj == p->name_strobj) { void **tptr; size_t index = (p - slotdefs); - if (slotdefs_name_counts[index] > 1) { - /* If multiple slotdefs have the same name, - * if we need to check that only one of them has the slot filled - * and return that, else return NULL. - */ + if (slotdefs_dups[index][0] > 1) { tptr = NULL; - for (pytype_slotdef *q = slotdefs; q->name_strobj; q++) { - if (q->name_strobj != p->name_strobj) - continue; + for (size_t i = 0; i < slotdefs_dups[index][0]; i++) { + pytype_slotdef *q = &slotdefs[slotdefs_dups[index][i + 1]]; void **qptr = slotptr(type, q->offset); if (qptr == NULL || *qptr == NULL) continue; @@ -11788,7 +11783,7 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p, pytype_slotdef **next_p, } } else { - tptr = slotptr(type, p->offset); + tptr = slotptr(type, offset); } if (tptr == NULL || tptr == ptr) @@ -12050,7 +12045,7 @@ _PyType_InitSlotDefs(PyInterpreterState *interp) Py_CLEAR(bytearray); } - memset(slotdefs_name_counts, 0, sizeof(slotdefs_name_counts)); + memset(slotdefs_dups, -1, sizeof(slotdefs_dups)); Py_ssize_t pos = 0; PyObject *key = NULL; @@ -12060,7 +12055,7 @@ _PyType_InitSlotDefs(PyInterpreterState *interp) uint8_t n = data[0]; for (uint8_t i = 0; i < n; i++) { uint8_t idx = data[i + 1]; - slotdefs_name_counts[idx] = n; + memcpy(&slotdefs_dups[idx], data, sizeof(uint8_t) * (n + 1)); } } diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index cbec0bf262f0e0..d2489387f46caa 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -351,7 +351,7 @@ Objects/obmalloc.c - obmalloc_state_initialized - Objects/typeobject.c - name_op - Objects/typeobject.c - slotdefs - # It initialized only once when main interpeter starts -Objects/typeobject.c - slotdefs_name_counts - +Objects/typeobject.c - slotdefs_dups - Objects/unicodeobject.c - stripfuncnames - Objects/unicodeobject.c - utf7_category - Objects/unicodeobject.c unicode_decode_call_errorhandler_wchar argparse - From a6829a800f170704ceefbb19d018957f84afa12d Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Mon, 30 Mar 2026 15:05:52 +0530 Subject: [PATCH 5/5] simplify loop --- Objects/typeobject.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index d8ac662e304603..6ceeb7dda08e9f 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11770,8 +11770,8 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p, pytype_slotdef **next_p, size_t index = (p - slotdefs); if (slotdefs_dups[index][0] > 1) { tptr = NULL; - for (size_t i = 0; i < slotdefs_dups[index][0]; i++) { - pytype_slotdef *q = &slotdefs[slotdefs_dups[index][i + 1]]; + for (size_t i = 1; i <= slotdefs_dups[index][0]; i++) { + pytype_slotdef *q = &slotdefs[slotdefs_dups[index][i]]; void **qptr = slotptr(type, q->offset); if (qptr == NULL || *qptr == NULL) continue;