py_class!: add `__hash__` slot

This commit is contained in:
Daniel Grunwald 2016-05-06 21:41:05 +02:00
parent 933e0ed11d
commit c06838a38e
5 changed files with 107 additions and 2 deletions

View File

@ -276,6 +276,15 @@ py_class!(class MyIterator |py| {
Special method that is used by the `format()` builtin and the `str.format()` method.
Possible return types are `PyResult<String>` or `PyResult<PyString>`.
## Comparison operators
TODO: implement support for `__cmp__`, `__lt__`, `__le__`, `__gt__`, `__ge__`, `__eq__`, `__ne__`.
* `def __hash__(&self) -> PyResult<impl PrimInt>`
Objects that compare equal must have the same hash value.
The return type must be `PyResult<T>` where `T` is one of Rust's primitive integer types.
*/
#[macro_export]
macro_rules! py_class {

View File

@ -595,7 +595,9 @@ special_names = {
'__eq__': unimplemented(),
'__ne__': unimplemented(),
'__cmp__': unimplemented(),
'__hash__': unimplemented(),
'__hash__': unary_operator('tp_hash',
res_conv='$crate::py_class::slots::HashConverter',
res_ffi_type='$crate::Py_hash_t'),
'__nonzero__': error('__nonzero__ is not supported by py_class!; use the Python 3 spelling __bool__ instead.'),
'__bool__': unimplemented(),
# Customizing attribute access

View File

@ -478,11 +478,34 @@ macro_rules! py_class_impl {
} => {
py_error! { "__gt__ is not supported by py_class! yet." }
};
{ $class:ident $py:ident $info:tt
/* slots: */ {
/* type_slots */ [ $( $tp_slot_name:ident : $tp_slot_value:expr, )* ]
$as_number:tt $as_sequence:tt
}
{ $( $imp:item )* }
$members:tt;
def __hash__(&$slf:ident) -> $res_type:ty { $($body:tt)* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info
/* slots: */ {
/* type_slots */ [
$( $tp_slot_name : $tp_slot_value, )*
tp_hash: py_class_unary_slot!($class::__hash__, $crate::Py_hash_t, $crate::py_class::slots::HashConverter),
]
$as_number $as_sequence
}
/* impl: */ {
$($imp)*
py_class_impl_item! { $class, $py, __hash__(&$slf,) $res_type; { $($body)* } [] }
}
$members; $($tail)*
}};
// def __hash__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __hash__ $($tail:tt)*
} => {
py_error! { "__hash__ is not supported by py_class! yet." }
py_error! { "Invalid signature for unary operator __hash__" }
};
// def __iadd__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;

View File

@ -23,6 +23,7 @@ use conversion::ToPyObject;
use function::CallbackConverter;
use err::{PyErr};
use exc;
use Py_hash_t;
#[macro_export]
#[doc(hidden)]
@ -199,3 +200,49 @@ impl <T> CallbackConverter<Option<T>, *mut ffi::PyObject>
}
}
pub trait WrappingCastTo<T> {
fn wrapping_cast(self) -> T;
}
macro_rules! wrapping_cast {
($from:ty, $to:ty) => {
impl WrappingCastTo<$to> for $from {
#[inline]
fn wrapping_cast(self) -> $to {
self as $to
}
}
}
}
wrapping_cast!(u8, Py_hash_t);
wrapping_cast!(u16, Py_hash_t);
wrapping_cast!(u32, Py_hash_t);
wrapping_cast!(usize, Py_hash_t);
wrapping_cast!(u64, Py_hash_t);
wrapping_cast!(i8, Py_hash_t);
wrapping_cast!(i16, Py_hash_t);
wrapping_cast!(i32, Py_hash_t);
wrapping_cast!(isize, Py_hash_t);
wrapping_cast!(i64, Py_hash_t);
pub struct HashConverter;
impl <T> CallbackConverter<T, Py_hash_t> for HashConverter
where T: WrappingCastTo<Py_hash_t>
{
#[inline]
fn convert(val: T, _py: Python) -> Py_hash_t {
let hash = val.wrapping_cast();
if hash == -1 {
-2
} else {
hash
}
}
#[inline]
fn error_value() -> Py_hash_t {
-1
}
}

View File

@ -385,3 +385,27 @@ fn python3_string_methods() {
}
py_class!(class Comparisons |py| {
data val: i32;
def __hash__(&self) -> PyResult<i32> {
Ok(*self.val(py))
}
});
#[test]
fn comparisons() {
let gil = Python::acquire_gil();
let py = gil.python();
let one = Comparisons::create_instance(py, 1).unwrap();
let ten = Comparisons::create_instance(py, 10).unwrap();
let minus_one = Comparisons::create_instance(py, -1).unwrap();
py_assert!(py, one, "hash(one) == 1");
py_assert!(py, ten, "hash(ten) == 10");
py_assert!(py, minus_one, "hash(minus_one) == -2");
}