Skip to content

Commit 634a6c2

Browse files
authored
Merge pull request #2875 from DimitrisJim/retry_set_ops
Retrying contains, remove, discard with a frozenset.
2 parents 724d75d + 4a46024 commit 634a6c2

File tree

2 files changed

+39
-11
lines changed

2 files changed

+39
-11
lines changed

Lib/test/test_set.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -379,8 +379,6 @@ class TestSet(TestJointOps, unittest.TestCase):
379379
thetype = set
380380
basetype = set
381381

382-
# TODO: RUSTPYTHON
383-
@unittest.expectedFailure
384382
def test_contains(self):
385383
super().test_contains()
386384

@@ -440,8 +438,6 @@ def test_add(self):
440438
self.assertEqual(self.s, dup)
441439
self.assertRaises(TypeError, self.s.add, [])
442440

443-
# TODO: RUSTPYTHON
444-
@unittest.expectedFailure
445441
def test_remove(self):
446442
self.s.remove('a')
447443
self.assertNotIn('a', self.s)
@@ -464,8 +460,6 @@ def test_remove_keyerror_unpacking(self):
464460
else:
465461
self.fail()
466462

467-
# TODO: RUSTPYTHON
468-
@unittest.expectedFailure
469463
def test_remove_keyerror_set(self):
470464
key = self.thetype([3, 4])
471465
try:
@@ -477,8 +471,6 @@ def test_remove_keyerror_set(self):
477471
else:
478472
self.fail()
479473

480-
# TODO: RUSTPYTHON
481-
@unittest.expectedFailure
482474
def test_discard(self):
483475
self.s.discard('a')
484476
self.assertNotIn('a', self.s)

vm/src/builtins/set.rs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ impl PySetInner {
9393
}
9494

9595
fn contains(&self, needle: &PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
96-
self.content.contains(vm, needle)
96+
self.retry_op_with_frozenset(needle, vm, |needle, vm| self.content.contains(vm, needle))
9797
}
9898

9999
fn compare(
@@ -213,11 +213,11 @@ impl PySetInner {
213213
}
214214

215215
fn remove(&self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
216-
self.content.delete(vm, item)
216+
self.retry_op_with_frozenset(&item, vm, |item, vm| self.content.delete(vm, item.clone()))
217217
}
218218

219219
fn discard(&self, item: &PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
220-
self.content.delete_if_exists(vm, item)
220+
self.retry_op_with_frozenset(item, vm, |item, vm| self.content.delete_if_exists(vm, item))
221221
}
222222

223223
fn clear(&self) {
@@ -285,6 +285,42 @@ impl PySetInner {
285285
fn hash(&self, vm: &VirtualMachine) -> PyResult<PyHash> {
286286
crate::utils::hash_iter_unordered(self.content.keys().iter(), vm)
287287
}
288+
289+
// Run operation, on failure, if item is a set/set subclass, convert it
290+
// into a frozenset and try the operation again. Propagates original error
291+
// on failure to convert and restores item in KeyError on failure (remove).
292+
fn retry_op_with_frozenset<T, F>(
293+
&self,
294+
item: &PyObjectRef,
295+
vm: &VirtualMachine,
296+
op: F,
297+
) -> PyResult<T>
298+
where
299+
F: Fn(&PyObjectRef, &VirtualMachine) -> PyResult<T>,
300+
{
301+
op(item, vm).or_else(|original_err| {
302+
item.payload_if_subclass::<PySet>(vm)
303+
// Keep original error around.
304+
.ok_or(original_err)
305+
.and_then(|set| {
306+
op(
307+
&PyFrozenSet {
308+
inner: set.inner.copy(),
309+
}
310+
.into_object(vm),
311+
vm,
312+
)
313+
// If operation raised KeyError, report original set (set.remove)
314+
.map_err(|op_err| {
315+
if op_err.isinstance(&vm.ctx.exceptions.key_error) {
316+
vm.new_key_error(item.clone())
317+
} else {
318+
op_err
319+
}
320+
})
321+
})
322+
})
323+
}
288324
}
289325

290326
fn extract_set(obj: &PyObjectRef) -> Option<&PySetInner> {

0 commit comments

Comments
 (0)