Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
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.
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure whether this is sufficient.

I didn't see another obvious way around unsendable other than to re-enable borrow tracking.

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, this is unfortunate and would be nice to resolve rather than forcing mutability silently. I think if we start with #[pyclass(immutable)] for now then we can simply start by raising a compile error that extends/unsendable and immutable are incompatible. We could then create issues to track and fix this properly later with some follow-up refactoring before we complete the change to #[pyclass(mutable)].

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