diff --git a/guide/src/class.md b/guide/src/class.md index 7c75c419..44ce57ea 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -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, @@ -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, @@ -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 @@ -410,7 +410,7 @@ can be used since Rust 2018). ```rust # use pyo3::prelude::*; -# #[pyclass] +# #[pyclass(mutable)] # struct MyClass { # num: i32, # } @@ -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, # } @@ -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, # } @@ -641,7 +641,7 @@ Example: # use pyo3::prelude::*; use pyo3::types::{PyDict, PyTuple}; # -# #[pyclass] +# #[pyclass(mutable)] # struct MyClass { # num: i32, # } @@ -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; diff --git a/guide/src/class/protocols.md b/guide/src/class/protocols.md index 251447be..84069ca0 100644 --- a/guide/src/class/protocols.md +++ b/guide/src/class/protocols.md @@ -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, @@ -453,7 +453,7 @@ use pyo3::prelude::*; use pyo3::PyTraverseError; use pyo3::gc::{PyGCProtocol, PyVisit}; -#[pyclass] +#[pyclass(mutable)] struct ClassWithGCSupport { obj: Option, } @@ -505,7 +505,7 @@ Example: use pyo3::prelude::*; use pyo3::PyIterProtocol; -#[pyclass] +#[pyclass(mutable)] struct MyIterator { iter: Box + Send>, } @@ -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, } diff --git a/guide/src/migration.md b/guide/src/migration.md index 56a714ac..724d5f61 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -436,7 +436,7 @@ Here is an example. ```rust # use pyo3::prelude::*; -#[pyclass] +#[pyclass(mutable)] struct Names { names: Vec } @@ -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::(); diff --git a/guide/src/trait_bounds.md b/guide/src/trait_bounds.md index a57a7580..7d118ab6 100644 --- a/guide/src/trait_bounds.md +++ b/guide/src/trait_bounds.md @@ -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, } @@ -173,7 +173,7 @@ This wrapper will also perform the type conversions between Python and Rust. # fn get_results(&self) -> Vec; # } # -# #[pyclass] +# #[pyclass(mutable)] # struct UserModel { # model: Py, # } @@ -342,7 +342,7 @@ We used in our `get_results` method the following call that performs the type co # fn get_results(&self) -> Vec; # } # -# #[pyclass] +# #[pyclass(mutable)] # struct UserModel { # model: Py, # } @@ -395,7 +395,7 @@ Let's break it down in order to perform better error handling: # fn get_results(&self) -> Vec; # } # -# #[pyclass] +# #[pyclass(mutable)] # struct UserModel { # model: Py, # } @@ -481,7 +481,7 @@ pub fn solve_wrapper(model: &mut UserModel) { solve(model); } -#[pyclass] +#[pyclass(mutable)] pub struct UserModel { model: Py, } diff --git a/guide/src/types.md b/guide/src/types.md index ed2fda6c..6052d029 100644 --- a/guide/src/types.md +++ b/guide/src/types.md @@ -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); @@ -191,7 +191,7 @@ For a `#[pyclass] struct MyClass`, the conversions for `Py` 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 = Py::new(py, MyClass { })?; @@ -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 = PyCell::new(py, MyClass { })?; diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index a75b2584..94d993c7 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -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, } @@ -54,6 +55,7 @@ impl Default for PyClassArgs { is_basetype: false, has_extends: false, has_unsendable: false, + is_mutable: false, } } } @@ -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" ), @@ -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) -> ::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) -> ::pyo3::pycell::PyRef<'_, Self> { + unsafe { ::pyo3::PyCell::immutable_pyclass_borrow(slf) } + } + + #[inline] + unsafe fn try_borrow_unguarded(slf: &::pyo3::PyCell) -> ::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) { + ::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; @@ -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 diff --git a/src/class/basic.rs b/src/class/basic.rs index 1e34d493..d3b7e100 100644 --- a/src/class/basic.rs +++ b/src/class/basic.rs @@ -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; @@ -128,12 +129,12 @@ pub trait PyObjectGetAttrProtocol<'p>: PyObjectProtocol<'p> { type Name: FromPyObject<'p>; type Result: IntoPyCallbackOutput; } -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<()>; } diff --git a/src/class/buffer.rs b/src/class/buffer.rs index 7b4c8acf..655653d7 100644 --- a/src/class/buffer.rs +++ b/src/class/buffer.rs @@ -5,7 +5,8 @@ //! 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 @@ -13,7 +14,7 @@ use std::os::raw::c_int; /// 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, view: *mut ffi::Py_buffer, flags: c_int) -> Self::Result @@ -51,7 +52,7 @@ where #[doc(hidden)] pub unsafe extern "C" fn releasebuffer(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::>(slf); diff --git a/src/class/gc.rs b/src/class/gc.rs index 3197c008..d5200bfe 100644 --- a/src/class/gc.rs +++ b/src/class/gc.rs @@ -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}; @@ -53,7 +54,7 @@ where #[doc(hidden)] pub unsafe extern "C" fn clear(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::>(slf); diff --git a/src/class/iter.rs b/src/class/iter.rs index 97b47fe3..3dd1db3e 100644 --- a/src/class/iter.rs +++ b/src/class/iter.rs @@ -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, /// } diff --git a/src/class/mapping.rs b/src/class/mapping.rs index 9961c5d1..3456eb26 100644 --- a/src/class/mapping.rs +++ b/src/class/mapping.rs @@ -4,8 +4,8 @@ //! Trait and support implementation for implementing mapping support use crate::callback::IntoPyCallbackOutput; +use crate::pyclass::MutablePyClass; use crate::{FromPyObject, PyClass, PyObject}; - /// Mapping interface #[allow(unused_variables)] pub trait PyMappingProtocol<'p>: PyClass { @@ -61,13 +61,13 @@ pub trait PyMappingGetItemProtocol<'p>: PyMappingProtocol<'p> { type Result: IntoPyCallbackOutput; } -pub trait PyMappingSetItemProtocol<'p>: PyMappingProtocol<'p> { +pub trait PyMappingSetItemProtocol<'p>: PyMappingProtocol<'p> + MutablePyClass { type Key: FromPyObject<'p>; type Value: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyMappingDelItemProtocol<'p>: PyMappingProtocol<'p> { +pub trait PyMappingDelItemProtocol<'p>: PyMappingProtocol<'p> + MutablePyClass { type Key: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } diff --git a/src/class/number.rs b/src/class/number.rs index 903744eb..17bf3969 100644 --- a/src/class/number.rs +++ b/src/class/number.rs @@ -4,6 +4,7 @@ //! Trait and support implementation for implementing number protocol use crate::callback::IntoPyCallbackOutput; use crate::err::PyErr; +use crate::pyclass::MutablePyClass; use crate::{ffi, FromPyObject, PyClass, PyObject}; /// Number interface @@ -481,74 +482,74 @@ pub trait PyNumberROrProtocol<'p>: PyNumberProtocol<'p> { type Result: IntoPyCallbackOutput; } -pub trait PyNumberIAddProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIAddProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberISubProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberISubProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIMulProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIMulProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIMatmulProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIMatmulProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberITruedivProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberITruedivProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIFloordivProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIFloordivProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIModProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIModProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIDivmodProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIDivmodProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIPowProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIPowProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } #[allow(clippy::upper_case_acronyms)] -pub trait PyNumberILShiftProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberILShiftProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } #[allow(clippy::upper_case_acronyms)] -pub trait PyNumberIRShiftProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIRShiftProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIAndProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIAndProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIXorProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIXorProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIOrProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIOrProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } @@ -750,7 +751,7 @@ pub unsafe extern "C" fn ipow( _modulo: *mut ffi::PyObject, ) -> *mut ffi::PyObject where - T: for<'p> PyNumberIPowProtocol<'p>, + T: for<'p> PyNumberIPowProtocol<'p> + MutablePyClass, { // NOTE: Somehow __ipow__ causes SIGSEGV in Python < 3.8 when we extract, // so we ignore it. It's the same as what CPython does. diff --git a/src/class/sequence.rs b/src/class/sequence.rs index 129a188a..a4e0ed91 100644 --- a/src/class/sequence.rs +++ b/src/class/sequence.rs @@ -6,6 +6,7 @@ use crate::callback::IntoPyCallbackOutput; use crate::conversion::{FromPyObject, IntoPy}; use crate::err::PyErr; +use crate::pyclass::MutablePyClass; use crate::{exceptions, ffi, PyAny, PyCell, PyClass, PyObject}; use std::os::raw::c_int; @@ -88,13 +89,13 @@ pub trait PySequenceGetItemProtocol<'p>: PySequenceProtocol<'p> { type Result: IntoPyCallbackOutput; } -pub trait PySequenceSetItemProtocol<'p>: PySequenceProtocol<'p> { +pub trait PySequenceSetItemProtocol<'p>: PySequenceProtocol<'p> + MutablePyClass { type Index: FromPyObject<'p> + From; type Value: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PySequenceDelItemProtocol<'p>: PySequenceProtocol<'p> { +pub trait PySequenceDelItemProtocol<'p>: PySequenceProtocol<'p> + MutablePyClass { type Index: FromPyObject<'p> + From; type Result: IntoPyCallbackOutput<()>; } @@ -115,14 +116,14 @@ pub trait PySequenceRepeatProtocol<'p>: PySequenceProtocol<'p> { } pub trait PySequenceInplaceConcatProtocol<'p>: - PySequenceProtocol<'p> + IntoPy + 'p + PySequenceProtocol<'p> + IntoPy + MutablePyClass + 'p { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput; } pub trait PySequenceInplaceRepeatProtocol<'p>: - PySequenceProtocol<'p> + IntoPy + 'p + PySequenceProtocol<'p> + IntoPy + MutablePyClass + 'p { type Index: FromPyObject<'p> + From; type Result: IntoPyCallbackOutput; diff --git a/src/conversion.rs b/src/conversion.rs index e1238edc..385e3c54 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -2,6 +2,7 @@ //! Defines conversions between Rust and Python types. use crate::err::{self, PyDowncastError, PyResult}; +use crate::pyclass::MutablePyClass; use crate::type_object::PyTypeInfo; use crate::types::PyTuple; use crate::{ @@ -145,7 +146,7 @@ where /// ```rust /// use pyo3::prelude::*; /// -/// #[pyclass] +/// #[pyclass(mutable)] /// struct Number { /// #[pyo3(get, set)] /// value: i32, @@ -329,7 +330,7 @@ where impl<'a, T> FromPyObject<'a> for PyRefMut<'a, T> where - T: PyClass, + T: MutablePyClass, { fn extract(obj: &'a PyAny) -> PyResult { let cell: &PyCell = PyTryFrom::try_from(obj)?; diff --git a/src/instance.rs b/src/instance.rs index 269d22f3..4ed03293 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -3,6 +3,7 @@ use crate::conversion::{PyTryFrom, ToBorrowedObject}; use crate::err::{self, PyDowncastError, PyErr, PyResult}; use crate::gil; use crate::pycell::{PyBorrowError, PyBorrowMutError, PyCell}; +use crate::pyclass::MutablePyClass; use crate::types::{PyDict, PyTuple}; use crate::{ ffi, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyAny, PyClass, PyClassInitializer, @@ -391,6 +392,23 @@ where self.as_ref(py).borrow() } + /// Attempts to immutably borrow the value `T`, returning an error if the value is currently mutably borrowed. + /// + /// The borrow lasts while the returned [`PyRef`] exists. + /// + /// This is the non-panicking variant of [`borrow`](#method.borrow). + /// + /// Equivalent to `self.as_ref(py).borrow_mut()` - + /// see [`PyCell::try_borrow`](crate::pycell::PyCell::try_borrow). + pub fn try_borrow<'py>(&'py self, py: Python<'py>) -> Result, PyBorrowError> { + self.as_ref(py).try_borrow() + } +} + +impl Py +where + T: MutablePyClass, +{ /// Mutably borrows the value `T`. /// /// This borrow lasts while the returned [`PyRefMut`] exists. @@ -403,7 +421,7 @@ where /// ``` /// # use pyo3::prelude::*; /// # - /// #[pyclass] + /// #[pyclass(mutable)] /// struct Foo { /// inner: u8, /// } @@ -427,18 +445,6 @@ where self.as_ref(py).borrow_mut() } - /// Attempts to immutably borrow the value `T`, returning an error if the value is currently mutably borrowed. - /// - /// The borrow lasts while the returned [`PyRef`] exists. - /// - /// This is the non-panicking variant of [`borrow`](#method.borrow). - /// - /// Equivalent to `self.as_ref(py).borrow_mut()` - - /// see [`PyCell::try_borrow`](crate::pycell::PyCell::try_borrow). - pub fn try_borrow<'py>(&'py self, py: Python<'py>) -> Result, PyBorrowError> { - self.as_ref(py).try_borrow() - } - /// Attempts to mutably borrow the value `T`, returning an error if the value is currently borrowed. /// /// The borrow lasts while the returned [`PyRefMut`] exists. @@ -802,7 +808,7 @@ where impl<'a, T> std::convert::From> for Py where - T: PyClass, + T: MutablePyClass, { fn from(pyref: PyRefMut<'a, T>) -> Self { unsafe { Py::from_borrowed_ptr(pyref.py(), pyref.as_ptr()) } diff --git a/src/pycell.rs b/src/pycell.rs index a47c9fed..9b277b81 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -25,7 +25,7 @@ //! ```rust //! use pyo3::prelude::*; //! -//! #[pyclass] +//! #[pyclass(mutable)] //! struct Number { //! inner: u32, //! } @@ -60,7 +60,7 @@ //! ```rust //! # use pyo3::prelude::*; //! # -//! # #[pyclass] +//! # #[pyclass(mutable)] //! # struct Number { //! # inner: u32, //! # } @@ -99,7 +99,7 @@ //! Suppose the following function that swaps the values of two `Number`s: //! ``` //! # use pyo3::prelude::*; -//! # #[pyclass] +//! # #[pyclass(mutable)] //! # pub struct Number { //! # inner: u32, //! # } @@ -131,7 +131,7 @@ //! It is better to write that function like this: //! ```rust //! # use pyo3::prelude::*; -//! # #[pyclass] +//! # #[pyclass(mutable)] //! # pub struct Number { //! # inner: u32, //! # } @@ -175,7 +175,7 @@ //! [Interior Mutability]: https://doc.rust-lang.org/book/ch15-05-interior-mutability.html "RefCell and the Interior Mutability Pattern - The Rust Programming Language" use crate::exceptions::PyRuntimeError; -use crate::pyclass::PyClass; +use crate::pyclass::{MutablePyClass, PyClass}; use crate::pyclass_init::PyClassInitializer; use crate::pyclass_slots::{PyClassDict, PyClassWeakRef}; use crate::type_object::{PyLayout, PySizedLayout}; @@ -214,7 +214,7 @@ unsafe impl PyLayout for PyCellBase where U: PySizedLayout {} /// ```rust /// use pyo3::prelude::*; /// -/// #[pyclass] +/// #[pyclass(mutable)] /// struct Number { /// inner: u32, /// } @@ -352,18 +352,15 @@ impl PyCell { /// /// Panics if the value is currently mutably borrowed. For a non-panicking variant, use /// [`try_borrow`](#method.try_borrow). + #[inline] pub fn borrow(&self) -> PyRef<'_, T> { - self.try_borrow().expect("Already mutably borrowed") + PyClass::borrow_as_pyref(self) } - /// Mutably borrows the value `T`. This borrow lasts as long as the returned `PyRefMut` exists. - /// - /// # Panics - /// - /// Panics if the value is currently borrowed. For a non-panicking variant, use - /// [`try_borrow_mut`](#method.try_borrow_mut). - pub fn borrow_mut(&self) -> PyRefMut<'_, T> { - self.try_borrow_mut().expect("Already borrowed") + #[inline] + #[doc(hidden)] + pub unsafe fn immutable_pyclass_borrow(&self) -> PyRef<'_, T> { + self.try_borrow().expect("Already mutably borrowed") } /// Immutably borrows the value `T`, returning an error if the value is currently @@ -375,7 +372,7 @@ impl PyCell { /// /// ``` /// # use pyo3::prelude::*; - /// #[pyclass] + /// #[pyclass(mutable)] /// struct Class {} /// /// Python::with_gil(|py| { @@ -391,7 +388,14 @@ impl PyCell { /// } /// }); /// ``` + #[inline] pub fn try_borrow(&self) -> Result, PyBorrowError> { + PyClass::try_borrow_as_pyref(self) + } + + #[inline] + #[doc(hidden)] + pub unsafe fn immutable_pyclass_try_borrow(&self) -> Result, PyBorrowError> { let flag = self.get_borrow_flag(); if flag == BorrowFlag::HAS_MUTABLE_BORROW { Err(PyBorrowError { _private: () }) @@ -401,6 +405,73 @@ impl PyCell { } } + /// Immutably borrows the value `T`, returning an error if the value is + /// currently mutably borrowed. + /// + /// # Safety + /// + /// This method is unsafe because it does not return a `PyRef`, + /// thus leaving the borrow flag untouched. Mutably borrowing the `PyCell` + /// while the reference returned by this method is alive is undefined behaviour. + /// + /// # Examples + /// + /// ``` + /// # use pyo3::prelude::*; + /// #[pyclass(mutable)] + /// struct Class {} + /// Python::with_gil(|py| { + /// let c = PyCell::new(py, Class {}).unwrap(); + /// + /// { + /// let m = c.borrow_mut(); + /// assert!(unsafe { c.try_borrow_unguarded() }.is_err()); + /// } + /// + /// { + /// let m = c.borrow(); + /// assert!(unsafe { c.try_borrow_unguarded() }.is_ok()); + /// } + /// }); + /// ``` + #[inline] + pub unsafe fn try_borrow_unguarded(&self) -> Result<&T, PyBorrowError> { + PyClass::try_borrow_unguarded(self) + } + + #[inline] + #[doc(hidden)] + pub unsafe fn immutable_pyclass_try_borrow_unguarded(&self) -> Result<&T, PyBorrowError> { + if self.get_borrow_flag() == BorrowFlag::HAS_MUTABLE_BORROW { + Err(PyBorrowError { _private: () }) + } else { + Ok(&*self.contents.value.get()) + } + } + + pub(crate) unsafe fn immutable_pyclass_try_borrow_unchecked_unguarded(&self) -> &T { + &*self.contents.value.get() + } + + pub(crate) unsafe fn borrow_unchecked_unincremented(&self) -> PyRef<'_, T> { + PyRef { inner: self } + } + + fn get_ptr(&self) -> *mut T { + self.contents.value.get() + } +} + +impl PyCell { + /// Mutably borrows the value `T`. This borrow lasts as long as the returned `PyRefMut` exists. + /// + /// # Panics + /// + /// Panics if the value is currently borrowed. For a non-panicking variant, use + /// [`try_borrow_mut`](#method.try_borrow_mut). + pub fn borrow_mut(&self) -> PyRefMut<'_, T> { + self.try_borrow_mut().expect("Already borrowed") + } /// Mutably borrows the value `T`, returning an error if the value is currently borrowed. /// This borrow lasts as long as the returned `PyRefMut` exists. /// @@ -410,7 +481,7 @@ impl PyCell { /// /// ``` /// # use pyo3::prelude::*; - /// #[pyclass] + /// #[pyclass(mutable)] /// struct Class {} /// Python::with_gil(|py| { /// let c = PyCell::new(py, Class {}).unwrap(); @@ -430,44 +501,6 @@ impl PyCell { Ok(PyRefMut { inner: self }) } } - - /// Immutably borrows the value `T`, returning an error if the value is - /// currently mutably borrowed. - /// - /// # Safety - /// - /// This method is unsafe because it does not return a `PyRef`, - /// thus leaving the borrow flag untouched. Mutably borrowing the `PyCell` - /// while the reference returned by this method is alive is undefined behaviour. - /// - /// # Examples - /// - /// ``` - /// # use pyo3::prelude::*; - /// #[pyclass] - /// struct Class {} - /// Python::with_gil(|py| { - /// let c = PyCell::new(py, Class {}).unwrap(); - /// - /// { - /// let m = c.borrow_mut(); - /// assert!(unsafe { c.try_borrow_unguarded() }.is_err()); - /// } - /// - /// { - /// let m = c.borrow(); - /// assert!(unsafe { c.try_borrow_unguarded() }.is_ok()); - /// } - /// }); - /// ``` - pub unsafe fn try_borrow_unguarded(&self) -> Result<&T, PyBorrowError> { - if self.get_borrow_flag() == BorrowFlag::HAS_MUTABLE_BORROW { - Err(PyBorrowError { _private: () }) - } else { - Ok(&*self.contents.value.get()) - } - } - /// Replaces the wrapped value with a new one, returning the old value. /// /// # Panics @@ -498,10 +531,6 @@ impl PyCell { pub fn swap(&self, other: &Self) { std::mem::swap(&mut *self.borrow_mut(), &mut *other.borrow_mut()) } - - fn get_ptr(&self) -> *mut T { - self.contents.value.get() - } } unsafe impl PyLayout for PyCell {} @@ -605,6 +634,12 @@ impl<'p, T: PyClass> PyRef<'p, T> { pub fn py(&self) -> Python { unsafe { Python::assume_gil_acquired() } } + + #[doc(hidden)] + pub unsafe fn decrement_flag(&mut self) { + let flag = self.inner.get_borrow_flag(); + self.inner.set_borrow_flag(flag.decrement()) + } } impl<'p, T, U> AsRef for PyRef<'p, T> @@ -687,8 +722,7 @@ impl<'p, T: PyClass> Deref for PyRef<'p, T> { impl<'p, T: PyClass> Drop for PyRef<'p, T> { fn drop(&mut self) { - let flag = self.inner.get_borrow_flag(); - self.inner.set_borrow_flag(flag.decrement()) + unsafe { PyClass::drop_pyref(self) } } } @@ -720,11 +754,11 @@ impl fmt::Debug for PyRef<'_, T> { /// A wrapper type for a mutably borrowed value from a[`PyCell`]``. /// /// See the [module-level documentation](self) for more information. -pub struct PyRefMut<'p, T: PyClass> { +pub struct PyRefMut<'p, T: MutablePyClass> { inner: &'p PyCell, } -impl<'p, T: PyClass> PyRefMut<'p, T> { +impl<'p, T: MutablePyClass> PyRefMut<'p, T> { /// Returns a `Python` token that is bound to the lifetime of the `PyRefMut`. pub fn py(&self) -> Python { unsafe { Python::assume_gil_acquired() } @@ -733,8 +767,8 @@ impl<'p, T: PyClass> PyRefMut<'p, T> { impl<'p, T, U> AsRef for PyRefMut<'p, T> where - T: PyClass, - U: PyClass, + T: PyClass + MutablePyClass, + U: MutablePyClass, { fn as_ref(&self) -> &T::BaseType { unsafe { &*self.inner.ob_base.get_ptr() } @@ -743,8 +777,8 @@ where impl<'p, T, U> AsMut for PyRefMut<'p, T> where - T: PyClass, - U: PyClass, + T: PyClass + MutablePyClass, + U: MutablePyClass, { fn as_mut(&mut self) -> &mut T::BaseType { unsafe { &mut *self.inner.ob_base.get_ptr() } @@ -753,8 +787,8 @@ where impl<'p, T, U> PyRefMut<'p, T> where - T: PyClass, - U: PyClass, + T: PyClass + MutablePyClass, + U: MutablePyClass, { /// Gets a `PyRef`. /// @@ -768,7 +802,7 @@ where } } -impl<'p, T: PyClass> Deref for PyRefMut<'p, T> { +impl<'p, T: MutablePyClass> Deref for PyRefMut<'p, T> { type Target = T; #[inline] @@ -777,39 +811,39 @@ impl<'p, T: PyClass> Deref for PyRefMut<'p, T> { } } -impl<'p, T: PyClass> DerefMut for PyRefMut<'p, T> { +impl<'p, T: MutablePyClass> DerefMut for PyRefMut<'p, T> { #[inline] fn deref_mut(&mut self) -> &mut T { unsafe { &mut *self.inner.get_ptr() } } } -impl<'p, T: PyClass> Drop for PyRefMut<'p, T> { +impl<'p, T: MutablePyClass> Drop for PyRefMut<'p, T> { fn drop(&mut self) { self.inner.set_borrow_flag(BorrowFlag::UNUSED) } } -impl IntoPy for PyRefMut<'_, T> { +impl IntoPy for PyRefMut<'_, T> { fn into_py(self, py: Python) -> PyObject { unsafe { PyObject::from_borrowed_ptr(py, self.inner.as_ptr()) } } } -impl<'a, T: PyClass> AsPyPointer for PyRefMut<'a, T> { +impl<'a, T: MutablePyClass> AsPyPointer for PyRefMut<'a, T> { fn as_ptr(&self) -> *mut ffi::PyObject { self.inner.as_ptr() } } -impl<'a, T: PyClass> std::convert::TryFrom<&'a PyCell> for crate::PyRefMut<'a, T> { +impl<'a, T: MutablePyClass> std::convert::TryFrom<&'a PyCell> for crate::PyRefMut<'a, T> { type Error = PyBorrowMutError; fn try_from(cell: &'a crate::PyCell) -> Result { cell.try_borrow_mut() } } -impl fmt::Debug for PyRefMut<'_, T> { +impl fmt::Debug for PyRefMut<'_, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&*(self.deref()), f) } diff --git a/src/pyclass.rs b/src/pyclass.rs index fc8a4efa..b53b04e6 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -1,4 +1,5 @@ //! `PyClass` and related traits. +use crate::pycell::{PyBorrowError, PyRef}; use crate::{ class::impl_::{fallback_new, tp_dealloc, PyClassImpl}, ffi, @@ -17,7 +18,14 @@ use std::{ /// /// The `#[pyclass]` attribute automatically implements this trait for your Rust struct, /// so you normally don't have to use this trait directly. -pub trait PyClass: +/// +/// # Safety +/// +/// If `T` implements [`MutablePyClass`], then implementations must override the default methods. +/// +/// If `T` does not implement [`MutablePyClass`], then implementations should not override the +/// default methods. +pub unsafe trait PyClass: PyTypeInfo> + PyClassImpl> { /// Specify this class has `#[pyclass(dict)]` or not. @@ -27,8 +35,65 @@ pub trait PyClass: /// The closest native ancestor. This is `PyAny` by default, and when you declare /// `#[pyclass(extends=PyDict)]`, it's `PyDict`. type BaseNativeType: PyTypeInfo + PyNativeType; + + /// Default implementation that borrows as a PyRef without checking or incrementing the + /// borrowflag. + /// + /// # Safety + /// + /// Implementations that implement [`MutablePyClass`] **must** override this method + /// by wrapping `PyCell::immutable_pyclass_try_borrow()` + #[inline] + fn try_borrow_as_pyref(slf: &PyCell) -> Result, PyBorrowError> { + Ok(Self::borrow_as_pyref(slf)) + } + + /// Default implementation that borrows as a PyRef without checking or incrementing the + /// borrowflag. + /// + /// # Safety + /// + /// Implementations that implement [`MutablePyClass`] **must** override this method + /// by wrapping `PyCell::immutable_pyclass_borrow()` + #[inline] + fn borrow_as_pyref(slf: &PyCell) -> PyRef<'_, Self> { + unsafe { PyCell::borrow_unchecked_unincremented(slf) } + } + + /// Default implementation that borrows as a PyRef without checking or incrementing the + /// borrowflag. + /// + /// # Safety + /// + /// Please see the safety requirements on [`PyCell::try_borrow_unguarded`]. + /// + /// Implementations that implement [`MutablePyClass`] **must** override this method + /// by wrapping `PyCell::immutable_pyclass_try_borrow_unguarded()`. + #[inline] + unsafe fn try_borrow_unguarded(slf: &PyCell) -> Result<&Self, PyBorrowError> { + Ok(PyCell::_try_borrow_unchecked_unguarded(slf)) + } + + /// Default implementation that does nothing. + /// + /// # Safety + /// + /// This function is only called inside [`PyRef`]s [`Drop`] implementation. + /// + /// Implementations that also implement [`MutablePyClass`] **must** make this method call + /// [`PyRef::decrement_flag()`] so that [`PyRef`]s [`Drop`] implementation correctly decrements + /// the borrowflag. + #[inline] + unsafe fn drop_pyref(_: &mut PyRef) {} } +/// Declares that a pyclass can be mutably borrowed. +/// +/// # Safety +/// +/// Implementations must correctly implement [`PyClass`]. +pub unsafe trait MutablePyClass: PyClass {} + /// For collecting slot items. #[derive(Default)] struct TypeSlots(Vec); diff --git a/tests/hygiene/pyclass.rs b/tests/hygiene/pyclass.rs index 985d05dc..6132c523 100644 --- a/tests/hygiene/pyclass.rs +++ b/tests/hygiene/pyclass.rs @@ -15,7 +15,8 @@ pub struct Foo2; unsendable, subclass, extends = ::pyo3::types::PyAny, - module = "Spam" + module = "Spam", + mutable )] pub struct Bar { #[pyo3(get, set)] diff --git a/tests/hygiene/pymethods.rs b/tests/hygiene/pymethods.rs index 37a916f9..49170781 100644 --- a/tests/hygiene/pymethods.rs +++ b/tests/hygiene/pymethods.rs @@ -1,10 +1,10 @@ #![no_implicit_prelude] #![allow(unused_variables)] -#[::pyo3::pyclass] +#[::pyo3::pyclass(mutable)] pub struct Dummy; -#[::pyo3::pyclass] +#[::pyo3::pyclass(mutable)] pub struct DummyIter; #[::pyo3::pymethods] diff --git a/tests/hygiene/pyproto.rs b/tests/hygiene/pyproto.rs index 9f6b0af8..6bba285d 100644 --- a/tests/hygiene/pyproto.rs +++ b/tests/hygiene/pyproto.rs @@ -16,7 +16,8 @@ pub struct Foo2; gc, subclass, extends = ::pyo3::types::PyAny, - module = "Spam" + module = "Spam", + mutable )] pub struct Bar { #[pyo3(get, set)] diff --git a/tests/test_arithmetics.rs b/tests/test_arithmetics.rs index 1faf8492..d747edf4 100644 --- a/tests/test_arithmetics.rs +++ b/tests/test_arithmetics.rs @@ -50,7 +50,7 @@ fn unary_arithmetic() { py_run!(py, c, "assert repr(round(c, 1)) == 'UA(3)'"); } -#[pyclass] +#[pyclass(mutable)] struct InPlaceOperations { value: u32, } @@ -500,7 +500,7 @@ fn rich_comparisons_python_3_type_error() { mod return_not_implemented { use super::*; - #[pyclass] + #[pyclass(mutable)] struct RichComparisonToSelf {} #[pymethods] diff --git a/tests/test_arithmetics_protos.rs b/tests/test_arithmetics_protos.rs index 9036d737..73767913 100644 --- a/tests/test_arithmetics_protos.rs +++ b/tests/test_arithmetics_protos.rs @@ -67,7 +67,7 @@ impl PyObjectProtocol for BinaryArithmetic { } } -#[pyclass] +#[pyclass(mutable)] struct InPlaceOperations { value: u32, } @@ -527,7 +527,7 @@ fn rich_comparisons_python_3_type_error() { mod return_not_implemented { use super::*; - #[pyclass] + #[pyclass(mutable)] struct RichComparisonToSelf {} #[pyproto] diff --git a/tests/test_buffer.rs b/tests/test_buffer.rs index bcdfda1c..eeb33e56 100644 --- a/tests/test_buffer.rs +++ b/tests/test_buffer.rs @@ -21,7 +21,7 @@ enum TestGetBufferError { IncorrectAlignment, } -#[pyclass] +#[pyclass(mutable)] struct TestBufferErrors { buf: Vec, error: Option, diff --git a/tests/test_buffer_protocol.rs b/tests/test_buffer_protocol.rs index f7174449..76cbfa8a 100644 --- a/tests/test_buffer_protocol.rs +++ b/tests/test_buffer_protocol.rs @@ -15,7 +15,7 @@ use std::sync::Arc; mod common; -#[pyclass] +#[pyclass(mutable)] struct TestBufferClass { vec: Vec, drop_called: Arc, diff --git a/tests/test_class_basics.rs b/tests/test_class_basics.rs index 193a216b..cfff132d 100644 --- a/tests/test_class_basics.rs +++ b/tests/test_class_basics.rs @@ -36,7 +36,7 @@ fn unit_class() { ///Line2 /// Line3 // this is not doc string -#[pyclass] +#[pyclass(mutable)] struct ClassWithDocs { /// Property field #[pyo3(get, set)] @@ -122,7 +122,7 @@ fn custom_names() { py_assert!(py, typeobj, "not hasattr(typeobj, 'foo')"); } -#[pyclass] +#[pyclass(mutable)] struct RawIdents { #[pyo3(get, set)] r#type: i64, @@ -171,7 +171,7 @@ fn empty_class_in_module() { assert_eq!(module, "builtins"); } -#[pyclass] +#[pyclass(mutable)] struct ClassWithObjectField { // It used to be that PyObject was not supported with (get, set) // - this test is just ensuring it compiles. @@ -316,7 +316,7 @@ fn test_pymethods_from_py_with() { }) } -#[pyclass] +#[pyclass(mutable)] struct TupleClass(#[pyo3(get, set, name = "value")] i32); #[test] diff --git a/tests/test_class_conversion.rs b/tests/test_class_conversion.rs index d379f774..3619d643 100644 --- a/tests/test_class_conversion.rs +++ b/tests/test_class_conversion.rs @@ -4,7 +4,7 @@ use pyo3::ToPyObject; #[macro_use] mod common; -#[pyclass] +#[pyclass(mutable)] #[derive(Clone, Debug, PartialEq)] struct Cloneable { x: i32, @@ -30,7 +30,7 @@ fn test_cloneable_pyclass() { assert_eq!(&c, &*mrc); } -#[pyclass(subclass)] +#[pyclass(subclass, mutable)] #[derive(Default)] struct BaseClass { value: i32, @@ -43,7 +43,7 @@ impl BaseClass { } } -#[pyclass(extends=BaseClass)] +#[pyclass(extends=BaseClass, mutable)] struct SubClass {} #[pymethods] @@ -53,7 +53,7 @@ impl SubClass { } } -#[pyclass] +#[pyclass(mutable)] struct PolymorphicContainer { #[pyo3(get, set)] inner: Py, diff --git a/tests/test_gc.rs b/tests/test_gc.rs index 3d8f64bc..642af2c6 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -81,7 +81,7 @@ fn data_is_dropped() { } #[allow(dead_code)] -#[pyclass] +#[pyclass(mutable)] struct GcIntegration { self_ref: PyObject, dropped: TestDropCall, @@ -127,7 +127,7 @@ fn gc_integration() { assert!(drop_called.load(Ordering::Relaxed)); } -#[pyclass(gc)] +#[pyclass(gc, mutable)] struct GcIntegration2 {} #[pyproto] @@ -181,7 +181,7 @@ fn inherited_weakref() { ); } -#[pyclass(subclass)] +#[pyclass(subclass, mutable)] struct BaseClassWithDrop { data: Option>, } @@ -202,7 +202,7 @@ impl Drop for BaseClassWithDrop { } } -#[pyclass(extends = BaseClassWithDrop)] +#[pyclass(extends = BaseClassWithDrop, mutable)] struct SubClassWithDrop { data: Option>, } @@ -249,7 +249,7 @@ fn inheritance_with_new_methods_with_drop() { assert!(drop_called2.load(Ordering::Relaxed)); } -#[pyclass(gc)] +#[pyclass(gc, mutable)] struct TraversableClass { traversed: AtomicBool, } diff --git a/tests/test_getter_setter.rs b/tests/test_getter_setter.rs index 38b9761a..673ff132 100644 --- a/tests/test_getter_setter.rs +++ b/tests/test_getter_setter.rs @@ -4,7 +4,7 @@ use pyo3::types::{IntoPyDict, PyList}; mod common; -#[pyclass] +#[pyclass(mutable)] struct ClassWithProperties { num: i32, } @@ -65,7 +65,7 @@ fn class_with_properties() { py_assert!(py, *d, "C.DATA.__doc__ == 'a getter for data'"); } -#[pyclass] +#[pyclass(mutable)] struct GetterSetter { #[pyo3(get, set)] num: i32, @@ -103,7 +103,7 @@ fn getter_setter_autogen() { ); } -#[pyclass] +#[pyclass(mutable)] struct RefGetterSetter { num: i32, } @@ -133,7 +133,7 @@ fn ref_getter_setter() { py_run!(py, inst, "inst.num = 20; assert inst.num == 20"); } -#[pyclass] +#[pyclass(mutable)] struct TupleClassGetterSetter(i32); #[pymethods] diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index 01b124b8..b6e4c832 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -5,7 +5,7 @@ use pyo3::types::IntoPyDict; mod common; -#[pyclass(subclass)] +#[pyclass(subclass, mutable)] struct BaseClass { #[pyo3(get)] val1: usize, @@ -45,7 +45,7 @@ impl BaseClass { } } -#[pyclass(extends=BaseClass)] +#[pyclass(extends=BaseClass, mutable)] struct SubClass { #[pyo3(get)] val2: usize, diff --git a/tests/test_macros.rs b/tests/test_macros.rs index 78d68ab5..bfcad50b 100644 --- a/tests/test_macros.rs +++ b/tests/test_macros.rs @@ -45,7 +45,7 @@ fn_macro!("(a, b=None, *, c=42)", a, b = "None", c = 42); macro_rules! property_rename_via_macro { ($prop_name:ident) => { - #[pyclass] + #[pyclass(mutable)] struct ClassWithProperty { member: u64, } diff --git a/tests/test_mapping.rs b/tests/test_mapping.rs index 42710094..27d5322e 100644 --- a/tests/test_mapping.rs +++ b/tests/test_mapping.rs @@ -11,7 +11,7 @@ use pyo3::PyMappingProtocol; mod common; -#[pyclass] +#[pyclass(mutable)] struct Mapping { index: HashMap, } diff --git a/tests/test_methods.rs b/tests/test_methods.rs index 1032acf9..a02b487b 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -675,7 +675,7 @@ fn method_with_lifetime() { ); } -#[pyclass] +#[pyclass(mutable)] struct MethodWithPyClassArg { #[pyo3(get)] value: i64, @@ -824,7 +824,7 @@ fn test_from_sequence() { py_assert!(py, typeobj, "typeobj(range(0, 4)).numbers == [0, 1, 2, 3]") } -#[pyclass] +#[pyclass(mutable)] struct r#RawIdents { #[pyo3(get, set)] r#type: PyObject, diff --git a/tests/test_proto_methods.rs b/tests/test_proto_methods.rs index d5324bf6..47c16e2f 100644 --- a/tests/test_proto_methods.rs +++ b/tests/test_proto_methods.rs @@ -6,7 +6,7 @@ use std::{isize, iter}; mod common; -#[pyclass] +#[pyclass(mutable)] struct ExampleClass { #[pyo3(get, set)] value: i32, @@ -194,7 +194,7 @@ fn len() { py_expect_exception!(py, inst, "len(inst)", PyOverflowError); } -#[pyclass] +#[pyclass(mutable)] struct Iterator { iter: Box + Send>, } @@ -278,7 +278,7 @@ mod deprecated { } } -#[pyclass] +#[pyclass(mutable)] #[derive(Debug)] struct SetItem { key: i32, @@ -308,7 +308,7 @@ fn setitem() { py_expect_exception!(py, c, "del c[1]", PyNotImplementedError); } -#[pyclass] +#[pyclass(mutable)] struct DelItem { key: i32, } @@ -334,7 +334,7 @@ fn delitem() { py_expect_exception!(py, c, "c[1] = 2", PyNotImplementedError); } -#[pyclass] +#[pyclass(mutable)] struct SetDelItem { val: Option, } @@ -418,7 +418,7 @@ fn test_getitem() { py_assert!(py, ob, "ob[100:200:1] == 'slice'"); } -#[pyclass] +#[pyclass(mutable)] struct ClassWithGetAttr { #[pyo3(get, set)] data: u32, @@ -441,7 +441,7 @@ fn getattr_doesnt_override_member() { } /// Wraps a Python future and yield it once. -#[pyclass] +#[pyclass(mutable)] struct OnceFuture { future: PyObject, polled: bool, @@ -505,7 +505,7 @@ loop.close() } /// Increment the count when `__get__` is called. -#[pyclass] +#[pyclass(mutable)] struct DescrCounter { #[pyo3(get)] count: usize, diff --git a/tests/test_pyproto.rs b/tests/test_pyproto.rs index da2dc0de..4ea1ee7f 100644 --- a/tests/test_pyproto.rs +++ b/tests/test_pyproto.rs @@ -47,7 +47,7 @@ fn len() { py_expect_exception!(py, inst, "len(inst)", PyOverflowError); } -#[pyclass] +#[pyclass(mutable)] struct Iterator { iter: Box + Send>, } @@ -149,7 +149,7 @@ fn comparisons() { py_assert!(py, zero, "not zero"); } -#[pyclass] +#[pyclass(mutable)] #[derive(Debug)] struct Sequence { fields: Vec, @@ -210,7 +210,7 @@ fn sequence() { py_expect_exception!(py, c, "c['abc']", PyTypeError); } -#[pyclass] +#[pyclass(mutable)] #[derive(Debug)] struct SetItem { key: i32, @@ -240,7 +240,7 @@ fn setitem() { py_expect_exception!(py, c, "del c[1]", PyNotImplementedError); } -#[pyclass] +#[pyclass(mutable)] struct DelItem { key: i32, } @@ -266,7 +266,7 @@ fn delitem() { py_expect_exception!(py, c, "c[1] = 2", PyNotImplementedError); } -#[pyclass] +#[pyclass(mutable)] struct SetDelItem { val: Option, } @@ -338,7 +338,7 @@ fn contains() { py_expect_exception!(py, c, "assert 'wrong type' not in c", PyTypeError); } -#[pyclass] +#[pyclass(mutable)] struct ContextManager { exit_called: bool, } @@ -507,7 +507,7 @@ fn weakref_dunder_dict_support() { ); } -#[pyclass] +#[pyclass(mutable)] struct ClassWithGetAttr { #[pyo3(get, set)] data: u32, @@ -530,7 +530,7 @@ fn getattr_doesnt_override_member() { } /// Wraps a Python future and yield it once. -#[pyclass] +#[pyclass(mutable)] struct OnceFuture { future: PyObject, polled: bool, @@ -600,7 +600,7 @@ loop.close() } /// Increment the count when `__get__` is called. -#[pyclass] +#[pyclass(mutable)] struct DescrCounter { #[pyo3(get)] count: usize, diff --git a/tests/test_pyself.rs b/tests/test_pyself.rs index 31dec01a..e117c63c 100644 --- a/tests/test_pyself.rs +++ b/tests/test_pyself.rs @@ -8,7 +8,7 @@ mod common; /// Assumes it's a file reader or so. /// Inspired by https://github.com/jothan/cordoba, thanks. -#[pyclass] +#[pyclass(mutable)] #[derive(Clone, Debug)] struct Reader { inner: HashMap, @@ -44,7 +44,7 @@ impl Reader { } } -#[pyclass] +#[pyclass(mutable)] #[derive(Debug)] struct Iter { reader: Py, diff --git a/tests/test_sequence.rs b/tests/test_sequence.rs index 0cb467ee..5549c9bf 100644 --- a/tests/test_sequence.rs +++ b/tests/test_sequence.rs @@ -7,7 +7,7 @@ use pyo3::py_run; mod common; -#[pyclass] +#[pyclass(mutable)] struct ByteSequence { elements: Vec, } @@ -232,7 +232,7 @@ fn test_inplace_repeat() { // Check that #[pyo3(get, set)] works correctly for Vec -#[pyclass] +#[pyclass(mutable)] struct GenericList { #[pyo3(get, set)] items: Vec, @@ -265,7 +265,7 @@ fn test_generic_list_set() { ); } -#[pyclass] +#[pyclass(mutable)] struct OptionList { #[pyo3(get, set)] items: Vec>, diff --git a/tests/test_various.rs b/tests/test_various.rs index 6e570fa2..dd31b6b7 100644 --- a/tests/test_various.rs +++ b/tests/test_various.rs @@ -6,7 +6,7 @@ use std::fmt; mod common; -#[pyclass] +#[pyclass(mutable)] struct MutRefArg { n: i32, }