diff --git a/src/conversions/mod.rs b/src/conversions/mod.rs index 23f284bb1bb..20d19e81234 100644 --- a/src/conversions/mod.rs +++ b/src/conversions/mod.rs @@ -1,13 +1,11 @@ //! This module contains conversions between various Rust object and their representation in Python. pub mod anyhow; -mod array; pub mod chrono; pub mod eyre; pub mod hashbrown; pub mod indexmap; pub mod num_bigint; pub mod num_complex; -mod osstr; -mod path; pub mod serde; +mod std; diff --git a/src/conversions/array.rs b/src/conversions/std/array.rs similarity index 100% rename from src/conversions/array.rs rename to src/conversions/std/array.rs diff --git a/src/conversions/std/map.rs b/src/conversions/std/map.rs new file mode 100644 index 00000000000..83d2f2c81dd --- /dev/null +++ b/src/conversions/std/map.rs @@ -0,0 +1,167 @@ +use std::{cmp, collections, hash}; + +use crate::{ + inspect::types::TypeInfo, + types::{IntoPyDict, PyDict}, + FromPyObject, IntoPy, PyAny, PyErr, PyObject, PyTryFrom, Python, ToPyObject, +}; + +impl ToPyObject for collections::HashMap +where + K: hash::Hash + cmp::Eq + ToPyObject, + V: ToPyObject, + H: hash::BuildHasher, +{ + fn to_object(&self, py: Python<'_>) -> PyObject { + IntoPyDict::into_py_dict(self, py).into() + } +} + +impl ToPyObject for collections::BTreeMap +where + K: cmp::Eq + ToPyObject, + V: ToPyObject, +{ + fn to_object(&self, py: Python<'_>) -> PyObject { + IntoPyDict::into_py_dict(self, py).into() + } +} + +impl IntoPy for collections::HashMap +where + K: hash::Hash + cmp::Eq + IntoPy, + V: IntoPy, + H: hash::BuildHasher, +{ + fn into_py(self, py: Python<'_>) -> PyObject { + let iter = self + .into_iter() + .map(|(k, v)| (k.into_py(py), v.into_py(py))); + IntoPyDict::into_py_dict(iter, py).into() + } + + fn type_output() -> TypeInfo { + TypeInfo::dict_of(K::type_output(), V::type_output()) + } +} + +impl IntoPy for collections::BTreeMap +where + K: cmp::Eq + IntoPy, + V: IntoPy, +{ + fn into_py(self, py: Python<'_>) -> PyObject { + let iter = self + .into_iter() + .map(|(k, v)| (k.into_py(py), v.into_py(py))); + IntoPyDict::into_py_dict(iter, py).into() + } + + fn type_output() -> TypeInfo { + TypeInfo::dict_of(K::type_output(), V::type_output()) + } +} + +impl<'source, K, V, S> FromPyObject<'source> for collections::HashMap +where + K: FromPyObject<'source> + cmp::Eq + hash::Hash, + V: FromPyObject<'source>, + S: hash::BuildHasher + Default, +{ + fn extract(ob: &'source PyAny) -> Result { + let dict = ::try_from(ob)?; + let mut ret = collections::HashMap::with_capacity_and_hasher(dict.len(), S::default()); + for (k, v) in dict.iter() { + ret.insert(K::extract(k)?, V::extract(v)?); + } + Ok(ret) + } + + fn type_input() -> TypeInfo { + TypeInfo::mapping_of(K::type_input(), V::type_input()) + } +} + +impl<'source, K, V> FromPyObject<'source> for collections::BTreeMap +where + K: FromPyObject<'source> + cmp::Ord, + V: FromPyObject<'source>, +{ + fn extract(ob: &'source PyAny) -> Result { + let dict = ::try_from(ob)?; + let mut ret = collections::BTreeMap::new(); + for (k, v) in dict.iter() { + ret.insert(K::extract(k)?, V::extract(v)?); + } + Ok(ret) + } + + fn type_input() -> TypeInfo { + TypeInfo::mapping_of(K::type_input(), V::type_input()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{IntoPy, PyObject, PyTryFrom, Python, ToPyObject}; + use std::collections::{BTreeMap, HashMap}; + + #[test] + fn test_hashmap_to_python() { + Python::with_gil(|py| { + let mut map = HashMap::::new(); + map.insert(1, 1); + + let m = map.to_object(py); + let py_map = ::try_from(m.as_ref(py)).unwrap(); + + assert!(py_map.len() == 1); + assert!(py_map.get_item(1).unwrap().extract::().unwrap() == 1); + assert_eq!(map, py_map.extract().unwrap()); + }); + } + + #[test] + fn test_btreemap_to_python() { + Python::with_gil(|py| { + let mut map = BTreeMap::::new(); + map.insert(1, 1); + + let m = map.to_object(py); + let py_map = ::try_from(m.as_ref(py)).unwrap(); + + assert!(py_map.len() == 1); + assert!(py_map.get_item(1).unwrap().extract::().unwrap() == 1); + assert_eq!(map, py_map.extract().unwrap()); + }); + } + + #[test] + fn test_hashmap_into_python() { + Python::with_gil(|py| { + let mut map = HashMap::::new(); + map.insert(1, 1); + + let m: PyObject = map.into_py(py); + let py_map = ::try_from(m.as_ref(py)).unwrap(); + + assert!(py_map.len() == 1); + assert!(py_map.get_item(1).unwrap().extract::().unwrap() == 1); + }); + } + + #[test] + fn test_btreemap_into_py() { + Python::with_gil(|py| { + let mut map = BTreeMap::::new(); + map.insert(1, 1); + + let m: PyObject = map.into_py(py); + let py_map = ::try_from(m.as_ref(py)).unwrap(); + + assert!(py_map.len() == 1); + assert!(py_map.get_item(1).unwrap().extract::().unwrap() == 1); + }); + } +} diff --git a/src/conversions/std/mod.rs b/src/conversions/std/mod.rs new file mode 100644 index 00000000000..6021c395288 --- /dev/null +++ b/src/conversions/std/mod.rs @@ -0,0 +1,9 @@ +mod array; +mod map; +mod num; +mod osstr; +mod path; +mod set; +mod slice; +mod string; +mod vec; diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs new file mode 100644 index 00000000000..7672221bc8e --- /dev/null +++ b/src/conversions/std/num.rs @@ -0,0 +1,490 @@ +use crate::{ + exceptions, ffi, inspect::types::TypeInfo, AsPyPointer, FromPyObject, IntoPy, PyAny, PyErr, + PyObject, PyResult, Python, ToPyObject, +}; +use std::convert::TryFrom; +use std::os::raw::c_long; + +macro_rules! int_fits_larger_int { + ($rust_type:ty, $larger_type:ty) => { + impl ToPyObject for $rust_type { + #[inline] + fn to_object(&self, py: Python<'_>) -> PyObject { + (*self as $larger_type).into_py(py) + } + } + impl IntoPy for $rust_type { + fn into_py(self, py: Python<'_>) -> PyObject { + (self as $larger_type).into_py(py) + } + + fn type_output() -> TypeInfo { + <$larger_type>::type_output() + } + } + + impl<'source> FromPyObject<'source> for $rust_type { + fn extract(obj: &'source PyAny) -> PyResult { + let val: $larger_type = obj.extract()?; + <$rust_type>::try_from(val) + .map_err(|e| exceptions::PyOverflowError::new_err(e.to_string())) + } + + fn type_input() -> TypeInfo { + <$larger_type>::type_input() + } + } + }; +} + +macro_rules! int_convert_u64_or_i64 { + ($rust_type:ty, $pylong_from_ll_or_ull:expr, $pylong_as_ll_or_ull:expr) => { + impl ToPyObject for $rust_type { + #[inline] + fn to_object(&self, py: Python<'_>) -> PyObject { + unsafe { PyObject::from_owned_ptr(py, $pylong_from_ll_or_ull(*self)) } + } + } + impl IntoPy for $rust_type { + #[inline] + fn into_py(self, py: Python<'_>) -> PyObject { + unsafe { PyObject::from_owned_ptr(py, $pylong_from_ll_or_ull(self)) } + } + + fn type_output() -> TypeInfo { + TypeInfo::builtin("int") + } + } + impl<'source> FromPyObject<'source> for $rust_type { + fn extract(ob: &'source PyAny) -> PyResult<$rust_type> { + let ptr = ob.as_ptr(); + unsafe { + let num = ffi::PyNumber_Index(ptr); + if num.is_null() { + Err(PyErr::fetch(ob.py())) + } else { + let result = err_if_invalid_value(ob.py(), !0, $pylong_as_ll_or_ull(num)); + ffi::Py_DECREF(num); + result + } + } + } + + fn type_input() -> TypeInfo { + Self::type_output() + } + } + }; +} + +macro_rules! int_fits_c_long { + ($rust_type:ty) => { + impl ToPyObject for $rust_type { + fn to_object(&self, py: Python<'_>) -> PyObject { + unsafe { PyObject::from_owned_ptr(py, ffi::PyLong_FromLong(*self as c_long)) } + } + } + impl IntoPy for $rust_type { + fn into_py(self, py: Python<'_>) -> PyObject { + unsafe { PyObject::from_owned_ptr(py, ffi::PyLong_FromLong(self as c_long)) } + } + + fn type_output() -> TypeInfo { + TypeInfo::builtin("int") + } + } + + impl<'source> FromPyObject<'source> for $rust_type { + fn extract(obj: &'source PyAny) -> PyResult { + let ptr = obj.as_ptr(); + let val = unsafe { + let num = ffi::PyNumber_Index(ptr); + if num.is_null() { + Err(PyErr::fetch(obj.py())) + } else { + let val = err_if_invalid_value(obj.py(), -1, ffi::PyLong_AsLong(num)); + ffi::Py_DECREF(num); + val + } + }?; + <$rust_type>::try_from(val) + .map_err(|e| exceptions::PyOverflowError::new_err(e.to_string())) + } + + fn type_input() -> TypeInfo { + Self::type_output() + } + } + }; +} + +int_fits_c_long!(i8); +int_fits_c_long!(u8); +int_fits_c_long!(i16); +int_fits_c_long!(u16); +int_fits_c_long!(i32); + +// If c_long is 64-bits, we can use more types with int_fits_c_long!: +#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))] +int_fits_c_long!(u32); +#[cfg(any(target_pointer_width = "32", target_os = "windows"))] +int_fits_larger_int!(u32, u64); + +#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))] +int_fits_c_long!(i64); + +// manual implementation for i64 on systems with 32-bit long +#[cfg(any(target_pointer_width = "32", target_os = "windows"))] +int_convert_u64_or_i64!(i64, ffi::PyLong_FromLongLong, ffi::PyLong_AsLongLong); + +#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))] +int_fits_c_long!(isize); +#[cfg(any(target_pointer_width = "32", target_os = "windows"))] +int_fits_larger_int!(isize, i64); + +int_fits_larger_int!(usize, u64); + +// u64 has a manual implementation as it never fits into signed long +int_convert_u64_or_i64!( + u64, + ffi::PyLong_FromUnsignedLongLong, + ffi::PyLong_AsUnsignedLongLong +); + +#[cfg(not(Py_LIMITED_API))] +mod fast_128bit_int_conversion { + use super::*; + + // for 128bit Integers + macro_rules! int_convert_128 { + ($rust_type: ty, $is_signed: expr) => { + impl ToPyObject for $rust_type { + #[inline] + fn to_object(&self, py: Python<'_>) -> PyObject { + (*self).into_py(py) + } + } + impl IntoPy for $rust_type { + fn into_py(self, py: Python<'_>) -> PyObject { + unsafe { + // Always use little endian + let bytes = self.to_le_bytes(); + let obj = ffi::_PyLong_FromByteArray( + bytes.as_ptr() as *const std::os::raw::c_uchar, + bytes.len(), + 1, + $is_signed, + ); + PyObject::from_owned_ptr(py, obj) + } + } + + fn type_output() -> TypeInfo { + TypeInfo::builtin("int") + } + } + + impl<'source> FromPyObject<'source> for $rust_type { + fn extract(ob: &'source PyAny) -> PyResult<$rust_type> { + unsafe { + let num = ffi::PyNumber_Index(ob.as_ptr()); + if num.is_null() { + return Err(PyErr::fetch(ob.py())); + } + let mut buffer = [0; std::mem::size_of::<$rust_type>()]; + let ok = ffi::_PyLong_AsByteArray( + num as *mut ffi::PyLongObject, + buffer.as_mut_ptr(), + buffer.len(), + 1, + $is_signed, + ); + ffi::Py_DECREF(num); + crate::err::error_on_minusone(ob.py(), ok)?; + Ok(<$rust_type>::from_le_bytes(buffer)) + } + } + + fn type_input() -> TypeInfo { + Self::type_output() + } + } + }; + } + + int_convert_128!(i128, 1); + int_convert_128!(u128, 0); +} + +// For ABI3 we implement the conversion manually. +#[cfg(Py_LIMITED_API)] +mod slow_128bit_int_conversion { + use super::*; + const SHIFT: usize = 64; + + // for 128bit Integers + macro_rules! int_convert_128 { + ($rust_type: ty, $half_type: ty) => { + impl ToPyObject for $rust_type { + #[inline] + fn to_object(&self, py: Python<'_>) -> PyObject { + (*self).into_py(py) + } + } + + impl IntoPy for $rust_type { + fn into_py(self, py: Python<'_>) -> PyObject { + let lower = self as u64; + let upper = (self >> SHIFT) as $half_type; + unsafe { + let shifted = PyObject::from_owned_ptr( + py, + ffi::PyNumber_Lshift( + upper.into_py(py).as_ptr(), + SHIFT.into_py(py).as_ptr(), + ), + ); + PyObject::from_owned_ptr( + py, + ffi::PyNumber_Or(shifted.as_ptr(), lower.into_py(py).as_ptr()), + ) + } + } + + fn type_output() -> TypeInfo { + TypeInfo::builtin("int") + } + } + + impl<'source> FromPyObject<'source> for $rust_type { + fn extract(ob: &'source PyAny) -> PyResult<$rust_type> { + let py = ob.py(); + unsafe { + let lower = err_if_invalid_value( + py, + -1 as _, + ffi::PyLong_AsUnsignedLongLongMask(ob.as_ptr()), + )? as $rust_type; + let shifted = PyObject::from_owned_ptr_or_err( + py, + ffi::PyNumber_Rshift(ob.as_ptr(), SHIFT.into_py(py).as_ptr()), + )?; + let upper: $half_type = shifted.extract(py)?; + Ok((<$rust_type>::from(upper) << SHIFT) | lower) + } + } + + fn type_input() -> TypeInfo { + Self::type_output() + } + } + }; + } + + int_convert_128!(i128, i64); + int_convert_128!(u128, u64); +} + +fn err_if_invalid_value( + py: Python<'_>, + invalid_value: T, + actual_value: T, +) -> PyResult { + if actual_value == invalid_value { + if let Some(err) = PyErr::take(py) { + return Err(err); + } + } + + Ok(actual_value) +} + +#[cfg(test)] +mod test_128bit_integers { + use super::*; + use crate::types::PyDict; + + #[cfg(not(target_arch = "wasm32"))] + use proptest::prelude::*; + + #[cfg(not(target_arch = "wasm32"))] + proptest! { + #[test] + fn test_i128_roundtrip(x: i128) { + Python::with_gil(|py| { + let x_py = x.into_py(py); + let locals = PyDict::new(py); + locals.set_item("x_py", x_py.clone_ref(py)).unwrap(); + py.run(&format!("assert x_py == {}", x), None, Some(locals)).unwrap(); + let roundtripped: i128 = x_py.extract(py).unwrap(); + assert_eq!(x, roundtripped); + }) + } + } + + #[cfg(not(target_arch = "wasm32"))] + proptest! { + #[test] + fn test_u128_roundtrip(x: u128) { + Python::with_gil(|py| { + let x_py = x.into_py(py); + let locals = PyDict::new(py); + locals.set_item("x_py", x_py.clone_ref(py)).unwrap(); + py.run(&format!("assert x_py == {}", x), None, Some(locals)).unwrap(); + let roundtripped: u128 = x_py.extract(py).unwrap(); + assert_eq!(x, roundtripped); + }) + } + } + + #[test] + fn test_i128_max() { + Python::with_gil(|py| { + let v = std::i128::MAX; + let obj = v.to_object(py); + assert_eq!(v, obj.extract::(py).unwrap()); + assert_eq!(v as u128, obj.extract::(py).unwrap()); + assert!(obj.extract::(py).is_err()); + }) + } + + #[test] + fn test_i128_min() { + Python::with_gil(|py| { + let v = std::i128::MIN; + let obj = v.to_object(py); + assert_eq!(v, obj.extract::(py).unwrap()); + assert!(obj.extract::(py).is_err()); + assert!(obj.extract::(py).is_err()); + }) + } + + #[test] + fn test_u128_max() { + Python::with_gil(|py| { + let v = std::u128::MAX; + let obj = v.to_object(py); + assert_eq!(v, obj.extract::(py).unwrap()); + assert!(obj.extract::(py).is_err()); + }) + } + + #[test] + fn test_i128_overflow() { + Python::with_gil(|py| { + let obj = py.eval("(1 << 130) * -1", None, None).unwrap(); + let err = obj.extract::().unwrap_err(); + assert!(err.is_instance_of::(py)); + }) + } + + #[test] + fn test_u128_overflow() { + Python::with_gil(|py| { + let obj = py.eval("1 << 130", None, None).unwrap(); + let err = obj.extract::().unwrap_err(); + assert!(err.is_instance_of::(py)); + }) + } +} + +#[cfg(test)] +mod tests { + use crate::Python; + use crate::ToPyObject; + + #[test] + fn test_u32_max() { + Python::with_gil(|py| { + let v = std::u32::MAX; + let obj = v.to_object(py); + assert_eq!(v, obj.extract::(py).unwrap()); + assert_eq!(u64::from(v), obj.extract::(py).unwrap()); + assert!(obj.extract::(py).is_err()); + }); + } + + #[test] + fn test_i64_max() { + Python::with_gil(|py| { + let v = std::i64::MAX; + let obj = v.to_object(py); + assert_eq!(v, obj.extract::(py).unwrap()); + assert_eq!(v as u64, obj.extract::(py).unwrap()); + assert!(obj.extract::(py).is_err()); + }); + } + + #[test] + fn test_i64_min() { + Python::with_gil(|py| { + let v = std::i64::MIN; + let obj = v.to_object(py); + assert_eq!(v, obj.extract::(py).unwrap()); + assert!(obj.extract::(py).is_err()); + assert!(obj.extract::(py).is_err()); + }); + } + + #[test] + fn test_u64_max() { + Python::with_gil(|py| { + let v = std::u64::MAX; + let obj = v.to_object(py); + assert_eq!(v, obj.extract::(py).unwrap()); + assert!(obj.extract::(py).is_err()); + }); + } + + macro_rules! test_common ( + ($test_mod_name:ident, $t:ty) => ( + mod $test_mod_name { + use crate::exceptions; + use crate::ToPyObject; + use crate::Python; + + #[test] + fn from_py_string_type_error() { + Python::with_gil(|py|{ + + + let obj = ("123").to_object(py); + let err = obj.extract::<$t>(py).unwrap_err(); + assert!(err.is_instance_of::(py)); + }); + } + + #[test] + fn from_py_float_type_error() { + Python::with_gil(|py|{ + + let obj = (12.3).to_object(py); + let err = obj.extract::<$t>(py).unwrap_err(); + assert!(err.is_instance_of::(py));}); + } + + #[test] + fn to_py_object_and_back() { + Python::with_gil(|py|{ + + let val = 123 as $t; + let obj = val.to_object(py); + assert_eq!(obj.extract::<$t>(py).unwrap(), val as $t);}); + } + } + ) + ); + + test_common!(i8, i8); + test_common!(u8, u8); + test_common!(i16, i16); + test_common!(u16, u16); + test_common!(i32, i32); + test_common!(u32, u32); + test_common!(i64, i64); + test_common!(u64, u64); + test_common!(isize, isize); + test_common!(usize, usize); + test_common!(i128, i128); + test_common!(u128, u128); +} diff --git a/src/conversions/osstr.rs b/src/conversions/std/osstr.rs similarity index 100% rename from src/conversions/osstr.rs rename to src/conversions/std/osstr.rs diff --git a/src/conversions/path.rs b/src/conversions/std/path.rs similarity index 100% rename from src/conversions/path.rs rename to src/conversions/std/path.rs diff --git a/src/conversions/std/set.rs b/src/conversions/std/set.rs new file mode 100644 index 00000000000..5fcdcdcd114 --- /dev/null +++ b/src/conversions/std/set.rs @@ -0,0 +1,144 @@ +use std::{cmp, collections, hash}; + +use crate::{ + inspect::types::TypeInfo, types::PySet, FromPyObject, IntoPy, PyAny, PyObject, PyResult, + Python, ToPyObject, +}; + +impl ToPyObject for collections::HashSet +where + T: hash::Hash + Eq + ToPyObject, + S: hash::BuildHasher + Default, +{ + fn to_object(&self, py: Python<'_>) -> PyObject { + let set = PySet::new::(py, &[]).expect("Failed to construct empty set"); + { + for val in self { + set.add(val).expect("Failed to add to set"); + } + } + set.into() + } +} + +impl ToPyObject for collections::BTreeSet +where + T: hash::Hash + Eq + ToPyObject, +{ + fn to_object(&self, py: Python<'_>) -> PyObject { + let set = PySet::new::(py, &[]).expect("Failed to construct empty set"); + { + for val in self { + set.add(val).expect("Failed to add to set"); + } + } + set.into() + } +} + +impl IntoPy for collections::HashSet +where + K: IntoPy + Eq + hash::Hash, + S: hash::BuildHasher + Default, +{ + fn into_py(self, py: Python<'_>) -> PyObject { + let set = PySet::empty(py).expect("Failed to construct empty set"); + { + for val in self { + set.add(val.into_py(py)).expect("Failed to add to set"); + } + } + set.into() + } + + fn type_output() -> TypeInfo { + TypeInfo::set_of(K::type_output()) + } +} + +impl<'source, K, S> FromPyObject<'source> for collections::HashSet +where + K: FromPyObject<'source> + cmp::Eq + hash::Hash, + S: hash::BuildHasher + Default, +{ + fn extract(ob: &'source PyAny) -> PyResult { + let set: &PySet = ob.downcast()?; + set.iter().map(K::extract).collect() + } + + fn type_input() -> TypeInfo { + TypeInfo::set_of(K::type_input()) + } +} + +impl IntoPy for collections::BTreeSet +where + K: IntoPy + cmp::Ord, +{ + fn into_py(self, py: Python<'_>) -> PyObject { + let set = PySet::empty(py).expect("Failed to construct empty set"); + { + for val in self { + set.add(val.into_py(py)).expect("Failed to add to set"); + } + } + set.into() + } + + fn type_output() -> TypeInfo { + TypeInfo::set_of(K::type_output()) + } +} + +impl<'source, K> FromPyObject<'source> for collections::BTreeSet +where + K: FromPyObject<'source> + cmp::Ord, +{ + fn extract(ob: &'source PyAny) -> PyResult { + let set: &PySet = ob.downcast()?; + set.iter().map(K::extract).collect() + } + + fn type_input() -> TypeInfo { + TypeInfo::set_of(K::type_input()) + } +} + +#[cfg(test)] +mod tests { + use super::PySet; + use crate::{IntoPy, PyObject, Python}; + use std::collections::{BTreeSet, HashSet}; + + #[test] + fn test_extract_hashset() { + Python::with_gil(|py| { + let set = PySet::new(py, &[1, 2, 3, 4, 5]).unwrap(); + let hash_set: HashSet = set.extract().unwrap(); + assert_eq!(hash_set, [1, 2, 3, 4, 5].iter().copied().collect()); + }); + } + + #[test] + fn test_extract_btreeset() { + Python::with_gil(|py| { + let set = PySet::new(py, &[1, 2, 3, 4, 5]).unwrap(); + let hash_set: BTreeSet = set.extract().unwrap(); + assert_eq!(hash_set, [1, 2, 3, 4, 5].iter().copied().collect()); + }); + } + + #[test] + fn test_set_into_py() { + Python::with_gil(|py| { + let bt: BTreeSet = [1, 2, 3, 4, 5].iter().cloned().collect(); + let hs: HashSet = [1, 2, 3, 4, 5].iter().cloned().collect(); + + let bto: PyObject = bt.clone().into_py(py); + let hso: PyObject = hs.clone().into_py(py); + + assert_eq!(bt, bto.extract(py).unwrap()); + assert_eq!(hs, hso.extract(py).unwrap()); + }); + } +} diff --git a/src/conversions/std/slice.rs b/src/conversions/std/slice.rs new file mode 100644 index 00000000000..8a215eea06b --- /dev/null +++ b/src/conversions/std/slice.rs @@ -0,0 +1,39 @@ +use crate::{ + inspect::types::TypeInfo, types::PyBytes, FromPyObject, IntoPy, PyAny, PyObject, PyResult, + PyTryFrom, Python, ToPyObject, +}; + +impl<'a> IntoPy for &'a [u8] { + fn into_py(self, py: Python<'_>) -> PyObject { + PyBytes::new(py, self).to_object(py) + } + + fn type_output() -> TypeInfo { + TypeInfo::builtin("bytes") + } +} + +impl<'a> FromPyObject<'a> for &'a [u8] { + fn extract(obj: &'a PyAny) -> PyResult { + Ok(::try_from(obj)?.as_bytes()) + } + + fn type_input() -> TypeInfo { + Self::type_output() + } +} + +#[cfg(test)] +mod tests { + use crate::FromPyObject; + use crate::Python; + + #[test] + fn test_extract_bytes() { + Python::with_gil(|py| { + let py_bytes = py.eval("b'Hello Python'", None, None).unwrap(); + let bytes: &[u8] = FromPyObject::extract(py_bytes).unwrap(); + assert_eq!(bytes, b"Hello Python"); + }); + } +} diff --git a/src/conversions/std/string.rs b/src/conversions/std/string.rs new file mode 100644 index 00000000000..0e332f0bb99 --- /dev/null +++ b/src/conversions/std/string.rs @@ -0,0 +1,197 @@ +use std::borrow::Cow; + +use crate::{ + inspect::types::TypeInfo, types::PyString, FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, + PyTryFrom, Python, ToPyObject, +}; + +/// Converts a Rust `str` to a Python object. +/// See `PyString::new` for details on the conversion. +impl ToPyObject for str { + #[inline] + fn to_object(&self, py: Python<'_>) -> PyObject { + PyString::new(py, self).into() + } +} + +impl<'a> IntoPy for &'a str { + #[inline] + fn into_py(self, py: Python<'_>) -> PyObject { + PyString::new(py, self).into() + } + + fn type_output() -> TypeInfo { + ::type_output() + } +} + +impl<'a> IntoPy> for &'a str { + #[inline] + fn into_py(self, py: Python<'_>) -> Py { + PyString::new(py, self).into() + } + + fn type_output() -> TypeInfo { + ::type_output() + } +} + +/// Converts a Rust `Cow<'_, str>` to a Python object. +/// See `PyString::new` for details on the conversion. +impl<'a> ToPyObject for Cow<'a, str> { + #[inline] + fn to_object(&self, py: Python<'_>) -> PyObject { + PyString::new(py, self).into() + } +} + +impl IntoPy for Cow<'_, str> { + #[inline] + fn into_py(self, py: Python<'_>) -> PyObject { + self.to_object(py) + } + + fn type_output() -> TypeInfo { + ::type_output() + } +} + +/// Converts a Rust `String` to a Python object. +/// See `PyString::new` for details on the conversion. +impl ToPyObject for String { + #[inline] + fn to_object(&self, py: Python<'_>) -> PyObject { + PyString::new(py, self).into() + } +} + +impl ToPyObject for char { + fn to_object(&self, py: Python<'_>) -> PyObject { + self.into_py(py) + } +} + +impl IntoPy for char { + fn into_py(self, py: Python<'_>) -> PyObject { + let mut bytes = [0u8; 4]; + PyString::new(py, self.encode_utf8(&mut bytes)).into() + } + + fn type_output() -> TypeInfo { + ::type_output() + } +} + +impl IntoPy for String { + fn into_py(self, py: Python<'_>) -> PyObject { + PyString::new(py, &self).into() + } + + fn type_output() -> TypeInfo { + TypeInfo::builtin("str") + } +} + +impl<'a> IntoPy for &'a String { + #[inline] + fn into_py(self, py: Python<'_>) -> PyObject { + PyString::new(py, self).into() + } + + fn type_output() -> TypeInfo { + ::type_output() + } +} + +/// Allows extracting strings from Python objects. +/// Accepts Python `str` and `unicode` objects. +impl<'source> FromPyObject<'source> for &'source str { + fn extract(ob: &'source PyAny) -> PyResult { + ::try_from(ob)?.to_str() + } + + fn type_input() -> TypeInfo { + ::type_input() + } +} + +/// Allows extracting strings from Python objects. +/// Accepts Python `str` and `unicode` objects. +impl FromPyObject<'_> for String { + fn extract(obj: &PyAny) -> PyResult { + ::try_from(obj)? + .to_str() + .map(ToOwned::to_owned) + } + + fn type_input() -> TypeInfo { + Self::type_output() + } +} + +impl FromPyObject<'_> for char { + fn extract(obj: &PyAny) -> PyResult { + let s = >::try_from(obj)?.to_str()?; + let mut iter = s.chars(); + if let (Some(ch), None) = (iter.next(), iter.next()) { + Ok(ch) + } else { + Err(crate::exceptions::PyValueError::new_err( + "expected a string of length 1", + )) + } + } + + fn type_input() -> TypeInfo { + ::type_input() + } +} + +#[cfg(test)] +mod tests { + use crate::Python; + use crate::{FromPyObject, ToPyObject}; + + #[test] + fn test_non_bmp() { + Python::with_gil(|py| { + let s = "\u{1F30F}"; + let py_string = s.to_object(py); + assert_eq!(s, py_string.extract::(py).unwrap()); + }) + } + + #[test] + fn test_extract_str() { + Python::with_gil(|py| { + let s = "Hello Python"; + let py_string = s.to_object(py); + + let s2: &str = FromPyObject::extract(py_string.as_ref(py)).unwrap(); + assert_eq!(s, s2); + }) + } + + #[test] + fn test_extract_char() { + Python::with_gil(|py| { + let ch = '😃'; + let py_string = ch.to_object(py); + let ch2: char = FromPyObject::extract(py_string.as_ref(py)).unwrap(); + assert_eq!(ch, ch2); + }) + } + + #[test] + fn test_extract_char_err() { + Python::with_gil(|py| { + let s = "Hello Python"; + let py_string = s.to_object(py); + let err: crate::PyResult = FromPyObject::extract(py_string.as_ref(py)); + assert!(err + .unwrap_err() + .to_string() + .contains("expected a string of length 1")); + }) + } +} diff --git a/src/conversions/std/vec.rs b/src/conversions/std/vec.rs new file mode 100644 index 00000000000..3c78b888544 --- /dev/null +++ b/src/conversions/std/vec.rs @@ -0,0 +1,38 @@ +use crate::inspect::types::TypeInfo; +use crate::types::list::new_from_iter; +use crate::{IntoPy, PyObject, Python, ToPyObject}; + +impl ToPyObject for [T] +where + T: ToPyObject, +{ + fn to_object(&self, py: Python<'_>) -> PyObject { + let mut iter = self.iter().map(|e| e.to_object(py)); + let list = new_from_iter(py, &mut iter); + list.into() + } +} + +impl ToPyObject for Vec +where + T: ToPyObject, +{ + fn to_object(&self, py: Python<'_>) -> PyObject { + self.as_slice().to_object(py) + } +} + +impl IntoPy for Vec +where + T: IntoPy, +{ + fn into_py(self, py: Python<'_>) -> PyObject { + let mut iter = self.into_iter().map(|e| e.into_py(py)); + let list = new_from_iter(py, &mut iter); + list.into() + } + + fn type_output() -> TypeInfo { + TypeInfo::list_of(T::type_output()) + } +} diff --git a/src/types/bytes.rs b/src/types/bytes.rs index 4ba195d157f..3302e8e661c 100644 --- a/src/types/bytes.rs +++ b/src/types/bytes.rs @@ -1,8 +1,4 @@ -use crate::inspect::types::TypeInfo; -use crate::{ - ffi, AsPyPointer, FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, PyTryFrom, Python, - ToPyObject, -}; +use crate::{ffi, AsPyPointer, Py, PyAny, PyResult, Python}; use std::ops::Index; use std::os::raw::c_char; use std::slice::SliceIndex; @@ -125,41 +121,12 @@ impl> Index for PyBytes { } } -impl<'a> IntoPy for &'a [u8] { - fn into_py(self, py: Python<'_>) -> PyObject { - PyBytes::new(py, self).to_object(py) - } - - fn type_output() -> TypeInfo { - TypeInfo::builtin("bytes") - } -} - -impl<'a> FromPyObject<'a> for &'a [u8] { - fn extract(obj: &'a PyAny) -> PyResult { - Ok(::try_from(obj)?.as_bytes()) - } - - fn type_input() -> TypeInfo { - Self::type_output() - } -} - #[cfg(test)] mod tests { use super::PyBytes; use crate::FromPyObject; use crate::Python; - #[test] - fn test_extract_bytes() { - Python::with_gil(|py| { - let py_bytes = py.eval("b'Hello Python'", None, None).unwrap(); - let bytes: &[u8] = FromPyObject::extract(py_bytes).unwrap(); - assert_eq!(bytes, b"Hello Python"); - }); - } - #[test] fn test_bytes_index() { Python::with_gil(|py| { diff --git a/src/types/dict.rs b/src/types/dict.rs index 853a45af5c3..af139694733 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -3,14 +3,11 @@ use super::PyMapping; use crate::err::{self, PyErr, PyResult}; use crate::ffi::Py_ssize_t; -use crate::inspect::types::TypeInfo; use crate::types::{PyAny, PyList}; +use crate::{ffi, AsPyPointer, PyTryFrom, Python, ToPyObject}; #[cfg(not(PyPy))] -use crate::IntoPyPointer; -use crate::{ffi, AsPyPointer, FromPyObject, IntoPy, PyObject, PyTryFrom, Python, ToPyObject}; -use std::collections::{BTreeMap, HashMap}; +use crate::{IntoPyPointer, PyObject}; use std::ptr::NonNull; -use std::{cmp, collections, hash}; /// Represents a Python `dict`. #[repr(transparent)] @@ -357,62 +354,6 @@ impl<'py> PyDictIterator<'py> { } } -impl ToPyObject for collections::HashMap -where - K: hash::Hash + cmp::Eq + ToPyObject, - V: ToPyObject, - H: hash::BuildHasher, -{ - fn to_object(&self, py: Python<'_>) -> PyObject { - IntoPyDict::into_py_dict(self, py).into() - } -} - -impl ToPyObject for collections::BTreeMap -where - K: cmp::Eq + ToPyObject, - V: ToPyObject, -{ - fn to_object(&self, py: Python<'_>) -> PyObject { - IntoPyDict::into_py_dict(self, py).into() - } -} - -impl IntoPy for collections::HashMap -where - K: hash::Hash + cmp::Eq + IntoPy, - V: IntoPy, - H: hash::BuildHasher, -{ - fn into_py(self, py: Python<'_>) -> PyObject { - let iter = self - .into_iter() - .map(|(k, v)| (k.into_py(py), v.into_py(py))); - IntoPyDict::into_py_dict(iter, py).into() - } - - fn type_output() -> TypeInfo { - TypeInfo::dict_of(K::type_output(), V::type_output()) - } -} - -impl IntoPy for collections::BTreeMap -where - K: cmp::Eq + IntoPy, - V: IntoPy, -{ - fn into_py(self, py: Python<'_>) -> PyObject { - let iter = self - .into_iter() - .map(|(k, v)| (k.into_py(py), v.into_py(py))); - IntoPyDict::into_py_dict(iter, py).into() - } - - fn type_output() -> TypeInfo { - TypeInfo::dict_of(K::type_output(), V::type_output()) - } -} - /// Conversion trait that allows a sequence of tuples to be converted into `PyDict` /// Primary use case for this trait is `call` and `call_method` methods as keywords argument. pub trait IntoPyDict { @@ -474,45 +415,6 @@ where } } -impl<'source, K, V, S> FromPyObject<'source> for HashMap -where - K: FromPyObject<'source> + cmp::Eq + hash::Hash, - V: FromPyObject<'source>, - S: hash::BuildHasher + Default, -{ - fn extract(ob: &'source PyAny) -> Result { - let dict = ::try_from(ob)?; - let mut ret = HashMap::with_capacity_and_hasher(dict.len(), S::default()); - for (k, v) in dict.iter() { - ret.insert(K::extract(k)?, V::extract(v)?); - } - Ok(ret) - } - - fn type_input() -> TypeInfo { - TypeInfo::mapping_of(K::type_input(), V::type_input()) - } -} - -impl<'source, K, V> FromPyObject<'source> for BTreeMap -where - K: FromPyObject<'source> + cmp::Ord, - V: FromPyObject<'source>, -{ - fn extract(ob: &'source PyAny) -> Result { - let dict = ::try_from(ob)?; - let mut ret = BTreeMap::new(); - for (k, v) in dict.iter() { - ret.insert(K::extract(k)?, V::extract(v)?); - } - Ok(ret) - } - - fn type_input() -> TypeInfo { - TypeInfo::mapping_of(K::type_input(), V::type_input()) - } -} - #[cfg(test)] mod tests { use super::*; @@ -520,7 +422,7 @@ mod tests { use crate::exceptions; #[cfg(not(PyPy))] use crate::{types::PyList, PyTypeInfo}; - use crate::{types::PyTuple, IntoPy, PyObject, PyTryFrom, Python, ToPyObject}; + use crate::{types::PyTuple, PyTryFrom, Python, ToPyObject}; use std::collections::{BTreeMap, HashMap}; #[test] @@ -900,50 +802,6 @@ mod tests { }); } - #[test] - fn test_hashmap_to_python() { - Python::with_gil(|py| { - let mut map = HashMap::::new(); - map.insert(1, 1); - - let m = map.to_object(py); - let py_map = ::try_from(m.as_ref(py)).unwrap(); - - assert!(py_map.len() == 1); - assert!(py_map.get_item(1).unwrap().extract::().unwrap() == 1); - assert_eq!(map, py_map.extract().unwrap()); - }); - } - - #[test] - fn test_btreemap_to_python() { - Python::with_gil(|py| { - let mut map = BTreeMap::::new(); - map.insert(1, 1); - - let m = map.to_object(py); - let py_map = ::try_from(m.as_ref(py)).unwrap(); - - assert!(py_map.len() == 1); - assert!(py_map.get_item(1).unwrap().extract::().unwrap() == 1); - assert_eq!(map, py_map.extract().unwrap()); - }); - } - - #[test] - fn test_hashmap_into_python() { - Python::with_gil(|py| { - let mut map = HashMap::::new(); - map.insert(1, 1); - - let m: PyObject = map.into_py(py); - let py_map = ::try_from(m.as_ref(py)).unwrap(); - - assert!(py_map.len() == 1); - assert!(py_map.get_item(1).unwrap().extract::().unwrap() == 1); - }); - } - #[test] fn test_hashmap_into_dict() { Python::with_gil(|py| { @@ -957,20 +815,6 @@ mod tests { }); } - #[test] - fn test_btreemap_into_py() { - Python::with_gil(|py| { - let mut map = BTreeMap::::new(); - map.insert(1, 1); - - let m: PyObject = map.into_py(py); - let py_map = ::try_from(m.as_ref(py)).unwrap(); - - assert!(py_map.len() == 1); - assert!(py_map.get_item(1).unwrap().extract::().unwrap() == 1); - }); - } - #[test] fn test_btreemap_into_dict() { Python::with_gil(|py| { diff --git a/src/types/list.rs b/src/types/list.rs index e12d34bbed2..31aecbe99e6 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -6,12 +6,9 @@ use std::convert::TryInto; use crate::err::{self, PyResult}; use crate::ffi::{self, Py_ssize_t}; -use crate::inspect::types::TypeInfo; use crate::internal_tricks::get_ssize_index; use crate::types::PySequence; -use crate::{ - AsPyPointer, IntoPy, IntoPyPointer, Py, PyAny, PyObject, PyTryFrom, Python, ToPyObject, -}; +use crate::{AsPyPointer, IntoPyPointer, Py, PyAny, PyObject, PyTryFrom, Python, ToPyObject}; /// Represents a Python `list`. #[repr(transparent)] @@ -21,7 +18,7 @@ pyobject_native_type_core!(PyList, ffi::PyList_Type, #checkfunction=ffi::PyList_ #[inline] #[track_caller] -fn new_from_iter( +pub(crate) fn new_from_iter( py: Python<'_>, elements: &mut dyn ExactSizeIterator, ) -> Py { @@ -342,41 +339,6 @@ impl<'a> std::iter::IntoIterator for &'a PyList { } } -impl ToPyObject for [T] -where - T: ToPyObject, -{ - fn to_object(&self, py: Python<'_>) -> PyObject { - let mut iter = self.iter().map(|e| e.to_object(py)); - let list = new_from_iter(py, &mut iter); - list.into() - } -} - -impl ToPyObject for Vec -where - T: ToPyObject, -{ - fn to_object(&self, py: Python<'_>) -> PyObject { - self.as_slice().to_object(py) - } -} - -impl IntoPy for Vec -where - T: IntoPy, -{ - fn into_py(self, py: Python<'_>) -> PyObject { - let mut iter = self.into_iter().map(|e| e.into_py(py)); - let list = new_from_iter(py, &mut iter); - list.into() - } - - fn type_output() -> TypeInfo { - TypeInfo::list_of(T::type_output()) - } -} - #[cfg(test)] mod tests { use crate::types::PyList; diff --git a/src/types/mod.rs b/src/types/mod.rs index da88acc4233..bf7f63cfb41 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -279,7 +279,7 @@ mod frame; mod frozenset; mod function; mod iterator; -mod list; +pub(crate) mod list; mod mapping; mod module; mod num; diff --git a/src/types/num.rs b/src/types/num.rs index 7b6c1b731bd..21f6d72d3cd 100644 --- a/src/types/num.rs +++ b/src/types/num.rs @@ -2,46 +2,7 @@ // // based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython -use crate::inspect::types::TypeInfo; -use crate::{ - exceptions, ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyErr, PyObject, PyResult, Python, - ToPyObject, -}; -use std::convert::TryFrom; -use std::i64; -use std::os::raw::c_long; - -macro_rules! int_fits_larger_int { - ($rust_type:ty, $larger_type:ty) => { - impl ToPyObject for $rust_type { - #[inline] - fn to_object(&self, py: Python<'_>) -> PyObject { - (*self as $larger_type).into_py(py) - } - } - impl IntoPy for $rust_type { - fn into_py(self, py: Python<'_>) -> PyObject { - (self as $larger_type).into_py(py) - } - - fn type_output() -> TypeInfo { - <$larger_type>::type_output() - } - } - - impl<'source> FromPyObject<'source> for $rust_type { - fn extract(obj: &'source PyAny) -> PyResult { - let val: $larger_type = obj.extract()?; - <$rust_type>::try_from(val) - .map_err(|e| exceptions::PyOverflowError::new_err(e.to_string())) - } - - fn type_input() -> TypeInfo { - <$larger_type>::type_input() - } - } - }; -} +use crate::{ffi, PyAny}; /// Represents a Python `int` object. /// @@ -53,455 +14,3 @@ macro_rules! int_fits_larger_int { pub struct PyLong(PyAny); pyobject_native_type_core!(PyLong, ffi::PyLong_Type, #checkfunction=ffi::PyLong_Check); - -macro_rules! int_fits_c_long { - ($rust_type:ty) => { - impl ToPyObject for $rust_type { - fn to_object(&self, py: Python<'_>) -> PyObject { - unsafe { PyObject::from_owned_ptr(py, ffi::PyLong_FromLong(*self as c_long)) } - } - } - impl IntoPy for $rust_type { - fn into_py(self, py: Python<'_>) -> PyObject { - unsafe { PyObject::from_owned_ptr(py, ffi::PyLong_FromLong(self as c_long)) } - } - - fn type_output() -> TypeInfo { - TypeInfo::builtin("int") - } - } - - impl<'source> FromPyObject<'source> for $rust_type { - fn extract(obj: &'source PyAny) -> PyResult { - let ptr = obj.as_ptr(); - let val = unsafe { - let num = ffi::PyNumber_Index(ptr); - if num.is_null() { - Err(PyErr::fetch(obj.py())) - } else { - let val = err_if_invalid_value(obj.py(), -1, ffi::PyLong_AsLong(num)); - ffi::Py_DECREF(num); - val - } - }?; - <$rust_type>::try_from(val) - .map_err(|e| exceptions::PyOverflowError::new_err(e.to_string())) - } - - fn type_input() -> TypeInfo { - Self::type_output() - } - } - }; -} - -macro_rules! int_convert_u64_or_i64 { - ($rust_type:ty, $pylong_from_ll_or_ull:expr, $pylong_as_ll_or_ull:expr) => { - impl ToPyObject for $rust_type { - #[inline] - fn to_object(&self, py: Python<'_>) -> PyObject { - unsafe { PyObject::from_owned_ptr(py, $pylong_from_ll_or_ull(*self)) } - } - } - impl IntoPy for $rust_type { - #[inline] - fn into_py(self, py: Python<'_>) -> PyObject { - unsafe { PyObject::from_owned_ptr(py, $pylong_from_ll_or_ull(self)) } - } - - fn type_output() -> TypeInfo { - TypeInfo::builtin("int") - } - } - impl<'source> FromPyObject<'source> for $rust_type { - fn extract(ob: &'source PyAny) -> PyResult<$rust_type> { - let ptr = ob.as_ptr(); - unsafe { - let num = ffi::PyNumber_Index(ptr); - if num.is_null() { - Err(PyErr::fetch(ob.py())) - } else { - let result = err_if_invalid_value(ob.py(), !0, $pylong_as_ll_or_ull(num)); - ffi::Py_DECREF(num); - result - } - } - } - - fn type_input() -> TypeInfo { - Self::type_output() - } - } - }; -} - -int_fits_c_long!(i8); -int_fits_c_long!(u8); -int_fits_c_long!(i16); -int_fits_c_long!(u16); -int_fits_c_long!(i32); - -// If c_long is 64-bits, we can use more types with int_fits_c_long!: -#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))] -int_fits_c_long!(u32); -#[cfg(any(target_pointer_width = "32", target_os = "windows"))] -int_fits_larger_int!(u32, u64); - -#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))] -int_fits_c_long!(i64); - -// manual implementation for i64 on systems with 32-bit long -#[cfg(any(target_pointer_width = "32", target_os = "windows"))] -int_convert_u64_or_i64!(i64, ffi::PyLong_FromLongLong, ffi::PyLong_AsLongLong); - -#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))] -int_fits_c_long!(isize); -#[cfg(any(target_pointer_width = "32", target_os = "windows"))] -int_fits_larger_int!(isize, i64); - -int_fits_larger_int!(usize, u64); - -// u64 has a manual implementation as it never fits into signed long -int_convert_u64_or_i64!( - u64, - ffi::PyLong_FromUnsignedLongLong, - ffi::PyLong_AsUnsignedLongLong -); - -#[cfg(not(Py_LIMITED_API))] -mod fast_128bit_int_conversion { - use super::*; - - // for 128bit Integers - macro_rules! int_convert_128 { - ($rust_type: ty, $is_signed: expr) => { - impl ToPyObject for $rust_type { - #[inline] - fn to_object(&self, py: Python<'_>) -> PyObject { - (*self).into_py(py) - } - } - impl IntoPy for $rust_type { - fn into_py(self, py: Python<'_>) -> PyObject { - unsafe { - // Always use little endian - let bytes = self.to_le_bytes(); - let obj = ffi::_PyLong_FromByteArray( - bytes.as_ptr() as *const std::os::raw::c_uchar, - bytes.len(), - 1, - $is_signed, - ); - PyObject::from_owned_ptr(py, obj) - } - } - - fn type_output() -> TypeInfo { - TypeInfo::builtin("int") - } - } - - impl<'source> FromPyObject<'source> for $rust_type { - fn extract(ob: &'source PyAny) -> PyResult<$rust_type> { - unsafe { - let num = ffi::PyNumber_Index(ob.as_ptr()); - if num.is_null() { - return Err(PyErr::fetch(ob.py())); - } - let mut buffer = [0; std::mem::size_of::<$rust_type>()]; - let ok = ffi::_PyLong_AsByteArray( - num as *mut ffi::PyLongObject, - buffer.as_mut_ptr(), - buffer.len(), - 1, - $is_signed, - ); - ffi::Py_DECREF(num); - crate::err::error_on_minusone(ob.py(), ok)?; - Ok(<$rust_type>::from_le_bytes(buffer)) - } - } - - fn type_input() -> TypeInfo { - Self::type_output() - } - } - }; - } - - int_convert_128!(i128, 1); - int_convert_128!(u128, 0); -} - -// For ABI3 we implement the conversion manually. -#[cfg(Py_LIMITED_API)] -mod slow_128bit_int_conversion { - use super::*; - const SHIFT: usize = 64; - - // for 128bit Integers - macro_rules! int_convert_128 { - ($rust_type: ty, $half_type: ty) => { - impl ToPyObject for $rust_type { - #[inline] - fn to_object(&self, py: Python<'_>) -> PyObject { - (*self).into_py(py) - } - } - - impl IntoPy for $rust_type { - fn into_py(self, py: Python<'_>) -> PyObject { - let lower = self as u64; - let upper = (self >> SHIFT) as $half_type; - unsafe { - let shifted = PyObject::from_owned_ptr( - py, - ffi::PyNumber_Lshift( - upper.into_py(py).as_ptr(), - SHIFT.into_py(py).as_ptr(), - ), - ); - PyObject::from_owned_ptr( - py, - ffi::PyNumber_Or(shifted.as_ptr(), lower.into_py(py).as_ptr()), - ) - } - } - - fn type_output() -> TypeInfo { - TypeInfo::builtin("int") - } - } - - impl<'source> FromPyObject<'source> for $rust_type { - fn extract(ob: &'source PyAny) -> PyResult<$rust_type> { - let py = ob.py(); - unsafe { - let lower = err_if_invalid_value( - py, - -1 as _, - ffi::PyLong_AsUnsignedLongLongMask(ob.as_ptr()), - )? as $rust_type; - let shifted = PyObject::from_owned_ptr_or_err( - py, - ffi::PyNumber_Rshift(ob.as_ptr(), SHIFT.into_py(py).as_ptr()), - )?; - let upper: $half_type = shifted.extract(py)?; - Ok((<$rust_type>::from(upper) << SHIFT) | lower) - } - } - - fn type_input() -> TypeInfo { - Self::type_output() - } - } - }; - } - - int_convert_128!(i128, i64); - int_convert_128!(u128, u64); -} - -fn err_if_invalid_value( - py: Python<'_>, - invalid_value: T, - actual_value: T, -) -> PyResult { - if actual_value == invalid_value { - if let Some(err) = PyErr::take(py) { - return Err(err); - } - } - - Ok(actual_value) -} - -#[cfg(test)] -mod test_128bit_intergers { - use super::*; - use crate::types::PyDict; - - #[cfg(not(target_arch = "wasm32"))] - use proptest::prelude::*; - - #[cfg(not(target_arch = "wasm32"))] - proptest! { - #[test] - fn test_i128_roundtrip(x: i128) { - Python::with_gil(|py| { - let x_py = x.into_py(py); - let locals = PyDict::new(py); - locals.set_item("x_py", x_py.clone_ref(py)).unwrap(); - py.run(&format!("assert x_py == {}", x), None, Some(locals)).unwrap(); - let roundtripped: i128 = x_py.extract(py).unwrap(); - assert_eq!(x, roundtripped); - }) - } - } - - #[cfg(not(target_arch = "wasm32"))] - proptest! { - #[test] - fn test_u128_roundtrip(x: u128) { - Python::with_gil(|py| { - let x_py = x.into_py(py); - let locals = PyDict::new(py); - locals.set_item("x_py", x_py.clone_ref(py)).unwrap(); - py.run(&format!("assert x_py == {}", x), None, Some(locals)).unwrap(); - let roundtripped: u128 = x_py.extract(py).unwrap(); - assert_eq!(x, roundtripped); - }) - } - } - - #[test] - fn test_i128_max() { - Python::with_gil(|py| { - let v = std::i128::MAX; - let obj = v.to_object(py); - assert_eq!(v, obj.extract::(py).unwrap()); - assert_eq!(v as u128, obj.extract::(py).unwrap()); - assert!(obj.extract::(py).is_err()); - }) - } - - #[test] - fn test_i128_min() { - Python::with_gil(|py| { - let v = std::i128::MIN; - let obj = v.to_object(py); - assert_eq!(v, obj.extract::(py).unwrap()); - assert!(obj.extract::(py).is_err()); - assert!(obj.extract::(py).is_err()); - }) - } - - #[test] - fn test_u128_max() { - Python::with_gil(|py| { - let v = std::u128::MAX; - let obj = v.to_object(py); - assert_eq!(v, obj.extract::(py).unwrap()); - assert!(obj.extract::(py).is_err()); - }) - } - - #[test] - fn test_i128_overflow() { - Python::with_gil(|py| { - let obj = py.eval("(1 << 130) * -1", None, None).unwrap(); - let err = obj.extract::().unwrap_err(); - assert!(err.is_instance_of::(py)); - }) - } - - #[test] - fn test_u128_overflow() { - Python::with_gil(|py| { - let obj = py.eval("1 << 130", None, None).unwrap(); - let err = obj.extract::().unwrap_err(); - assert!(err.is_instance_of::(py)); - }) - } -} - -#[cfg(test)] -mod tests { - use crate::Python; - use crate::ToPyObject; - - #[test] - fn test_u32_max() { - Python::with_gil(|py| { - let v = std::u32::MAX; - let obj = v.to_object(py); - assert_eq!(v, obj.extract::(py).unwrap()); - assert_eq!(u64::from(v), obj.extract::(py).unwrap()); - assert!(obj.extract::(py).is_err()); - }); - } - - #[test] - fn test_i64_max() { - Python::with_gil(|py| { - let v = std::i64::MAX; - let obj = v.to_object(py); - assert_eq!(v, obj.extract::(py).unwrap()); - assert_eq!(v as u64, obj.extract::(py).unwrap()); - assert!(obj.extract::(py).is_err()); - }); - } - - #[test] - fn test_i64_min() { - Python::with_gil(|py| { - let v = std::i64::MIN; - let obj = v.to_object(py); - assert_eq!(v, obj.extract::(py).unwrap()); - assert!(obj.extract::(py).is_err()); - assert!(obj.extract::(py).is_err()); - }); - } - - #[test] - fn test_u64_max() { - Python::with_gil(|py| { - let v = std::u64::MAX; - let obj = v.to_object(py); - assert_eq!(v, obj.extract::(py).unwrap()); - assert!(obj.extract::(py).is_err()); - }); - } - - macro_rules! test_common ( - ($test_mod_name:ident, $t:ty) => ( - mod $test_mod_name { - use crate::exceptions; - use crate::ToPyObject; - use crate::Python; - - #[test] - fn from_py_string_type_error() { - Python::with_gil(|py|{ - - - let obj = ("123").to_object(py); - let err = obj.extract::<$t>(py).unwrap_err(); - assert!(err.is_instance_of::(py)); - }); - } - - #[test] - fn from_py_float_type_error() { - Python::with_gil(|py|{ - - let obj = (12.3).to_object(py); - let err = obj.extract::<$t>(py).unwrap_err(); - assert!(err.is_instance_of::(py));}); - } - - #[test] - fn to_py_object_and_back() { - Python::with_gil(|py|{ - - let val = 123 as $t; - let obj = val.to_object(py); - assert_eq!(obj.extract::<$t>(py).unwrap(), val as $t);}); - } - } - ) - ); - - test_common!(i8, i8); - test_common!(u8, u8); - test_common!(i16, i16); - test_common!(u16, u16); - test_common!(i32, i32); - test_common!(u32, u32); - test_common!(i64, i64); - test_common!(u64, u64); - test_common!(isize, isize); - test_common!(usize, usize); - test_common!(i128, i128); - test_common!(u128, u128); -} diff --git a/src/types/set.rs b/src/types/set.rs index fbe5df64aaf..305146df931 100644 --- a/src/types/set.rs +++ b/src/types/set.rs @@ -2,13 +2,10 @@ // use crate::err::{self, PyErr, PyResult}; -use crate::inspect::types::TypeInfo; #[cfg(Py_LIMITED_API)] use crate::types::PyIterator; -use crate::{ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyObject, Python, ToPyObject}; -use std::cmp; -use std::collections::{BTreeSet, HashSet}; -use std::{collections, hash, ptr}; +use crate::{ffi, AsPyPointer, PyAny, PyObject, Python, ToPyObject}; +use std::ptr; /// Represents a Python `set` #[repr(transparent)] @@ -233,110 +230,11 @@ mod impl_ { pub use impl_::*; -impl ToPyObject for collections::HashSet -where - T: hash::Hash + Eq + ToPyObject, - S: hash::BuildHasher + Default, -{ - fn to_object(&self, py: Python<'_>) -> PyObject { - let set = PySet::new::(py, &[]).expect("Failed to construct empty set"); - { - for val in self { - set.add(val).expect("Failed to add to set"); - } - } - set.into() - } -} - -impl ToPyObject for collections::BTreeSet -where - T: hash::Hash + Eq + ToPyObject, -{ - fn to_object(&self, py: Python<'_>) -> PyObject { - let set = PySet::new::(py, &[]).expect("Failed to construct empty set"); - { - for val in self { - set.add(val).expect("Failed to add to set"); - } - } - set.into() - } -} - -impl IntoPy for HashSet -where - K: IntoPy + Eq + hash::Hash, - S: hash::BuildHasher + Default, -{ - fn into_py(self, py: Python<'_>) -> PyObject { - let set = PySet::empty(py).expect("Failed to construct empty set"); - { - for val in self { - set.add(val.into_py(py)).expect("Failed to add to set"); - } - } - set.into() - } - - fn type_output() -> TypeInfo { - TypeInfo::set_of(K::type_output()) - } -} - -impl<'source, K, S> FromPyObject<'source> for HashSet -where - K: FromPyObject<'source> + cmp::Eq + hash::Hash, - S: hash::BuildHasher + Default, -{ - fn extract(ob: &'source PyAny) -> PyResult { - let set: &PySet = ob.downcast()?; - set.iter().map(K::extract).collect() - } - - fn type_input() -> TypeInfo { - TypeInfo::set_of(K::type_input()) - } -} - -impl IntoPy for BTreeSet -where - K: IntoPy + cmp::Ord, -{ - fn into_py(self, py: Python<'_>) -> PyObject { - let set = PySet::empty(py).expect("Failed to construct empty set"); - { - for val in self { - set.add(val.into_py(py)).expect("Failed to add to set"); - } - } - set.into() - } - - fn type_output() -> TypeInfo { - TypeInfo::set_of(K::type_output()) - } -} - -impl<'source, K> FromPyObject<'source> for BTreeSet -where - K: FromPyObject<'source> + cmp::Ord, -{ - fn extract(ob: &'source PyAny) -> PyResult { - let set: &PySet = ob.downcast()?; - set.iter().map(K::extract).collect() - } - - fn type_input() -> TypeInfo { - TypeInfo::set_of(K::type_input()) - } -} - #[cfg(test)] mod tests { use super::PySet; - use crate::{IntoPy, PyObject, PyTryFrom, Python, ToPyObject}; - use std::collections::{BTreeSet, HashSet}; + use crate::{PyTryFrom, Python, ToPyObject}; + use std::collections::HashSet; #[test] fn test_set_new() { @@ -482,36 +380,4 @@ mod tests { } }); } - - #[test] - fn test_extract_hashset() { - Python::with_gil(|py| { - let set = PySet::new(py, &[1, 2, 3, 4, 5]).unwrap(); - let hash_set: HashSet = set.extract().unwrap(); - assert_eq!(hash_set, [1, 2, 3, 4, 5].iter().copied().collect()); - }); - } - - #[test] - fn test_extract_btreeset() { - Python::with_gil(|py| { - let set = PySet::new(py, &[1, 2, 3, 4, 5]).unwrap(); - let hash_set: BTreeSet = set.extract().unwrap(); - assert_eq!(hash_set, [1, 2, 3, 4, 5].iter().copied().collect()); - }); - } - - #[test] - fn test_set_into_py() { - Python::with_gil(|py| { - let bt: BTreeSet = [1, 2, 3, 4, 5].iter().cloned().collect(); - let hs: HashSet = [1, 2, 3, 4, 5].iter().cloned().collect(); - - let bto: PyObject = bt.clone().into_py(py); - let hso: PyObject = hs.clone().into_py(py); - - assert_eq!(bt, bto.extract(py).unwrap()); - assert_eq!(hs, hso.extract(py).unwrap()); - }); - } } diff --git a/src/types/string.rs b/src/types/string.rs index 37911232257..0aac69cfd16 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -2,12 +2,8 @@ #[cfg(all(not(Py_LIMITED_API), target_endian = "little"))] use crate::exceptions::PyUnicodeDecodeError; -use crate::inspect::types::TypeInfo; use crate::types::PyBytes; -use crate::{ - ffi, AsPyPointer, FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, PyTryFrom, Python, - ToPyObject, -}; +use crate::{ffi, AsPyPointer, PyAny, PyResult, Python}; use std::borrow::Cow; use std::os::raw::c_char; use std::str; @@ -283,201 +279,16 @@ impl PyString { } } -/// Converts a Rust `str` to a Python object. -/// See `PyString::new` for details on the conversion. -impl ToPyObject for str { - #[inline] - fn to_object(&self, py: Python<'_>) -> PyObject { - PyString::new(py, self).into() - } -} - -impl<'a> IntoPy for &'a str { - #[inline] - fn into_py(self, py: Python<'_>) -> PyObject { - PyString::new(py, self).into() - } - - fn type_output() -> TypeInfo { - ::type_output() - } -} - -impl<'a> IntoPy> for &'a str { - #[inline] - fn into_py(self, py: Python<'_>) -> Py { - PyString::new(py, self).into() - } - - fn type_output() -> TypeInfo { - ::type_output() - } -} - -/// Converts a Rust `Cow<'_, str>` to a Python object. -/// See `PyString::new` for details on the conversion. -impl<'a> ToPyObject for Cow<'a, str> { - #[inline] - fn to_object(&self, py: Python<'_>) -> PyObject { - PyString::new(py, self).into() - } -} - -impl IntoPy for Cow<'_, str> { - #[inline] - fn into_py(self, py: Python<'_>) -> PyObject { - self.to_object(py) - } - - fn type_output() -> TypeInfo { - ::type_output() - } -} - -/// Converts a Rust `String` to a Python object. -/// See `PyString::new` for details on the conversion. -impl ToPyObject for String { - #[inline] - fn to_object(&self, py: Python<'_>) -> PyObject { - PyString::new(py, self).into() - } -} - -impl ToPyObject for char { - fn to_object(&self, py: Python<'_>) -> PyObject { - self.into_py(py) - } -} - -impl IntoPy for char { - fn into_py(self, py: Python<'_>) -> PyObject { - let mut bytes = [0u8; 4]; - PyString::new(py, self.encode_utf8(&mut bytes)).into() - } - - fn type_output() -> TypeInfo { - ::type_output() - } -} - -impl IntoPy for String { - fn into_py(self, py: Python<'_>) -> PyObject { - PyString::new(py, &self).into() - } - - fn type_output() -> TypeInfo { - TypeInfo::builtin("str") - } -} - -impl<'a> IntoPy for &'a String { - #[inline] - fn into_py(self, py: Python<'_>) -> PyObject { - PyString::new(py, self).into() - } - - fn type_output() -> TypeInfo { - ::type_output() - } -} - -/// Allows extracting strings from Python objects. -/// Accepts Python `str` and `unicode` objects. -impl<'source> FromPyObject<'source> for &'source str { - fn extract(ob: &'source PyAny) -> PyResult { - ::try_from(ob)?.to_str() - } - - fn type_input() -> TypeInfo { - ::type_input() - } -} - -/// Allows extracting strings from Python objects. -/// Accepts Python `str` and `unicode` objects. -impl FromPyObject<'_> for String { - fn extract(obj: &PyAny) -> PyResult { - ::try_from(obj)? - .to_str() - .map(ToOwned::to_owned) - } - - fn type_input() -> TypeInfo { - Self::type_output() - } -} - -impl FromPyObject<'_> for char { - fn extract(obj: &PyAny) -> PyResult { - let s = >::try_from(obj)?.to_str()?; - let mut iter = s.chars(); - if let (Some(ch), None) = (iter.next(), iter.next()) { - Ok(ch) - } else { - Err(crate::exceptions::PyValueError::new_err( - "expected a string of length 1", - )) - } - } - - fn type_input() -> TypeInfo { - ::type_input() - } -} - #[cfg(test)] mod tests { use super::*; #[cfg(all(not(Py_LIMITED_API), target_endian = "little"))] use crate::PyTypeInfo; use crate::Python; - use crate::{FromPyObject, PyObject, PyTryFrom, ToPyObject}; + use crate::{PyObject, PyTryFrom, ToPyObject}; #[cfg(all(not(Py_LIMITED_API), target_endian = "little"))] use std::borrow::Cow; - #[test] - fn test_non_bmp() { - Python::with_gil(|py| { - let s = "\u{1F30F}"; - let py_string = s.to_object(py); - assert_eq!(s, py_string.extract::(py).unwrap()); - }) - } - - #[test] - fn test_extract_str() { - Python::with_gil(|py| { - let s = "Hello Python"; - let py_string = s.to_object(py); - - let s2: &str = FromPyObject::extract(py_string.as_ref(py)).unwrap(); - assert_eq!(s, s2); - }) - } - - #[test] - fn test_extract_char() { - Python::with_gil(|py| { - let ch = '😃'; - let py_string = ch.to_object(py); - let ch2: char = FromPyObject::extract(py_string.as_ref(py)).unwrap(); - assert_eq!(ch, ch2); - }) - } - - #[test] - fn test_extract_char_err() { - Python::with_gil(|py| { - let s = "Hello Python"; - let py_string = s.to_object(py); - let err: crate::PyResult = FromPyObject::extract(py_string.as_ref(py)); - assert!(err - .unwrap_err() - .to_string() - .contains("expected a string of length 1")); - }) - } - #[test] fn test_to_str_ascii() { Python::with_gil(|py| {