diff --git a/src/conversions/std/osstr.rs b/src/conversions/std/osstr.rs index c140ef703d4..4f956c64ce8 100644 --- a/src/conversions/std/osstr.rs +++ b/src/conversions/std/osstr.rs @@ -64,6 +64,17 @@ impl<'py> IntoPyObject<'py> for &OsStr { } } +impl<'py> IntoPyObject<'py> for &&OsStr { + type Target = PyString; + type Output = Bound<'py, Self::Target>; + type Error = Infallible; + + #[inline] + fn into_pyobject(self, py: Python<'py>) -> Result { + (*self).into_pyobject(py) + } +} + // There's no FromPyObject implementation for &OsStr because albeit possible on Unix, this would // be impossible to implement on Windows. Hence it's omitted entirely @@ -208,8 +219,10 @@ impl<'py> IntoPyObject<'py> for &OsString { #[cfg(test)] mod tests { + use crate::prelude::IntoPyObject; use crate::types::{PyAnyMethods, PyStringMethods}; - use crate::{types::PyString, IntoPy, PyObject, Python, ToPyObject}; + use crate::BoundObject; + use crate::{types::PyString, IntoPy, PyObject, Python}; use std::fmt::Debug; use std::{ borrow::Cow, @@ -239,9 +252,13 @@ mod tests { #[test] fn test_topyobject_roundtrip() { Python::with_gil(|py| { - fn test_roundtrip + Debug>(py: Python<'_>, obj: T) { - let pyobject = obj.to_object(py); - let pystring = pyobject.downcast_bound::(py).unwrap(); + fn test_roundtrip<'py, T>(py: Python<'py>, obj: T) + where + T: IntoPyObject<'py> + AsRef + Debug + Clone, + T::Error: Debug, + { + let pyobject = obj.clone().into_pyobject(py).unwrap().into_any(); + let pystring = pyobject.as_borrowed().downcast::().unwrap(); assert_eq!(pystring.to_string_lossy(), obj.as_ref().to_string_lossy()); let roundtripped_obj: OsString = pystring.extract().unwrap(); assert_eq!(obj.as_ref(), roundtripped_obj.as_os_str()); diff --git a/src/conversions/std/path.rs b/src/conversions/std/path.rs index 758c4bbe198..f012cc81a27 100644 --- a/src/conversions/std/path.rs +++ b/src/conversions/std/path.rs @@ -44,6 +44,17 @@ impl<'py> IntoPyObject<'py> for &Path { } } +impl<'py> IntoPyObject<'py> for &&Path { + type Target = PyString; + type Output = Bound<'py, Self::Target>; + type Error = Infallible; + + #[inline] + fn into_pyobject(self, py: Python<'py>) -> Result { + (*self).into_pyobject(py) + } +} + impl<'a> ToPyObject for Cow<'a, Path> { #[inline] fn to_object(&self, py: Python<'_>) -> PyObject { @@ -125,8 +136,10 @@ impl<'py> IntoPyObject<'py> for &PathBuf { #[cfg(test)] mod tests { + use crate::prelude::IntoPyObject; use crate::types::{PyAnyMethods, PyStringMethods}; - use crate::{types::PyString, IntoPy, PyObject, Python, ToPyObject}; + use crate::BoundObject; + use crate::{types::PyString, IntoPy, PyObject, Python}; use std::borrow::Cow; use std::fmt::Debug; use std::path::{Path, PathBuf}; @@ -155,9 +168,13 @@ mod tests { #[test] fn test_topyobject_roundtrip() { Python::with_gil(|py| { - fn test_roundtrip + Debug>(py: Python<'_>, obj: T) { - let pyobject = obj.to_object(py); - let pystring = pyobject.downcast_bound::(py).unwrap(); + fn test_roundtrip<'py, T>(py: Python<'py>, obj: T) + where + T: IntoPyObject<'py> + AsRef + Debug + Clone, + T::Error: Debug, + { + let pyobject = obj.clone().into_pyobject(py).unwrap().into_any(); + let pystring = pyobject.as_borrowed().downcast::().unwrap(); assert_eq!(pystring.to_string_lossy(), obj.as_ref().to_string_lossy()); let roundtripped_obj: PathBuf = pystring.extract().unwrap(); assert_eq!(obj.as_ref(), roundtripped_obj.as_path()); diff --git a/src/pybacked.rs b/src/pybacked.rs index b50416062b5..c27383ba9bc 100644 --- a/src/pybacked.rs +++ b/src/pybacked.rs @@ -1,13 +1,16 @@ //! Contains types for working with Python objects that own the underlying data. -use std::{ops::Deref, ptr::NonNull, sync::Arc}; +use std::{convert::Infallible, ops::Deref, ptr::NonNull, sync::Arc}; +#[allow(deprecated)] +use crate::ToPyObject; use crate::{ + prelude::IntoPyObject, types::{ any::PyAnyMethods, bytearray::PyByteArrayMethods, bytes::PyBytesMethods, string::PyStringMethods, PyByteArray, PyBytes, PyString, }, - Bound, DowncastError, FromPyObject, IntoPy, Py, PyAny, PyErr, PyResult, Python, ToPyObject, + Bound, DowncastError, FromPyObject, IntoPy, Py, PyAny, PyErr, PyResult, Python, }; /// A wrapper around `str` where the storage is owned by a Python `bytes` or `str` object. @@ -61,7 +64,7 @@ impl TryFrom> for PyBackedStr { let s = py_string.to_str()?; let data = NonNull::from(s); Ok(Self { - storage: py_string.as_any().to_owned().unbind(), + storage: py_string.into_any().unbind(), data, }) } @@ -85,10 +88,11 @@ impl FromPyObject<'_> for PyBackedStr { } } +#[allow(deprecated)] impl ToPyObject for PyBackedStr { #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] fn to_object(&self, py: Python<'_>) -> Py { - self.storage.clone_ref(py) + self.storage.as_any().clone_ref(py) } #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))] fn to_object(&self, py: Python<'_>) -> Py { @@ -99,7 +103,7 @@ impl ToPyObject for PyBackedStr { impl IntoPy> for PyBackedStr { #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] fn into_py(self, _py: Python<'_>) -> Py { - self.storage + self.storage.into_any() } #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))] fn into_py(self, py: Python<'_>) -> Py { @@ -107,6 +111,38 @@ impl IntoPy> for PyBackedStr { } } +impl<'py> IntoPyObject<'py> for PyBackedStr { + type Target = PyAny; + type Output = Bound<'py, Self::Target>; + type Error = Infallible; + + #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] + fn into_pyobject(self, py: Python<'py>) -> Result { + Ok(self.storage.into_bound(py)) + } + + #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))] + fn into_pyobject(self, py: Python<'py>) -> Result { + Ok(PyString::new(py, &self).into_any()) + } +} + +impl<'py> IntoPyObject<'py> for &PyBackedStr { + type Target = PyAny; + type Output = Bound<'py, Self::Target>; + type Error = Infallible; + + #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] + fn into_pyobject(self, py: Python<'py>) -> Result { + Ok(self.storage.bind(py).to_owned()) + } + + #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))] + fn into_pyobject(self, py: Python<'py>) -> Result { + Ok(PyString::new(py, self).into_any()) + } +} + /// A wrapper around `[u8]` where the storage is either owned by a Python `bytes` object, or a Rust `Box<[u8]>`. /// /// This type gives access to the underlying data via a `Deref` implementation. @@ -203,6 +239,7 @@ impl FromPyObject<'_> for PyBackedBytes { } } +#[allow(deprecated)] impl ToPyObject for PyBackedBytes { fn to_object(&self, py: Python<'_>) -> Py { match &self.storage { @@ -221,6 +258,32 @@ impl IntoPy> for PyBackedBytes { } } +impl<'py> IntoPyObject<'py> for PyBackedBytes { + type Target = PyBytes; + type Output = Bound<'py, Self::Target>; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + match self.storage { + PyBackedBytesStorage::Python(bytes) => Ok(bytes.into_bound(py)), + PyBackedBytesStorage::Rust(bytes) => Ok(PyBytes::new(py, &bytes)), + } + } +} + +impl<'py> IntoPyObject<'py> for &PyBackedBytes { + type Target = PyBytes; + type Output = Bound<'py, Self::Target>; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + match &self.storage { + PyBackedBytesStorage::Python(bytes) => Ok(bytes.bind(py).clone()), + PyBackedBytesStorage::Rust(bytes) => Ok(PyBytes::new(py, bytes)), + } + } +} + macro_rules! impl_traits { ($slf:ty, $equiv:ty) => { impl std::fmt::Debug for $slf { @@ -297,6 +360,7 @@ use impl_traits; #[cfg(test)] mod test { use super::*; + use crate::prelude::IntoPyObject; use crate::Python; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; @@ -329,12 +393,12 @@ mod test { } #[test] - fn py_backed_str_to_object() { + fn py_backed_str_into_pyobject() { Python::with_gil(|py| { let orig_str = PyString::new(py, "hello"); let py_backed_str = orig_str.extract::().unwrap(); - let new_str = py_backed_str.to_object(py); - assert_eq!(new_str.extract::(py).unwrap(), "hello"); + let new_str = py_backed_str.into_pyobject(py).unwrap(); + assert_eq!(new_str.extract::().unwrap(), "hello"); #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] assert!(new_str.is(&orig_str)); }); @@ -389,17 +453,20 @@ mod test { } #[test] - fn py_backed_bytes_into_py() { + fn py_backed_bytes_into_pyobject() { Python::with_gil(|py| { let orig_bytes = PyBytes::new(py, b"abcde"); let py_backed_bytes = PyBackedBytes::from(orig_bytes.clone()); - assert!(py_backed_bytes.to_object(py).is(&orig_bytes)); + assert!((&py_backed_bytes) + .into_pyobject(py) + .unwrap() + .is(&orig_bytes)); assert!(py_backed_bytes.into_py(py).is(&orig_bytes)); }); } #[test] - fn rust_backed_bytes_into_py() { + fn rust_backed_bytes_into_pyobject() { Python::with_gil(|py| { let orig_bytes = PyByteArray::new(py, b"abcde"); let rust_backed_bytes = PyBackedBytes::from(orig_bytes); @@ -407,7 +474,7 @@ mod test { rust_backed_bytes.storage, PyBackedBytesStorage::Rust(_) )); - let to_object = rust_backed_bytes.to_object(py).into_bound(py); + let to_object = (&rust_backed_bytes).into_pyobject(py).unwrap(); assert!(&to_object.is_exact_instance_of::()); assert_eq!(&to_object.extract::().unwrap(), b"abcde"); let into_py = rust_backed_bytes.into_py(py).into_bound(py); diff --git a/src/types/slice.rs b/src/types/slice.rs index b9fad06ebdb..528e7893476 100644 --- a/src/types/slice.rs +++ b/src/types/slice.rs @@ -1,8 +1,10 @@ use crate::err::{PyErr, PyResult}; use crate::ffi; use crate::ffi_ptr_ext::FfiPtrExt; +use crate::prelude::IntoPyObject; use crate::types::any::PyAnyMethods; use crate::{Bound, PyAny, PyObject, Python, ToPyObject}; +use std::convert::Infallible; /// Represents a Python `slice`. /// @@ -139,6 +141,26 @@ impl ToPyObject for PySliceIndices { } } +impl<'py> IntoPyObject<'py> for PySliceIndices { + type Target = PySlice; + type Output = Bound<'py, Self::Target>; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + Ok(PySlice::new(py, self.start, self.stop, self.step)) + } +} + +impl<'py> IntoPyObject<'py> for &PySliceIndices { + type Target = PySlice; + type Output = Bound<'py, Self::Target>; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + Ok(PySlice::new(py, self.start, self.stop, self.step)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/tests/ui/invalid_property_args.stderr b/tests/ui/invalid_property_args.stderr index 786533efd53..03f3ba963d8 100644 --- a/tests/ui/invalid_property_args.stderr +++ b/tests/ui/invalid_property_args.stderr @@ -55,14 +55,14 @@ error[E0277]: `PhantomData` cannot be converted to a Python object = help: the trait `IntoPyObject<'_>` is not implemented for `PhantomData`, which is required by `for<'py> PhantomData: PyO3GetField<'py>` = note: implement `IntoPyObject` for `&PhantomData` or `IntoPyObject + Clone` for `PhantomData` to define the conversion = help: the following other types implement trait `IntoPyObject<'py>`: + &&OsStr + &&Path &&str &'a (T0, T1) &'a (T0, T1, T2) &'a (T0, T1, T2, T3) &'a (T0, T1, T2, T3, T4) &'a (T0, T1, T2, T3, T4, T5) - &'a (T0, T1, T2, T3, T4, T5, T6) - &'a (T0, T1, T2, T3, T4, T5, T6, T7) and $N others = note: required for `PhantomData` to implement `for<'py> PyO3GetField<'py>` note: required by a bound in `PyClassGetterGenerator::::generate` diff --git a/tests/ui/missing_intopy.stderr b/tests/ui/missing_intopy.stderr index 653fb785dfd..587ebd479de 100644 --- a/tests/ui/missing_intopy.stderr +++ b/tests/ui/missing_intopy.stderr @@ -8,14 +8,14 @@ error[E0277]: `Blah` cannot be converted to a Python object = note: if you do not wish to have a corresponding Python type, implement it manually = note: if you do not own `Blah` you can perform a manual conversion to one of the types in `pyo3::types::*` = help: the following other types implement trait `IntoPyObject<'py>`: + &&OsStr + &&Path &&str &'a (T0, T1) &'a (T0, T1, T2) &'a (T0, T1, T2, T3) &'a (T0, T1, T2, T3, T4) &'a (T0, T1, T2, T3, T4, T5) - &'a (T0, T1, T2, T3, T4, T5, T6) - &'a (T0, T1, T2, T3, T4, T5, T6, T7) and $N others note: required by a bound in `UnknownReturnType::::wrap` --> src/impl_/wrap.rs