Commits

Armin Rigo committed f16be5a

Same issue and same fix for sets.

Comments (0)

Files changed (3)

pypy/objspace/std/setobject.py

         w_set.sstorage = w_other.get_storage_copy()
 
     def iter(self, w_set):
-        return EmptyIteratorImplementation(self.space, w_set)
+        return EmptyIteratorImplementation(self.space, self, w_set)
 
     def popitem(self, w_set):
         raise OperationError(self.space.w_KeyError,
             d_obj[w_item] = None
 
 class IteratorImplementation(object):
-    def __init__(self, space, implementation):
+    def __init__(self, space, strategy, implementation):
         self.space = space
+        self.strategy = strategy
         self.setimplementation = implementation
         self.len = implementation.length()
         self.pos = 0
         if self.pos < self.len:
             result = self.next_entry()
             self.pos += 1
-            return result
+            if self.strategy is self.setimplementation.strategy:
+                return result      # common case
+            else:
+                # waaa, obscure case: the strategy changed, but not the
+                # length of the set.  The 'result' might be out-of-date.
+                # We try to explicitly look it up in the set.
+                if not self.setimplementation.has_key(result):
+                    self.len = -1   # Make this error state sticky
+                    raise OperationError(self.space.w_RuntimeError,
+                        self.space.wrap("dictionary changed during iteration"))
+                return result
         # no more entries
         self.setimplementation = None
         return None
 
 class StringIteratorImplementation(IteratorImplementation):
     def __init__(self, space, strategy, w_set):
-        IteratorImplementation.__init__(self, space, w_set)
+        IteratorImplementation.__init__(self, space, strategy, w_set)
         d = strategy.unerase(w_set.sstorage)
         self.iterator = d.iterkeys()
 
 
 class IntegerIteratorImplementation(IteratorImplementation):
     #XXX same implementation in dictmultiobject on dictstrategy-branch
-    def __init__(self, space, strategy, dictimplementation):
-        IteratorImplementation.__init__(self, space, dictimplementation)
-        d = strategy.unerase(dictimplementation.sstorage)
+    def __init__(self, space, strategy, w_set):
+        IteratorImplementation.__init__(self, space, strategy, w_set)
+        d = strategy.unerase(w_set.sstorage)
         self.iterator = d.iterkeys()
 
     def next_entry(self):
             return None
 
 class RDictIteratorImplementation(IteratorImplementation):
-    def __init__(self, space, strategy, dictimplementation):
-        IteratorImplementation.__init__(self, space, dictimplementation)
-        d = strategy.unerase(dictimplementation.sstorage)
+    def __init__(self, space, strategy, w_set):
+        IteratorImplementation.__init__(self, space, strategy, w_set)
+        d = strategy.unerase(w_set.sstorage)
         self.iterator = d.iterkeys()
 
     def next_entry(self):

pypy/objspace/std/test/test_dictmultiobject.py

         class Foo(object):
             def __eq__(self, other):
                 return False
-        d.get(Foo())    # this changes the strategy of 'd'
+        assert d.get(Foo()) is None    # this changes the strategy of 'd'
         lst = list(it)  # but iterating still works
         assert sorted(lst) == [(1, 2), (3, 4), (5, 6)]
 
         it = d.iteritems()
         d['foo'] = 'bar'
         del d[1]
-        # 'd' is still length 3, but its strategy changed
-        raises(RuntimeError, it.next)
+        # 'd' is still length 3, but its strategy changed.  we are
+        # getting a RuntimeError because iterating over the old storage
+        # gives us (1, 2), but 1 is not in the dict any longer.
+        raises(RuntimeError, list, it)
 
 
 class FakeString(str):

pypy/objspace/std/test/test_setobject.py

                 return [5, 3, 4][i]
         s = set([10,3,2]).intersection(Obj())
         assert list(s) == [3]
+
+    def test_iter_set_length_change(self):
+        s = set([1, 3, 5])
+        it = iter(s)
+        s.add(7)
+        # 's' is now length 4
+        raises(RuntimeError, it.next)
+
+    def test_iter_set_strategy_only_change_1(self):
+        s = set([1, 3, 5])
+        it = iter(s)
+        class Foo(object):
+            def __eq__(self, other):
+                return False
+        assert Foo() not in s      # this changes the strategy of 'd'
+        lst = list(s)  # but iterating still works
+        assert sorted(lst) == [1, 3, 5]
+
+    def test_iter_set_strategy_only_change_2(self):
+        s = set([1, 3, 5])
+        it = iter(s)
+        s.add('foo')
+        s.remove(1)
+        # 's' is still length 3, but its strategy changed.  we are
+        # getting a RuntimeError because iterating over the old storage
+        # gives us 1, but 1 is not in the set any longer.
+        raises(RuntimeError, list, it)