diff --git a/Lib/test/test_slice.py b/Lib/test/test_slice.py index 9e79775ac..53d4c7761 100644 --- a/Lib/test/test_slice.py +++ b/Lib/test/test_slice.py @@ -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): diff --git a/extra_tests/snippets/builtin_slice.py b/extra_tests/snippets/builtin_slice.py index 57fb7e21c..b5c3a8ceb 100644 --- a/extra_tests/snippets/builtin_slice.py +++ b/extra_tests/snippets/builtin_slice.py @@ -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) diff --git a/vm/src/builtins/slice.rs b/vm/src/builtins/slice.rs index e06aee682..5da364911 100644 --- a/vm/src/builtins/slice.rs +++ b/vm/src/builtins/slice.rs @@ -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, vm: &VirtualMachine) -> PyResult { + 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,