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

[WIP] FromPyObject for #[pyclass] with T: Clone #730

Merged
merged 1 commit into from
Jan 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Added

* `FromPyObject` is now automatically implemented for `T: Clone` pyclasses. [#730](https://github.com/PyO3/pyo3/pull/730)
* Implemented `IntoIterator` for `PySet` and `PyFrozenSet`. [#716](https://github.com/PyO3/pyo3/pull/716)
* `PyClass`, `PyClassShell`, `PyObjectLayout`, `PyClassInitializer` [#683](https://github.com/PyO3/pyo3/pull/683)

Expand Down
12 changes: 12 additions & 0 deletions pyo3-derive-backend/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,18 @@ fn impl_class(
#weakref
}

impl pyo3::conversion::FromPyObjectImpl for #cls {
type Impl = pyo3::conversion::extract_impl::Cloned;
}

impl pyo3::conversion::FromPyObjectImpl for &'_ #cls {
type Impl = pyo3::conversion::extract_impl::Reference;
}

impl pyo3::conversion::FromPyObjectImpl for &'_ mut #cls {
type Impl = pyo3::conversion::extract_impl::MutReference;
}

#into_pyobject

#inventory_impl
Expand Down
81 changes: 68 additions & 13 deletions src/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,25 +244,80 @@ where
}
}

/// Extract reference to instance from `PyObject`
impl<'a, T> FromPyObject<'a> for &'a T
where
T: PyTryFrom<'a>,
{
#[inline]
fn extract(ob: &'a PyAny) -> PyResult<&'a T> {
Ok(T::try_from(ob)?)
#[doc(hidden)]
pub mod extract_impl {
davidhewitt marked this conversation as resolved.
Show resolved Hide resolved
use super::*;

pub trait ExtractImpl<'a, Target>: Sized {
fn extract(source: &'a PyAny) -> PyResult<Target>;
}

pub struct Cloned;
pub struct Reference;
pub struct MutReference;
davidhewitt marked this conversation as resolved.
Show resolved Hide resolved

impl<'a, T: 'a> ExtractImpl<'a, T> for Cloned
where
T: Clone,
Reference: ExtractImpl<'a, &'a T>,
{
fn extract(source: &'a PyAny) -> PyResult<T> {
Ok(Reference::extract(source)?.clone())
}
}

impl<'a, T> ExtractImpl<'a, &'a T> for Reference
where
T: PyTryFrom<'a>,
{
fn extract(source: &'a PyAny) -> PyResult<&'a T> {
Ok(T::try_from(source)?)
}
}

impl<'a, T> ExtractImpl<'a, &'a mut T> for MutReference
where
T: PyTryFrom<'a>,
{
fn extract(source: &'a PyAny) -> PyResult<&'a mut T> {
Ok(T::try_from_mut(source)?)
}
}
}

/// Extract mutable reference to instance from `PyObject`
impl<'a, T> FromPyObject<'a> for &'a mut T
use extract_impl::ExtractImpl;

/// This is an internal trait for re-using `FromPyObject` implementations for many pyo3 types.
///
/// Users should implement `FromPyObject` directly instead of via this trait.
pub trait FromPyObjectImpl {
// Implement this trait with to specify the implementor of `extract_impl::ExtractImpl` to use for
// extracting this type from Python objects.
//
// Example valid implementations are `extract_impl::Cloned`, `extract_impl::Reference`, and
// `extract_impl::MutReference`, which are for extracting `T`, `&T` and `&mut T` respectively via
// PyTryFrom.
//
// We deliberately don't require Impl: ExtractImpl here because we allow #[pyclass]
// to specify an Impl which doesn't satisfy the ExtractImpl constraints.
//
// e.g. non-clone #[pyclass] can still have Impl: Cloned.
//
// We catch invalid Impls in the blanket impl for FromPyObject, which only
// complains when .extract() is actually used.

/// The type which implements `ExtractImpl`.
type Impl;
}

impl<'a, T> FromPyObject<'a> for T
where
T: PyTryFrom<'a>,
T: FromPyObjectImpl,
<T as FromPyObjectImpl>::Impl: ExtractImpl<'a, Self>,
{
#[inline]
fn extract(ob: &'a PyAny) -> PyResult<&'a mut T> {
Ok(T::try_from_mut(ob)?)
fn extract(ob: &'a PyAny) -> PyResult<T> {
<T as FromPyObjectImpl>::Impl::extract(ob)
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ pub mod buffer;
#[doc(hidden)]
pub mod callback;
pub mod class;
mod conversion;
pub mod conversion;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only change which you might want to check. I think this is acceptable?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

#[doc(hidden)]
pub mod derive_utils;
mod err;
Expand Down
4 changes: 4 additions & 0 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ macro_rules! pyobject_native_type_convert(
}
}

impl<$($type_param,)*> $crate::conversion::FromPyObjectImpl for &'_ $name {
type Impl = $crate::conversion::extract_impl::Reference;
}

impl<$($type_param,)*> ::std::fmt::Debug for $name {
fn fmt(&self, f: &mut ::std::fmt::Formatter)
-> Result<(), ::std::fmt::Error>
Expand Down
25 changes: 25 additions & 0 deletions tests/test_class_conversion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use pyo3::prelude::*;

#[pyclass]
#[derive(Clone, Debug, PartialEq)]
struct Cloneable {
x: i32,
}

#[test]
fn test_cloneable_pyclass() {
let c = Cloneable { x: 10 };

let gil = Python::acquire_gil();
let py = gil.python();

let py_c = Py::new(py, c.clone()).unwrap().to_object(py);

let c2: Cloneable = py_c.extract(py).unwrap();
let rc: &Cloneable = py_c.extract(py).unwrap();
let mrc: &mut Cloneable = py_c.extract(py).unwrap();

assert_eq!(c, c2);
assert_eq!(&c, rc);
assert_eq!(&c, mrc);
}
3 changes: 2 additions & 1 deletion tests/test_compile_error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#[test]
fn test_compile_errors() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/invalid_pymethod_names.rs");
t.compile_fail("tests/ui/missing_clone.rs");
t.compile_fail("tests/ui/reject_generics.rs");
t.compile_fail("tests/ui/too_many_args_to_getter.rs");
t.compile_fail("tests/ui/invalid_pymethod_names.rs");
}
16 changes: 16 additions & 0 deletions tests/ui/missing_clone.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use pyo3::prelude::*;

#[pyclass]
struct TestClass {
num: u32,
}

fn main() {
let t = TestClass { num: 10 };

let gil = Python::acquire_gil();
let py = gil.python();

let pyvalue = Py::new(py, t).unwrap().to_object(py);
let t: TestClass = pyvalue.extract(py).unwrap();
}
8 changes: 8 additions & 0 deletions tests/ui/missing_clone.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
error[E0277]: the trait bound `TestClass: std::clone::Clone` is not satisfied
--> $DIR/missing_clone.rs:15:32
|
15 | let t: TestClass = pyvalue.extract(py).unwrap();
| ^^^^^^^ the trait `std::clone::Clone` is not implemented for `TestClass`
|
= note: required because of the requirements on the impl of `pyo3::conversion::extract_impl::ExtractImpl<'_, TestClass>` for `pyo3::conversion::extract_impl::Cloned`
= note: required because of the requirements on the impl of `pyo3::conversion::FromPyObject<'_>` for `TestClass`