Skip to content

Commit

Permalink
add Output type allowing optimizing impls which borrow from the input
Browse files Browse the repository at this point in the history
  • Loading branch information
Icxolu committed Jul 9, 2024
1 parent 744cc5b commit 45968b9
Show file tree
Hide file tree
Showing 27 changed files with 266 additions and 114 deletions.
6 changes: 4 additions & 2 deletions pyo3-macros-backend/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -957,9 +957,10 @@ fn impl_complex_enum(
quote! {
impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls {
type Target = Self;
type Output = #pyo3_path::Bound<'py, Self::Target>;
type Error = #pyo3_path::PyErr;

fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result<#pyo3_path::Bound<'py, Self::Target>, Self::Error> {
fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result<Self::Output, Self::Error> {
match self {
#(#match_arms)*
}
Expand Down Expand Up @@ -2027,9 +2028,10 @@ impl<'a> PyClassImplsBuilder<'a> {

impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls {
type Target = Self;
type Output = #pyo3_path::Bound<'py, Self::Target>;
type Error = #pyo3_path::PyErr;

fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result<#pyo3_path::Bound<'py, Self::Target>, Self::Error> {
fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result<Self::Output, Self::Error> {
#pyo3_path::Bound::new(py, self)
}
}
Expand Down
117 changes: 104 additions & 13 deletions src/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,62 +179,152 @@ pub trait IntoPy<T>: Sized {
}
}

/// Owned or borrowed gil-bound Python smart pointer
pub trait AnyBound<'py, T> {
/// Borrow this smart pointer.
fn as_borrowed(&self) -> Borrowed<'_, 'py, T>;
/// Turns this smart pointer into an owned [`Bound<'py, T>`]
fn into_bound(self) -> Bound<'py, T>;
/// Upcast the target type of this smart pointer
fn into_any(self) -> impl AnyBound<'py, PyAny>;
/// Turn this smart pointer into a strong reference pointer
fn into_ptr(self) -> *mut ffi::PyObject;
/// Turn this smart pointer into an owned [`Py<T>`]
fn unbind(self) -> Py<T>;
}

impl<'py, T> AnyBound<'py, T> for Bound<'py, T> {
fn as_borrowed(&self) -> Borrowed<'_, 'py, T> {
Bound::as_borrowed(self)
}

fn into_bound(self) -> Bound<'py, T> {
self
}

fn into_any(self) -> impl AnyBound<'py, PyAny> {
self.into_any()
}

fn into_ptr(self) -> *mut ffi::PyObject {
self.into_ptr()
}

fn unbind(self) -> Py<T> {
self.unbind()
}
}

impl<'py, T> AnyBound<'py, T> for &Bound<'py, T> {
fn as_borrowed(&self) -> Borrowed<'_, 'py, T> {
Bound::as_borrowed(self)
}

fn into_bound(self) -> Bound<'py, T> {
self.clone()
}

fn into_any(self) -> impl AnyBound<'py, PyAny> {
self.as_any()
}

fn into_ptr(self) -> *mut ffi::PyObject {
self.clone().into_ptr()
}

fn unbind(self) -> Py<T> {
self.clone().unbind()
}
}

impl<'py, T> AnyBound<'py, T> for Borrowed<'_, 'py, T> {
fn as_borrowed(&self) -> Borrowed<'_, 'py, T> {
*self
}

fn into_bound(self) -> Bound<'py, T> {
(*self).to_owned()
}

fn into_any(self) -> impl AnyBound<'py, PyAny> {
self.to_any()
}

fn into_ptr(self) -> *mut ffi::PyObject {
(*self).to_owned().into_ptr()
}

fn unbind(self) -> Py<T> {
(*self).to_owned().unbind()
}
}

/// Defines a conversion from a Rust type to a Python object, which may fail.
///
/// It functions similarly to std's [`TryInto`] trait, but requires a [GIL token](Python)
/// as an argument.
pub trait IntoPyObject<'py>: Sized {
/// The Python output type
type Target;
/// The smart pointer type to use.
///
/// This will usually be [`Bound<'py, Target>`], but can special cases `&'a Bound<'py, Target>`
/// or [`Borrowed<'a, 'py, Target>`] can be used to minimize reference counting overhead.
type Output: AnyBound<'py, Self::Target>;
/// The type returned in the event of a conversion error.
type Error;

/// Performs the conversion.
fn into_pyobject(self, py: Python<'py>) -> Result<Bound<'py, Self::Target>, Self::Error>;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error>;
}

impl<'py, T> IntoPyObject<'py> for Bound<'py, T> {
type Target = T;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;

fn into_pyobject(self, _py: Python<'py>) -> Result<Bound<'py, Self::Target>, Self::Error> {
fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self)
}
}

impl<'py, T> IntoPyObject<'py> for &Bound<'py, T> {
impl<'a, 'py, T> IntoPyObject<'py> for &'a Bound<'py, T> {
type Target = T;
type Output = &'a Bound<'py, Self::Target>;
type Error = Infallible;

fn into_pyobject(self, _py: Python<'py>) -> Result<Bound<'py, Self::Target>, Self::Error> {
Ok(self.clone())
fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self)
}
}

impl<'py, T> IntoPyObject<'py> for Borrowed<'_, 'py, T> {
impl<'a, 'py, T> IntoPyObject<'py> for Borrowed<'a, 'py, T> {
type Target = T;
type Output = Borrowed<'a, 'py, Self::Target>;
type Error = Infallible;

fn into_pyobject(self, _py: Python<'py>) -> Result<Bound<'py, Self::Target>, Self::Error> {
Ok(self.to_owned())
fn into_pyobject(self, _py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self)
}
}

impl<'py, T> IntoPyObject<'py> for Py<T> {
type Target = T;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;

fn into_pyobject(self, py: Python<'py>) -> Result<Bound<'py, Self::Target>, Self::Error> {
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.into_bound(py))
}
}

impl<'py, T> IntoPyObject<'py> for &Py<T> {
impl<'a, 'py, T> IntoPyObject<'py> for &'a Py<T> {
type Target = T;
type Output = Borrowed<'a, 'py, Self::Target>;
type Error = Infallible;

fn into_pyobject(self, py: Python<'py>) -> Result<Bound<'py, Self::Target>, Self::Error> {
Ok(self.bind(py).clone())
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.bind_borrowed(py))
}
}

Expand Down Expand Up @@ -579,9 +669,10 @@ impl IntoPy<Py<PyTuple>> for () {

impl<'py> IntoPyObject<'py> for () {
type Target = PyTuple;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;

fn into_pyobject(self, py: Python<'py>) -> Result<Bound<'py, Self::Target>, Self::Error> {
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(PyTuple::empty_bound(py))
}
}
Expand Down
21 changes: 14 additions & 7 deletions src/conversions/chrono.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,10 @@ impl<'py> IntoPyObject<'py> for Duration {
type Target = PyAny;
#[cfg(not(Py_LIMITED_API))]
type Target = PyDelta;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;

fn into_pyobject(self, py: Python<'py>) -> Result<Bound<'py, Self::Target>, Self::Error> {
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
// Total number of days
let days = self.num_days();
// Remainder of seconds
Expand Down Expand Up @@ -213,9 +214,10 @@ impl<'py> IntoPyObject<'py> for NaiveDate {
type Target = PyAny;
#[cfg(not(Py_LIMITED_API))]
type Target = PyDate;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;

fn into_pyobject(self, py: Python<'py>) -> Result<Bound<'py, Self::Target>, Self::Error> {
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let DateArgs { year, month, day } = (&self).into();
#[cfg(not(Py_LIMITED_API))]
{
Expand Down Expand Up @@ -280,9 +282,10 @@ impl<'py> IntoPyObject<'py> for NaiveTime {
type Target = PyAny;
#[cfg(not(Py_LIMITED_API))]
type Target = PyTime;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;

fn into_pyobject(self, py: Python<'py>) -> Result<Bound<'py, Self::Target>, Self::Error> {
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let TimeArgs {
hour,
min,
Expand Down Expand Up @@ -338,9 +341,10 @@ impl<'py> IntoPyObject<'py> for NaiveDateTime {
type Target = PyAny;
#[cfg(not(Py_LIMITED_API))]
type Target = PyDateTime;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;

fn into_pyobject(self, py: Python<'py>) -> Result<Bound<'py, Self::Target>, Self::Error> {
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let DateArgs { year, month, day } = (&self.date()).into();
let TimeArgs {
hour,
Expand Down Expand Up @@ -412,9 +416,10 @@ impl<'py, Tz: TimeZone> IntoPyObject<'py> for DateTime<Tz> {
type Target = PyAny;
#[cfg(not(Py_LIMITED_API))]
type Target = PyDateTime;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;

fn into_pyobject(self, py: Python<'py>) -> Result<Bound<'py, Self::Target>, Self::Error> {
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let tz = self.offset().fix().into_pyobject(py)?;
let DateArgs { year, month, day } = (&self.naive_local().date()).into();
let TimeArgs {
Expand Down Expand Up @@ -507,9 +512,10 @@ impl<'py> IntoPyObject<'py> for FixedOffset {
type Target = PyAny;
#[cfg(not(Py_LIMITED_API))]
type Target = PyTzInfo;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;

fn into_pyobject(self, py: Python<'py>) -> Result<Bound<'py, Self::Target>, Self::Error> {
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let seconds_offset = self.local_minus_utc();
#[cfg(not(Py_LIMITED_API))]
{
Expand Down Expand Up @@ -573,9 +579,10 @@ impl<'py> IntoPyObject<'py> for Utc {
type Target = PyAny;
#[cfg(not(Py_LIMITED_API))]
type Target = PyTzInfo;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;

fn into_pyobject(self, py: Python<'py>) -> Result<Bound<'py, Self::Target>, Self::Error> {
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
#[cfg(Py_LIMITED_API)]
{
Ok(timezone_utc_bound(py).into_any())
Expand Down
3 changes: 2 additions & 1 deletion src/conversions/chrono_tz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,10 @@ impl IntoPy<PyObject> for Tz {

impl<'py> IntoPyObject<'py> for Tz {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;

fn into_pyobject(self, py: Python<'py>) -> Result<Bound<'py, PyAny>, Self::Error> {
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
static ZONE_INFO: GILOnceCell<Py<PyType>> = GILOnceCell::new();
ZONE_INFO
.get_or_try_init_type_ref(py, "zoneinfo", "ZoneInfo")
Expand Down
11 changes: 7 additions & 4 deletions src/conversions/either.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
//!
//! [either](https://docs.rs/either/ "A library for easy idiomatic error handling and reporting in Rust applications")’s
use crate::conversion::AnyBound;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::types::TypeInfo;
use crate::{
Expand All @@ -67,15 +68,17 @@ where
}

#[cfg_attr(docsrs, doc(cfg(feature = "either")))]
impl<'py, L, R, T, E> IntoPyObject<'py> for Either<L, R>
impl<'py, L, R, T, E, O> IntoPyObject<'py> for Either<L, R>
where
L: IntoPyObject<'py, Target = T, Error = E>,
R: IntoPyObject<'py, Target = T, Error = E>,
L: IntoPyObject<'py, Target = T, Error = E, Output = O>,
R: IntoPyObject<'py, Target = T, Error = E, Output = O>,
O: AnyBound<'py, T>,
{
type Target = T;
type Output = O;
type Error = E;

fn into_pyobject(self, py: Python<'py>) -> Result<Bound<'py, Self::Target>, Self::Error> {
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
match self {
Either::Left(l) => l.into_pyobject(py),
Either::Right(r) => r.into_pyobject(py),
Expand Down
17 changes: 11 additions & 6 deletions src/conversions/hashbrown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
//! Note that you must use compatible versions of hashbrown and PyO3.
//! The required hashbrown version may vary based on the version of PyO3.
use crate::{
conversion::IntoPyObject,
conversion::{AnyBound, IntoPyObject},
types::{
any::PyAnyMethods,
dict::PyDictMethods,
Expand Down Expand Up @@ -62,12 +62,16 @@ where
PyErr: From<K::Error> + From<V::Error>,
{
type Target = PyDict;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;

fn into_pyobject(self, py: Python<'py>) -> Result<Bound<'py, Self::Target>, Self::Error> {
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let dict = PyDict::new_bound(py);
for (k, v) in self {
dict.set_item(k.into_pyobject(py)?, v.into_pyobject(py)?)?;
dict.set_item(
k.into_pyobject(py)?.into_bound(),
v.into_pyobject(py)?.into_bound(),
)?;
}
Ok(dict)
}
Expand Down Expand Up @@ -119,15 +123,16 @@ where
PyErr: From<K::Error>,
{
type Target = PySet;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;

fn into_pyobject(self, py: Python<'py>) -> Result<Bound<'py, Self::Target>, Self::Error> {
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
try_new_from_iter(
py,
self.into_iter().map(|item| {
item.into_pyobject(py)
.map(Bound::into_any)
.map(Bound::unbind)
.map(AnyBound::into_any)
.map(AnyBound::unbind)
.map_err(Into::into)
}),
)
Expand Down
Loading

0 comments on commit 45968b9

Please sign in to comment.