From 2c4327c5009c9bde6a9143989a62baa8ece8d4b5 Mon Sep 17 00:00:00 2001 From: Igor Stadnyk <33496176+fallenmi@users.noreply.github.com> Date: Mon, 12 Oct 2026 09:21:48 +0200 Subject: [PATCH] Fix IndexedSet indices after trimming a fragmented dead tail --- boltons/setutils.py | 5 +++-- tests/test_setutils.py | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/boltons/setutils.py b/boltons/setutils.py index c9a02815..8a884a62 100644 --- a/boltons/setutils.py +++ b/boltons/setutils.py @@ -152,9 +152,10 @@ def _cull(self): num_dead = 1 while items[-(num_dead + 1)] is _MISSING: num_dead += 1 - if ded and ded[-1][1] == len(items): - del ded[-1] del items[-num_dead:] + # The dead tail may span several adjacent, unmerged intervals. + while ded and ded[-1][0] >= len(items): + del ded[-1] def _get_real_index(self, index): if index < 0: diff --git a/tests/test_setutils.py b/tests/test_setutils.py index cfed65ff..ce5a868a 100644 --- a/tests/test_setutils.py +++ b/tests/test_setutils.py @@ -331,3 +331,27 @@ def test_small_difference_update_unchanged(): thing = IndexedSet(range(100)) thing.difference_update({1, 3, 5}) assert list(thing)[:5] == [0, 2, 4, 6, 7] + + +@mark.parametrize('method', ['remove', 'discard']) +@mark.parametrize('size, removed', [ + (39, (0, 37, 36, 38)), + (80, (0, 78, 77, 76, 79)), + (39, (0, 36, 37, 38)), +]) +def test_iset_add_after_trimming_dead_tail(method, size, removed): + iset = IndexedSet(range(size)) + expected = list(range(size)) + for value in removed: + getattr(iset, method)(value) + expected.remove(value) + assert list(iset) == expected + + for value in (size, size + 1): + iset.add(value) + expected.append(value) + assert iset.index(value) == len(expected) - 1 + assert list(iset) == expected + assert [iset[i] for i in range(len(iset))] == expected + assert [iset[i] for i in range(-len(iset), 0)] == expected + assert [iset.index(item) for item in expected] == list(range(len(expected)))