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): 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. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 3374051c42af8a..6ceeb7dda08e9f 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,11 +11768,22 @@ 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_dups[index][0] > 1) { + tptr = NULL; + 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; + if (tptr != NULL) { + tptr = NULL; + break; + } + tptr = qptr; + } } else { - tptr = NULL; + tptr = slotptr(type, offset); } if (tptr == NULL || tptr == ptr) @@ -12034,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; @@ -12044,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 -