Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experiment: Fallible conversion trait #4060

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
8 changes: 7 additions & 1 deletion pyo3-macros-backend/src/method.rs
Expand Up @@ -673,7 +673,13 @@ impl<'a> FnSpec<'a> {
#pyo3_path::intern!(py, stringify!(#python_name)),
#qualname_prefix,
#throw_callback,
async move { #pyo3_path::impl_::wrap::OkWrap::wrap(future.await) },
async move {
let fut = future.await;
{
use #pyo3_path::impl_::wrap::{IntoPyKind, IntoPyObjectKind};
(&&&fut).conversion_kind().wrap(fut)
}
},
)
}};
if cancel_handle.is_some() {
Expand Down
38 changes: 38 additions & 0 deletions pyo3-macros-backend/src/pyclass.rs
Expand Up @@ -918,10 +918,39 @@ fn impl_complex_enum(
}
};

let enum_into_pyobject_impl = {
let match_arms = variants
.iter()
.map(|variant| {
let variant_ident = variant.get_ident();
let variant_cls = gen_complex_enum_variant_class_ident(cls, variant.get_ident());
quote! {
#cls::#variant_ident { .. } => {
let pyclass_init = #pyo3_path::PyClassInitializer::from(self).add_subclass(#variant_cls);
unsafe { #pyo3_path::Bound::new(py, pyclass_init).map(|b| #pyo3_path::types::PyAnyMethods::downcast_into_unchecked(b.into_any())) }
}
}
});

quote! {
impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls {
type Target = Self;
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> {
match self {
#(#match_arms)*
}
}
}
}
};

let pyclass_impls: TokenStream = [
impl_builder.impl_pyclass(ctx),
impl_builder.impl_extractext(ctx),
enum_into_py_impl,
enum_into_pyobject_impl,
impl_builder.impl_pyclassimpl(ctx)?,
impl_builder.impl_add_to_module(ctx),
impl_builder.impl_freelist(ctx),
Expand Down Expand Up @@ -1758,6 +1787,15 @@ impl<'a> PyClassImplsBuilder<'a> {
#pyo3_path::IntoPy::into_py(#pyo3_path::Py::new(py, self).unwrap(), py)
}
}

impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls {
type Target = Self;
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> {
#pyo3_path::Bound::new(py, self)
}
}
}
} else {
quote! {}
Expand Down
19 changes: 14 additions & 5 deletions pyo3-macros-backend/src/quotes.rs
Expand Up @@ -11,13 +11,22 @@ pub(crate) fn some_wrap(obj: TokenStream, ctx: &Ctx) -> TokenStream {

pub(crate) fn ok_wrap(obj: TokenStream, ctx: &Ctx) -> TokenStream {
let Ctx { pyo3_path } = ctx;
quote! {
#pyo3_path::impl_::wrap::OkWrap::wrap(#obj)
.map_err(::core::convert::Into::<#pyo3_path::PyErr>::into)
}
quote! {{
let obj = #obj;
{
use #pyo3_path::impl_::wrap::{IntoPyKind, IntoPyObjectKind};
(&&&obj).conversion_kind().wrap(obj).map_err(::core::convert::Into::<#pyo3_path::PyErr>::into)
}
}}
}

pub(crate) fn map_result_into_ptr(result: TokenStream, ctx: &Ctx) -> TokenStream {
let Ctx { pyo3_path } = ctx;
quote! { #pyo3_path::impl_::wrap::map_result_into_ptr(py, #result) }
quote! {{
let result = #result;
{
use #pyo3_path::impl_::wrap::{IntoPyKind, IntoPyObjectKind, IntoPyNoneKind};
(&&&result).conversion_kind().map_into_ptr(py, result)
}
}}
}
69 changes: 69 additions & 0 deletions src/conversion.rs
Expand Up @@ -6,6 +6,7 @@ use crate::pyclass::boolean_struct::False;
use crate::types::any::PyAnyMethods;
use crate::types::PyTuple;
use crate::{ffi, Borrowed, Bound, Py, PyAny, PyClass, PyObject, PyRef, PyRefMut, Python};
use std::convert::Infallible;
#[cfg(feature = "gil-refs")]
use {
crate::{
Expand Down Expand Up @@ -170,6 +171,65 @@ pub trait IntoPy<T>: Sized {
}
}

/// 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 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>;
}

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

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

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

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

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

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

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

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

impl<'py, T> IntoPyObject<'py> for &Py<T> {
type Target = T;
type Error = Infallible;

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

/// Extract a type from a Python object.
///
///
Expand Down Expand Up @@ -509,6 +569,15 @@ impl IntoPy<Py<PyTuple>> for () {
}
}

impl<'py> IntoPyObject<'py> for () {
type Target = PyTuple;
type Error = Infallible;

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

/// Raw level conversion between `*mut ffi::PyObject` and PyO3 types.
///
/// # Safety
Expand Down