Skip to content

Commit

Permalink
Implement by default immutable pyclasses
Browse files Browse the repository at this point in the history
  • Loading branch information
mejrs committed Nov 10, 2021
1 parent 00c84eb commit 7f31000
Show file tree
Hide file tree
Showing 37 changed files with 358 additions and 201 deletions.
16 changes: 8 additions & 8 deletions guide/src/class.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ For users who are not very familiar with `RefCell`, here is a reminder of Rust's

```rust
# use pyo3::prelude::*;
#[pyclass]
#[pyclass(mutable)]
struct MyClass {
#[pyo3(get)]
num: i32,
Expand Down Expand Up @@ -299,7 +299,7 @@ use pyo3::types::PyDict;
use pyo3::AsPyPointer;
use std::collections::HashMap;

#[pyclass(extends=PyDict)]
#[pyclass(extends=PyDict, mutable)]
#[derive(Default)]
struct DictWithCounter {
counter: HashMap<String, usize>,
Expand Down Expand Up @@ -362,7 +362,7 @@ For simple cases where a member variable is just read and written with no side e

```rust
# use pyo3::prelude::*;
#[pyclass]
#[pyclass(mutable)]
struct MyClass {
#[pyo3(get, set)]
num: i32
Expand Down Expand Up @@ -410,7 +410,7 @@ can be used since Rust 2018).

```rust
# use pyo3::prelude::*;
# #[pyclass]
# #[pyclass(mutable)]
# struct MyClass {
# num: i32,
# }
Expand All @@ -436,7 +436,7 @@ If this parameter is specified, it is used as the property name, i.e.

```rust
# use pyo3::prelude::*;
# #[pyclass]
# #[pyclass(mutable)]
# struct MyClass {
# num: i32,
# }
Expand Down Expand Up @@ -473,7 +473,7 @@ between those accessible to Python (and Rust) and those accessible only to Rust.

```rust
# use pyo3::prelude::*;
# #[pyclass]
# #[pyclass(mutable)]
# struct MyClass {
# num: i32,
# }
Expand Down Expand Up @@ -641,7 +641,7 @@ Example:
# use pyo3::prelude::*;
use pyo3::types::{PyDict, PyTuple};
#
# #[pyclass]
# #[pyclass(mutable)]
# struct MyClass {
# num: i32,
# }
Expand Down Expand Up @@ -731,7 +731,7 @@ unsafe impl pyo3::PyTypeInfo for MyClass {
}
}

impl pyo3::pyclass::PyClass for MyClass {
unsafe impl pyo3::pyclass::PyClass for MyClass {
type Dict = pyo3::pyclass_slots::PyClassDummySlot;
type WeakRef = pyo3::pyclass_slots::PyClassDummySlot;
type BaseNativeType = PyAny;
Expand Down
8 changes: 4 additions & 4 deletions guide/src/class/protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ as argument and calls that object when called.
# use pyo3::prelude::*;
# use pyo3::types::{PyDict, PyTuple};
#
#[pyclass(name = "counter")]
#[pyclass(name = "counter", mutable)]
struct PyCounter {
count: u64,
wraps: Py<PyAny>,
Expand Down Expand Up @@ -453,7 +453,7 @@ use pyo3::prelude::*;
use pyo3::PyTraverseError;
use pyo3::gc::{PyGCProtocol, PyVisit};

#[pyclass]
#[pyclass(mutable)]
struct ClassWithGCSupport {
obj: Option<PyObject>,
}
Expand Down Expand Up @@ -505,7 +505,7 @@ Example:
use pyo3::prelude::*;
use pyo3::PyIterProtocol;

#[pyclass]
#[pyclass(mutable)]
struct MyIterator {
iter: Box<dyn Iterator<Item = PyObject> + Send>,
}
Expand All @@ -530,7 +530,7 @@ implementations in `PyIterProtocol` will ensure that the objects behave correctl
# use pyo3::prelude::*;
# use pyo3::PyIterProtocol;

#[pyclass]
#[pyclass(mutable)]
struct Iter {
inner: std::vec::IntoIter<usize>,
}
Expand Down
4 changes: 2 additions & 2 deletions guide/src/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ Here is an example.
```rust
# use pyo3::prelude::*;

#[pyclass]
#[pyclass(mutable)]
struct Names {
names: Vec<String>
}
Expand Down Expand Up @@ -514,7 +514,7 @@ After:
```rust
# use pyo3::prelude::*;
# use pyo3::types::IntoPyDict;
# #[pyclass] #[derive(Clone)] struct MyClass {}
# #[pyclass(mutable)] #[derive(Clone)] struct MyClass {}
# #[pymethods] impl MyClass { #[new]fn new() -> Self { MyClass {} }}
# Python::with_gil(|py| {
# let typeobj = py.get_type::<MyClass>();
Expand Down
10 changes: 5 additions & 5 deletions guide/src/trait_bounds.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ Let's add the PyO3 annotations and add a constructor:
# use pyo3::prelude::*;
# use pyo3::types::PyAny;

#[pyclass]
#[pyclass(mutable)]
struct UserModel {
model: Py<PyAny>,
}
Expand Down Expand Up @@ -173,7 +173,7 @@ This wrapper will also perform the type conversions between Python and Rust.
# fn get_results(&self) -> Vec<f64>;
# }
#
# #[pyclass]
# #[pyclass(mutable)]
# struct UserModel {
# model: Py<PyAny>,
# }
Expand Down Expand Up @@ -342,7 +342,7 @@ We used in our `get_results` method the following call that performs the type co
# fn get_results(&self) -> Vec<f64>;
# }
#
# #[pyclass]
# #[pyclass(mutable)]
# struct UserModel {
# model: Py<PyAny>,
# }
Expand Down Expand Up @@ -395,7 +395,7 @@ Let's break it down in order to perform better error handling:
# fn get_results(&self) -> Vec<f64>;
# }
#
# #[pyclass]
# #[pyclass(mutable)]
# struct UserModel {
# model: Py<PyAny>,
# }
Expand Down Expand Up @@ -481,7 +481,7 @@ pub fn solve_wrapper(model: &mut UserModel) {
solve(model);
}

#[pyclass]
#[pyclass(mutable)]
pub struct UserModel {
model: Py<PyAny>,
}
Expand Down
6 changes: 3 additions & 3 deletions guide/src/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ For a `&PyAny` object reference `any` where the underlying object is a `#[pyclas
```rust
# use pyo3::prelude::*;
# use pyo3::{Py, Python, PyAny, PyResult};
# #[pyclass] #[derive(Clone)] struct MyClass { }
# #[pyclass(mutable)] #[derive(Clone)] struct MyClass { }
# Python::with_gil(|py| -> PyResult<()> {
let obj: &PyAny = Py::new(py, MyClass { })?.into_ref(py);

Expand Down Expand Up @@ -191,7 +191,7 @@ For a `#[pyclass] struct MyClass`, the conversions for `Py<MyClass>` are below:
```rust
# use pyo3::prelude::*;
# Python::with_gil(|py| {
# #[pyclass] struct MyClass { }
# #[pyclass(mutable)] struct MyClass { }
# Python::with_gil(|py| -> PyResult<()> {
let my_class: Py<MyClass> = Py::new(py, MyClass { })?;

Expand Down Expand Up @@ -236,7 +236,7 @@ so it also exposes all of the methods on `PyAny`.

```rust
# use pyo3::prelude::*;
# #[pyclass] struct MyClass { }
# #[pyclass(mutable)] struct MyClass { }
# Python::with_gil(|py| -> PyResult<()> {
let cell: &PyCell<MyClass> = PyCell::new(py, MyClass { })?;

Expand Down
62 changes: 53 additions & 9 deletions pyo3-macros-backend/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub struct PyClassArgs {
pub is_basetype: bool,
pub has_extends: bool,
pub has_unsendable: bool,
pub is_mutable: bool,
pub module: Option<syn::LitStr>,
}

Expand Down Expand Up @@ -54,6 +55,7 @@ impl Default for PyClassArgs {
is_basetype: false,
has_extends: false,
has_unsendable: false,
is_mutable: false,
}
}
}
Expand Down Expand Up @@ -158,6 +160,9 @@ impl PyClassArgs {
"unsendable" => {
self.has_unsendable = true;
}
"mutable" => {
self.is_mutable = true;
}
_ => bail_spanned!(
exp.path.span() => "expected one of gc/weakref/subclass/dict/unsendable"
),
Expand Down Expand Up @@ -515,6 +520,52 @@ fn impl_class(
let is_basetype = attr.is_basetype;
let is_subclass = attr.has_extends;

// If the pyclass has extends/unsendable, we must opt back into PyCell checking
// so that the inner Rust object is not inappropriately shared between threads.
let impl_pyclass = if attr.has_unsendable || attr.has_extends || attr.is_mutable {
quote! {
unsafe impl ::pyo3::pyclass::MutablePyClass for #cls {}

unsafe impl ::pyo3::PyClass for #cls {
type Dict = #dict;
type WeakRef = #weakref;
type BaseNativeType = #base_nativetype;

#[inline]
fn try_borrow_as_pyref(slf: &::pyo3::PyCell<Self>) -> ::std::result::Result<::pyo3::pycell::PyRef<'_, Self>, ::pyo3::pycell::PyBorrowError> {
unsafe { ::pyo3::PyCell::immutable_pyclass_try_borrow(slf) }
}

#[inline]
fn borrow_as_pyref(slf: &::pyo3::PyCell<Self>) -> ::pyo3::pycell::PyRef<'_, Self> {
unsafe { ::pyo3::PyCell::immutable_pyclass_borrow(slf) }
}

#[inline]
unsafe fn try_borrow_unguarded(slf: &::pyo3::PyCell<Self>) -> ::std::result::Result<&Self, ::pyo3::pycell::PyBorrowError> {
::pyo3::PyCell::immutable_pyclass_try_borrow_unguarded(slf)
}

#[inline]
unsafe fn drop_pyref(pyref: &mut ::pyo3::pycell::PyRef<Self>) {
::pyo3::pycell::PyRef::decrement_flag(pyref)
}
}

impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a mut #cls {
type Target = ::pyo3::PyRefMut<'a, #cls>;
}
}
} else {
quote! {
unsafe impl ::pyo3::PyClass for #cls {
type Dict = #dict;
type WeakRef = #weakref;
type BaseNativeType = #base_nativetype;
}
}
};

Ok(quote! {
unsafe impl ::pyo3::type_object::PyTypeInfo for #cls {
type AsRefTarget = ::pyo3::PyCell<Self>;
Expand All @@ -532,21 +583,14 @@ fn impl_class(
}
}

impl ::pyo3::PyClass for #cls {
type Dict = #dict;
type WeakRef = #weakref;
type BaseNativeType = #base_nativetype;
}
#impl_pyclass

impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a #cls
{
type Target = ::pyo3::PyRef<'a, #cls>;
}

impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a mut #cls
{
type Target = ::pyo3::PyRefMut<'a, #cls>;
}


#into_pyobject

Expand Down
5 changes: 3 additions & 2 deletions src/class/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
//! [typeobj docs](https://docs.python.org/3/c-api/typeobj.html)

use crate::callback::{HashCallbackOutput, IntoPyCallbackOutput};
use crate::pyclass::MutablePyClass;
use crate::{exceptions, ffi, FromPyObject, PyAny, PyCell, PyClass, PyObject};
use std::os::raw::c_int;

Expand Down Expand Up @@ -128,12 +129,12 @@ pub trait PyObjectGetAttrProtocol<'p>: PyObjectProtocol<'p> {
type Name: FromPyObject<'p>;
type Result: IntoPyCallbackOutput<PyObject>;
}
pub trait PyObjectSetAttrProtocol<'p>: PyObjectProtocol<'p> {
pub trait PyObjectSetAttrProtocol<'p>: PyObjectProtocol<'p> + MutablePyClass {
type Name: FromPyObject<'p>;
type Value: FromPyObject<'p>;
type Result: IntoPyCallbackOutput<()>;
}
pub trait PyObjectDelAttrProtocol<'p>: PyObjectProtocol<'p> {
pub trait PyObjectDelAttrProtocol<'p>: PyObjectProtocol<'p> + MutablePyClass {
type Name: FromPyObject<'p>;
type Result: IntoPyCallbackOutput<()>;
}
Expand Down
7 changes: 4 additions & 3 deletions src/class/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
//! For more information check [buffer protocol](https://docs.python.org/3/c-api/buffer.html)
//! c-api
use crate::callback::IntoPyCallbackOutput;
use crate::{ffi, PyCell, PyClass, PyRefMut};
use crate::pyclass::MutablePyClass;
use crate::{ffi, PyCell, PyRefMut};
use std::os::raw::c_int;

/// Buffer protocol interface
///
/// For more information check [buffer protocol](https://docs.python.org/3/c-api/buffer.html)
/// c-api.
#[allow(unused_variables)]
pub trait PyBufferProtocol<'p>: PyClass {
pub trait PyBufferProtocol<'p>: MutablePyClass {
// No default implementations so that implementors of this trait provide both methods.

fn bf_getbuffer(slf: PyRefMut<Self>, view: *mut ffi::Py_buffer, flags: c_int) -> Self::Result
Expand Down Expand Up @@ -51,7 +52,7 @@ where
#[doc(hidden)]
pub unsafe extern "C" fn releasebuffer<T>(slf: *mut ffi::PyObject, arg1: *mut ffi::Py_buffer)
where
T: for<'p> PyBufferReleaseBufferProtocol<'p>,
T: for<'p> PyBufferReleaseBufferProtocol<'p> + MutablePyClass,
{
crate::callback_body!(py, {
let slf = py.from_borrowed_ptr::<crate::PyCell<T>>(slf);
Expand Down
3 changes: 2 additions & 1 deletion src/class/gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

//! Python GC support

use crate::pyclass::MutablePyClass;
use crate::{ffi, AsPyPointer, PyCell, PyClass, Python};
use std::os::raw::{c_int, c_void};

Expand Down Expand Up @@ -53,7 +54,7 @@ where
#[doc(hidden)]
pub unsafe extern "C" fn clear<T>(slf: *mut ffi::PyObject) -> c_int
where
T: for<'p> PyGCClearProtocol<'p>,
T: for<'p> PyGCClearProtocol<'p> + MutablePyClass,
{
let pool = crate::GILPool::new();
let slf = pool.python().from_borrowed_ptr::<PyCell<T>>(slf);
Expand Down
2 changes: 1 addition & 1 deletion src/class/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::{ffi, IntoPy, IntoPyPointer, PyClass, PyObject, Python};
/// use pyo3::prelude::*;
/// use pyo3::PyIterProtocol;
///
/// #[pyclass]
/// #[pyclass(mutable)]
/// struct Iter {
/// count: usize,
/// }
Expand Down
Loading

0 comments on commit 7f31000

Please sign in to comment.