Skip to content

Commit dc4f699

Browse files
authored
Support slice hash (RustPython#5123)
* make slice object hashable * Update test_slice.py from CPython v3.12 * remove TODO * remove outdated tests
1 parent 8fc2c39 commit dc4f699

File tree

3 files changed

+92
-16
lines changed

3 files changed

+92
-16
lines changed

Lib/test/test_slice.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import sys
66
import unittest
77
import weakref
8+
import copy
89

910
from pickle import loads, dumps
1011
from test import support
@@ -79,10 +80,16 @@ def test_repr(self):
7980
self.assertEqual(repr(slice(1, 2, 3)), "slice(1, 2, 3)")
8081

8182
def test_hash(self):
82-
# Verify clearing of SF bug #800796
83-
self.assertRaises(TypeError, hash, slice(5))
83+
self.assertEqual(hash(slice(5)), slice(5).__hash__())
84+
self.assertEqual(hash(slice(1, 2)), slice(1, 2).__hash__())
85+
self.assertEqual(hash(slice(1, 2, 3)), slice(1, 2, 3).__hash__())
86+
self.assertNotEqual(slice(5), slice(6))
87+
88+
with self.assertRaises(TypeError):
89+
hash(slice(1, 2, []))
90+
8491
with self.assertRaises(TypeError):
85-
slice(5).__hash__()
92+
hash(slice(4, {}))
8693

8794
def test_cmp(self):
8895
s1 = slice(1, 2, 3)
@@ -235,13 +242,50 @@ def __setitem__(self, i, k):
235242
self.assertEqual(tmp, [(slice(1, 2), 42)])
236243

237244
def test_pickle(self):
245+
import pickle
246+
238247
s = slice(10, 20, 3)
239-
for protocol in (0,1,2):
248+
for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
240249
t = loads(dumps(s, protocol))
241250
self.assertEqual(s, t)
242251
self.assertEqual(s.indices(15), t.indices(15))
243252
self.assertNotEqual(id(s), id(t))
244253

254+
def test_copy(self):
255+
s = slice(1, 10)
256+
c = copy.copy(s)
257+
self.assertIs(s, c)
258+
259+
s = slice(1, 10, 2)
260+
c = copy.copy(s)
261+
self.assertIs(s, c)
262+
263+
# Corner case for mutable indices:
264+
s = slice([1, 2], [3, 4], [5, 6])
265+
c = copy.copy(s)
266+
self.assertIs(s, c)
267+
self.assertIs(s.start, c.start)
268+
self.assertIs(s.stop, c.stop)
269+
self.assertIs(s.step, c.step)
270+
271+
def test_deepcopy(self):
272+
s = slice(1, 10)
273+
c = copy.deepcopy(s)
274+
self.assertEqual(s, c)
275+
276+
s = slice(1, 10, 2)
277+
c = copy.deepcopy(s)
278+
self.assertEqual(s, c)
279+
280+
# Corner case for mutable indices:
281+
s = slice([1, 2], [3, 4], [5, 6])
282+
c = copy.deepcopy(s)
283+
self.assertIsNot(s, c)
284+
self.assertEqual(s, c)
285+
self.assertIsNot(s.start, c.start)
286+
self.assertIsNot(s.stop, c.stop)
287+
self.assertIsNot(s.step, c.step)
288+
245289
# TODO: RUSTPYTHON
246290
@unittest.expectedFailure
247291
def test_cycle(self):

extra_tests/snippets/builtin_slice.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,6 @@
8282
assert_raises(TypeError, lambda: slice(0) <= 3)
8383
assert_raises(TypeError, lambda: slice(0) >= 3)
8484

85-
# TODO: slice is hashable in CPython 3.12
86-
# assert_raises(TypeError, hash, slice(0))
87-
# assert_raises(TypeError, hash, slice(None))
88-
#
89-
# def dict_slice():
90-
# d = {}
91-
# d[slice(0)] = 3
92-
#
93-
# assert_raises(TypeError, dict_slice)
94-
9585
assert slice(None ).indices(10) == (0, 10, 1)
9686
assert slice(None, None, 2).indices(10) == (0, 10, 2)
9787
assert slice(1, None, 2).indices(10) == (1, 10, 2)

vm/src/builtins/slice.rs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
use super::{PyStrRef, PyTupleRef, PyType, PyTypeRef};
44
use crate::{
55
class::PyClassImpl,
6+
common::hash::{PyHash, PyUHash},
67
convert::ToPyObject,
78
function::{ArgIndex, FuncArgs, OptionalArg, PyComparisonValue},
89
sliceable::SaturatedSlice,
9-
types::{Comparable, Constructor, PyComparisonOp, Representable},
10+
types::{Comparable, Constructor, Hashable, PyComparisonOp, Representable},
1011
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
1112
};
1213
use malachite_bigint::{BigInt, ToBigInt};
@@ -26,7 +27,7 @@ impl PyPayload for PySlice {
2627
}
2728
}
2829

29-
#[pyclass(with(Comparable, Representable))]
30+
#[pyclass(with(Comparable, Representable, Hashable))]
3031
impl PySlice {
3132
#[pygetset]
3233
fn start(&self, vm: &VirtualMachine) -> PyObjectRef {
@@ -197,6 +198,47 @@ impl PySlice {
197198
}
198199
}
199200

201+
impl Hashable for PySlice {
202+
#[inline]
203+
fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> {
204+
const XXPRIME_1: PyUHash = if cfg!(target_pointer_width = "64") {
205+
11400714785074694791
206+
} else {
207+
2654435761
208+
};
209+
const XXPRIME_2: PyUHash = if cfg!(target_pointer_width = "64") {
210+
14029467366897019727
211+
} else {
212+
2246822519
213+
};
214+
const XXPRIME_5: PyUHash = if cfg!(target_pointer_width = "64") {
215+
2870177450012600261
216+
} else {
217+
374761393
218+
};
219+
const ROTATE: u32 = if cfg!(target_pointer_width = "64") {
220+
31
221+
} else {
222+
13
223+
};
224+
225+
let mut acc = XXPRIME_5;
226+
for part in [zelf.start_ref(vm), &zelf.stop, zelf.step_ref(vm)].iter() {
227+
let lane = part.hash(vm)? as PyUHash;
228+
if lane == u64::MAX as PyUHash {
229+
return Ok(-1 as PyHash);
230+
}
231+
acc = acc.wrapping_add(lane.wrapping_mul(XXPRIME_2));
232+
acc = acc.rotate_left(ROTATE);
233+
acc = acc.wrapping_mul(XXPRIME_1);
234+
}
235+
if acc == u64::MAX as PyUHash {
236+
return Ok(1546275796 as PyHash);
237+
}
238+
Ok(acc as PyHash)
239+
}
240+
}
241+
200242
impl Comparable for PySlice {
201243
fn cmp(
202244
zelf: &Py<Self>,

0 commit comments

Comments
 (0)