Support slice hash (#5123)

* make slice object hashable

* Update test_slice.py from CPython v3.12

* remove TODO

* remove outdated tests
This commit is contained in:
ChenyG
2023-11-25 12:11:17 +08:00
committed by GitHub
parent 8fc2c39d62
commit dc4f6994fb
3 changed files with 92 additions and 16 deletions

View File

@@ -5,6 +5,7 @@ import operator
import sys
import unittest
import weakref
import copy
from pickle import loads, dumps
from test import support
@@ -79,10 +80,16 @@ class SliceTest(unittest.TestCase):
self.assertEqual(repr(slice(1, 2, 3)), "slice(1, 2, 3)")
def test_hash(self):
# Verify clearing of SF bug #800796
self.assertRaises(TypeError, hash, slice(5))
self.assertEqual(hash(slice(5)), slice(5).__hash__())
self.assertEqual(hash(slice(1, 2)), slice(1, 2).__hash__())
self.assertEqual(hash(slice(1, 2, 3)), slice(1, 2, 3).__hash__())
self.assertNotEqual(slice(5), slice(6))
with self.assertRaises(TypeError):
slice(5).__hash__()
hash(slice(1, 2, []))
with self.assertRaises(TypeError):
hash(slice(4, {}))
def test_cmp(self):
s1 = slice(1, 2, 3)
@@ -235,13 +242,50 @@ class SliceTest(unittest.TestCase):
self.assertEqual(tmp, [(slice(1, 2), 42)])
def test_pickle(self):
import pickle
s = slice(10, 20, 3)
for protocol in (0,1,2):
for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
t = loads(dumps(s, protocol))
self.assertEqual(s, t)
self.assertEqual(s.indices(15), t.indices(15))
self.assertNotEqual(id(s), id(t))
def test_copy(self):
s = slice(1, 10)
c = copy.copy(s)
self.assertIs(s, c)
s = slice(1, 10, 2)
c = copy.copy(s)
self.assertIs(s, c)
# Corner case for mutable indices:
s = slice([1, 2], [3, 4], [5, 6])
c = copy.copy(s)
self.assertIs(s, c)
self.assertIs(s.start, c.start)
self.assertIs(s.stop, c.stop)
self.assertIs(s.step, c.step)
def test_deepcopy(self):
s = slice(1, 10)
c = copy.deepcopy(s)
self.assertEqual(s, c)
s = slice(1, 10, 2)
c = copy.deepcopy(s)
self.assertEqual(s, c)
# Corner case for mutable indices:
s = slice([1, 2], [3, 4], [5, 6])
c = copy.deepcopy(s)
self.assertIsNot(s, c)
self.assertEqual(s, c)
self.assertIsNot(s.start, c.start)
self.assertIsNot(s.stop, c.stop)
self.assertIsNot(s.step, c.step)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_cycle(self):

View File

@@ -82,16 +82,6 @@ assert_raises(TypeError, lambda: slice(0) > 3)
assert_raises(TypeError, lambda: slice(0) <= 3)
assert_raises(TypeError, lambda: slice(0) >= 3)
# TODO: slice is hashable in CPython 3.12
# assert_raises(TypeError, hash, slice(0))
# assert_raises(TypeError, hash, slice(None))
#
# def dict_slice():
# d = {}
# d[slice(0)] = 3
#
# assert_raises(TypeError, dict_slice)
assert slice(None ).indices(10) == (0, 10, 1)
assert slice(None, None, 2).indices(10) == (0, 10, 2)
assert slice(1, None, 2).indices(10) == (1, 10, 2)

View File

@@ -3,10 +3,11 @@
use super::{PyStrRef, PyTupleRef, PyType, PyTypeRef};
use crate::{
class::PyClassImpl,
common::hash::{PyHash, PyUHash},
convert::ToPyObject,
function::{ArgIndex, FuncArgs, OptionalArg, PyComparisonValue},
sliceable::SaturatedSlice,
types::{Comparable, Constructor, PyComparisonOp, Representable},
types::{Comparable, Constructor, Hashable, PyComparisonOp, Representable},
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
};
use malachite_bigint::{BigInt, ToBigInt};
@@ -26,7 +27,7 @@ impl PyPayload for PySlice {
}
}
#[pyclass(with(Comparable, Representable))]
#[pyclass(with(Comparable, Representable, Hashable))]
impl PySlice {
#[pygetset]
fn start(&self, vm: &VirtualMachine) -> PyObjectRef {
@@ -197,6 +198,47 @@ impl PySlice {
}
}
impl Hashable for PySlice {
#[inline]
fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> {
const XXPRIME_1: PyUHash = if cfg!(target_pointer_width = "64") {
11400714785074694791
} else {
2654435761
};
const XXPRIME_2: PyUHash = if cfg!(target_pointer_width = "64") {
14029467366897019727
} else {
2246822519
};
const XXPRIME_5: PyUHash = if cfg!(target_pointer_width = "64") {
2870177450012600261
} else {
374761393
};
const ROTATE: u32 = if cfg!(target_pointer_width = "64") {
31
} else {
13
};
let mut acc = XXPRIME_5;
for part in [zelf.start_ref(vm), &zelf.stop, zelf.step_ref(vm)].iter() {
let lane = part.hash(vm)? as PyUHash;
if lane == u64::MAX as PyUHash {
return Ok(-1 as PyHash);
}
acc = acc.wrapping_add(lane.wrapping_mul(XXPRIME_2));
acc = acc.rotate_left(ROTATE);
acc = acc.wrapping_mul(XXPRIME_1);
}
if acc == u64::MAX as PyUHash {
return Ok(1546275796 as PyHash);
}
Ok(acc as PyHash)
}
}
impl Comparable for PySlice {
fn cmp(
zelf: &Py<Self>,