#![cfg(feature = "pyo3")]
#![cfg_attr(docsrs, doc(cfg(feature = "pyo3")))]
use crate::Uint;
use pyo3::{
Bound, FromPyObject, IntoPyObject, PyAny, PyErr, PyResult, Python,
exceptions::PyOverflowError,
ffi,
types::{PyAnyMethods, PyInt},
};
impl<'a, const BITS: usize, const LIMBS: usize> IntoPyObject<'a> for Uint<BITS, LIMBS> {
type Target = PyInt;
type Output = Bound<'a, Self::Target>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'a>) -> Result<Self::Output, Self::Error> {
(&self).into_pyobject(py)
}
}
impl<'a, const BITS: usize, const LIMBS: usize> IntoPyObject<'a> for &Uint<BITS, LIMBS> {
type Target = PyInt;
type Output = Bound<'a, Self::Target>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'a>) -> Result<Self::Output, Self::Error> {
if BITS <= 64 {
let value = self.as_limbs().first().copied().unwrap_or(0);
return Ok(value.into_pyobject(py).unwrap());
}
let bytes = self.as_le_bytes();
unsafe {
let obj =
ffi::_PyLong_FromByteArray(bytes.as_ptr().cast(), bytes.len(), 1, false.into());
let bound = Bound::from_owned_ptr(py, obj);
Ok(bound.downcast_into_unchecked())
}
}
}
impl<'a, const BITS: usize, const LIMBS: usize> FromPyObject<'a> for Uint<BITS, LIMBS> {
fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult<Self> {
let mut result = Self::ZERO;
#[cfg(target_endian = "little")]
let py_result = unsafe {
let raw = result.as_le_slice_mut();
ffi::_PyLong_AsByteArray(
ob.as_ptr().cast::<ffi::PyLongObject>(),
raw.as_mut_ptr(),
raw.len(),
1,
0,
)
};
#[cfg(not(target_endian = "little"))]
let py_result = {
let mut raw = vec![0_u8; LIMBS * 8];
let py_result = unsafe {
ffi::_PyLong_AsByteArray(
ob.as_ptr().cast::<ffi::PyLongObject>(),
raw.as_mut_ptr(),
raw.len(),
1,
0,
)
};
result = Self::try_from_le_slice(raw.as_slice()).ok_or_else(|| {
PyOverflowError::new_err(format!("Number to large to fit Uint<{}>", Self::BITS))
})?;
py_result
};
if py_result != 0 {
return Err(PyErr::fetch(ob.py()));
}
#[cfg(target_endian = "little")]
if let Some(last) = result.as_limbs().last() {
if *last > Self::MASK {
return Err(PyOverflowError::new_err(format!(
"Number to large to fit Uint<{}>",
Self::BITS
)));
}
}
Ok(result)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{
aliases::{U0, U8, U64, U256, U512},
const_for, nlimbs,
};
use proptest::proptest;
#[test]
fn test_roundtrip() {
pyo3::prepare_freethreaded_python();
Python::with_gil(|py| {
const_for!(BITS in SIZES {
const LIMBS: usize = nlimbs(BITS);
type U = Uint<BITS, LIMBS>;
proptest!(|(value: U)| {
let obj = value.into_pyobject(py).unwrap();
let native = Uint::<BITS, LIMBS>::extract_bound(&obj).unwrap();
assert_eq!(value, native);
});
});
});
}
#[test]
fn test_errors() {
pyo3::prepare_freethreaded_python();
Python::with_gil(|py: Python<'_>| {
let obj = (-1_i64).into_pyobject(py).unwrap();
assert!(U0::extract_bound(&obj).is_err());
assert!(U256::extract_bound(&obj).is_err());
let obj = (1000_i64).into_pyobject(py).unwrap();
assert!(U0::extract_bound(&obj).is_err());
assert!(U8::extract_bound(&obj).is_err());
let obj = U512::MAX.into_pyobject(py).unwrap();
assert!(U0::extract_bound(&obj).is_err());
assert!(U64::extract_bound(&obj).is_err());
assert!(U256::extract_bound(&obj).is_err());
});
}
}