diff --git a/src/lib.rs b/src/lib.rs index 80e4274..5269c28 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,10 @@ clippy::manual_strip, clippy::match_like_matches_macro )] +#![warn( + // TODO: We need to fix this + macro_expanded_macro_exports_accessed_by_absolute_paths, +)] //! Rust bindings to the Python interpreter. //! @@ -100,7 +104,7 @@ pub use ffi::Py_ssize_t; pub use crate::conversion::{FromPyObject, RefFromPyObject, ToPyObject}; pub use crate::err::{PyErr, PyResult}; -pub use crate::objectprotocol::ObjectProtocol; +pub use crate::objectprotocol::{ObjectProtocol, NumberProtocol}; pub use crate::objects::*; pub use crate::py_class::CompareOp; pub use crate::python::{ @@ -210,13 +214,14 @@ pub mod buffer; mod conversion; mod err; mod function; -mod objectprotocol; mod objects; mod python; mod pythonrun; //pub mod rustobject; +#[macro_use] pub mod py_class; mod sharedref; +mod objectprotocol; #[cfg(feature = "serde-convert")] pub mod serde; diff --git a/src/objectprotocol.rs b/src/objectprotocol.rs index 385581d..2b2e62b 100644 --- a/src/objectprotocol.rs +++ b/src/objectprotocol.rs @@ -25,6 +25,10 @@ use crate::ffi; use crate::objects::{PyDict, PyObject, PyString, PyTuple}; use crate::python::{Python, PythonObject, ToPythonPointer}; +mod number; + +pub use self::number::NumberProtocol; + /// Trait that contains methods pub trait ObjectProtocol: PythonObject { /// Determines whether this object has the given attribute. diff --git a/src/objectprotocol/number.rs b/src/objectprotocol/number.rs new file mode 100644 index 0000000..47f318c --- /dev/null +++ b/src/objectprotocol/number.rs @@ -0,0 +1,323 @@ +use std::cmp::Ordering; +use std::fmt; + +use crate::conversion::ToPyObject; +use crate::err::{self, PyErr, PyResult}; +use crate::ffi; +use crate::objects::{PyObject, PyInt, PyLong, PyFloat}; +use crate::python::{Python, PythonObject, ToPythonPointer}; + + +use super::ObjectProtocol; + +/// Operations on numeric objects +pub trait NumberProtocol: ObjectProtocol { + /// Perform addition (self + other) + /// + /// Invokes the `__add__` magic-method + #[inline] + fn add(&self, py: Python, other: impl ToPyObject) -> PyResult { + other.with_borrowed_ptr(py, |other| unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_Add(self.as_ptr(), other)) + }) + } + /// Perform subtraction (self - other) + /// + /// Invokes the `__sub__` magic-method + #[inline] + fn subtract(&self, py: Python, other: impl ToPyObject) -> PyResult { + other.with_borrowed_ptr(py, |other| unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_Subtract(self.as_ptr(), other)) + }) + } + /// Perform multiplication (self * other) + /// + /// Invokes the `__mul__` magic-method + #[inline] + fn multiply(&self, py: Python, other: impl ToPyObject) -> PyResult { + other.with_borrowed_ptr(py, |other| unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_Multiply(self.as_ptr(), other)) + }) + } + /// Perform matrix multiplication, equivalent to the Python expression `self @ other` + /// + /// Invokes the `__matmul__` magic-method + /// + /// This was added in Python 3.5, and will unconditionally + /// throw an exception on any version before that. + /// + /// See [PEP 0456](https://www.python.org/dev/peps/pep-0465/) for details. + #[inline] + fn matrix_multiply(&self, py: Python, other: impl ToPyObject) -> PyResult { + #[cfg(not(any(feature = "python3-4", feature = "python2-sys")))] { + other.with_borrowed_ptr(py, |other| unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_MatrixMultiply(self.as_ptr(), other)) + }) + } + #[cfg(any(feature = "python3-4", feature = "python2-sys"))] { + + drop(other); + Err(crate::PyErr::new::( + py, + "Matrix multiplication is unsupported before Python 3.5" + )) + } + } + /// Perform exponentiation, equivalent to the Python expression `self ** other`, + /// or the two-argument form of the builtin method pow: `pow(self, other)` + /// + /// Invokes the `__pow__` magic-method + /// + /// See also [NumberProtocol::power_modulo]. + #[inline] + fn power(&self, py: Python, other: impl ToPyObject) -> PyResult { + self.power_modulo(py, other, py.None()) + } + /// Perform exponentiation modulo an integer, + /// mathematically equivalent to `self ** other % mod` + /// but computed much more efficiently. + /// + /// Equivalent to invoking the three-argument form + /// of the builtin `pow` method: `pow(self, other, z)` + /// + /// Invoking with a `None` for modulo is equivalent to + /// the regular power operation. + /// + /// Invokes the `__pow__` magic-method + #[inline] + fn power_modulo(&self, py: Python, exp: impl ToPyObject, z: impl ToPyObject) -> PyResult { + exp.with_borrowed_ptr(py, |exp| { + z.with_borrowed_ptr(py, |z| unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_Power(self.as_ptr(), exp, z)) + }) + }) + } + /// Perform the "true division" operation, + /// equivalent to the Python expression `self / other`, + /// + /// Invokes the `__truediv__` magic-method. + #[inline] + fn true_divide(&self, py: Python, other: impl ToPyObject) -> PyResult { + other.with_borrowed_ptr(py, |other| unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_TrueDivide(self.as_ptr(), other)) + }) + } + /// Perform the "floor division" operation, + /// equivalent to the Python expression `self // other`, + /// + /// This method was added in Python 3. + /// If compiling against Python 2, it unconditional throws an error. + /// + /// Invokes the `__floordiv__` magic-method. + #[inline] + fn floor_divide(&self, py: Python, other: impl ToPyObject) -> PyResult { + other.with_borrowed_ptr(py, |other| unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_FloorDivide(self.as_ptr(), other)) + }) + } + /// Return the remainder of dividing `self` by `other`, + /// equivalent to the Python expression `self % other` + /// + /// Invokes the `__mod__` magic-method. + #[inline] + fn modulo(&self, py: Python, other: impl ToPyObject) -> PyResult { + other.with_borrowed_ptr(py, |other| unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_Remainder(self.as_ptr(), other)) + }) + } + /// Perform combined division and modulo, + /// equivalent to the builtin method `divmod(self, other)` + /// + /// Invokes the `__divmod__` magic-method. + #[inline] + fn div_mod(&self, py: Python, other: impl ToPyObject) -> PyResult { + other.with_borrowed_ptr(py, |other| unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_Divmod(self.as_ptr(), other)) + }) + } + /// Perform the negation of self (-self) + /// + /// Invokes the `__neg__` magic-method. + #[inline] + fn negative(&self, py: Python) -> PyResult { + unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_Negative(self.as_ptr())) + } + } + /// Invoke the 'positive' operation, equivalent to the + /// Python expression `+self` + /// + /// Invokes the `__pos__` magic-method + #[inline] + fn positive(&self, py: Python) -> PyResult { + unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_Positive(self.as_ptr())) + } + } + /// Return the absolute value of self, + /// equivalent to calling the builtin function `abs` + /// + /// Invokes the `__abs__` magic-method. + #[inline] + fn absolute(&self, py: Python) -> PyResult { + unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_Absolute(self.as_ptr())) + } + } + /// Perform the bitwise negation of self, + /// equivalent to the Python expression `~self` + /// + /// Invokes the `__invert__` magic-method + #[inline] + fn bitwise_invert(&self, py: Python) -> PyResult { + unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_Invert(self.as_ptr())) + } + } + /// Shift this value to the left by the specified number of bits, + /// equivalent to the Python expression `self << bits` + /// + /// Invokes the `__lshift__` magic-method + #[inline] + fn left_shift(&self, py: Python, bits: impl ToPyObject) -> PyResult { + bits.with_borrowed_ptr(py, |other| unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_Lshift(self.as_ptr(), other)) + }) + } + /// Shift this value to the right by the specified number of bits, + /// equivalent to the Python expression `self >> bits` + /// + /// Invokes the `__rshift__` magic-method + #[inline] + fn right_shift(&self, py: Python, bits: impl ToPyObject) -> PyResult { + bits.with_borrowed_ptr(py, |other| unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_Rshift(self.as_ptr(), other)) + }) + } + /// Perform the "bitwise and" of `self & other` + /// + /// Invokes the `__and__` magic-method. + #[inline] + fn bitwise_and(&self, py: Python, other: impl ToPyObject) -> PyResult { + other.with_borrowed_ptr(py, |other| unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_And(self.as_ptr(), other)) + }) + } + /// Perform the "bitwise exclusive or", + /// equivalent to Python expression `self ^ other` + /// + /// Invokes the `__xor__` magic-method. + #[inline] + fn bitwise_xor(&self, py: Python, other: impl ToPyObject) -> PyResult { + other.with_borrowed_ptr(py, |other| unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_Xor(self.as_ptr(), other)) + }) + } + /// Perform the "bitwise or" of `self | other` + /// + /// Invokes the `__or__` magic-method. + #[inline] + fn bitwise_or(&self, py: Python, other: impl ToPyObject) -> PyResult { + other.with_borrowed_ptr(py, |other| unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_Or(self.as_ptr(), other)) + }) + } + /// Convert this object to an integer, + /// equivalent to the builtin function `int(self)` + /// + /// Invokes the `__int__` magic-method. + /// + /// Throws an exception if unable to perform + /// the conversion. + #[inline] + fn as_int(&self, py: Python) -> PyResult { + let obj = unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_Long(self.as_ptr()))? + }; + Ok(obj.cast_into::(py)?) + } + /// Convert this object to a float, + /// equivalent to the builtin function `float(self)` + /// + /// Invokes the `__float__` magic-method. + /// + /// Throws an exception if unable to perform + /// the conversion. + #[inline] + fn as_float(&self, py: Python) -> PyResult { + let obj = unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_Float(self.as_ptr()))? + }; + Ok(obj.cast_into::(py)?) + } + /// Losslessly convert this object to an integer index, + /// as if calling `operator.index()` + /// + /// The presence of this method indicates + /// this object is an integer type. + /// + /// Calls the `__index__` magic-method. + /// + /// See also: [Documentation on the corresponding magic-method](https://docs.python.org/3/reference/datamodel.html?highlight=__index__#object.__index__) + #[inline] + fn as_int_index(&self, py: Python) -> PyResult { + let obj = unsafe { + err::result_from_owned_ptr(py, ffi::PyNumber_Index(self.as_ptr()))? + }; + Ok(obj.cast_into::(py)?) + } +} + +impl NumberProtocol for PyObject {} + + +#[cfg(test)] +mod test { + use crate::*; + use super::*; + + #[test] + fn addition() { + let guard = Python::acquire_gil(); + let py = guard.python(); + let i1 = (5i32).to_py_object(py).into_object(); + let i2 = (12i32).to_py_object(py).into_object(); + let actual_res = i1.add(py, i2).unwrap(); + let expected_res = (17i32).to_py_object(py).into_object(); + assert_eq!( + actual_res.compare(py, expected_res).unwrap(), + Ordering::Equal + ); + } + + py_class!(class DummyMatMul |py| { + data number: i32; + def __new__(_cls, arg: i32) -> PyResult { + DummyMatMul::create_instance(py, arg) + } + def __matmul__(left, other) -> PyResult { + // Do a dummy operation that can be easily tested + left.cast_as::(py)?.number(py) + .to_py_object(py) + .into_object() + .multiply(py, other)? + .add(py, 3) + } + }); + + #[test] + #[cfg_attr(any(feature = "python3-4", feature = "python2-sys"), should_panic)] + fn matrix_multiply() { + let guard = Python::acquire_gil(); + let py = guard.python(); + let first = DummyMatMul::create_instance(py, 5).unwrap().into_object(); + let seven = (7i32).to_py_object(py).into_object(); + let actual_res = first.matrix_multiply(py, seven).unwrap(); + // 5 * 7 + 3 => 38 + let expected_res = (38i32).to_py_object(py).into_object(); + assert_eq!( + actual_res.compare(py, expected_res).unwrap(), + Ordering::Equal + ); + } +} diff --git a/src/py_class/py_class_impl.py b/src/py_class/py_class_impl.py index e68148d..280f397 100644 --- a/src/py_class/py_class_impl.py +++ b/src/py_class/py_class_impl.py @@ -838,7 +838,7 @@ def inplace_numeric_operator(special_name, slot): '__add__': binary_numeric_operator('nb_add'), '__sub__': binary_numeric_operator('nb_subtract'), '__mul__': binary_numeric_operator('nb_multiply'), - '__matmul__': unimplemented(), + '__matmul__': binary_numeric_operator('nb_matrix_multiply') if not PY2 else unimplemented(), '__div__': unimplemented(), '__truediv__': unimplemented(), '__floordiv__': unimplemented(), diff --git a/src/py_class/py_class_impl3.rs b/src/py_class/py_class_impl3.rs index 3b7fdc1..e07f5c9 100644 --- a/src/py_class/py_class_impl3.rs +++ b/src/py_class/py_class_impl3.rs @@ -2108,9 +2108,35 @@ macro_rules! py_class_impl { { { def __lt__ $($tail:tt)* } $( $stuff:tt )* } => { $crate::py_error! { "__lt__ is not supported by py_class! use __richcmp__ instead." } }; + { { def __matmul__($left:ident, $right:ident) -> $res_type:ty { $($body:tt)* } $($tail:tt)* } + $class:ident $py:ident $info:tt + /* slots: */ { + $type_slots:tt + /* as_number */ [ $( $nb_slot_name:ident : $nb_slot_value:expr, )* ] + $as_sequence:tt $as_mapping:tt $setdelitem:tt + } + { $( $imp:item )* } + $members:tt $props:tt + } => { $crate::py_class_impl! { + { $($tail)* } + $class $py $info + /* slots: */ { + $type_slots + /* as_number */ [ + $( $nb_slot_name : $nb_slot_value, )* + nb_matrix_multiply: $crate::py_class_binary_numeric_slot!($class::__matmul__), + ] + $as_sequence $as_mapping $setdelitem + } + /* impl: */ { + $($imp)* + $crate::py_class_impl_item! { $class, $py, pub, __matmul__() $res_type; { $($body)* } [ { $left : &$crate::PyObject = {} } { $right : &$crate::PyObject = {} } ] } + } + $members $props + }}; { { def __matmul__ $($tail:tt)* } $( $stuff:tt )* } => { - $crate::py_error! { "__matmul__ is not supported by py_class! yet." } + $crate::py_error! { "Invalid signature for binary numeric operator __matmul__" } }; { { def __mod__ $($tail:tt)* } $( $stuff:tt )* } => {