use std::hash::{Hash, Hasher};
use num_bigint::BigInt;
use num_traits::FromPrimitive as _;
use crate::int::Int;
use crate::object::Object;
use crate::text::Str;
pub const MODULUS: u64 = (1 << 61) - 1;
const BITS: u32 = 61;
const INF: i64 = 314_159;
const NONE: i64 = 0xFCA8_6420;
const ELLIPSIS: i64 = 0x1CE1_1195;
const NOT_IMPLEMENTED: i64 = 0x2B0E_9C7A;
const XXPRIME_1: u64 = 11_400_714_785_074_694_791;
const XXPRIME_2: u64 = 14_029_467_366_897_019_727;
const XXPRIME_5: u64 = 2_870_177_450_012_600_261;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Unhashable {
pub type_name: &'static str,
}
impl Unhashable {
#[must_use]
pub fn message(&self) -> String {
format!("unhashable type: '{}'", self.type_name)
}
}
pub fn hash(object: &Object) -> Result<i64, Unhashable> {
match object {
Object::None => Ok(NONE),
Object::Ellipsis => Ok(ELLIPSIS),
Object::NotImplemented => Ok(NOT_IMPLEMENTED),
Object::Bool(value) => Ok(i64::from(*value)),
Object::Int(value) => Ok(int(value)),
Object::Float(value) => Ok(float(*value)),
Object::Str(value) => Ok(text(value)),
Object::Bytes(value) => Ok(blob(value)),
Object::Tuple(items) => tuple(items),
Object::Slice(value) => lanes(value.parts().into_iter(), 3),
Object::Native(value) => Ok(address(std::ptr::from_ref(value.as_ref()).cast::<()>())),
Object::List(_) => Err(Unhashable { type_name: "list" }),
Object::Dict(_) => Err(Unhashable { type_name: "dict" }),
Object::Set(_) => Err(Unhashable { type_name: "set" }),
}
}
fn address(pointer: *const ()) -> i64 {
let rotated = pointer.addr().rotate_right(4);
settle(rotated.cast_signed() as i64)
}
fn int(value: &Int) -> i64 {
let reduced: i128 = match value {
Int::Small(n) => i128::from(*n) % i128::from(MODULUS),
Int::Big(n) => (n.as_ref() % BigInt::from(MODULUS))
.try_into()
.expect("a remainder mod 2^61-1 is far inside an i128"),
};
let reduced = i64::try_from(reduced).expect("a remainder mod 2^61-1 fits in an i64");
settle(reduced)
}
#[expect(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_precision_loss,
clippy::cast_sign_loss,
reason = "every cast in here is exact by construction, and the comment \
next to each one says what makes it exact"
)]
fn float(value: f64) -> i64 {
if !value.is_finite() {
return if value.is_infinite() {
if value > 0.0 { INF } else { -INF }
} else {
0
};
}
let (mut mantissa, mut exponent) = frexp(value);
let sign = if mantissa < 0.0 {
mantissa = -mantissa;
-1
} else {
1
};
let mut x: u64 = 0;
while mantissa != 0.0 {
x = ((x << 28) & MODULUS) | (x >> (BITS - 28));
mantissa *= 268_435_456.0; exponent -= 28;
let digit = mantissa as u64;
mantissa -= digit as f64;
x += digit;
if x >= MODULUS {
x -= MODULUS;
}
}
let bits = BITS as i32;
let exponent = if exponent >= 0 {
exponent % bits
} else {
bits - 1 - ((-1 - exponent) % bits)
} as u32;
x = ((x << exponent) & MODULUS) | (x >> (BITS - exponent));
settle((x as i64) * sign)
}
fn frexp(value: f64) -> (f64, i32) {
if value == 0.0 {
return (value, 0);
}
let bits = value.to_bits();
let biased = ((bits >> 52) & 0x7ff) as i32;
if biased == 0 {
let (mantissa, exponent) = frexp(value * f64::from_bits(0x43f0_0000_0000_0000));
return (mantissa, exponent - 64);
}
let mantissa = f64::from_bits((bits & !(0x7ffu64 << 52)) | (1022u64 << 52));
(mantissa, biased - 1022)
}
fn tuple(items: &[Object]) -> Result<i64, Unhashable> {
lanes(items.iter(), items.len())
}
#[expect(
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::decimal_bitwise_operands,
reason = "the arithmetic is unsigned and wrapping on purpose, and the odd \
constant is written the way CPython writes it so the two can be \
compared by eye"
)]
fn lanes<'a>(items: impl Iterator<Item = &'a Object>, len: usize) -> Result<i64, Unhashable> {
let mut acc = XXPRIME_5;
for item in items {
let lane = hash(item)? as u64;
acc = acc.wrapping_add(lane.wrapping_mul(XXPRIME_2));
acc = acc.rotate_left(31);
acc = acc.wrapping_mul(XXPRIME_1);
}
acc = acc.wrapping_add((len as u64) ^ (XXPRIME_5 ^ 3_527_539));
if acc == u64::MAX {
return Ok(1_546_275_796);
}
Ok(acc as i64)
}
#[expect(
clippy::cast_possible_wrap,
reason = "a hash is a number, and which half of the range it lands in is \
not information anyone is entitled to"
)]
fn text(value: &Str) -> i64 {
let mut hasher = std::hash::DefaultHasher::new();
match value {
Str::Utf8(s) => {
0u8.hash(&mut hasher);
s.hash(&mut hasher);
}
Str::Wide(w) => {
1u8.hash(&mut hasher);
w.hash(&mut hasher);
}
}
settle(hasher.finish() as i64)
}
#[expect(clippy::cast_possible_wrap, reason = "the same as for a string")]
fn blob(value: &[u8]) -> i64 {
let mut hasher = std::hash::DefaultHasher::new();
2u8.hash(&mut hasher);
value.hash(&mut hasher);
settle(hasher.finish() as i64)
}
const fn settle(value: i64) -> i64 {
if value == -1 { -2 } else { value }
}
#[derive(Debug, Clone)]
pub struct Key {
object: Object,
hash: i64,
}
impl Key {
pub fn new(object: Object) -> Result<Self, Unhashable> {
let hash = hash(&object)?;
Ok(Key { object, hash })
}
#[must_use]
pub const fn object(&self) -> &Object {
&self.object
}
#[must_use]
pub fn into_object(self) -> Object {
self.object
}
#[must_use]
pub const fn hash(&self) -> i64 {
self.hash
}
}
impl Hash for Key {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_i64(self.hash);
}
}
impl PartialEq for Key {
fn eq(&self, other: &Self) -> bool {
self.object.same_value(&other.object)
}
}
impl Eq for Key {}
#[expect(
clippy::cast_possible_truncation,
reason = "the cast is guarded by the range check on the line above it, and \
both ends of that range are exactly representable"
)]
pub(crate) fn int_eq_float(int: &Int, float: f64) -> bool {
if !float.is_finite() || float.fract() != 0.0 {
return false;
}
if let Int::Small(n) = int {
if (-9_223_372_036_854_775_808.0..9_223_372_036_854_775_808.0).contains(&float) {
return *n == float as i64;
}
}
BigInt::from_f64(float).is_some_and(|value| value == int.to_big())
}
#[cfg(test)]
#[expect(
clippy::unreadable_literal,
clippy::approx_constant,
reason = "these are the numbers a CPython 3.14 printed, kept in the form it \
printed them so that a reader can check them against it"
)]
mod tests {
use super::*;
fn h(object: &Object) -> i64 {
hash(object).expect("expected this to be hashable")
}
#[test]
fn an_integer_hashes_as_its_value_modulo_the_prime() {
for (value, expected) in [
(0i64, 0i64),
(1, 1),
(2, 2),
(7, 7),
(2305843009213693950, 2305843009213693950),
(2305843009213693951, 0),
(2305843009213693952, 1),
(4611686018427387904, 2),
(-2, -2),
] {
assert_eq!(h(&Object::int(value)), expected, "hash({value})");
}
}
#[test]
fn the_one_hash_nothing_is_allowed_to_have() {
assert_eq!(h(&Object::int(-1)), -2);
assert_eq!(h(&Object::Float(-1.0)), -2);
assert_eq!(h(&Object::int(-2)), -2);
}
#[test]
fn a_big_integer_hashes_the_same_way_a_small_one_does() {
for (digits, expected) in [
("100000000000000000000", 848750603811160107i64),
("-100000000000000000000", -848750603811160107),
("1208925819614629174706176", 524288),
("-1208925819614629174706176", -524288),
(
"1606938044258990275541962092341162602522202993782792835313721",
143417,
),
(
"-1606938044258990275541962092341162602522202993782792835313721",
-143417,
),
] {
let (text, sign) = digits
.strip_prefix('-')
.map_or((digits, 1), |rest| (rest, -1));
let value = Int::parse(text, 10).expect("expected this to parse");
let value = if sign < 0 { value.neg() } else { value };
assert_eq!(h(&Object::Int(value)), expected, "hash({digits})");
}
}
#[test]
fn a_float_hashes_as_its_value_too() {
for (value, expected) in [
(0.0f64, 0i64),
(-0.0, 0),
(1.0, 1),
(1024.0, 1024),
(0.5, 1152921504606846976),
(1.5, 1152921504606846977),
(-1.5, -1152921504606846977),
(-2.5, -1152921504606846978),
(0.1, 230584300921369408),
(-0.1, -230584300921369408),
(1e16, 10000000000000000),
(1e300, 1224995262755759164),
(-1e300, -1224995262755759164),
(1e-300, 482449582752280463),
(3.14159265358979, 326490430436033539),
(f64::MAX, 2234066890152476671),
(f64::MIN_POSITIVE, 32768),
(5e-324, 16777216),
] {
assert_eq!(h(&Object::Float(value)), expected, "hash({value:?})");
}
}
#[test]
fn an_infinity_hashes_to_the_number_it_always_has() {
assert_eq!(h(&Object::Float(f64::INFINITY)), 314159);
assert_eq!(h(&Object::Float(f64::NEG_INFINITY)), -314159);
}
#[test]
fn the_same_number_in_three_types_has_one_hash() {
assert_eq!(h(&Object::int(1)), h(&Object::Float(1.0)));
assert_eq!(h(&Object::int(1)), h(&Object::Bool(true)));
assert_eq!(h(&Object::int(0)), h(&Object::Bool(false)));
let big = Int::parse("1208925819614629174706176", 10).expect("expected this to parse");
assert_eq!(h(&Object::Int(big)), h(&Object::Float(2.0f64.powi(80))));
}
#[test]
fn a_tuple_hashes_the_way_cpython_hashes_one() {
let t = |items: Vec<Object>| h(&Object::tuple(items));
assert_eq!(t(vec![]), 5740354900026072187);
assert_eq!(t(vec![Object::int(1)]), -6644214454873602895);
assert_eq!(t(vec![Object::int(0)]), -8753497827991233192);
assert_eq!(t(vec![Object::int(-1)]), 8078679518589016365);
assert_eq!(
t(vec![Object::int(1), Object::int(2)]),
-3550055125485641917
);
assert_eq!(
t(vec![Object::int(1), Object::int(2), Object::int(3)]),
529344067295497451
);
assert_eq!(
t(vec![
Object::tuple(vec![Object::int(1), Object::int(2)]),
Object::int(3)
]),
-333907151259015829
);
assert_eq!(t((0..20).map(Object::int).collect()), -9217304902224717415);
}
#[test]
fn a_list_has_no_hash_and_neither_does_a_tuple_holding_one() {
let refused = hash(&Object::list(vec![])).expect_err("a list has no hash");
assert_eq!(refused.message(), "unhashable type: 'list'");
let nested = Object::tuple(vec![Object::int(1), Object::list(vec![])]);
assert_eq!(
hash(&nested).expect_err("a tuple holding a list has no hash"),
refused
);
}
#[test]
fn equal_strings_hash_equally_and_different_ones_usually_do_not() {
assert_eq!(h(&Object::str("hello")), h(&Object::str("hello")));
assert_ne!(h(&Object::str("hello")), h(&Object::str("hellp")));
assert_ne!(
h(&Object::str("abc")),
h(&Object::Bytes(std::rc::Rc::from(&b"abc"[..])))
);
}
#[test]
fn a_key_is_the_hash_and_pythons_equality_rather_than_rusts() {
let key = |object| Key::new(object).expect("expected this to be hashable");
assert_eq!(key(Object::int(1)), key(Object::Float(1.0)));
assert_eq!(key(Object::int(1)), key(Object::Bool(true)));
assert_eq!(key(Object::int(0)), key(Object::Bool(false)));
assert_ne!(key(Object::int(1)), key(Object::int(2)));
assert_ne!(key(Object::str("1")), key(Object::int(1)));
assert_eq!(key(Object::int(1)).hash(), 1);
let refused = Key::new(Object::list(vec![])).expect_err("a list is not a key");
assert_eq!(refused.message(), "unhashable type: 'list'");
}
#[test]
fn a_nan_can_be_a_key_and_can_be_found_again() {
let nan = Object::Float(f64::NAN);
let key = Key::new(nan.clone()).expect("expected this to be hashable");
let same = Key::new(nan).expect("expected this to be hashable");
assert_eq!(key, same);
let other = Key::new(Object::Float(1.0)).expect("expected this to be hashable");
assert_ne!(key, other);
assert!(!Object::Float(f64::NAN).equals(&Object::Float(f64::NAN)));
}
#[test]
fn an_integer_and_a_float_are_equal_when_they_are_the_same_number() {
assert!(int_eq_float(&Int::Small(1), 1.0));
assert!(!int_eq_float(&Int::Small(1), 1.5));
assert!(!int_eq_float(&Int::Small(1), f64::NAN));
assert!(!int_eq_float(&Int::Small(1), f64::INFINITY));
assert!(int_eq_float(
&Int::Small(i64::MIN),
-9_223_372_036_854_775_808.0
));
let big = Int::parse("1208925819614629174706176", 10).expect("expected this to parse");
assert!(int_eq_float(&big, 2.0f64.powi(80)));
assert!(!int_eq_float(&big.add(&Int::Small(1)), 2.0f64.powi(80)));
}
}