From d279067c78dfa4933357cc11b796e22e045383d3 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 11 Mar 2026 14:57:25 +0100 Subject: [PATCH 1/6] Fix bug in tracking of structseq --- Objects/structseq.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Objects/structseq.c b/Objects/structseq.c index b8bb041f0cff21..8fa9cbba3bcce3 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -445,6 +445,7 @@ structseq_replace(PyObject *op, PyObject *args, PyObject *kwargs) } } + _PyObject_GC_TRACK(result); return (PyObject *)result; error: From bf01d4c3cff8edb857fe2218075d1e789d9ae077 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 11 Mar 2026 15:02:40 +0100 Subject: [PATCH 2/6] add test --- Lib/test/test_structseq.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Lib/test/test_structseq.py b/Lib/test/test_structseq.py index 9622151143cd78..68c04d9bf1d296 100644 --- a/Lib/test/test_structseq.py +++ b/Lib/test/test_structseq.py @@ -1,4 +1,5 @@ import copy +import gc import os import pickle import re @@ -355,6 +356,14 @@ def test_reference_cycle(self): type(t).refcyle = t """)) + def test_replace_gc_tracked(self): + # Verify that __replace__ results are properly GC-tracked + time_struct= time.gmtime(0) + lst = [] + replaced_struct = time_struct.__replace__(tm_year=lst) + lst.append(replaced_struct) + + self.assertTrue(gc.is_tracked(replaced_struct)) if __name__ == "__main__": unittest.main() From 0d3d149cc645a9b8ea48fef7d92cd02d7818322a Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 11 Mar 2026 15:05:23 +0100 Subject: [PATCH 3/6] pep8 --- Lib/test/test_structseq.py | 2 +- Objects/typeobject.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_structseq.py b/Lib/test/test_structseq.py index 68c04d9bf1d296..74506fc54de50e 100644 --- a/Lib/test/test_structseq.py +++ b/Lib/test/test_structseq.py @@ -358,7 +358,7 @@ def test_reference_cycle(self): def test_replace_gc_tracked(self): # Verify that __replace__ results are properly GC-tracked - time_struct= time.gmtime(0) + time_struct = time.gmtime(0) lst = [] replaced_struct = time_struct.__replace__(tm_year=lst) lst.append(replaced_struct) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 81abb0990eebe9..f5a03ceb7a3044 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5978,7 +5978,7 @@ PyObject_GetItemData(PyObject *obj) } /* Internal API to look for a name through the MRO, bypassing the method cache. - This returns a borrowed reference, and might set an exception. + This returns a new reference, and might set an exception. 'error' is set to: -1: error with exception; 1: error without exception; 0: ok */ static PyObject * find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) From 0b54103c43a0f1b1f745c2616f1001c052c14f00 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 11 Mar 2026 15:07:14 +0100 Subject: [PATCH 4/6] pep8 --- Lib/test/test_structseq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_structseq.py b/Lib/test/test_structseq.py index 68c04d9bf1d296..74506fc54de50e 100644 --- a/Lib/test/test_structseq.py +++ b/Lib/test/test_structseq.py @@ -358,7 +358,7 @@ def test_reference_cycle(self): def test_replace_gc_tracked(self): # Verify that __replace__ results are properly GC-tracked - time_struct= time.gmtime(0) + time_struct = time.gmtime(0) lst = [] replaced_struct = time_struct.__replace__(tm_year=lst) lst.append(replaced_struct) From 3bd205fb7e8b166e96c4cc7a3dab1944bfd1313c Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 11 Mar 2026 15:09:14 +0100 Subject: [PATCH 5/6] revert unrelated changes --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index f5a03ceb7a3044..81abb0990eebe9 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5978,7 +5978,7 @@ PyObject_GetItemData(PyObject *obj) } /* Internal API to look for a name through the MRO, bypassing the method cache. - This returns a new reference, and might set an exception. + This returns a borrowed reference, and might set an exception. 'error' is set to: -1: error with exception; 1: error without exception; 0: ok */ static PyObject * find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) From 2e048646a8109fc704dd608df69dd9cae4b0a912 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 21:27:36 +0000 Subject: [PATCH 6/6] =?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-11-21-27-28.gh-issue-145376.LfDvyw.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-03-11-21-27-28.gh-issue-145376.LfDvyw.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-11-21-27-28.gh-issue-145376.LfDvyw.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-11-21-27-28.gh-issue-145376.LfDvyw.rst new file mode 100644 index 00000000000000..f49c672c76e9f3 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-11-21-27-28.gh-issue-145376.LfDvyw.rst @@ -0,0 +1 @@ +Fix GC tracking for ``structseq`` instances created with ``__replace__``.