From 7f31000be23bcbf3173d36ba147832f103aea5f1 Mon Sep 17 00:00:00 2001 From: mejrs Date: Wed, 10 Nov 2021 15:28:19 +0100 Subject: [PATCH 01/16] Implement by default immutable pyclasses --- guide/src/class.md | 16 +-- guide/src/class/protocols.md | 8 +- guide/src/migration.md | 4 +- guide/src/trait_bounds.md | 10 +- guide/src/types.md | 6 +- pyo3-macros-backend/src/pyclass.rs | 62 +++++++++-- src/class/basic.rs | 5 +- src/class/buffer.rs | 7 +- src/class/gc.rs | 3 +- src/class/iter.rs | 2 +- src/class/mapping.rs | 6 +- src/class/number.rs | 31 +++--- src/class/sequence.rs | 9 +- src/conversion.rs | 5 +- src/instance.rs | 34 +++--- src/pycell.rs | 170 +++++++++++++++++------------ src/pyclass.rs | 67 +++++++++++- tests/hygiene/pyclass.rs | 3 +- tests/hygiene/pymethods.rs | 4 +- tests/hygiene/pyproto.rs | 3 +- tests/test_arithmetics.rs | 4 +- tests/test_arithmetics_protos.rs | 4 +- tests/test_buffer.rs | 2 +- tests/test_buffer_protocol.rs | 2 +- tests/test_class_basics.rs | 8 +- tests/test_class_conversion.rs | 8 +- tests/test_gc.rs | 10 +- tests/test_getter_setter.rs | 8 +- tests/test_inheritance.rs | 4 +- tests/test_macros.rs | 2 +- tests/test_mapping.rs | 2 +- tests/test_methods.rs | 4 +- tests/test_proto_methods.rs | 16 +-- tests/test_pyproto.rs | 18 +-- tests/test_pyself.rs | 4 +- tests/test_sequence.rs | 6 +- tests/test_various.rs | 2 +- 37 files changed, 358 insertions(+), 201 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index 7c75c41985c..44ce57eac49 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 251447be1a6..84069ca0f0c 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 56a714accc6..724d5f61d28 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 a57a7580d53..7d118ab665b 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 ed2fda6c06e..6052d0294aa 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 a75b2584c9d..94d993c73cc 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 1e34d493e5d..d3b7e1007d4 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 7b4c8acf958..655653d7d2a 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 3197c0088c3..d5200bfe7d5 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 97b47fe3775..3dd1db3e747 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 9961c5d1770..3456eb26582 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 903744ebc72..17bf396914d 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 129a188a646..a4e0ed9193a 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 e1238edc80e..385e3c543f3 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 269d22f3107..4ed03293465 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 a47c9fedd23..9b277b81e78 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,36 +405,6 @@ impl PyCell { } } - /// Mutably borrows the value `T`, returning an error if the value is currently borrowed. - /// This borrow lasts as long as the returned `PyRefMut` exists. - /// - /// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut). - /// - /// # Examples - /// - /// ``` - /// # use pyo3::prelude::*; - /// #[pyclass] - /// struct Class {} - /// Python::with_gil(|py| { - /// let c = PyCell::new(py, Class {}).unwrap(); - /// { - /// let m = c.borrow(); - /// assert!(c.try_borrow_mut().is_err()); - /// } - /// - /// assert!(c.try_borrow_mut().is_ok()); - /// }); - /// ``` - pub fn try_borrow_mut(&self) -> Result, PyBorrowMutError> { - if self.get_borrow_flag() != BorrowFlag::UNUSED { - Err(PyBorrowMutError { _private: () }) - } else { - self.set_borrow_flag(BorrowFlag::HAS_MUTABLE_BORROW); - Ok(PyRefMut { inner: self }) - } - } - /// Immutably borrows the value `T`, returning an error if the value is /// currently mutably borrowed. /// @@ -444,7 +418,7 @@ impl PyCell { /// /// ``` /// # use pyo3::prelude::*; - /// #[pyclass] + /// #[pyclass(mutable)] /// struct Class {} /// Python::with_gil(|py| { /// let c = PyCell::new(py, Class {}).unwrap(); @@ -460,7 +434,14 @@ impl PyCell { /// } /// }); /// ``` + #[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 { @@ -468,6 +449,58 @@ impl PyCell { } } + 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. + /// + /// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut). + /// + /// # Examples + /// + /// ``` + /// # use pyo3::prelude::*; + /// #[pyclass(mutable)] + /// struct Class {} + /// Python::with_gil(|py| { + /// let c = PyCell::new(py, Class {}).unwrap(); + /// { + /// let m = c.borrow(); + /// assert!(c.try_borrow_mut().is_err()); + /// } + /// + /// assert!(c.try_borrow_mut().is_ok()); + /// }); + /// ``` + pub fn try_borrow_mut(&self) -> Result, PyBorrowMutError> { + if self.get_borrow_flag() != BorrowFlag::UNUSED { + Err(PyBorrowMutError { _private: () }) + } else { + self.set_borrow_flag(BorrowFlag::HAS_MUTABLE_BORROW); + Ok(PyRefMut { inner: self }) + } + } /// 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 fc8a4efae93..b53b04e6b3e 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 985d05dc2da..6132c523ad5 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 37a916f9c6b..491707812a3 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 9f6b0af8b8c..6bba285db22 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 1faf8492950..d747edf4277 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 9036d7375a7..73767913231 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 bcdfda1cd5f..eeb33e56cd4 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 f7174449004..76cbfa8a8e8 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 193a216b14a..cfff132d951 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 d379f774e0c..3619d643a76 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 3d8f64bca20..642af2c6c59 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 38b9761ae8e..673ff132dbe 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 01b124b8be9..b6e4c8328c7 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 78d68ab586e..bfcad50b918 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 42710094e76..27d5322eeac 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 1032acf9a0d..a02b487b690 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 d5324bf67d2..47c16e2ffd1 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 da2dc0dee32..4ea1ee7fa04 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 31dec01a639..e117c63ce76 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 0cb467ee648..5549c9bfa74 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 6e570fa21a1..dd31b6b737d 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, } From 4adb257cbaacf11d0c0ba823ab07702cc23c75a0 Mon Sep 17 00:00:00 2001 From: mejrs Date: Wed, 10 Nov 2021 15:59:05 +0100 Subject: [PATCH 02/16] Fix method names --- src/pycell.rs | 2 +- src/pyclass.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pycell.rs b/src/pycell.rs index 9b277b81e78..3dfe72c7422 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -449,7 +449,7 @@ impl PyCell { } } - pub(crate) unsafe fn immutable_pyclass_try_borrow_unchecked_unguarded(&self) -> &T { + pub(crate) unsafe fn _borrow_unchecked_unguarded(&self) -> &T { &*self.contents.value.get() } diff --git a/src/pyclass.rs b/src/pyclass.rs index b53b04e6b3e..1b06fffcf93 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -71,7 +71,7 @@ pub unsafe trait PyClass: /// 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)) + Ok(PyCell::_borrow_unchecked_unguarded(slf)) } /// Default implementation that does nothing. From 1d5df1bffe010dccaa455087ffbfc4b1fe89db48 Mon Sep 17 00:00:00 2001 From: mejrs Date: Sat, 20 Nov 2021 14:56:39 +0100 Subject: [PATCH 03/16] Revert "Fix method names" This reverts commit 4adb257cbaacf11d0c0ba823ab07702cc23c75a0. --- src/pycell.rs | 2 +- src/pyclass.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pycell.rs b/src/pycell.rs index 3dfe72c7422..9b277b81e78 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -449,7 +449,7 @@ impl PyCell { } } - pub(crate) unsafe fn _borrow_unchecked_unguarded(&self) -> &T { + pub(crate) unsafe fn immutable_pyclass_try_borrow_unchecked_unguarded(&self) -> &T { &*self.contents.value.get() } diff --git a/src/pyclass.rs b/src/pyclass.rs index 1b06fffcf93..b53b04e6b3e 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -71,7 +71,7 @@ pub unsafe trait PyClass: /// by wrapping `PyCell::immutable_pyclass_try_borrow_unguarded()`. #[inline] unsafe fn try_borrow_unguarded(slf: &PyCell) -> Result<&Self, PyBorrowError> { - Ok(PyCell::_borrow_unchecked_unguarded(slf)) + Ok(PyCell::_try_borrow_unchecked_unguarded(slf)) } /// Default implementation that does nothing. From a28be4d9bf2caa659dd2dc4f47f523723263f521 Mon Sep 17 00:00:00 2001 From: mejrs Date: Sat, 20 Nov 2021 14:58:56 +0100 Subject: [PATCH 04/16] Revert "Implement by default immutable pyclasses" This reverts commit 7f31000be23bcbf3173d36ba147832f103aea5f1. --- guide/src/class.md | 16 +-- guide/src/class/protocols.md | 8 +- guide/src/migration.md | 4 +- guide/src/trait_bounds.md | 10 +- guide/src/types.md | 6 +- pyo3-macros-backend/src/pyclass.rs | 62 ++--------- src/class/basic.rs | 5 +- src/class/buffer.rs | 7 +- src/class/gc.rs | 3 +- src/class/iter.rs | 2 +- src/class/mapping.rs | 6 +- src/class/number.rs | 31 +++--- src/class/sequence.rs | 9 +- src/conversion.rs | 5 +- src/instance.rs | 34 +++--- src/pycell.rs | 170 ++++++++++++----------------- src/pyclass.rs | 67 +----------- tests/hygiene/pyclass.rs | 3 +- tests/hygiene/pymethods.rs | 4 +- tests/hygiene/pyproto.rs | 3 +- tests/test_arithmetics.rs | 4 +- tests/test_arithmetics_protos.rs | 4 +- tests/test_buffer.rs | 2 +- tests/test_buffer_protocol.rs | 2 +- tests/test_class_basics.rs | 8 +- tests/test_class_conversion.rs | 8 +- tests/test_gc.rs | 10 +- tests/test_getter_setter.rs | 8 +- tests/test_inheritance.rs | 4 +- tests/test_macros.rs | 2 +- tests/test_mapping.rs | 2 +- tests/test_methods.rs | 4 +- tests/test_proto_methods.rs | 16 +-- tests/test_pyproto.rs | 18 +-- tests/test_pyself.rs | 4 +- tests/test_sequence.rs | 6 +- tests/test_various.rs | 2 +- 37 files changed, 201 insertions(+), 358 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index 44ce57eac49..7c75c41985c 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(mutable)] +#[pyclass] struct MyClass { #[pyo3(get)] num: i32, @@ -299,7 +299,7 @@ use pyo3::types::PyDict; use pyo3::AsPyPointer; use std::collections::HashMap; -#[pyclass(extends=PyDict, mutable)] +#[pyclass(extends=PyDict)] #[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(mutable)] +#[pyclass] struct MyClass { #[pyo3(get, set)] num: i32 @@ -410,7 +410,7 @@ can be used since Rust 2018). ```rust # use pyo3::prelude::*; -# #[pyclass(mutable)] +# #[pyclass] # 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(mutable)] +# #[pyclass] # 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(mutable)] +# #[pyclass] # struct MyClass { # num: i32, # } @@ -641,7 +641,7 @@ Example: # use pyo3::prelude::*; use pyo3::types::{PyDict, PyTuple}; # -# #[pyclass(mutable)] +# #[pyclass] # struct MyClass { # num: i32, # } @@ -731,7 +731,7 @@ unsafe impl pyo3::PyTypeInfo for MyClass { } } -unsafe impl pyo3::pyclass::PyClass for MyClass { +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 84069ca0f0c..251447be1a6 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", mutable)] +#[pyclass(name = "counter")] struct PyCounter { count: u64, wraps: Py, @@ -453,7 +453,7 @@ use pyo3::prelude::*; use pyo3::PyTraverseError; use pyo3::gc::{PyGCProtocol, PyVisit}; -#[pyclass(mutable)] +#[pyclass] struct ClassWithGCSupport { obj: Option, } @@ -505,7 +505,7 @@ Example: use pyo3::prelude::*; use pyo3::PyIterProtocol; -#[pyclass(mutable)] +#[pyclass] 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(mutable)] +#[pyclass] struct Iter { inner: std::vec::IntoIter, } diff --git a/guide/src/migration.md b/guide/src/migration.md index 724d5f61d28..56a714accc6 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -436,7 +436,7 @@ Here is an example. ```rust # use pyo3::prelude::*; -#[pyclass(mutable)] +#[pyclass] struct Names { names: Vec } @@ -514,7 +514,7 @@ After: ```rust # use pyo3::prelude::*; # use pyo3::types::IntoPyDict; -# #[pyclass(mutable)] #[derive(Clone)] struct MyClass {} +# #[pyclass] #[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 7d118ab665b..a57a7580d53 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(mutable)] +#[pyclass] 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(mutable)] +# #[pyclass] # 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(mutable)] +# #[pyclass] # 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(mutable)] +# #[pyclass] # struct UserModel { # model: Py, # } @@ -481,7 +481,7 @@ pub fn solve_wrapper(model: &mut UserModel) { solve(model); } -#[pyclass(mutable)] +#[pyclass] pub struct UserModel { model: Py, } diff --git a/guide/src/types.md b/guide/src/types.md index 6052d0294aa..ed2fda6c06e 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(mutable)] #[derive(Clone)] struct MyClass { } +# #[pyclass] #[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(mutable)] struct MyClass { } +# #[pyclass] 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(mutable)] struct MyClass { } +# #[pyclass] 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 94d993c73cc..a75b2584c9d 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -26,7 +26,6 @@ pub struct PyClassArgs { pub is_basetype: bool, pub has_extends: bool, pub has_unsendable: bool, - pub is_mutable: bool, pub module: Option, } @@ -55,7 +54,6 @@ impl Default for PyClassArgs { is_basetype: false, has_extends: false, has_unsendable: false, - is_mutable: false, } } } @@ -160,9 +158,6 @@ 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" ), @@ -520,52 +515,6 @@ 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; @@ -583,14 +532,21 @@ fn impl_class( } } - #impl_pyclass + impl ::pyo3::PyClass for #cls { + type Dict = #dict; + type WeakRef = #weakref; + type BaseNativeType = #base_nativetype; + } 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 d3b7e1007d4..1e34d493e5d 100644 --- a/src/class/basic.rs +++ b/src/class/basic.rs @@ -9,7 +9,6 @@ //! [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; @@ -129,12 +128,12 @@ pub trait PyObjectGetAttrProtocol<'p>: PyObjectProtocol<'p> { type Name: FromPyObject<'p>; type Result: IntoPyCallbackOutput; } -pub trait PyObjectSetAttrProtocol<'p>: PyObjectProtocol<'p> + MutablePyClass { +pub trait PyObjectSetAttrProtocol<'p>: PyObjectProtocol<'p> { type Name: FromPyObject<'p>; type Value: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyObjectDelAttrProtocol<'p>: PyObjectProtocol<'p> + MutablePyClass { +pub trait PyObjectDelAttrProtocol<'p>: PyObjectProtocol<'p> { type Name: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } diff --git a/src/class/buffer.rs b/src/class/buffer.rs index 655653d7d2a..7b4c8acf958 100644 --- a/src/class/buffer.rs +++ b/src/class/buffer.rs @@ -5,8 +5,7 @@ //! For more information check [buffer protocol](https://docs.python.org/3/c-api/buffer.html) //! c-api use crate::callback::IntoPyCallbackOutput; -use crate::pyclass::MutablePyClass; -use crate::{ffi, PyCell, PyRefMut}; +use crate::{ffi, PyCell, PyClass, PyRefMut}; use std::os::raw::c_int; /// Buffer protocol interface @@ -14,7 +13,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>: MutablePyClass { +pub trait PyBufferProtocol<'p>: PyClass { // 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 @@ -52,7 +51,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> + MutablePyClass, + T: for<'p> PyBufferReleaseBufferProtocol<'p>, { crate::callback_body!(py, { let slf = py.from_borrowed_ptr::>(slf); diff --git a/src/class/gc.rs b/src/class/gc.rs index d5200bfe7d5..3197c0088c3 100644 --- a/src/class/gc.rs +++ b/src/class/gc.rs @@ -2,7 +2,6 @@ //! Python GC support -use crate::pyclass::MutablePyClass; use crate::{ffi, AsPyPointer, PyCell, PyClass, Python}; use std::os::raw::{c_int, c_void}; @@ -54,7 +53,7 @@ where #[doc(hidden)] pub unsafe extern "C" fn clear(slf: *mut ffi::PyObject) -> c_int where - T: for<'p> PyGCClearProtocol<'p> + MutablePyClass, + T: for<'p> PyGCClearProtocol<'p>, { 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 3dd1db3e747..97b47fe3775 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(mutable)] +/// #[pyclass] /// struct Iter { /// count: usize, /// } diff --git a/src/class/mapping.rs b/src/class/mapping.rs index 3456eb26582..9961c5d1770 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> + MutablePyClass { +pub trait PyMappingSetItemProtocol<'p>: PyMappingProtocol<'p> { type Key: FromPyObject<'p>; type Value: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyMappingDelItemProtocol<'p>: PyMappingProtocol<'p> + MutablePyClass { +pub trait PyMappingDelItemProtocol<'p>: PyMappingProtocol<'p> { type Key: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } diff --git a/src/class/number.rs b/src/class/number.rs index 17bf396914d..903744ebc72 100644 --- a/src/class/number.rs +++ b/src/class/number.rs @@ -4,7 +4,6 @@ //! 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 @@ -482,74 +481,74 @@ pub trait PyNumberROrProtocol<'p>: PyNumberProtocol<'p> { type Result: IntoPyCallbackOutput; } -pub trait PyNumberIAddProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIAddProtocol<'p>: PyNumberProtocol<'p> { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberISubProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberISubProtocol<'p>: PyNumberProtocol<'p> { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIMulProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIMulProtocol<'p>: PyNumberProtocol<'p> { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIMatmulProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIMatmulProtocol<'p>: PyNumberProtocol<'p> { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberITruedivProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberITruedivProtocol<'p>: PyNumberProtocol<'p> { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIFloordivProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIFloordivProtocol<'p>: PyNumberProtocol<'p> { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIModProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIModProtocol<'p>: PyNumberProtocol<'p> { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIDivmodProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIDivmodProtocol<'p>: PyNumberProtocol<'p> { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIPowProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIPowProtocol<'p>: PyNumberProtocol<'p> { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } #[allow(clippy::upper_case_acronyms)] -pub trait PyNumberILShiftProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberILShiftProtocol<'p>: PyNumberProtocol<'p> { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } #[allow(clippy::upper_case_acronyms)] -pub trait PyNumberIRShiftProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIRShiftProtocol<'p>: PyNumberProtocol<'p> { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIAndProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIAndProtocol<'p>: PyNumberProtocol<'p> { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIXorProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIXorProtocol<'p>: PyNumberProtocol<'p> { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIOrProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIOrProtocol<'p>: PyNumberProtocol<'p> { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } @@ -751,7 +750,7 @@ pub unsafe extern "C" fn ipow( _modulo: *mut ffi::PyObject, ) -> *mut ffi::PyObject where - T: for<'p> PyNumberIPowProtocol<'p> + MutablePyClass, + T: for<'p> PyNumberIPowProtocol<'p>, { // 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 a4e0ed9193a..129a188a646 100644 --- a/src/class/sequence.rs +++ b/src/class/sequence.rs @@ -6,7 +6,6 @@ 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; @@ -89,13 +88,13 @@ pub trait PySequenceGetItemProtocol<'p>: PySequenceProtocol<'p> { type Result: IntoPyCallbackOutput; } -pub trait PySequenceSetItemProtocol<'p>: PySequenceProtocol<'p> + MutablePyClass { +pub trait PySequenceSetItemProtocol<'p>: PySequenceProtocol<'p> { type Index: FromPyObject<'p> + From; type Value: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PySequenceDelItemProtocol<'p>: PySequenceProtocol<'p> + MutablePyClass { +pub trait PySequenceDelItemProtocol<'p>: PySequenceProtocol<'p> { type Index: FromPyObject<'p> + From; type Result: IntoPyCallbackOutput<()>; } @@ -116,14 +115,14 @@ pub trait PySequenceRepeatProtocol<'p>: PySequenceProtocol<'p> { } pub trait PySequenceInplaceConcatProtocol<'p>: - PySequenceProtocol<'p> + IntoPy + MutablePyClass + 'p + PySequenceProtocol<'p> + IntoPy + 'p { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput; } pub trait PySequenceInplaceRepeatProtocol<'p>: - PySequenceProtocol<'p> + IntoPy + MutablePyClass + 'p + PySequenceProtocol<'p> + IntoPy + 'p { type Index: FromPyObject<'p> + From; type Result: IntoPyCallbackOutput; diff --git a/src/conversion.rs b/src/conversion.rs index 385e3c543f3..e1238edc80e 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -2,7 +2,6 @@ //! 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::{ @@ -146,7 +145,7 @@ where /// ```rust /// use pyo3::prelude::*; /// -/// #[pyclass(mutable)] +/// #[pyclass] /// struct Number { /// #[pyo3(get, set)] /// value: i32, @@ -330,7 +329,7 @@ where impl<'a, T> FromPyObject<'a> for PyRefMut<'a, T> where - T: MutablePyClass, + T: PyClass, { fn extract(obj: &'a PyAny) -> PyResult { let cell: &PyCell = PyTryFrom::try_from(obj)?; diff --git a/src/instance.rs b/src/instance.rs index 4ed03293465..269d22f3107 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -3,7 +3,6 @@ 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, @@ -392,23 +391,6 @@ 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. @@ -421,7 +403,7 @@ where /// ``` /// # use pyo3::prelude::*; /// # - /// #[pyclass(mutable)] + /// #[pyclass] /// struct Foo { /// inner: u8, /// } @@ -445,6 +427,18 @@ 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. @@ -808,7 +802,7 @@ where impl<'a, T> std::convert::From> for Py where - T: MutablePyClass, + T: PyClass, { 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 9b277b81e78..a47c9fedd23 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -25,7 +25,7 @@ //! ```rust //! use pyo3::prelude::*; //! -//! #[pyclass(mutable)] +//! #[pyclass] //! struct Number { //! inner: u32, //! } @@ -60,7 +60,7 @@ //! ```rust //! # use pyo3::prelude::*; //! # -//! # #[pyclass(mutable)] +//! # #[pyclass] //! # struct Number { //! # inner: u32, //! # } @@ -99,7 +99,7 @@ //! Suppose the following function that swaps the values of two `Number`s: //! ``` //! # use pyo3::prelude::*; -//! # #[pyclass(mutable)] +//! # #[pyclass] //! # pub struct Number { //! # inner: u32, //! # } @@ -131,7 +131,7 @@ //! It is better to write that function like this: //! ```rust //! # use pyo3::prelude::*; -//! # #[pyclass(mutable)] +//! # #[pyclass] //! # 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::{MutablePyClass, PyClass}; +use crate::pyclass::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(mutable)] +/// #[pyclass] /// struct Number { /// inner: u32, /// } @@ -352,15 +352,18 @@ 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> { - PyClass::borrow_as_pyref(self) + self.try_borrow().expect("Already mutably borrowed") } - #[inline] - #[doc(hidden)] - pub unsafe fn immutable_pyclass_borrow(&self) -> PyRef<'_, T> { - self.try_borrow().expect("Already mutably borrowed") + /// 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") } /// Immutably borrows the value `T`, returning an error if the value is currently @@ -372,7 +375,7 @@ impl PyCell { /// /// ``` /// # use pyo3::prelude::*; - /// #[pyclass(mutable)] + /// #[pyclass] /// struct Class {} /// /// Python::with_gil(|py| { @@ -388,14 +391,7 @@ 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: () }) @@ -405,102 +401,73 @@ impl PyCell { } } - /// Immutably borrows the value `T`, returning an error if the value is - /// currently mutably borrowed. - /// - /// # Safety + /// Mutably borrows the value `T`, returning an error if the value is currently borrowed. + /// This borrow lasts as long as the returned `PyRefMut` exists. /// - /// 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. + /// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut). /// /// # Examples /// /// ``` /// # use pyo3::prelude::*; - /// #[pyclass(mutable)] + /// #[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()); + /// assert!(c.try_borrow_mut().is_err()); /// } + /// + /// assert!(c.try_borrow_mut().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: () }) + pub fn try_borrow_mut(&self) -> Result, PyBorrowMutError> { + if self.get_borrow_flag() != BorrowFlag::UNUSED { + Err(PyBorrowMutError { _private: () }) } else { - Ok(&*self.contents.value.get()) + self.set_borrow_flag(BorrowFlag::HAS_MUTABLE_BORROW); + Ok(PyRefMut { inner: self }) } } - 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 + /// Immutably borrows the value `T`, returning an error if the value is + /// currently mutably borrowed. /// - /// 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. + /// # Safety /// - /// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut). + /// 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)] + /// #[pyclass] /// struct Class {} /// Python::with_gil(|py| { /// let c = PyCell::new(py, Class {}).unwrap(); + /// /// { - /// let m = c.borrow(); - /// assert!(c.try_borrow_mut().is_err()); + /// let m = c.borrow_mut(); + /// assert!(unsafe { c.try_borrow_unguarded() }.is_err()); /// } /// - /// assert!(c.try_borrow_mut().is_ok()); + /// { + /// let m = c.borrow(); + /// assert!(unsafe { c.try_borrow_unguarded() }.is_ok()); + /// } /// }); /// ``` - pub fn try_borrow_mut(&self) -> Result, PyBorrowMutError> { - if self.get_borrow_flag() != BorrowFlag::UNUSED { - Err(PyBorrowMutError { _private: () }) + pub unsafe fn try_borrow_unguarded(&self) -> Result<&T, PyBorrowError> { + if self.get_borrow_flag() == BorrowFlag::HAS_MUTABLE_BORROW { + Err(PyBorrowError { _private: () }) } else { - self.set_borrow_flag(BorrowFlag::HAS_MUTABLE_BORROW); - Ok(PyRefMut { inner: self }) + Ok(&*self.contents.value.get()) } } + /// Replaces the wrapped value with a new one, returning the old value. /// /// # Panics @@ -531,6 +498,10 @@ 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 {} @@ -634,12 +605,6 @@ 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> @@ -722,7 +687,8 @@ impl<'p, T: PyClass> Deref for PyRef<'p, T> { impl<'p, T: PyClass> Drop for PyRef<'p, T> { fn drop(&mut self) { - unsafe { PyClass::drop_pyref(self) } + let flag = self.inner.get_borrow_flag(); + self.inner.set_borrow_flag(flag.decrement()) } } @@ -754,11 +720,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: MutablePyClass> { +pub struct PyRefMut<'p, T: PyClass> { inner: &'p PyCell, } -impl<'p, T: MutablePyClass> PyRefMut<'p, T> { +impl<'p, T: PyClass> 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() } @@ -767,8 +733,8 @@ impl<'p, T: MutablePyClass> PyRefMut<'p, T> { impl<'p, T, U> AsRef for PyRefMut<'p, T> where - T: PyClass + MutablePyClass, - U: MutablePyClass, + T: PyClass, + U: PyClass, { fn as_ref(&self) -> &T::BaseType { unsafe { &*self.inner.ob_base.get_ptr() } @@ -777,8 +743,8 @@ where impl<'p, T, U> AsMut for PyRefMut<'p, T> where - T: PyClass + MutablePyClass, - U: MutablePyClass, + T: PyClass, + U: PyClass, { fn as_mut(&mut self) -> &mut T::BaseType { unsafe { &mut *self.inner.ob_base.get_ptr() } @@ -787,8 +753,8 @@ where impl<'p, T, U> PyRefMut<'p, T> where - T: PyClass + MutablePyClass, - U: MutablePyClass, + T: PyClass, + U: PyClass, { /// Gets a `PyRef`. /// @@ -802,7 +768,7 @@ where } } -impl<'p, T: MutablePyClass> Deref for PyRefMut<'p, T> { +impl<'p, T: PyClass> Deref for PyRefMut<'p, T> { type Target = T; #[inline] @@ -811,39 +777,39 @@ impl<'p, T: MutablePyClass> Deref for PyRefMut<'p, T> { } } -impl<'p, T: MutablePyClass> DerefMut for PyRefMut<'p, T> { +impl<'p, T: PyClass> DerefMut for PyRefMut<'p, T> { #[inline] fn deref_mut(&mut self) -> &mut T { unsafe { &mut *self.inner.get_ptr() } } } -impl<'p, T: MutablePyClass> Drop for PyRefMut<'p, T> { +impl<'p, T: PyClass> 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: MutablePyClass> AsPyPointer for PyRefMut<'a, T> { +impl<'a, T: PyClass> AsPyPointer for PyRefMut<'a, T> { fn as_ptr(&self) -> *mut ffi::PyObject { self.inner.as_ptr() } } -impl<'a, T: MutablePyClass> std::convert::TryFrom<&'a PyCell> for crate::PyRefMut<'a, T> { +impl<'a, T: PyClass> 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 b53b04e6b3e..fc8a4efae93 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -1,5 +1,4 @@ //! `PyClass` and related traits. -use crate::pycell::{PyBorrowError, PyRef}; use crate::{ class::impl_::{fallback_new, tp_dealloc, PyClassImpl}, ffi, @@ -18,14 +17,7 @@ use std::{ /// /// The `#[pyclass]` attribute automatically implements this trait for your Rust struct, /// so you normally don't have to use this trait directly. -/// -/// # 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: +pub trait PyClass: PyTypeInfo> + PyClassImpl> { /// Specify this class has `#[pyclass(dict)]` or not. @@ -35,65 +27,8 @@ pub unsafe 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 6132c523ad5..985d05dc2da 100644 --- a/tests/hygiene/pyclass.rs +++ b/tests/hygiene/pyclass.rs @@ -15,8 +15,7 @@ pub struct Foo2; unsendable, subclass, extends = ::pyo3::types::PyAny, - module = "Spam", - mutable + module = "Spam" )] pub struct Bar { #[pyo3(get, set)] diff --git a/tests/hygiene/pymethods.rs b/tests/hygiene/pymethods.rs index 491707812a3..37a916f9c6b 100644 --- a/tests/hygiene/pymethods.rs +++ b/tests/hygiene/pymethods.rs @@ -1,10 +1,10 @@ #![no_implicit_prelude] #![allow(unused_variables)] -#[::pyo3::pyclass(mutable)] +#[::pyo3::pyclass] pub struct Dummy; -#[::pyo3::pyclass(mutable)] +#[::pyo3::pyclass] pub struct DummyIter; #[::pyo3::pymethods] diff --git a/tests/hygiene/pyproto.rs b/tests/hygiene/pyproto.rs index 6bba285db22..9f6b0af8b8c 100644 --- a/tests/hygiene/pyproto.rs +++ b/tests/hygiene/pyproto.rs @@ -16,8 +16,7 @@ pub struct Foo2; gc, subclass, extends = ::pyo3::types::PyAny, - module = "Spam", - mutable + module = "Spam" )] pub struct Bar { #[pyo3(get, set)] diff --git a/tests/test_arithmetics.rs b/tests/test_arithmetics.rs index d747edf4277..1faf8492950 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(mutable)] +#[pyclass] struct InPlaceOperations { value: u32, } @@ -500,7 +500,7 @@ fn rich_comparisons_python_3_type_error() { mod return_not_implemented { use super::*; - #[pyclass(mutable)] + #[pyclass] struct RichComparisonToSelf {} #[pymethods] diff --git a/tests/test_arithmetics_protos.rs b/tests/test_arithmetics_protos.rs index 73767913231..9036d7375a7 100644 --- a/tests/test_arithmetics_protos.rs +++ b/tests/test_arithmetics_protos.rs @@ -67,7 +67,7 @@ impl PyObjectProtocol for BinaryArithmetic { } } -#[pyclass(mutable)] +#[pyclass] struct InPlaceOperations { value: u32, } @@ -527,7 +527,7 @@ fn rich_comparisons_python_3_type_error() { mod return_not_implemented { use super::*; - #[pyclass(mutable)] + #[pyclass] struct RichComparisonToSelf {} #[pyproto] diff --git a/tests/test_buffer.rs b/tests/test_buffer.rs index eeb33e56cd4..bcdfda1cd5f 100644 --- a/tests/test_buffer.rs +++ b/tests/test_buffer.rs @@ -21,7 +21,7 @@ enum TestGetBufferError { IncorrectAlignment, } -#[pyclass(mutable)] +#[pyclass] struct TestBufferErrors { buf: Vec, error: Option, diff --git a/tests/test_buffer_protocol.rs b/tests/test_buffer_protocol.rs index 76cbfa8a8e8..f7174449004 100644 --- a/tests/test_buffer_protocol.rs +++ b/tests/test_buffer_protocol.rs @@ -15,7 +15,7 @@ use std::sync::Arc; mod common; -#[pyclass(mutable)] +#[pyclass] struct TestBufferClass { vec: Vec, drop_called: Arc, diff --git a/tests/test_class_basics.rs b/tests/test_class_basics.rs index cfff132d951..193a216b14a 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(mutable)] +#[pyclass] struct ClassWithDocs { /// Property field #[pyo3(get, set)] @@ -122,7 +122,7 @@ fn custom_names() { py_assert!(py, typeobj, "not hasattr(typeobj, 'foo')"); } -#[pyclass(mutable)] +#[pyclass] struct RawIdents { #[pyo3(get, set)] r#type: i64, @@ -171,7 +171,7 @@ fn empty_class_in_module() { assert_eq!(module, "builtins"); } -#[pyclass(mutable)] +#[pyclass] 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(mutable)] +#[pyclass] struct TupleClass(#[pyo3(get, set, name = "value")] i32); #[test] diff --git a/tests/test_class_conversion.rs b/tests/test_class_conversion.rs index 3619d643a76..d379f774e0c 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(mutable)] +#[pyclass] #[derive(Clone, Debug, PartialEq)] struct Cloneable { x: i32, @@ -30,7 +30,7 @@ fn test_cloneable_pyclass() { assert_eq!(&c, &*mrc); } -#[pyclass(subclass, mutable)] +#[pyclass(subclass)] #[derive(Default)] struct BaseClass { value: i32, @@ -43,7 +43,7 @@ impl BaseClass { } } -#[pyclass(extends=BaseClass, mutable)] +#[pyclass(extends=BaseClass)] struct SubClass {} #[pymethods] @@ -53,7 +53,7 @@ impl SubClass { } } -#[pyclass(mutable)] +#[pyclass] struct PolymorphicContainer { #[pyo3(get, set)] inner: Py, diff --git a/tests/test_gc.rs b/tests/test_gc.rs index 642af2c6c59..3d8f64bca20 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -81,7 +81,7 @@ fn data_is_dropped() { } #[allow(dead_code)] -#[pyclass(mutable)] +#[pyclass] struct GcIntegration { self_ref: PyObject, dropped: TestDropCall, @@ -127,7 +127,7 @@ fn gc_integration() { assert!(drop_called.load(Ordering::Relaxed)); } -#[pyclass(gc, mutable)] +#[pyclass(gc)] struct GcIntegration2 {} #[pyproto] @@ -181,7 +181,7 @@ fn inherited_weakref() { ); } -#[pyclass(subclass, mutable)] +#[pyclass(subclass)] struct BaseClassWithDrop { data: Option>, } @@ -202,7 +202,7 @@ impl Drop for BaseClassWithDrop { } } -#[pyclass(extends = BaseClassWithDrop, mutable)] +#[pyclass(extends = BaseClassWithDrop)] struct SubClassWithDrop { data: Option>, } @@ -249,7 +249,7 @@ fn inheritance_with_new_methods_with_drop() { assert!(drop_called2.load(Ordering::Relaxed)); } -#[pyclass(gc, mutable)] +#[pyclass(gc)] struct TraversableClass { traversed: AtomicBool, } diff --git a/tests/test_getter_setter.rs b/tests/test_getter_setter.rs index 673ff132dbe..38b9761ae8e 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(mutable)] +#[pyclass] struct ClassWithProperties { num: i32, } @@ -65,7 +65,7 @@ fn class_with_properties() { py_assert!(py, *d, "C.DATA.__doc__ == 'a getter for data'"); } -#[pyclass(mutable)] +#[pyclass] struct GetterSetter { #[pyo3(get, set)] num: i32, @@ -103,7 +103,7 @@ fn getter_setter_autogen() { ); } -#[pyclass(mutable)] +#[pyclass] struct RefGetterSetter { num: i32, } @@ -133,7 +133,7 @@ fn ref_getter_setter() { py_run!(py, inst, "inst.num = 20; assert inst.num == 20"); } -#[pyclass(mutable)] +#[pyclass] struct TupleClassGetterSetter(i32); #[pymethods] diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index b6e4c8328c7..01b124b8be9 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -5,7 +5,7 @@ use pyo3::types::IntoPyDict; mod common; -#[pyclass(subclass, mutable)] +#[pyclass(subclass)] struct BaseClass { #[pyo3(get)] val1: usize, @@ -45,7 +45,7 @@ impl BaseClass { } } -#[pyclass(extends=BaseClass, mutable)] +#[pyclass(extends=BaseClass)] struct SubClass { #[pyo3(get)] val2: usize, diff --git a/tests/test_macros.rs b/tests/test_macros.rs index bfcad50b918..78d68ab586e 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(mutable)] + #[pyclass] struct ClassWithProperty { member: u64, } diff --git a/tests/test_mapping.rs b/tests/test_mapping.rs index 27d5322eeac..42710094e76 100644 --- a/tests/test_mapping.rs +++ b/tests/test_mapping.rs @@ -11,7 +11,7 @@ use pyo3::PyMappingProtocol; mod common; -#[pyclass(mutable)] +#[pyclass] struct Mapping { index: HashMap, } diff --git a/tests/test_methods.rs b/tests/test_methods.rs index a02b487b690..1032acf9a0d 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -675,7 +675,7 @@ fn method_with_lifetime() { ); } -#[pyclass(mutable)] +#[pyclass] 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(mutable)] +#[pyclass] struct r#RawIdents { #[pyo3(get, set)] r#type: PyObject, diff --git a/tests/test_proto_methods.rs b/tests/test_proto_methods.rs index 47c16e2ffd1..d5324bf67d2 100644 --- a/tests/test_proto_methods.rs +++ b/tests/test_proto_methods.rs @@ -6,7 +6,7 @@ use std::{isize, iter}; mod common; -#[pyclass(mutable)] +#[pyclass] struct ExampleClass { #[pyo3(get, set)] value: i32, @@ -194,7 +194,7 @@ fn len() { py_expect_exception!(py, inst, "len(inst)", PyOverflowError); } -#[pyclass(mutable)] +#[pyclass] struct Iterator { iter: Box + Send>, } @@ -278,7 +278,7 @@ mod deprecated { } } -#[pyclass(mutable)] +#[pyclass] #[derive(Debug)] struct SetItem { key: i32, @@ -308,7 +308,7 @@ fn setitem() { py_expect_exception!(py, c, "del c[1]", PyNotImplementedError); } -#[pyclass(mutable)] +#[pyclass] struct DelItem { key: i32, } @@ -334,7 +334,7 @@ fn delitem() { py_expect_exception!(py, c, "c[1] = 2", PyNotImplementedError); } -#[pyclass(mutable)] +#[pyclass] struct SetDelItem { val: Option, } @@ -418,7 +418,7 @@ fn test_getitem() { py_assert!(py, ob, "ob[100:200:1] == 'slice'"); } -#[pyclass(mutable)] +#[pyclass] 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(mutable)] +#[pyclass] struct OnceFuture { future: PyObject, polled: bool, @@ -505,7 +505,7 @@ loop.close() } /// Increment the count when `__get__` is called. -#[pyclass(mutable)] +#[pyclass] struct DescrCounter { #[pyo3(get)] count: usize, diff --git a/tests/test_pyproto.rs b/tests/test_pyproto.rs index 4ea1ee7fa04..da2dc0dee32 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(mutable)] +#[pyclass] struct Iterator { iter: Box + Send>, } @@ -149,7 +149,7 @@ fn comparisons() { py_assert!(py, zero, "not zero"); } -#[pyclass(mutable)] +#[pyclass] #[derive(Debug)] struct Sequence { fields: Vec, @@ -210,7 +210,7 @@ fn sequence() { py_expect_exception!(py, c, "c['abc']", PyTypeError); } -#[pyclass(mutable)] +#[pyclass] #[derive(Debug)] struct SetItem { key: i32, @@ -240,7 +240,7 @@ fn setitem() { py_expect_exception!(py, c, "del c[1]", PyNotImplementedError); } -#[pyclass(mutable)] +#[pyclass] struct DelItem { key: i32, } @@ -266,7 +266,7 @@ fn delitem() { py_expect_exception!(py, c, "c[1] = 2", PyNotImplementedError); } -#[pyclass(mutable)] +#[pyclass] struct SetDelItem { val: Option, } @@ -338,7 +338,7 @@ fn contains() { py_expect_exception!(py, c, "assert 'wrong type' not in c", PyTypeError); } -#[pyclass(mutable)] +#[pyclass] struct ContextManager { exit_called: bool, } @@ -507,7 +507,7 @@ fn weakref_dunder_dict_support() { ); } -#[pyclass(mutable)] +#[pyclass] 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(mutable)] +#[pyclass] struct OnceFuture { future: PyObject, polled: bool, @@ -600,7 +600,7 @@ loop.close() } /// Increment the count when `__get__` is called. -#[pyclass(mutable)] +#[pyclass] struct DescrCounter { #[pyo3(get)] count: usize, diff --git a/tests/test_pyself.rs b/tests/test_pyself.rs index e117c63ce76..31dec01a639 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(mutable)] +#[pyclass] #[derive(Clone, Debug)] struct Reader { inner: HashMap, @@ -44,7 +44,7 @@ impl Reader { } } -#[pyclass(mutable)] +#[pyclass] #[derive(Debug)] struct Iter { reader: Py, diff --git a/tests/test_sequence.rs b/tests/test_sequence.rs index 5549c9bfa74..0cb467ee648 100644 --- a/tests/test_sequence.rs +++ b/tests/test_sequence.rs @@ -7,7 +7,7 @@ use pyo3::py_run; mod common; -#[pyclass(mutable)] +#[pyclass] struct ByteSequence { elements: Vec, } @@ -232,7 +232,7 @@ fn test_inplace_repeat() { // Check that #[pyo3(get, set)] works correctly for Vec -#[pyclass(mutable)] +#[pyclass] struct GenericList { #[pyo3(get, set)] items: Vec, @@ -265,7 +265,7 @@ fn test_generic_list_set() { ); } -#[pyclass(mutable)] +#[pyclass] struct OptionList { #[pyo3(get, set)] items: Vec>, diff --git a/tests/test_various.rs b/tests/test_various.rs index dd31b6b737d..6e570fa21a1 100644 --- a/tests/test_various.rs +++ b/tests/test_various.rs @@ -6,7 +6,7 @@ use std::fmt; mod common; -#[pyclass(mutable)] +#[pyclass] struct MutRefArg { n: i32, } From 0fa03a67cd18621c9130fd530502fad0232b3330 Mon Sep 17 00:00:00 2001 From: mejrs Date: Mon, 22 Nov 2021 09:26:34 +0100 Subject: [PATCH 05/16] Implement opt-in immutable pyclasses --- pyo3-macros-backend/src/pyclass.rs | 47 +++++++- src/class/basic.rs | 12 +- src/class/buffer.rs | 4 +- src/class/gc.rs | 4 +- src/class/impl_.rs | 25 +++- src/class/mapping.rs | 6 +- src/class/number.rs | 30 ++--- src/class/sequence.rs | 10 +- src/conversion.rs | 5 +- src/instance.rs | 16 ++- src/pycell.rs | 109 +++++++++++++----- src/pyclass.rs | 3 + tests/test_compile_error.rs | 1 + tests/ui/invalid_immutable_pyclass_borrow.rs | 13 +++ .../invalid_immutable_pyclass_borrow.stderr | 5 + tests/ui/pyclass_send.stderr | 4 +- 16 files changed, 220 insertions(+), 74 deletions(-) create mode 100644 tests/ui/invalid_immutable_pyclass_borrow.rs create mode 100644 tests/ui/invalid_immutable_pyclass_borrow.stderr diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index a75b2584c9d..9c07c26a516 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -27,6 +27,7 @@ pub struct PyClassArgs { pub has_extends: bool, pub has_unsendable: bool, pub module: Option, + pub is_immutable: bool, } impl Parse for PyClassArgs { @@ -54,6 +55,7 @@ impl Default for PyClassArgs { is_basetype: false, has_extends: false, has_unsendable: false, + is_immutable: false, } } } @@ -158,6 +160,9 @@ impl PyClassArgs { "unsendable" => { self.has_unsendable = true; } + "immutable" => { + self.is_immutable = true; + } _ => bail_spanned!( exp.path.span() => "expected one of gc/weakref/subclass/dict/unsendable" ), @@ -515,6 +520,41 @@ fn impl_class( let is_basetype = attr.is_basetype; let is_subclass = attr.has_extends; + let mutability = if attr.is_immutable { + quote! { + unsafe impl ::pyo3::pyclass::ImmutablePyClass for #cls {} + + unsafe impl ::pyo3::class::impl_::BorrowImpl for #cls { + fn get_borrow_flag() -> for<'r> fn(&'r ::pyo3::pycell::PyCell) -> ::pyo3::pycell::BorrowFlag + where Self: ::pyo3::PyClass + { + ::pyo3::pycell::impl_::get_borrow_flag_dummy + } + fn increment_borrow_flag() -> for<'r> fn(&'r ::pyo3::pycell::PyCell, ::pyo3::pycell::BorrowFlag) + where Self: ::pyo3::PyClass + { + ::pyo3::pycell::impl_::increment_borrow_flag_dummy + } + fn decrement_borrow_flag() -> for<'r> fn(&'r ::pyo3::pycell::PyCell, ::pyo3::pycell::BorrowFlag) + where Self: ::pyo3::PyClass + { + ::pyo3::pycell::impl_::decrement_borrow_flag_dummy + } + } + } + } else { + quote! { + unsafe impl ::pyo3::pyclass::MutablePyClass for #cls {} + + unsafe impl ::pyo3::class::impl_::BorrowImpl for #cls {} + + impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a mut #cls + { + type Target = ::pyo3::PyRefMut<'a, #cls>; + } + } + }; + Ok(quote! { unsafe impl ::pyo3::type_object::PyTypeInfo for #cls { type AsRefTarget = ::pyo3::PyCell; @@ -538,15 +578,14 @@ fn impl_class( type BaseNativeType = #base_nativetype; } + #mutability + 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 1e34d493e5d..d4c1bd3ff3d 100644 --- a/src/class/basic.rs +++ b/src/class/basic.rs @@ -9,7 +9,9 @@ //! [typeobj docs](https://docs.python.org/3/c-api/typeobj.html) use crate::callback::{HashCallbackOutput, IntoPyCallbackOutput}; -use crate::{exceptions, ffi, FromPyObject, PyAny, PyCell, PyClass, PyObject}; +use crate::{ + exceptions, ffi, pyclass::MutablePyClass, FromPyObject, PyAny, PyCell, PyClass, PyObject, +}; use std::os::raw::c_int; /// Operators for the `__richcmp__` method @@ -55,14 +57,14 @@ pub trait PyObjectProtocol<'p>: PyClass { fn __setattr__(&'p mut self, name: Self::Name, value: Self::Value) -> Self::Result where - Self: PyObjectSetAttrProtocol<'p>, + Self: PyObjectSetAttrProtocol<'p> + MutablePyClass, { unimplemented!() } fn __delattr__(&'p mut self, name: Self::Name) -> Self::Result where - Self: PyObjectDelAttrProtocol<'p>, + Self: PyObjectDelAttrProtocol<'p> + MutablePyClass, { unimplemented!() } @@ -128,12 +130,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 7b4c8acf958..be47a5c2bac 100644 --- a/src/class/buffer.rs +++ b/src/class/buffer.rs @@ -5,7 +5,7 @@ //! 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::{ffi, pyclass::MutablePyClass, PyCell, PyRefMut}; use std::os::raw::c_int; /// Buffer protocol interface @@ -13,7 +13,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 diff --git a/src/class/gc.rs b/src/class/gc.rs index 3197c0088c3..9e942a17e33 100644 --- a/src/class/gc.rs +++ b/src/class/gc.rs @@ -2,7 +2,7 @@ //! Python GC support -use crate::{ffi, AsPyPointer, PyCell, PyClass, Python}; +use crate::{ffi, pyclass::MutablePyClass, AsPyPointer, PyCell, Python}; use std::os::raw::{c_int, c_void}; #[repr(transparent)] @@ -10,7 +10,7 @@ pub struct PyTraverseError(c_int); /// GC support #[allow(clippy::upper_case_acronyms)] -pub trait PyGCProtocol<'p>: PyClass { +pub trait PyGCProtocol<'p>: MutablePyClass { fn __traverse__(&'p self, visit: PyVisit) -> Result<(), PyTraverseError>; fn __clear__(&'p mut self); } diff --git a/src/class/impl_.rs b/src/class/impl_.rs index e9fb6492034..f44cf3e0cc1 100644 --- a/src/class/impl_.rs +++ b/src/class/impl_.rs @@ -4,7 +4,7 @@ use crate::{ exceptions::{PyAttributeError, PyNotImplementedError}, ffi, impl_::freelist::FreeList, - pycell::PyCellLayout, + pycell::{self, PyCellLayout}, pyclass_init::PyObjectInit, type_object::{PyLayout, PyTypeObject}, PyClass, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python, @@ -39,7 +39,7 @@ impl Copy for PyClassImplCollector {} /// /// Users are discouraged from implementing this trait manually; it is a PyO3 implementation detail /// and may be changed at any time. -pub trait PyClassImpl: Sized { +pub trait PyClassImpl: Sized + BorrowImpl { /// Class doc string const DOC: &'static str = "\0"; @@ -83,6 +83,27 @@ pub trait PyClassImpl: Sized { } } +pub unsafe trait BorrowImpl { + fn get_borrow_flag() -> for<'r> fn(&'r pycell::PyCell) -> pycell::BorrowFlag + where + Self: PyClass, + { + pycell::impl_::get_borrow_flag + } + fn increment_borrow_flag() -> for<'r> fn(&'r pycell::PyCell, pycell::BorrowFlag) + where + Self: PyClass, + { + pycell::impl_::increment_borrow_flag + } + fn decrement_borrow_flag() -> for<'r> fn(&'r pycell::PyCell, pycell::BorrowFlag) + where + Self: PyClass, + { + pycell::impl_::decrement_borrow_flag + } +} + // Traits describing known special methods. pub trait PyClassNewImpl { diff --git a/src/class/mapping.rs b/src/class/mapping.rs index 9961c5d1770..d0ecceea212 100644 --- a/src/class/mapping.rs +++ b/src/class/mapping.rs @@ -4,7 +4,7 @@ //! Trait and support implementation for implementing mapping support use crate::callback::IntoPyCallbackOutput; -use crate::{FromPyObject, PyClass, PyObject}; +use crate::{pyclass::MutablePyClass, FromPyObject, PyClass, PyObject}; /// Mapping interface #[allow(unused_variables)] @@ -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 903744ebc72..dc7f7c2380d 100644 --- a/src/class/number.rs +++ b/src/class/number.rs @@ -4,7 +4,7 @@ //! Trait and support implementation for implementing number protocol use crate::callback::IntoPyCallbackOutput; use crate::err::PyErr; -use crate::{ffi, FromPyObject, PyClass, PyObject}; +use crate::{ffi, pyclass::MutablePyClass, FromPyObject, PyClass, PyObject}; /// Number interface #[allow(unused_variables)] @@ -481,74 +481,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<()>; } diff --git a/src/class/sequence.rs b/src/class/sequence.rs index 129a188a646..6f453d78561 100644 --- a/src/class/sequence.rs +++ b/src/class/sequence.rs @@ -6,7 +6,7 @@ use crate::callback::IntoPyCallbackOutput; use crate::conversion::{FromPyObject, IntoPy}; use crate::err::PyErr; -use crate::{exceptions, ffi, PyAny, PyCell, PyClass, PyObject}; +use crate::{exceptions, ffi, pyclass::MutablePyClass, PyAny, PyCell, PyClass, PyObject}; use std::os::raw::c_int; /// Sequence interface @@ -88,13 +88,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 +115,14 @@ pub trait PySequenceRepeatProtocol<'p>: PySequenceProtocol<'p> { } pub trait PySequenceInplaceConcatProtocol<'p>: - PySequenceProtocol<'p> + IntoPy + 'p + PySequenceProtocol<'p> + IntoPy + MutablePyClass { 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 e1238edc80e..be7ec5e766d 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -5,7 +5,8 @@ use crate::err::{self, PyDowncastError, PyResult}; use crate::type_object::PyTypeInfo; use crate::types::PyTuple; use crate::{ - ffi, gil, Py, PyAny, PyCell, PyClass, PyNativeType, PyObject, PyRef, PyRefMut, Python, + ffi, gil, pyclass::MutablePyClass, Py, PyAny, PyCell, PyClass, PyNativeType, PyObject, PyRef, + PyRefMut, Python, }; use std::ptr::NonNull; @@ -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 269d22f3107..77dcd74f605 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -5,8 +5,8 @@ use crate::gil; use crate::pycell::{PyBorrowError, PyBorrowMutError, PyCell}; use crate::types::{PyDict, PyTuple}; use crate::{ - ffi, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyAny, PyClass, PyClassInitializer, - PyRef, PyRefMut, PyTypeInfo, Python, ToPyObject, + ffi, pyclass::MutablePyClass, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyAny, PyClass, + PyClassInitializer, PyRef, PyRefMut, PyTypeInfo, Python, ToPyObject, }; use std::marker::PhantomData; use std::mem; @@ -423,7 +423,10 @@ where /// # Panics /// Panics if the value is currently mutably borrowed. For a non-panicking variant, use /// [`try_borrow_mut`](#method.try_borrow_mut). - pub fn borrow_mut<'py>(&'py self, py: Python<'py>) -> PyRefMut<'py, T> { + pub fn borrow_mut<'py>(&'py self, py: Python<'py>) -> PyRefMut<'py, T> + where + T: MutablePyClass, + { self.as_ref(py).borrow_mut() } @@ -450,7 +453,10 @@ where pub fn try_borrow_mut<'py>( &'py self, py: Python<'py>, - ) -> Result, PyBorrowMutError> { + ) -> Result, PyBorrowMutError> + where + T: MutablePyClass, + { self.as_ref(py).try_borrow_mut() } } @@ -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 a47c9fedd23..caad504f686 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -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::{ImmutablePyClass, MutablePyClass, PyClass}; use crate::pyclass_init::PyClassInitializer; use crate::pyclass_slots::{PyClassDict, PyClassWeakRef}; use crate::type_object::{PyLayout, PySizedLayout}; @@ -362,7 +362,10 @@ impl PyCell { /// /// 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> { + pub fn borrow_mut(&self) -> PyRefMut<'_, T> + where + T: MutablePyClass, + { self.try_borrow_mut().expect("Already borrowed") } @@ -392,11 +395,11 @@ impl PyCell { /// }); /// ``` pub fn try_borrow(&self) -> Result, PyBorrowError> { - let flag = self.get_borrow_flag(); + let flag = crate::class::impl_::BorrowImpl::get_borrow_flag()(self); if flag == BorrowFlag::HAS_MUTABLE_BORROW { Err(PyBorrowError { _private: () }) } else { - self.set_borrow_flag(flag.increment()); + crate::class::impl_::BorrowImpl::increment_borrow_flag()(self, flag); Ok(PyRef { inner: self }) } } @@ -422,7 +425,10 @@ impl PyCell { /// assert!(c.try_borrow_mut().is_ok()); /// }); /// ``` - pub fn try_borrow_mut(&self) -> Result, PyBorrowMutError> { + pub fn try_borrow_mut(&self) -> Result, PyBorrowMutError> + where + T: MutablePyClass, + { if self.get_borrow_flag() != BorrowFlag::UNUSED { Err(PyBorrowMutError { _private: () }) } else { @@ -461,7 +467,9 @@ impl PyCell { /// }); /// ``` pub unsafe fn try_borrow_unguarded(&self) -> Result<&T, PyBorrowError> { - if self.get_borrow_flag() == BorrowFlag::HAS_MUTABLE_BORROW { + if crate::class::impl_::BorrowImpl::get_borrow_flag()(self) + == BorrowFlag::HAS_MUTABLE_BORROW + { Err(PyBorrowError { _private: () }) } else { Ok(&*self.contents.value.get()) @@ -474,7 +482,10 @@ impl PyCell { /// /// Panics if the value is currently borrowed. #[inline] - pub fn replace(&self, t: T) -> T { + pub fn replace(&self, t: T) -> T + where + T: MutablePyClass, + { std::mem::replace(&mut *self.borrow_mut(), t) } @@ -483,7 +494,10 @@ impl PyCell { /// # Panics /// /// Panics if the value is currently borrowed. - pub fn replace_with T>(&self, f: F) -> T { + pub fn replace_with T>(&self, f: F) -> T + where + T: MutablePyClass, + { let mut_borrow = &mut *self.borrow_mut(); let replacement = f(mut_borrow); std::mem::replace(mut_borrow, replacement) @@ -495,7 +509,10 @@ impl PyCell { /// /// Panics if the value in either `PyCell` is currently borrowed. #[inline] - pub fn swap(&self, other: &Self) { + pub fn swap(&self, other: &Self) + where + T: MutablePyClass, + { std::mem::swap(&mut *self.borrow_mut(), &mut *other.borrow_mut()) } @@ -504,6 +521,42 @@ impl PyCell { } } +#[doc(hidden)] +pub mod impl_ { + use super::*; + + #[inline] + pub fn get_borrow_flag(slf: &PyCell) -> BorrowFlag { + PyCellLayout::get_borrow_flag(slf) + } + + #[inline] + pub fn get_borrow_flag_dummy(_slf: &PyCell) -> BorrowFlag { + debug_assert_eq!(PyCellLayout::get_borrow_flag(_slf), BorrowFlag::UNUSED); + BorrowFlag::UNUSED + } + + #[inline] + pub fn increment_borrow_flag(slf: &PyCell, flag: BorrowFlag) { + PyCellLayout::set_borrow_flag(slf, flag.increment()); + } + + #[inline] + pub fn increment_borrow_flag_dummy(_slf: &PyCell, _flag: BorrowFlag) { + debug_assert_eq!(PyCellLayout::get_borrow_flag(_slf), BorrowFlag::UNUSED); + } + + #[inline] + pub fn decrement_borrow_flag(slf: &PyCell, flag: BorrowFlag) { + PyCellLayout::set_borrow_flag(slf, flag.decrement()); + } + + #[inline] + pub fn decrement_borrow_flag_dummy(_slf: &PyCell, _flag: BorrowFlag) { + debug_assert_eq!(PyCellLayout::get_borrow_flag(_slf), BorrowFlag::UNUSED); + } +} + unsafe impl PyLayout for PyCell {} impl PySizedLayout for PyCell {} @@ -687,8 +740,8 @@ 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()) + let flag = crate::class::impl_::BorrowImpl::get_borrow_flag()(self.inner); + crate::class::impl_::BorrowImpl::decrement_borrow_flag()(self.inner, flag); } } @@ -720,11 +773,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 +786,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 +796,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 +806,8 @@ where impl<'p, T, U> PyRefMut<'p, T> where - T: PyClass, - U: PyClass, + T: PyClass + MutablePyClass, + U: MutablePyClass, { /// Gets a `PyRef`. /// @@ -768,7 +821,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,46 +830,46 @@ 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) } } #[doc(hidden)] -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct BorrowFlag(usize); impl BorrowFlag { @@ -884,6 +937,7 @@ impl From for PyErr { pub trait PyCellLayout: PyLayout { fn get_borrow_flag(&self) -> BorrowFlag; fn set_borrow_flag(&self, flag: BorrowFlag); + /// Implementation of tp_dealloc. /// # Safety /// - slf must be a valid pointer to an instance of a T or a subclass. @@ -902,6 +956,7 @@ where fn set_borrow_flag(&self, flag: BorrowFlag) { self.borrow_flag.set(flag) } + unsafe fn tp_dealloc(slf: *mut ffi::PyObject, py: Python) { // For `#[pyclass]` types which inherit from PyAny, we can just call tp_free if T::type_object_raw(py) == &mut PyBaseObject_Type { diff --git a/src/pyclass.rs b/src/pyclass.rs index fc8a4efae93..92b9628822d 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -29,6 +29,9 @@ pub trait PyClass: type BaseNativeType: PyTypeInfo + PyNativeType; } +pub unsafe trait MutablePyClass: PyClass {} +pub unsafe trait ImmutablePyClass: PyClass {} + /// For collecting slot items. #[derive(Default)] struct TypeSlots(Vec); diff --git a/tests/test_compile_error.rs b/tests/test_compile_error.rs index 4751f774d0e..3c5bf5feca6 100644 --- a/tests/test_compile_error.rs +++ b/tests/test_compile_error.rs @@ -24,6 +24,7 @@ fn _test_compile_errors() { t.compile_fail("tests/ui/invalid_pymethod_names.rs"); t.compile_fail("tests/ui/invalid_argument_attributes.rs"); t.compile_fail("tests/ui/reject_generics.rs"); + t.compile_fail("tests/ui/invalid_immutable_pyclass_borrow.rs"); tests_rust_1_48(&t); tests_rust_1_49(&t); diff --git a/tests/ui/invalid_immutable_pyclass_borrow.rs b/tests/ui/invalid_immutable_pyclass_borrow.rs new file mode 100644 index 00000000000..d020fbec578 --- /dev/null +++ b/tests/ui/invalid_immutable_pyclass_borrow.rs @@ -0,0 +1,13 @@ +use pyo3::prelude::*; + +#[pyclass(immutable)] +pub struct Foo { + #[pyo3(get)] + field: u32, +} + +fn borrow_mut_fails(foo: Py, py: Python){ + let borrow = foo.as_ref(py).borrow_mut(); +} + +fn main(){} \ No newline at end of file diff --git a/tests/ui/invalid_immutable_pyclass_borrow.stderr b/tests/ui/invalid_immutable_pyclass_borrow.stderr new file mode 100644 index 00000000000..3574139a5b3 --- /dev/null +++ b/tests/ui/invalid_immutable_pyclass_borrow.stderr @@ -0,0 +1,5 @@ +error[E0277]: the trait bound `Foo: MutablePyClass` is not satisfied + --> tests/ui/invalid_immutable_pyclass_borrow.rs:10:33 + | +10 | let borrow = foo.as_ref(py).borrow_mut(); + | ^^^^^^^^^^ the trait `MutablePyClass` is not implemented for `Foo` diff --git a/tests/ui/pyclass_send.stderr b/tests/ui/pyclass_send.stderr index 56217f1e916..54f518bd4c7 100644 --- a/tests/ui/pyclass_send.stderr +++ b/tests/ui/pyclass_send.stderr @@ -11,8 +11,8 @@ note: required because it appears within the type `NotThreadSafe` 5 | struct NotThreadSafe { | ^^^^^^^^^^^^^ note: required by a bound in `ThreadCheckerStub` - --> src/class/impl_.rs:710:33 + --> src/class/impl_.rs:731:33 | -710 | pub struct ThreadCheckerStub(PhantomData); +731 | pub struct ThreadCheckerStub(PhantomData); | ^^^^ required by this bound in `ThreadCheckerStub` = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) From a35dc9772d14bf3d08ac29ad44d468b01254268e Mon Sep 17 00:00:00 2001 From: mejrs Date: Mon, 22 Nov 2021 09:36:13 +0100 Subject: [PATCH 06/16] Update guide macro output --- guide/src/class.md | 89 ++++++++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index 7c75c41985c..6538a95443e 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -710,51 +710,57 @@ The `#[pyclass]` macro expands to roughly the code seen below. The `PyClassImplC # #[cfg(not(feature = "multiple-pymethods"))] { # use pyo3::prelude::*; // Note: the implementation differs slightly with the `multiple-pymethods` feature enabled. - -/// Class for demonstration struct MyClass { # #[allow(dead_code)] num: i32, } - -unsafe impl pyo3::PyTypeInfo for MyClass { - type AsRefTarget = PyCell; - +unsafe impl ::pyo3::type_object::PyTypeInfo for MyClass { + type AsRefTarget = ::pyo3::PyCell; const NAME: &'static str = "MyClass"; - const MODULE: Option<&'static str> = None; - + const MODULE: ::std::option::Option<&'static str> = ::std::option::Option::None; #[inline] - fn type_object_raw(py: pyo3::Python) -> *mut pyo3::ffi::PyTypeObject { - use pyo3::type_object::LazyStaticType; + fn type_object_raw(py: ::pyo3::Python<'_>) -> *mut ::pyo3::ffi::PyTypeObject { + use ::pyo3::type_object::LazyStaticType; static TYPE_OBJECT: LazyStaticType = LazyStaticType::new(); TYPE_OBJECT.get_or_init::(py) } } -impl pyo3::pyclass::PyClass for MyClass { - type Dict = pyo3::pyclass_slots::PyClassDummySlot; - type WeakRef = pyo3::pyclass_slots::PyClassDummySlot; - type BaseNativeType = PyAny; +impl ::pyo3::PyClass for MyClass { + type Dict = ::pyo3::pyclass_slots::PyClassDummySlot; + type WeakRef = ::pyo3::pyclass_slots::PyClassDummySlot; + type BaseNativeType = ::pyo3::PyAny; +} + +unsafe impl ::pyo3::pyclass::MutablePyClass for MyClass {} + +unsafe impl ::pyo3::class::impl_::BorrowImpl for MyClass {} + +impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a mut MyClass { + type Target = ::pyo3::PyRefMut<'a, MyClass>; +} + +impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a MyClass { + type Target = ::pyo3::PyRef<'a, MyClass>; } -impl pyo3::IntoPy for MyClass { - fn into_py(self, py: pyo3::Python) -> pyo3::PyObject { - pyo3::IntoPy::into_py(pyo3::Py::new(py, self).unwrap(), py) +impl ::pyo3::IntoPy<::pyo3::PyObject> for MyClass { + fn into_py(self, py: ::pyo3::Python) -> ::pyo3::PyObject { + ::pyo3::IntoPy::into_py(::pyo3::Py::new(py, self).unwrap(), py) } } -impl pyo3::class::impl_::PyClassImpl for MyClass { - const DOC: &'static str = "Class for demonstration\u{0}"; +impl ::pyo3::class::impl_::PyClassImpl for MyClass { + const DOC: &'static str = "\u{0}"; const IS_GC: bool = false; const IS_BASETYPE: bool = false; const IS_SUBCLASS: bool = false; - type Layout = PyCell; - type BaseType = PyAny; - type ThreadChecker = pyo3::class::impl_::ThreadCheckerStub; - - fn for_each_method_def(visitor: &mut dyn FnMut(&[pyo3::class::PyMethodDefType])) { - use pyo3::class::impl_::*; - let collector = PyClassImplCollector::::new(); + type Layout = ::pyo3::PyCell; + type BaseType = ::pyo3::PyAny; + type ThreadChecker = ::pyo3::class::impl_::ThreadCheckerStub; + fn for_each_method_def(visitor: &mut dyn ::std::ops::FnMut(&[::pyo3::class::PyMethodDefType])) { + use ::pyo3::class::impl_::*; + let collector = PyClassImplCollector::::new(); visitor(collector.py_methods()); visitor(collector.py_class_descriptors()); visitor(collector.object_protocol_methods()); @@ -764,24 +770,23 @@ impl pyo3::class::impl_::PyClassImpl for MyClass { visitor(collector.mapping_protocol_methods()); visitor(collector.number_protocol_methods()); } - fn get_new() -> Option { - use pyo3::class::impl_::*; + fn get_new() -> ::std::option::Option<::pyo3::ffi::newfunc> { + use ::pyo3::class::impl_::*; let collector = PyClassImplCollector::::new(); collector.new_impl() } - fn get_alloc() -> Option { - use pyo3::class::impl_::*; + fn get_alloc() -> ::std::option::Option<::pyo3::ffi::allocfunc> { + use ::pyo3::class::impl_::*; let collector = PyClassImplCollector::::new(); collector.alloc_impl() } - fn get_free() -> Option { - use pyo3::class::impl_::*; + fn get_free() -> ::std::option::Option<::pyo3::ffi::freefunc> { + use ::pyo3::class::impl_::*; let collector = PyClassImplCollector::::new(); collector.free_impl() } - fn for_each_proto_slot(visitor: &mut dyn FnMut(&[pyo3::ffi::PyType_Slot])) { - // Implementation which uses dtolnay specialization to load all slots. - use pyo3::class::impl_::*; + fn for_each_proto_slot(visitor: &mut dyn ::std::ops::FnMut(&[::pyo3::ffi::PyType_Slot])) { + use ::pyo3::class::impl_::*; let collector = PyClassImplCollector::::new(); visitor(collector.object_protocol_slots()); visitor(collector.number_protocol_slots()); @@ -794,13 +799,21 @@ impl pyo3::class::impl_::PyClassImpl for MyClass { visitor(collector.buffer_protocol_slots()); visitor(collector.methods_protocol_slots()); } - - fn get_buffer() -> Option<&'static pyo3::class::impl_::PyBufferProcs> { - use pyo3::class::impl_::*; + fn get_buffer() -> ::std::option::Option<&'static ::pyo3::class::impl_::PyBufferProcs> { + use ::pyo3::class::impl_::*; let collector = PyClassImplCollector::::new(); collector.buffer_procs() } } + +impl ::pyo3::class::impl_::PyClassDescriptors + for ::pyo3::class::impl_::PyClassImplCollector +{ + fn py_class_descriptors(self) -> &'static [::pyo3::class::methods::PyMethodDefType] { + static METHODS: &[::pyo3::class::methods::PyMethodDefType] = &[]; + METHODS + } +} # Python::with_gil(|py| { # let cls = py.get_type::(); # pyo3::py_run!(py, cls, "assert cls.__name__ == 'MyClass'") From a44e2f8eeaf715d3afec839e7339cab6956768c8 Mon Sep 17 00:00:00 2001 From: mejrs Date: Mon, 22 Nov 2021 10:19:46 +0100 Subject: [PATCH 07/16] Fix formatting. --- pyo3-macros-backend/src/pyclass.rs | 10 ++++------ tests/ui/pyclass_send.stderr | 5 ++--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 5b38890d39b..8b5e8088a7a 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -632,8 +632,6 @@ impl<'a> PyClassImplsBuilder<'a> { quote! { ::pyo3::PyAny } }; - - quote! { impl ::pyo3::PyClass for #cls { type Dict = #dict; @@ -651,7 +649,7 @@ impl<'a> PyClassImplsBuilder<'a> { type Target = ::pyo3::PyRef<'a, #cls>; } } - } else{ + } else { quote! { impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a #cls { @@ -830,7 +828,7 @@ impl<'a> PyClassImplsBuilder<'a> { if self.attr.is_immutable { quote! { unsafe impl ::pyo3::pyclass::ImmutablePyClass for #cls {} - + unsafe impl ::pyo3::class::impl_::BorrowImpl for #cls { fn get_borrow_flag() -> for<'r> fn(&'r ::pyo3::pycell::PyCell) -> ::pyo3::pycell::BorrowFlag where Self: ::pyo3::PyClass @@ -852,7 +850,7 @@ impl<'a> PyClassImplsBuilder<'a> { } else { quote! { unsafe impl ::pyo3::pyclass::MutablePyClass for #cls {} - + unsafe impl ::pyo3::class::impl_::BorrowImpl for #cls {} } } @@ -912,4 +910,4 @@ impl<'a> PyClassImplsBuilder<'a> { quote! {} } } -} \ No newline at end of file +} diff --git a/tests/ui/pyclass_send.stderr b/tests/ui/pyclass_send.stderr index 2a1831b7136..defaebc41e3 100644 --- a/tests/ui/pyclass_send.stderr +++ b/tests/ui/pyclass_send.stderr @@ -11,9 +11,8 @@ note: required because it appears within the type `NotThreadSafe` 5 | struct NotThreadSafe { | ^^^^^^^^^^^^^ note: required by a bound in `ThreadCheckerStub` - --> src/class/impl_.rs:731:33 + --> src/class/impl_.rs:728:33 | -731 | pub struct ThreadCheckerStub(PhantomData); - +728 | pub struct ThreadCheckerStub(PhantomData); | ^^^^ required by this bound in `ThreadCheckerStub` = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) From 5648815fffea5ef24079df10dda9c4462e7fb92d Mon Sep 17 00:00:00 2001 From: mejrs Date: Fri, 26 Nov 2021 17:16:05 +0100 Subject: [PATCH 08/16] part1 --- guide/src/class.md | 108 ----------------- pyo3-macros-backend/src/pyclass.rs | 34 ++---- src/class/impl_.rs | 25 +--- src/pycell.rs | 185 +++++++++++++++++------------ src/types/mod.rs | 2 +- 5 files changed, 126 insertions(+), 228 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index 3b31e248514..a1b8b8726b0 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -814,115 +814,7 @@ The `#[pyclass]` macro expands to roughly the code seen below. The `PyClassImplC ```rust # #[cfg(not(feature = "multiple-pymethods"))] { -# use pyo3::prelude::*; -// Note: the implementation differs slightly with the `multiple-pymethods` feature enabled. -struct MyClass { - # #[allow(dead_code)] - num: i32, -} -unsafe impl ::pyo3::type_object::PyTypeInfo for MyClass { - type AsRefTarget = ::pyo3::PyCell; - const NAME: &'static str = "MyClass"; - const MODULE: ::std::option::Option<&'static str> = ::std::option::Option::None; - #[inline] - fn type_object_raw(py: ::pyo3::Python<'_>) -> *mut ::pyo3::ffi::PyTypeObject { - use ::pyo3::type_object::LazyStaticType; - static TYPE_OBJECT: LazyStaticType = LazyStaticType::new(); - TYPE_OBJECT.get_or_init::(py) - } -} - -impl ::pyo3::PyClass for MyClass { - type Dict = ::pyo3::pyclass_slots::PyClassDummySlot; - type WeakRef = ::pyo3::pyclass_slots::PyClassDummySlot; - type BaseNativeType = ::pyo3::PyAny; -} - -unsafe impl ::pyo3::pyclass::MutablePyClass for MyClass {} - -unsafe impl ::pyo3::class::impl_::BorrowImpl for MyClass {} - -impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a mut MyClass { - type Target = ::pyo3::PyRefMut<'a, MyClass>; -} - -impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a MyClass { - type Target = ::pyo3::PyRef<'a, MyClass>; -} -impl ::pyo3::IntoPy<::pyo3::PyObject> for MyClass { - fn into_py(self, py: ::pyo3::Python) -> ::pyo3::PyObject { - ::pyo3::IntoPy::into_py(::pyo3::Py::new(py, self).unwrap(), py) - } -} - -impl ::pyo3::class::impl_::PyClassImpl for MyClass { - const DOC: &'static str = "\u{0}"; - const IS_GC: bool = false; - const IS_BASETYPE: bool = false; - const IS_SUBCLASS: bool = false; - type Layout = ::pyo3::PyCell; - type BaseType = ::pyo3::PyAny; - type ThreadChecker = ::pyo3::class::impl_::ThreadCheckerStub; - fn for_each_method_def(visitor: &mut dyn ::std::ops::FnMut(&[::pyo3::class::PyMethodDefType])) { - use ::pyo3::class::impl_::*; - let collector = PyClassImplCollector::::new(); - visitor(collector.py_methods()); - visitor(collector.py_class_descriptors()); - visitor(collector.object_protocol_methods()); - visitor(collector.async_protocol_methods()); - visitor(collector.descr_protocol_methods()); - visitor(collector.mapping_protocol_methods()); - visitor(collector.number_protocol_methods()); - } - fn get_new() -> ::std::option::Option<::pyo3::ffi::newfunc> { - use ::pyo3::class::impl_::*; - let collector = PyClassImplCollector::::new(); - collector.new_impl() - } - fn get_alloc() -> ::std::option::Option<::pyo3::ffi::allocfunc> { - use ::pyo3::class::impl_::*; - let collector = PyClassImplCollector::::new(); - collector.alloc_impl() - } - fn get_free() -> ::std::option::Option<::pyo3::ffi::freefunc> { - use ::pyo3::class::impl_::*; - let collector = PyClassImplCollector::::new(); - collector.free_impl() - } - fn for_each_proto_slot(visitor: &mut dyn ::std::ops::FnMut(&[::pyo3::ffi::PyType_Slot])) { - use ::pyo3::class::impl_::*; - let collector = PyClassImplCollector::::new(); - visitor(collector.object_protocol_slots()); - visitor(collector.number_protocol_slots()); - visitor(collector.iter_protocol_slots()); - visitor(collector.gc_protocol_slots()); - visitor(collector.descr_protocol_slots()); - visitor(collector.mapping_protocol_slots()); - visitor(collector.sequence_protocol_slots()); - visitor(collector.async_protocol_slots()); - visitor(collector.buffer_protocol_slots()); - visitor(collector.methods_protocol_slots()); - } - fn get_buffer() -> ::std::option::Option<&'static ::pyo3::class::impl_::PyBufferProcs> { - use ::pyo3::class::impl_::*; - let collector = PyClassImplCollector::::new(); - collector.buffer_procs() - } -} - -impl ::pyo3::class::impl_::PyClassDescriptors - for ::pyo3::class::impl_::PyClassImplCollector -{ - fn py_class_descriptors(self) -> &'static [::pyo3::class::methods::PyMethodDefType] { - static METHODS: &[::pyo3::class::methods::PyMethodDefType] = &[]; - METHODS - } -} -# Python::with_gil(|py| { -# let cls = py.get_type::(); -# pyo3::py_run!(py, cls, "assert cls.__name__ == 'MyClass'") -# }); # } ``` diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 8b5e8088a7a..21b8c2dd474 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -760,6 +760,17 @@ impl<'a> PyClassImplsBuilder<'a> { } }, }; + + let mutability = if self.attr.is_immutable { + quote! { + ::pyo3::pycell::Immutable + } + } else { + quote! { + ::pyo3::pycell::Mutable + } + }; + quote! { impl ::pyo3::class::impl_::PyClassImpl for #cls { const DOC: &'static str = #doc; @@ -770,6 +781,7 @@ impl<'a> PyClassImplsBuilder<'a> { type Layout = ::pyo3::PyCell; type BaseType = #base; type ThreadChecker = #thread_checker; + type Mutability= #mutability; fn for_each_method_def(visitor: &mut dyn ::std::ops::FnMut(&[::pyo3::class::PyMethodDefType])) { use ::pyo3::class::impl_::*; @@ -828,30 +840,10 @@ impl<'a> PyClassImplsBuilder<'a> { if self.attr.is_immutable { quote! { unsafe impl ::pyo3::pyclass::ImmutablePyClass for #cls {} - - unsafe impl ::pyo3::class::impl_::BorrowImpl for #cls { - fn get_borrow_flag() -> for<'r> fn(&'r ::pyo3::pycell::PyCell) -> ::pyo3::pycell::BorrowFlag - where Self: ::pyo3::PyClass - { - ::pyo3::pycell::impl_::get_borrow_flag_dummy - } - fn increment_borrow_flag() -> for<'r> fn(&'r ::pyo3::pycell::PyCell, ::pyo3::pycell::BorrowFlag) - where Self: ::pyo3::PyClass - { - ::pyo3::pycell::impl_::increment_borrow_flag_dummy - } - fn decrement_borrow_flag() -> for<'r> fn(&'r ::pyo3::pycell::PyCell, ::pyo3::pycell::BorrowFlag) - where Self: ::pyo3::PyClass - { - ::pyo3::pycell::impl_::decrement_borrow_flag_dummy - } - } - } + } } else { quote! { unsafe impl ::pyo3::pyclass::MutablePyClass for #cls {} - - unsafe impl ::pyo3::class::impl_::BorrowImpl for #cls {} } } } diff --git a/src/class/impl_.rs b/src/class/impl_.rs index 84258801667..ddcfeef56c6 100644 --- a/src/class/impl_.rs +++ b/src/class/impl_.rs @@ -39,7 +39,7 @@ impl Copy for PyClassImplCollector {} /// /// Users are discouraged from implementing this trait manually; it is a PyO3 implementation detail /// and may be changed at any time. -pub trait PyClassImpl: Sized + BorrowImpl { +pub trait PyClassImpl: Sized { /// Class doc string const DOC: &'static str = "\0"; @@ -67,6 +67,8 @@ pub trait PyClassImpl: Sized + BorrowImpl { /// can be accessed by multiple threads by `threading` module. type ThreadChecker: PyClassThreadChecker; + type Mutability: crate::pycell::Mutability; + fn for_each_method_def(_visitor: &mut dyn FnMut(&[PyMethodDefType])) {} fn get_new() -> Option { None @@ -83,27 +85,6 @@ pub trait PyClassImpl: Sized + BorrowImpl { } } -pub unsafe trait BorrowImpl { - fn get_borrow_flag() -> for<'r> fn(&'r pycell::PyCell) -> pycell::BorrowFlag - where - Self: PyClass, - { - pycell::impl_::get_borrow_flag - } - fn increment_borrow_flag() -> for<'r> fn(&'r pycell::PyCell, pycell::BorrowFlag) - where - Self: PyClass, - { - pycell::impl_::increment_borrow_flag - } - fn decrement_borrow_flag() -> for<'r> fn(&'r pycell::PyCell, pycell::BorrowFlag) - where - Self: PyClass, - { - pycell::impl_::decrement_borrow_flag - } -} - // Traits describing known special methods. pub trait PyClassNewImpl { diff --git a/src/pycell.rs b/src/pycell.rs index caad504f686..7159db4ffa9 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -192,17 +192,112 @@ use std::cell::{Cell, UnsafeCell}; use std::fmt; use std::mem::ManuallyDrop; use std::ops::{Deref, DerefMut}; +use std::marker::PhantomData; + +pub trait Mutability { + /// Creates a new borrow checker + fn new() -> Self; + /// Increments immutable borrow count, if possible + fn try_borrow(&self) -> Result<(), PyBorrowError>; + + fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError>; + + /// Decrements immutable borrow count + fn release_borrow(&self); + /// Increments mutable borrow count, if possible + fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError>; + /// Decremements mutable borrow count + fn release_borrow_mut(&self); +} + +pub struct Mutable { + flag: Cell, +} +impl Mutability for Mutable { + fn new() -> Self{ + Self{flag: Cell::new(BorrowFlag::UNUSED)} + } + + fn try_borrow(&self) -> Result<(), PyBorrowError>{ + let flag = self.flag.get(); + if flag != BorrowFlag::HAS_MUTABLE_BORROW { + self.flag.set(flag.increment()); + Ok(()) + } + else{ + Err(PyBorrowError { _private: () }) + } + } + + fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError>{ + let flag = self.flag.get(); + if flag != BorrowFlag::HAS_MUTABLE_BORROW { + Ok(()) + } + else{ + Err(PyBorrowError { _private: () }) + } + } + + fn release_borrow(&self){ + let flag = self.flag.get(); + self.flag.set(flag.decrement()) + } + + fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError>{ + let flag = self.flag.get(); + if flag == BorrowFlag::UNUSED { + self.flag.set(BorrowFlag::HAS_MUTABLE_BORROW); + Ok(()) + } + else{ + Err(PyBorrowMutError { _private: () }) + } + } + + fn release_borrow_mut(&self){ + self.flag.set(BorrowFlag::UNUSED) + } +} + +pub struct Immutable{ + flag: PhantomData> +} +impl Mutability for Immutable { + fn new() -> Self{ + Self{flag: PhantomData} + } + + fn try_borrow(&self) -> Result<(), PyBorrowError>{ + Ok(()) + } + + fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError>{ + Ok(()) + } + + fn release_borrow(&self){ + } + + fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError>{ + unreachable!() + } + + fn release_borrow_mut(&self){ + unreachable!() + } +} /// Base layout of PyCell. /// This is necessary for sharing BorrowFlag between parents and children. #[doc(hidden)] #[repr(C)] -pub struct PyCellBase { +pub struct PyCellBase { ob_base: T, - borrow_flag: Cell, + borrow_impl: M, } -unsafe impl PyLayout for PyCellBase where U: PySizedLayout {} +unsafe impl PyLayout for PyCellBase where U: PySizedLayout, M: Mutability {} /// A container type for (mutably) accessing [`PyClass`] values /// @@ -395,13 +490,7 @@ impl PyCell { /// }); /// ``` pub fn try_borrow(&self) -> Result, PyBorrowError> { - let flag = crate::class::impl_::BorrowImpl::get_borrow_flag()(self); - if flag == BorrowFlag::HAS_MUTABLE_BORROW { - Err(PyBorrowError { _private: () }) - } else { - crate::class::impl_::BorrowImpl::increment_borrow_flag()(self, flag); - Ok(PyRef { inner: self }) - } + self.borrow_checker().try_borrow().map(|_| PyRef { inner: self }) } /// Mutably borrows the value `T`, returning an error if the value is currently borrowed. @@ -429,12 +518,7 @@ impl PyCell { where T: MutablePyClass, { - if self.get_borrow_flag() != BorrowFlag::UNUSED { - Err(PyBorrowMutError { _private: () }) - } else { - self.set_borrow_flag(BorrowFlag::HAS_MUTABLE_BORROW); - Ok(PyRefMut { inner: self }) - } + self.borrow_checker().try_borrow_mut().map(|_| PyRefMut { inner: self }) } /// Immutably borrows the value `T`, returning an error if the value is @@ -467,13 +551,7 @@ impl PyCell { /// }); /// ``` pub unsafe fn try_borrow_unguarded(&self) -> Result<&T, PyBorrowError> { - if crate::class::impl_::BorrowImpl::get_borrow_flag()(self) - == BorrowFlag::HAS_MUTABLE_BORROW - { - Err(PyBorrowError { _private: () }) - } else { - Ok(&*self.contents.value.get()) - } + self.borrow_checker().try_borrow_unguarded().map(|_:()| &*self.contents.value.get()) } /// Replaces the wrapped value with a new one, returning the old value. @@ -521,42 +599,6 @@ impl PyCell { } } -#[doc(hidden)] -pub mod impl_ { - use super::*; - - #[inline] - pub fn get_borrow_flag(slf: &PyCell) -> BorrowFlag { - PyCellLayout::get_borrow_flag(slf) - } - - #[inline] - pub fn get_borrow_flag_dummy(_slf: &PyCell) -> BorrowFlag { - debug_assert_eq!(PyCellLayout::get_borrow_flag(_slf), BorrowFlag::UNUSED); - BorrowFlag::UNUSED - } - - #[inline] - pub fn increment_borrow_flag(slf: &PyCell, flag: BorrowFlag) { - PyCellLayout::set_borrow_flag(slf, flag.increment()); - } - - #[inline] - pub fn increment_borrow_flag_dummy(_slf: &PyCell, _flag: BorrowFlag) { - debug_assert_eq!(PyCellLayout::get_borrow_flag(_slf), BorrowFlag::UNUSED); - } - - #[inline] - pub fn decrement_borrow_flag(slf: &PyCell, flag: BorrowFlag) { - PyCellLayout::set_borrow_flag(slf, flag.decrement()); - } - - #[inline] - pub fn decrement_borrow_flag_dummy(_slf: &PyCell, _flag: BorrowFlag) { - debug_assert_eq!(PyCellLayout::get_borrow_flag(_slf), BorrowFlag::UNUSED); - } -} - unsafe impl PyLayout for PyCell {} impl PySizedLayout for PyCell {} @@ -740,8 +782,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 = crate::class::impl_::BorrowImpl::get_borrow_flag()(self.inner); - crate::class::impl_::BorrowImpl::decrement_borrow_flag()(self.inner, flag); + self.inner.borrow_checker().release_borrow() } } @@ -839,7 +880,7 @@ impl<'p, T: MutablePyClass> DerefMut for PyRefMut<'p, T> { impl<'p, T: MutablePyClass> Drop for PyRefMut<'p, T> { fn drop(&mut self) { - self.inner.set_borrow_flag(BorrowFlag::UNUSED) + self.inner.borrow_checker().release_borrow_mut() } } @@ -935,9 +976,7 @@ impl From for PyErr { #[doc(hidden)] pub trait PyCellLayout: PyLayout { - fn get_borrow_flag(&self) -> BorrowFlag; - fn set_borrow_flag(&self, flag: BorrowFlag); - + fn borrow_checker(&self) -> &Mutable; /// Implementation of tp_dealloc. /// # Safety /// - slf must be a valid pointer to an instance of a T or a subclass. @@ -945,16 +984,13 @@ pub trait PyCellLayout: PyLayout { unsafe fn tp_dealloc(slf: *mut ffi::PyObject, py: Python); } -impl PyCellLayout for PyCellBase +impl PyCellLayout for PyCellBase where U: PySizedLayout, T: PyTypeInfo, { - fn get_borrow_flag(&self) -> BorrowFlag { - self.borrow_flag.get() - } - fn set_borrow_flag(&self, flag: BorrowFlag) { - self.borrow_flag.set(flag) + fn borrow_checker(&self) -> &Mutable { + &self.borrow_impl } unsafe fn tp_dealloc(slf: *mut ffi::PyObject, py: Python) { @@ -982,12 +1018,9 @@ impl PyCellLayout for PyCell where ::LayoutAsBase: PyCellLayout, { - fn get_borrow_flag(&self) -> BorrowFlag { + fn borrow_checker(&self) -> &Mutable { self.contents.thread_checker.ensure(); - self.ob_base.get_borrow_flag() - } - fn set_borrow_flag(&self, flag: BorrowFlag) { - self.ob_base.set_borrow_flag(flag) + self.ob_base.borrow_checker() } unsafe fn tp_dealloc(slf: *mut ffi::PyObject, py: Python) { // Safety: Python only calls tp_dealloc when no references to the object remain. diff --git a/src/types/mod.rs b/src/types/mod.rs index f670a32db92..d8285412d4d 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -198,7 +198,7 @@ macro_rules! pyobject_native_type_sized { impl<'a, $($generics,)*> $crate::class::impl_::PyClassBaseType for $name { type Dict = $crate::pyclass_slots::PyClassDummySlot; type WeakRef = $crate::pyclass_slots::PyClassDummySlot; - type LayoutAsBase = $crate::pycell::PyCellBase<$layout>; + type LayoutAsBase = $crate::pycell::PyCellBase<$layout, $crate::pycell::Mutable>; type BaseNativeType = $name; type ThreadChecker = $crate::class::impl_::ThreadCheckerStub<$crate::PyObject>; type Initializer = $crate::pyclass_init::PyNativeTypeInitializer; From 63eda2f5c97be3f68f47096f8f393332c92a3b76 Mon Sep 17 00:00:00 2001 From: mejrs Date: Fri, 26 Nov 2021 21:19:17 +0100 Subject: [PATCH 09/16] part 2 --- src/class/impl_.rs | 4 +++- src/pycell.rs | 13 ++++++++----- src/pyclass.rs | 3 +++ src/pyclass_init.rs | 4 ++-- src/types/mod.rs | 1 + 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/class/impl_.rs b/src/class/impl_.rs index ddcfeef56c6..b77fe57b2f3 100644 --- a/src/class/impl_.rs +++ b/src/class/impl_.rs @@ -4,7 +4,7 @@ use crate::{ exceptions::{PyAttributeError, PyNotImplementedError}, ffi, impl_::freelist::FreeList, - pycell::{self, PyCellLayout}, + pycell::{PyCellLayout, Mutability}, pyclass_init::PyObjectInit, type_object::{PyLayout, PyTypeObject}, PyClass, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python, @@ -769,6 +769,7 @@ pub trait PyClassBaseType: Sized { type BaseNativeType; type ThreadChecker: PyClassThreadChecker; type Initializer: PyObjectInit; + type Mutability: Mutability; } /// All PyClasses can be used as a base type. @@ -779,6 +780,7 @@ impl PyClassBaseType for T { type BaseNativeType = T::BaseNativeType; type ThreadChecker = T::ThreadChecker; type Initializer = crate::pyclass_init::PyClassInitializer; + type Mutability = T::Mutability; } /// Default new implementation diff --git a/src/pycell.rs b/src/pycell.rs index 7159db4ffa9..57fe8eeb58b 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -175,12 +175,13 @@ //! [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::{ImmutablePyClass, MutablePyClass, PyClass}; +use crate::pyclass::{MutablePyClass, PyClass}; use crate::pyclass_init::PyClassInitializer; use crate::pyclass_slots::{PyClassDict, PyClassWeakRef}; use crate::type_object::{PyLayout, PySizedLayout}; use crate::types::PyAny; use crate::{class::impl_::PyClassBaseType, class::impl_::PyClassThreadChecker}; +use crate::class::impl_::PyClassImpl; use crate::{ conversion::{AsPyPointer, FromPyPointer, ToPyObject}, ffi::PyBaseObject_Type, @@ -976,7 +977,7 @@ impl From for PyErr { #[doc(hidden)] pub trait PyCellLayout: PyLayout { - fn borrow_checker(&self) -> &Mutable; + fn borrow_checker(&self) -> &T::Mutability where T: PyClass; /// Implementation of tp_dealloc. /// # Safety /// - slf must be a valid pointer to an instance of a T or a subclass. @@ -984,12 +985,14 @@ pub trait PyCellLayout: PyLayout { unsafe fn tp_dealloc(slf: *mut ffi::PyObject, py: Python); } -impl PyCellLayout for PyCellBase +impl PyCellLayout for PyCellBase where U: PySizedLayout, T: PyTypeInfo, + M: Mutability + { - fn borrow_checker(&self) -> &Mutable { + fn borrow_checker(&self) -> &T::Mutability where T: PyClass{ &self.borrow_impl } @@ -1018,7 +1021,7 @@ impl PyCellLayout for PyCell where ::LayoutAsBase: PyCellLayout, { - fn borrow_checker(&self) -> &Mutable { + fn borrow_checker(&self) -> &T ::Mutability { self.contents.thread_checker.ensure(); self.ob_base.borrow_checker() } diff --git a/src/pyclass.rs b/src/pyclass.rs index aaf73bf8cb7..886333d4a39 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -5,6 +5,7 @@ use crate::{ pyclass_slots::{PyClassDict, PyClassWeakRef}, PyCell, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python, }; +use crate::pycell::Mutability; use std::{ convert::TryInto, ffi::CString, @@ -27,6 +28,8 @@ 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; + + //type Mutability: Mutability; } pub unsafe trait MutablePyClass: PyClass {} diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index 51dacd09528..20ca795b8d3 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -271,7 +271,7 @@ impl PyObjectInit for PyClassInitializer { impl From for PyClassInitializer where T: PyClass, - T::BaseType: PyClassBaseType>, + T::BaseType: PyClassBaseType>, { #[inline] fn from(value: T) -> PyClassInitializer { @@ -283,7 +283,7 @@ impl From<(S, B)> for PyClassInitializer where S: PyClass, B: PyClass, - B::BaseType: PyClassBaseType>, + B::BaseType: PyClassBaseType>, { fn from(sub_and_base: (S, B)) -> PyClassInitializer { let (sub, base) = sub_and_base; diff --git a/src/types/mod.rs b/src/types/mod.rs index d8285412d4d..289dac36068 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -202,6 +202,7 @@ macro_rules! pyobject_native_type_sized { type BaseNativeType = $name; type ThreadChecker = $crate::class::impl_::ThreadCheckerStub<$crate::PyObject>; type Initializer = $crate::pyclass_init::PyNativeTypeInitializer; + type Mutability = $crate::pycell::Mutable; } } } From 64d85e5ebf333bdb6f12e48df674b90231b8b80c Mon Sep 17 00:00:00 2001 From: mejrs Date: Sat, 27 Nov 2021 22:50:01 +0100 Subject: [PATCH 10/16] part 3 --- pyo3-macros-backend/src/pyclass.rs | 10 ++-- src/class/impl_.rs | 27 +++++---- src/pycell.rs | 96 +++++++++++++++++------------- src/pyclass.rs | 6 +- src/pyclass_init.rs | 19 +++--- src/types/mod.rs | 3 +- 6 files changed, 88 insertions(+), 73 deletions(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 21b8c2dd474..2162b6849a1 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -637,6 +637,7 @@ impl<'a> PyClassImplsBuilder<'a> { type Dict = #dict; type WeakRef = #weakref; type BaseNativeType = #base_nativetype; + type Mutability = ::pyo3::pycell::Immutable; } } } @@ -763,8 +764,8 @@ impl<'a> PyClassImplsBuilder<'a> { let mutability = if self.attr.is_immutable { quote! { - ::pyo3::pycell::Immutable - } + ::pyo3::pycell::Immutable + } } else { quote! { ::pyo3::pycell::Mutable @@ -781,7 +782,6 @@ impl<'a> PyClassImplsBuilder<'a> { type Layout = ::pyo3::PyCell; type BaseType = #base; type ThreadChecker = #thread_checker; - type Mutability= #mutability; fn for_each_method_def(visitor: &mut dyn ::std::ops::FnMut(&[::pyo3::class::PyMethodDefType])) { use ::pyo3::class::impl_::*; @@ -839,8 +839,8 @@ impl<'a> PyClassImplsBuilder<'a> { let cls = self.cls; if self.attr.is_immutable { quote! { - unsafe impl ::pyo3::pyclass::ImmutablePyClass for #cls {} - } + unsafe impl ::pyo3::pyclass::ImmutablePyClass for #cls {} + } } else { quote! { unsafe impl ::pyo3::pyclass::MutablePyClass for #cls {} diff --git a/src/class/impl_.rs b/src/class/impl_.rs index b77fe57b2f3..ea0f8ee8f1a 100644 --- a/src/class/impl_.rs +++ b/src/class/impl_.rs @@ -4,7 +4,7 @@ use crate::{ exceptions::{PyAttributeError, PyNotImplementedError}, ffi, impl_::freelist::FreeList, - pycell::{PyCellLayout, Mutability}, + pycell::{Mutability, PyCellLayout}, pyclass_init::PyObjectInit, type_object::{PyLayout, PyTypeObject}, PyClass, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python, @@ -39,7 +39,7 @@ impl Copy for PyClassImplCollector {} /// /// Users are discouraged from implementing this trait manually; it is a PyO3 implementation detail /// and may be changed at any time. -pub trait PyClassImpl: Sized { +pub trait PyClassImpl: Sized { /// Class doc string const DOC: &'static str = "\0"; @@ -56,7 +56,7 @@ pub trait PyClassImpl: Sized { type Layout: PyLayout; /// Base class - type BaseType: PyTypeInfo + PyTypeObject + PyClassBaseType; + type BaseType: PyTypeInfo + PyTypeObject + PyClassBaseType; /// This handles following two situations: /// 1. In case `T` is `Send`, stub `ThreadChecker` is used and does nothing. @@ -67,8 +67,6 @@ pub trait PyClassImpl: Sized { /// can be accessed by multiple threads by `threading` module. type ThreadChecker: PyClassThreadChecker; - type Mutability: crate::pycell::Mutability; - fn for_each_method_def(_visitor: &mut dyn FnMut(&[PyMethodDefType])) {} fn get_new() -> Option { None @@ -749,9 +747,14 @@ impl PyClassThreadChecker for ThreadCheckerImpl { /// Thread checker for types that have `Send` and `extends=...`. /// Ensures that `T: Send` and the parent is not accessed by another thread. #[doc(hidden)] -pub struct ThreadCheckerInherited(PhantomData, U::ThreadChecker); - -impl PyClassThreadChecker for ThreadCheckerInherited { +pub struct ThreadCheckerInherited, M: Mutability>( + PhantomData, + U::ThreadChecker, +); + +impl, M: Mutability> PyClassThreadChecker + for ThreadCheckerInherited +{ fn ensure(&self) { self.1.ensure(); } @@ -762,25 +765,23 @@ impl PyClassThreadChecker for ThreadCheckerInher } /// Trait denoting that this class is suitable to be used as a base type for PyClass. -pub trait PyClassBaseType: Sized { +pub trait PyClassBaseType: Sized { type Dict; type WeakRef; - type LayoutAsBase: PyCellLayout; + type LayoutAsBase: PyCellLayout; type BaseNativeType; type ThreadChecker: PyClassThreadChecker; type Initializer: PyObjectInit; - type Mutability: Mutability; } /// All PyClasses can be used as a base type. -impl PyClassBaseType for T { +impl PyClassBaseType for T { type Dict = T::Dict; type WeakRef = T::WeakRef; type LayoutAsBase = crate::pycell::PyCell; type BaseNativeType = T::BaseNativeType; type ThreadChecker = T::ThreadChecker; type Initializer = crate::pyclass_init::PyClassInitializer; - type Mutability = T::Mutability; } /// Default new implementation diff --git a/src/pycell.rs b/src/pycell.rs index 57fe8eeb58b..9b696406921 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -174,6 +174,7 @@ //! [guide]: https://pyo3.rs/latest/class.html#pycell-and-interior-mutability "PyCell and interior mutability" //! [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::class::impl_::PyClassImpl; use crate::exceptions::PyRuntimeError; use crate::pyclass::{MutablePyClass, PyClass}; use crate::pyclass_init::PyClassInitializer; @@ -181,7 +182,6 @@ use crate::pyclass_slots::{PyClassDict, PyClassWeakRef}; use crate::type_object::{PyLayout, PySizedLayout}; use crate::types::PyAny; use crate::{class::impl_::PyClassBaseType, class::impl_::PyClassThreadChecker}; -use crate::class::impl_::PyClassImpl; use crate::{ conversion::{AsPyPointer, FromPyPointer, ToPyObject}, ffi::PyBaseObject_Type, @@ -191,9 +191,9 @@ use crate::{ use crate::{ffi, IntoPy, PyErr, PyNativeType, PyObject, PyResult, Python}; use std::cell::{Cell, UnsafeCell}; use std::fmt; +use std::marker::PhantomData; use std::mem::ManuallyDrop; use std::ops::{Deref, DerefMut}; -use std::marker::PhantomData; pub trait Mutability { /// Creates a new borrow checker @@ -202,7 +202,7 @@ pub trait Mutability { fn try_borrow(&self) -> Result<(), PyBorrowError>; fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError>; - + /// Decrements immutable borrow count fn release_borrow(&self); /// Increments mutable borrow count, if possible @@ -215,76 +215,74 @@ pub struct Mutable { flag: Cell, } impl Mutability for Mutable { - fn new() -> Self{ - Self{flag: Cell::new(BorrowFlag::UNUSED)} + fn new() -> Self { + Self { + flag: Cell::new(BorrowFlag::UNUSED), + } } - fn try_borrow(&self) -> Result<(), PyBorrowError>{ + fn try_borrow(&self) -> Result<(), PyBorrowError> { let flag = self.flag.get(); if flag != BorrowFlag::HAS_MUTABLE_BORROW { self.flag.set(flag.increment()); Ok(()) - } - else{ + } else { Err(PyBorrowError { _private: () }) } } - fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError>{ + fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError> { let flag = self.flag.get(); if flag != BorrowFlag::HAS_MUTABLE_BORROW { Ok(()) - } - else{ + } else { Err(PyBorrowError { _private: () }) } } - fn release_borrow(&self){ + fn release_borrow(&self) { let flag = self.flag.get(); self.flag.set(flag.decrement()) } - fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError>{ + fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError> { let flag = self.flag.get(); if flag == BorrowFlag::UNUSED { self.flag.set(BorrowFlag::HAS_MUTABLE_BORROW); Ok(()) - } - else{ + } else { Err(PyBorrowMutError { _private: () }) } } - fn release_borrow_mut(&self){ + fn release_borrow_mut(&self) { self.flag.set(BorrowFlag::UNUSED) } } -pub struct Immutable{ - flag: PhantomData> +pub struct Immutable { + flag: PhantomData>, } impl Mutability for Immutable { - fn new() -> Self{ - Self{flag: PhantomData} + fn new() -> Self { + Self { flag: PhantomData } } - fn try_borrow(&self) -> Result<(), PyBorrowError>{ - Ok(()) + fn try_borrow(&self) -> Result<(), PyBorrowError> { + Ok(()) } - fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError>{ + fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError> { Ok(()) } - fn release_borrow(&self){ - } + fn release_borrow(&self) {} - fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError>{ + fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError> { unreachable!() } - fn release_borrow_mut(&self){ + fn release_borrow_mut(&self) { unreachable!() } } @@ -298,7 +296,12 @@ pub struct PyCellBase { borrow_impl: M, } -unsafe impl PyLayout for PyCellBase where U: PySizedLayout, M: Mutability {} +unsafe impl PyLayout for PyCellBase +where + U: PySizedLayout, + M: Mutability, +{ +} /// A container type for (mutably) accessing [`PyClass`] values /// @@ -337,7 +340,7 @@ unsafe impl PyLayout for PyCellBase where U: PySizedLayout, /// [module-level documentation](self). #[repr(C)] pub struct PyCell { - ob_base: ::LayoutAsBase, + ob_base: >::LayoutAsBase, contents: PyCellContents, } @@ -491,7 +494,9 @@ impl PyCell { /// }); /// ``` pub fn try_borrow(&self) -> Result, PyBorrowError> { - self.borrow_checker().try_borrow().map(|_| PyRef { inner: self }) + self.borrow_checker() + .try_borrow() + .map(|_| PyRef { inner: self }) } /// Mutably borrows the value `T`, returning an error if the value is currently borrowed. @@ -519,7 +524,9 @@ impl PyCell { where T: MutablePyClass, { - self.borrow_checker().try_borrow_mut().map(|_| PyRefMut { inner: self }) + self.borrow_checker() + .try_borrow_mut() + .map(|_| PyRefMut { inner: self }) } /// Immutably borrows the value `T`, returning an error if the value is @@ -552,7 +559,9 @@ impl PyCell { /// }); /// ``` pub unsafe fn try_borrow_unguarded(&self) -> Result<&T, PyBorrowError> { - self.borrow_checker().try_borrow_unguarded().map(|_:()| &*self.contents.value.get()) + self.borrow_checker() + .try_borrow_unguarded() + .map(|_: ()| &*self.contents.value.get()) } /// Replaces the wrapped value with a new one, returning the old value. @@ -976,8 +985,11 @@ impl From for PyErr { } #[doc(hidden)] -pub trait PyCellLayout: PyLayout { - fn borrow_checker(&self) -> &T::Mutability where T: PyClass; +pub trait PyCellLayout: PyLayout +where + M: Mutability, +{ + fn borrow_checker(&self) -> &M; /// Implementation of tp_dealloc. /// # Safety /// - slf must be a valid pointer to an instance of a T or a subclass. @@ -985,14 +997,13 @@ pub trait PyCellLayout: PyLayout { unsafe fn tp_dealloc(slf: *mut ffi::PyObject, py: Python); } -impl PyCellLayout for PyCellBase +impl PyCellLayout for PyCellBase where U: PySizedLayout, T: PyTypeInfo, - M: Mutability - + M: Mutability, { - fn borrow_checker(&self) -> &T::Mutability where T: PyClass{ + fn borrow_checker(&self) -> &M { &self.borrow_impl } @@ -1017,11 +1028,12 @@ where } } -impl PyCellLayout for PyCell +impl PyCellLayout for PyCell where - ::LayoutAsBase: PyCellLayout, + >::LayoutAsBase: + PyCellLayout, { - fn borrow_checker(&self) -> &T ::Mutability { + fn borrow_checker(&self) -> &T::Mutability { self.contents.thread_checker.ensure(); self.ob_base.borrow_checker() } @@ -1031,6 +1043,6 @@ where ManuallyDrop::drop(&mut cell.contents.value); cell.contents.dict.clear_dict(py); cell.contents.weakref.clear_weakrefs(slf, py); - ::LayoutAsBase::tp_dealloc(slf, py) + >::LayoutAsBase::tp_dealloc(slf, py) } } diff --git a/src/pyclass.rs b/src/pyclass.rs index 886333d4a39..67dfc885583 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -1,11 +1,11 @@ //! `PyClass` and related traits. +use crate::pycell::Mutability; use crate::{ class::impl_::{fallback_new, tp_dealloc, PyClassImpl}, ffi, pyclass_slots::{PyClassDict, PyClassWeakRef}, PyCell, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python, }; -use crate::pycell::Mutability; use std::{ convert::TryInto, ffi::CString, @@ -19,7 +19,7 @@ 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: - PyTypeInfo> + PyClassImpl> + PyTypeInfo> + PyClassImpl> { /// Specify this class has `#[pyclass(dict)]` or not. type Dict: PyClassDict; @@ -29,7 +29,7 @@ pub trait PyClass: /// `#[pyclass(extends=PyDict)]`, it's `PyDict`. type BaseNativeType: PyTypeInfo + PyNativeType; - //type Mutability: Mutability; + type Mutability: Mutability; } pub unsafe trait MutablePyClass: PyClass {} diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index 20ca795b8d3..7ce0fe9bf6c 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -127,14 +127,17 @@ impl PyObjectInit for PyNativeTypeInitializer { /// ``` pub struct PyClassInitializer { init: T, - super_init: ::Initializer, + super_init: >::Initializer, } impl PyClassInitializer { /// Constructs a new initializer from value `T` and base class' initializer. /// /// It is recommended to use `add_subclass` instead of this method for most usage. - pub fn new(init: T, super_init: ::Initializer) -> Self { + pub fn new( + init: T, + super_init: >::Initializer, + ) -> Self { Self { init, super_init } } @@ -187,7 +190,7 @@ impl PyClassInitializer { pub fn add_subclass(self, subclass_value: S) -> PyClassInitializer where S: PyClass, - S::BaseType: PyClassBaseType, + S::BaseType: PyClassBaseType, { PyClassInitializer::new(subclass_value, self) } @@ -228,16 +231,16 @@ impl PyObjectInit for PyClassInitializer { /// Layout of a PyCellBase after base new has been called, but the borrow flag has not /// yet been initialized. #[repr(C)] - struct PartiallyInitializedPyCellBase { + struct PartiallyInitializedPyCellBase { _ob_base: T, - borrow_flag: MaybeUninit>, + borrow_flag: MaybeUninit, } /// Layout of a PyCell after base new has been called, but the contents have not yet been /// written. #[repr(C)] struct PartiallyInitializedPyCell { - _ob_base: ::LayoutAsBase, + _ob_base: >::LayoutAsBase, contents: MaybeUninit>, } @@ -271,7 +274,7 @@ impl PyObjectInit for PyClassInitializer { impl From for PyClassInitializer where T: PyClass, - T::BaseType: PyClassBaseType>, + T::BaseType: PyClassBaseType>, { #[inline] fn from(value: T) -> PyClassInitializer { @@ -283,7 +286,7 @@ impl From<(S, B)> for PyClassInitializer where S: PyClass, B: PyClass, - B::BaseType: PyClassBaseType>, + B::BaseType: PyClassBaseType>, { fn from(sub_and_base: (S, B)) -> PyClassInitializer { let (sub, base) = sub_and_base; diff --git a/src/types/mod.rs b/src/types/mod.rs index 289dac36068..14db301c37a 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -195,14 +195,13 @@ macro_rules! pyobject_native_type_sized { ($name:ty, $layout:path $(;$generics:ident)*) => { unsafe impl $crate::type_object::PyLayout<$name> for $layout {} impl $crate::type_object::PySizedLayout<$name> for $layout {} - impl<'a, $($generics,)*> $crate::class::impl_::PyClassBaseType for $name { + impl<'a, $($generics,)*> $crate::class::impl_::PyClassBaseType<$crate::pycell::Mutable> for $name { type Dict = $crate::pyclass_slots::PyClassDummySlot; type WeakRef = $crate::pyclass_slots::PyClassDummySlot; type LayoutAsBase = $crate::pycell::PyCellBase<$layout, $crate::pycell::Mutable>; type BaseNativeType = $name; type ThreadChecker = $crate::class::impl_::ThreadCheckerStub<$crate::PyObject>; type Initializer = $crate::pyclass_init::PyNativeTypeInitializer; - type Mutability = $crate::pycell::Mutable; } } } From 7520b49ac1517755e2f54c6fc88ad9e8fc6543cd Mon Sep 17 00:00:00 2001 From: mejrs Date: Mon, 22 Nov 2021 09:26:34 +0100 Subject: [PATCH 11/16] Implement opt-in immutable pyclasses --- guide/src/class.md | 49 ++-- pyo3-macros-backend/src/pyclass.rs | 62 ++++- src/class/basic.rs | 12 +- src/class/buffer.rs | 4 +- src/class/gc.rs | 4 +- src/class/mapping.rs | 6 +- src/class/number.rs | 30 +-- src/class/sequence.rs | 10 +- src/conversion.rs | 5 +- src/impl_/pyclass.rs | 23 +- src/instance.rs | 16 +- src/pycell.rs | 230 +++++++++++++----- src/pyclass.rs | 8 +- src/pyclass_init.rs | 17 +- src/types/mod.rs | 4 +- tests/test_compile_error.rs | 4 + tests/ui/invalid_immutable_pyclass_borrow.rs | 13 + .../invalid_immutable_pyclass_borrow.stderr | 5 + tests/ui/pyclass_send.stderr | 12 + 19 files changed, 365 insertions(+), 149 deletions(-) create mode 100644 tests/ui/invalid_immutable_pyclass_borrow.rs create mode 100644 tests/ui/invalid_immutable_pyclass_borrow.stderr diff --git a/guide/src/class.md b/guide/src/class.md index fdc5fc264ff..1156ae1cef7 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -932,36 +932,41 @@ The `#[pyclass]` macro expands to roughly the code seen below. The `PyClassImplC # #[cfg(not(feature = "multiple-pymethods"))] { # use pyo3::prelude::*; // Note: the implementation differs slightly with the `multiple-pymethods` feature enabled. - -/// Class for demonstration struct MyClass { # #[allow(dead_code)] num: i32, } - -unsafe impl pyo3::PyTypeInfo for MyClass { - type AsRefTarget = PyCell; - +unsafe impl ::pyo3::type_object::PyTypeInfo for MyClass { + type AsRefTarget = ::pyo3::PyCell; const NAME: &'static str = "MyClass"; - const MODULE: Option<&'static str> = None; - + const MODULE: ::std::option::Option<&'static str> = ::std::option::Option::None; #[inline] - fn type_object_raw(py: pyo3::Python) -> *mut pyo3::ffi::PyTypeObject { - use pyo3::type_object::LazyStaticType; + fn type_object_raw(py: ::pyo3::Python<'_>) -> *mut ::pyo3::ffi::PyTypeObject { + use ::pyo3::type_object::LazyStaticType; static TYPE_OBJECT: LazyStaticType = LazyStaticType::new(); TYPE_OBJECT.get_or_init::(py) } } -impl pyo3::pyclass::PyClass for MyClass { - type Dict = pyo3::impl_::pyclass::PyClassDummySlot; - type WeakRef = pyo3::impl_::pyclass::PyClassDummySlot; - type BaseNativeType = PyAny; +impl ::pyo3::PyClass for MyClass { + type Dict = ::pyo3::impl_::pyclass::PyClassDummySlot; + type WeakRef = ::pyo3::impl_::pyclass::PyClassDummySlot; + type BaseNativeType = ::pyo3::PyAny; +} + +unsafe impl ::pyo3::pyclass::MutablePyClass for MyClass {} + +impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a mut MyClass { + type Target = ::pyo3::PyRefMut<'a, MyClass>; +} + +impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a MyClass { + type Target = ::pyo3::PyRef<'a, MyClass>; } -impl pyo3::IntoPy for MyClass { - fn into_py(self, py: pyo3::Python) -> pyo3::PyObject { - pyo3::IntoPy::into_py(pyo3::Py::new(py, self).unwrap(), py) +impl ::pyo3::IntoPy<::pyo3::PyObject> for MyClass { + fn into_py(self, py: ::pyo3::Python) -> ::pyo3::PyObject { + ::pyo3::IntoPy::into_py(::pyo3::Py::new(py, self).unwrap(), py) } } @@ -973,6 +978,7 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass { type Layout = PyCell; type BaseType = PyAny; type ThreadChecker = pyo3::impl_::pyclass::ThreadCheckerStub; + type Mutabilty = pyo3::pyclass::Mutable; fn for_all_items(visitor: &mut dyn FnMut(&pyo3::impl_::pyclass::PyClassItems)) { use pyo3::impl_::pyclass::*; @@ -997,6 +1003,15 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass { collector.free_impl() } } + +impl ::pyo3::impl_::pyclass::PyClassDescriptors + for ::pyo3::impl_::pyclass::PyClassImplCollector +{ + fn py_class_descriptors(self) -> &'static [::pyo3::class::methods::PyMethodDefType] { + static METHODS: &[::pyo3::class::methods::PyMethodDefType] = &[]; + METHODS + } +} # Python::with_gil(|py| { # let cls = py.get_type::(); # pyo3::py_run!(py, cls, "assert cls.__name__ == 'MyClass'") diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 18c29957033..c233900e9d5 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -35,6 +35,7 @@ pub struct PyClassArgs { pub has_unsendable: bool, pub module: Option, pub class_kind: PyClassKind, + pub is_immutable: bool, } impl PyClassArgs { @@ -67,6 +68,7 @@ impl PyClassArgs { is_basetype: false, has_extends: false, has_unsendable: false, + is_immutable: false, class_kind, } } @@ -176,6 +178,9 @@ impl PyClassArgs { "unsendable" => { self.has_unsendable = true; } + "immutable" => { + self.is_immutable = true; + } _ => bail_spanned!( exp.path.span() => "expected one of gc/weakref/subclass/dict/unsendable" ), @@ -587,12 +592,29 @@ fn impl_enum_class( let default_items = gen_default_items(cls, vec![default_repr_impl, default_richcmp, default_int]); + let mutability = if args.is_immutable { + quote! { + unsafe impl _pyo3::pyclass::ImmutablePyClass for #cls {} + } + } else { + quote! { + unsafe impl _pyo3::pyclass::MutablePyClass for #cls {} + + impl<'a> _pyo3::derive_utils::ExtractExt<'a> for &'a mut #cls + { + type Target = _pyo3::PyRefMut<'a, #cls>; + } + } + }; + Ok(quote! { const _: () = { use #krate as _pyo3; #pytypeinfo + #mutability + #pyclass_impls #default_items @@ -788,20 +810,30 @@ impl<'a> PyClassImplsBuilder<'a> { type Dict = #dict; type WeakRef = #weakref; type BaseNativeType = #base_nativetype; + type Mutability = _pyo3::pycell::Immutable; } } } fn impl_extractext(&self) -> TokenStream { let cls = self.cls; - quote! { - impl<'a> _pyo3::derive_utils::ExtractExt<'a> for &'a #cls - { - type Target = _pyo3::PyRef<'a, #cls>; + if self.attr.is_immutable { + quote! { + impl<'a> _pyo3::derive_utils::ExtractExt<'a> for &'a #cls + { + type Target = _pyo3::PyRef<'a, #cls>; + } } + } else { + quote! { + 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>; + impl<'a> _pyo3::derive_utils::ExtractExt<'a> for &'a mut #cls + { + type Target = _pyo3::PyRefMut<'a, #cls>; + } } } } @@ -901,8 +933,18 @@ impl<'a> PyClassImplsBuilder<'a> { let default_items = &self.default_items; + let mutability = if self.attr.is_immutable { + quote! { + _pyo3::pycell::Immutable + } + } else { + quote! { + _pyo3::pycell::Mutable + } + }; + quote! { - impl _pyo3::impl_::pyclass::PyClassImpl for #cls { + impl _pyo3::impl_::pyclass::PyClassImpl<#mutability> for #cls { const DOC: &'static str = #doc; const IS_GC: bool = #is_gc; const IS_BASETYPE: bool = #is_basetype; @@ -943,9 +985,9 @@ impl<'a> PyClassImplsBuilder<'a> { #dict_offset #weaklist_offset - } - #inventory_class + #inventory_class + } } } diff --git a/src/class/basic.rs b/src/class/basic.rs index b2d0e9ab1d7..90f941f26b0 100644 --- a/src/class/basic.rs +++ b/src/class/basic.rs @@ -9,7 +9,9 @@ //! [typeobj docs](https://docs.python.org/3/c-api/typeobj.html) use crate::callback::{HashCallbackOutput, IntoPyCallbackOutput}; -use crate::{exceptions, ffi, FromPyObject, PyAny, PyCell, PyClass, PyObject}; +use crate::{ + exceptions, ffi, pyclass::MutablePyClass, FromPyObject, PyAny, PyCell, PyClass, PyObject, +}; use std::os::raw::c_int; /// Basic Python class customization @@ -24,14 +26,14 @@ pub trait PyObjectProtocol<'p>: PyClass { fn __setattr__(&'p mut self, name: Self::Name, value: Self::Value) -> Self::Result where - Self: PyObjectSetAttrProtocol<'p>, + Self: PyObjectSetAttrProtocol<'p> + MutablePyClass, { unimplemented!() } fn __delattr__(&'p mut self, name: Self::Name) -> Self::Result where - Self: PyObjectDelAttrProtocol<'p>, + Self: PyObjectDelAttrProtocol<'p> + MutablePyClass, { unimplemented!() } @@ -75,12 +77,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 7b4c8acf958..be47a5c2bac 100644 --- a/src/class/buffer.rs +++ b/src/class/buffer.rs @@ -5,7 +5,7 @@ //! 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::{ffi, pyclass::MutablePyClass, PyCell, PyRefMut}; use std::os::raw::c_int; /// Buffer protocol interface @@ -13,7 +13,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 diff --git a/src/class/gc.rs b/src/class/gc.rs index 2641a2b3f21..2f1085d0de4 100644 --- a/src/class/gc.rs +++ b/src/class/gc.rs @@ -2,14 +2,14 @@ //! Python GC support -use crate::{ffi, AsPyPointer, PyCell, PyClass, Python}; +use crate::{ffi, pyclass::MutablePyClass, AsPyPointer, PyCell, Python}; use std::os::raw::{c_int, c_void}; #[repr(transparent)] pub struct PyTraverseError(c_int); /// GC support -pub trait PyGCProtocol<'p>: PyClass { +pub trait PyGCProtocol<'p>: MutablePyClass { fn __traverse__(&'p self, visit: PyVisit) -> Result<(), PyTraverseError>; fn __clear__(&'p mut self); } diff --git a/src/class/mapping.rs b/src/class/mapping.rs index b68685215e7..627ef99739e 100644 --- a/src/class/mapping.rs +++ b/src/class/mapping.rs @@ -4,7 +4,7 @@ //! Trait and support implementation for implementing mapping support use crate::callback::IntoPyCallbackOutput; -use crate::{FromPyObject, PyClass, PyObject}; +use crate::{pyclass::MutablePyClass, FromPyObject, PyClass, PyObject}; /// Mapping interface #[allow(unused_variables)] @@ -50,13 +50,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 448834d2f8e..e261db67c7c 100644 --- a/src/class/number.rs +++ b/src/class/number.rs @@ -4,7 +4,7 @@ //! Trait and support implementation for implementing number protocol use crate::callback::IntoPyCallbackOutput; use crate::err::PyErr; -use crate::{ffi, FromPyObject, PyClass, PyObject}; +use crate::{ffi, pyclass::MutablePyClass, FromPyObject, PyClass, PyObject}; /// Number interface #[allow(unused_variables)] @@ -459,74 +459,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<()>; // See https://bugs.python.org/issue36379 type Modulo: FromPyObject<'p>; } -pub trait PyNumberILShiftProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberILShiftProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -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<()>; } diff --git a/src/class/sequence.rs b/src/class/sequence.rs index 129a188a646..6f453d78561 100644 --- a/src/class/sequence.rs +++ b/src/class/sequence.rs @@ -6,7 +6,7 @@ use crate::callback::IntoPyCallbackOutput; use crate::conversion::{FromPyObject, IntoPy}; use crate::err::PyErr; -use crate::{exceptions, ffi, PyAny, PyCell, PyClass, PyObject}; +use crate::{exceptions, ffi, pyclass::MutablePyClass, PyAny, PyCell, PyClass, PyObject}; use std::os::raw::c_int; /// Sequence interface @@ -88,13 +88,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 +115,14 @@ pub trait PySequenceRepeatProtocol<'p>: PySequenceProtocol<'p> { } pub trait PySequenceInplaceConcatProtocol<'p>: - PySequenceProtocol<'p> + IntoPy + 'p + PySequenceProtocol<'p> + IntoPy + MutablePyClass { 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 ccd690bee15..f2da392de0e 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -5,7 +5,8 @@ use crate::err::{self, PyDowncastError, PyResult}; use crate::type_object::PyTypeInfo; use crate::types::PyTuple; use crate::{ - ffi, gil, Py, PyAny, PyCell, PyClass, PyNativeType, PyObject, PyRef, PyRefMut, Python, + ffi, gil, pyclass::MutablePyClass, Py, PyAny, PyCell, PyClass, PyNativeType, PyObject, PyRef, + PyRefMut, Python, }; use std::ptr::NonNull; @@ -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/impl_/pyclass.rs b/src/impl_/pyclass.rs index 8f33b964196..5ef2423d2dd 100644 --- a/src/impl_/pyclass.rs +++ b/src/impl_/pyclass.rs @@ -2,7 +2,7 @@ use crate::{ exceptions::{PyAttributeError, PyNotImplementedError}, ffi, impl_::freelist::FreeList, - pycell::PyCellLayout, + pycell::{Mutability, PyCellLayout}, pyclass_init::PyObjectInit, type_object::{PyLayout, PyTypeObject}, PyCell, PyClass, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python, @@ -145,7 +145,7 @@ unsafe impl Sync for PyClassItems {} /// /// Users are discouraged from implementing this trait manually; it is a PyO3 implementation detail /// and may be changed at any time. -pub trait PyClassImpl: Sized { +pub trait PyClassImpl: Sized { /// Class doc string const DOC: &'static str = "\0"; @@ -162,7 +162,7 @@ pub trait PyClassImpl: Sized { type Layout: PyLayout; /// Base class - type BaseType: PyTypeInfo + PyTypeObject + PyClassBaseType; + type BaseType: PyTypeInfo + PyTypeObject + PyClassBaseType; /// This handles following two situations: /// 1. In case `T` is `Send`, stub `ThreadChecker` is used and does nothing. @@ -818,9 +818,14 @@ impl PyClassThreadChecker for ThreadCheckerImpl { /// Thread checker for types that have `Send` and `extends=...`. /// Ensures that `T: Send` and the parent is not accessed by another thread. #[doc(hidden)] -pub struct ThreadCheckerInherited(PhantomData, U::ThreadChecker); - -impl PyClassThreadChecker for ThreadCheckerInherited { +pub struct ThreadCheckerInherited, M: Mutability>( + PhantomData, + U::ThreadChecker, +); + +impl, M: Mutability> PyClassThreadChecker + for ThreadCheckerInherited +{ fn ensure(&self) { self.1.ensure(); } @@ -831,15 +836,15 @@ impl PyClassThreadChecker for ThreadCheckerInher } /// Trait denoting that this class is suitable to be used as a base type for PyClass. -pub trait PyClassBaseType: Sized { - type LayoutAsBase: PyCellLayout; +pub trait PyClassBaseType: Sized { + type LayoutAsBase: PyCellLayout; type BaseNativeType; type ThreadChecker: PyClassThreadChecker; type Initializer: PyObjectInit; } /// All PyClasses can be used as a base type. -impl PyClassBaseType for T { +impl PyClassBaseType for T { type LayoutAsBase = crate::pycell::PyCell; type BaseNativeType = T::BaseNativeType; type ThreadChecker = T::ThreadChecker; diff --git a/src/instance.rs b/src/instance.rs index 780d35fa0fe..694e85f59a8 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -5,8 +5,8 @@ use crate::gil; use crate::pycell::{PyBorrowError, PyBorrowMutError, PyCell}; use crate::types::{PyDict, PyTuple}; use crate::{ - ffi, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyAny, PyClass, PyClassInitializer, - PyRef, PyRefMut, PyTypeInfo, Python, ToPyObject, + ffi, pyclass::MutablePyClass, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyAny, PyClass, + PyClassInitializer, PyRef, PyRefMut, PyTypeInfo, Python, ToPyObject, }; use std::marker::PhantomData; use std::mem; @@ -427,7 +427,10 @@ where /// # Panics /// Panics if the value is currently mutably borrowed. For a non-panicking variant, use /// [`try_borrow_mut`](#method.try_borrow_mut). - pub fn borrow_mut<'py>(&'py self, py: Python<'py>) -> PyRefMut<'py, T> { + pub fn borrow_mut<'py>(&'py self, py: Python<'py>) -> PyRefMut<'py, T> + where + T: MutablePyClass, + { self.as_ref(py).borrow_mut() } @@ -454,7 +457,10 @@ where pub fn try_borrow_mut<'py>( &'py self, py: Python<'py>, - ) -> Result, PyBorrowMutError> { + ) -> Result, PyBorrowMutError> + where + T: MutablePyClass, + { self.as_ref(py).try_borrow_mut() } } @@ -819,7 +825,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 b8b2e395498..4d116d45687 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -176,7 +176,7 @@ use crate::exceptions::PyRuntimeError; use crate::impl_::pyclass::{PyClassBaseType, PyClassDict, PyClassThreadChecker, PyClassWeakRef}; -use crate::pyclass::PyClass; +use crate::pyclass::{MutablePyClass, PyClass}; use crate::pyclass_init::PyClassInitializer; use crate::type_object::{PyLayout, PySizedLayout}; use crate::types::PyAny; @@ -189,19 +189,117 @@ use crate::{ use crate::{ffi, IntoPy, PyErr, PyNativeType, PyObject, PyResult, Python}; use std::cell::{Cell, UnsafeCell}; use std::fmt; +use std::marker::PhantomData; use std::mem::ManuallyDrop; use std::ops::{Deref, DerefMut}; +pub trait Mutability { + /// Creates a new borrow checker + fn new() -> Self; + /// Increments immutable borrow count, if possible + fn try_borrow(&self) -> Result<(), PyBorrowError>; + + fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError>; + + /// Decrements immutable borrow count + fn release_borrow(&self); + /// Increments mutable borrow count, if possible + fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError>; + /// Decremements mutable borrow count + fn release_borrow_mut(&self); +} + +pub struct Mutable { + flag: Cell, +} +impl Mutability for Mutable { + fn new() -> Self { + Self { + flag: Cell::new(BorrowFlag::UNUSED), + } + } + + fn try_borrow(&self) -> Result<(), PyBorrowError> { + let flag = self.flag.get(); + if flag != BorrowFlag::HAS_MUTABLE_BORROW { + self.flag.set(flag.increment()); + Ok(()) + } else { + Err(PyBorrowError { _private: () }) + } + } + + fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError> { + let flag = self.flag.get(); + if flag != BorrowFlag::HAS_MUTABLE_BORROW { + Ok(()) + } else { + Err(PyBorrowError { _private: () }) + } + } + + fn release_borrow(&self) { + let flag = self.flag.get(); + self.flag.set(flag.decrement()) + } + + fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError> { + let flag = self.flag.get(); + if flag == BorrowFlag::UNUSED { + self.flag.set(BorrowFlag::HAS_MUTABLE_BORROW); + Ok(()) + } else { + Err(PyBorrowMutError { _private: () }) + } + } + + fn release_borrow_mut(&self) { + self.flag.set(BorrowFlag::UNUSED) + } +} + +pub struct Immutable { + flag: PhantomData>, +} +impl Mutability for Immutable { + fn new() -> Self { + Self { flag: PhantomData } + } + + fn try_borrow(&self) -> Result<(), PyBorrowError> { + Ok(()) + } + + fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError> { + Ok(()) + } + + fn release_borrow(&self) {} + + fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError> { + unreachable!() + } + + fn release_borrow_mut(&self) { + unreachable!() + } +} + /// Base layout of PyCell. /// This is necessary for sharing BorrowFlag between parents and children. #[doc(hidden)] #[repr(C)] -pub struct PyCellBase { +pub struct PyCellBase { ob_base: T, - borrow_flag: Cell, + borrow_impl: M, } -unsafe impl PyLayout for PyCellBase where U: PySizedLayout {} +unsafe impl PyLayout for PyCellBase +where + U: PySizedLayout, + M: Mutability, +{ +} /// A container type for (mutably) accessing [`PyClass`] values /// @@ -240,7 +338,7 @@ unsafe impl PyLayout for PyCellBase where U: PySizedLayout {} /// [module-level documentation](self). #[repr(C)] pub struct PyCell { - ob_base: ::LayoutAsBase, + ob_base: >::LayoutAsBase, contents: PyCellContents, } @@ -283,7 +381,10 @@ impl PyCell { /// /// 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> { + pub fn borrow_mut(&self) -> PyRefMut<'_, T> + where + T: MutablePyClass, + { self.try_borrow_mut().expect("Already borrowed") } @@ -313,13 +414,9 @@ impl PyCell { /// }); /// ``` pub fn try_borrow(&self) -> Result, PyBorrowError> { - let flag = self.get_borrow_flag(); - if flag == BorrowFlag::HAS_MUTABLE_BORROW { - Err(PyBorrowError { _private: () }) - } else { - self.set_borrow_flag(flag.increment()); - Ok(PyRef { inner: self }) - } + self.borrow_checker() + .try_borrow() + .map(|_| PyRef { inner: self }) } /// Mutably borrows the value `T`, returning an error if the value is currently borrowed. @@ -343,13 +440,13 @@ impl PyCell { /// assert!(c.try_borrow_mut().is_ok()); /// }); /// ``` - pub fn try_borrow_mut(&self) -> Result, PyBorrowMutError> { - if self.get_borrow_flag() != BorrowFlag::UNUSED { - Err(PyBorrowMutError { _private: () }) - } else { - self.set_borrow_flag(BorrowFlag::HAS_MUTABLE_BORROW); - Ok(PyRefMut { inner: self }) - } + pub fn try_borrow_mut(&self) -> Result, PyBorrowMutError> + where + T: MutablePyClass, + { + self.borrow_checker() + .try_borrow_mut() + .map(|_| PyRefMut { inner: self }) } /// Immutably borrows the value `T`, returning an error if the value is @@ -382,11 +479,9 @@ impl PyCell { /// }); /// ``` 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()) - } + self.borrow_checker() + .try_borrow_unguarded() + .map(|_: ()| &*self.contents.value.get()) } /// Replaces the wrapped value with a new one, returning the old value. @@ -395,7 +490,10 @@ impl PyCell { /// /// Panics if the value is currently borrowed. #[inline] - pub fn replace(&self, t: T) -> T { + pub fn replace(&self, t: T) -> T + where + T: MutablePyClass, + { std::mem::replace(&mut *self.borrow_mut(), t) } @@ -404,7 +502,10 @@ impl PyCell { /// # Panics /// /// Panics if the value is currently borrowed. - pub fn replace_with T>(&self, f: F) -> T { + pub fn replace_with T>(&self, f: F) -> T + where + T: MutablePyClass, + { let mut_borrow = &mut *self.borrow_mut(); let replacement = f(mut_borrow); std::mem::replace(mut_borrow, replacement) @@ -416,7 +517,10 @@ impl PyCell { /// /// Panics if the value in either `PyCell` is currently borrowed. #[inline] - pub fn swap(&self, other: &Self) { + pub fn swap(&self, other: &Self) + where + T: MutablePyClass, + { std::mem::swap(&mut *self.borrow_mut(), &mut *other.borrow_mut()) } @@ -674,8 +778,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()) + self.inner.borrow_checker().release_borrow() } } @@ -707,11 +810,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() } @@ -720,8 +823,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() } @@ -730,8 +833,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() } @@ -740,8 +843,8 @@ where impl<'p, T, U> PyRefMut<'p, T> where - T: PyClass, - U: PyClass, + T: PyClass + MutablePyClass, + U: MutablePyClass, { /// Gets a `PyRef`. /// @@ -755,7 +858,7 @@ where } } -impl<'p, T: PyClass> Deref for PyRefMut<'p, T> { +impl<'p, T: MutablePyClass> Deref for PyRefMut<'p, T> { type Target = T; #[inline] @@ -764,46 +867,46 @@ 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) + self.inner.borrow_checker().release_borrow_mut() } } -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) } } #[doc(hidden)] -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct BorrowFlag(usize); impl BorrowFlag { @@ -868,9 +971,11 @@ impl From for PyErr { } #[doc(hidden)] -pub trait PyCellLayout: PyLayout { - fn get_borrow_flag(&self) -> BorrowFlag; - fn set_borrow_flag(&self, flag: BorrowFlag); +pub trait PyCellLayout: PyLayout +where + M: Mutability, +{ + fn borrow_checker(&self) -> &M; /// Implementation of tp_dealloc. /// # Safety /// - slf must be a valid pointer to an instance of a T or a subclass. @@ -878,17 +983,16 @@ pub trait PyCellLayout: PyLayout { unsafe fn tp_dealloc(slf: *mut ffi::PyObject, py: Python); } -impl PyCellLayout for PyCellBase +impl PyCellLayout for PyCellBase where U: PySizedLayout, T: PyTypeInfo, + M: Mutability, { - fn get_borrow_flag(&self) -> BorrowFlag { - self.borrow_flag.get() - } - fn set_borrow_flag(&self, flag: BorrowFlag) { - self.borrow_flag.set(flag) + fn borrow_checker(&self) -> &M { + &self.borrow_impl } + unsafe fn tp_dealloc(slf: *mut ffi::PyObject, py: Python) { // For `#[pyclass]` types which inherit from PyAny, we can just call tp_free if T::type_object_raw(py) == &mut PyBaseObject_Type { @@ -910,16 +1014,14 @@ where } } -impl PyCellLayout for PyCell +impl PyCellLayout for PyCell where - ::LayoutAsBase: PyCellLayout, + >::LayoutAsBase: + PyCellLayout, { - fn get_borrow_flag(&self) -> BorrowFlag { + fn borrow_checker(&self) -> &T::Mutability { self.contents.thread_checker.ensure(); - self.ob_base.get_borrow_flag() - } - fn set_borrow_flag(&self, flag: BorrowFlag) { - self.ob_base.set_borrow_flag(flag) + self.ob_base.borrow_checker() } unsafe fn tp_dealloc(slf: *mut ffi::PyObject, py: Python) { // Safety: Python only calls tp_dealloc when no references to the object remain. @@ -927,6 +1029,6 @@ where ManuallyDrop::drop(&mut cell.contents.value); cell.contents.dict.clear_dict(py); cell.contents.weakref.clear_weakrefs(slf, py); - ::LayoutAsBase::tp_dealloc(slf, py) + >::LayoutAsBase::tp_dealloc(slf, py) } } diff --git a/src/pyclass.rs b/src/pyclass.rs index f08669ad674..ee448b32d2c 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -1,4 +1,5 @@ //! `PyClass` and related traits. +use crate::pycell::Mutability; use crate::{ callback::IntoPyCallbackOutput, ffi, @@ -22,7 +23,7 @@ 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: - PyTypeInfo> + PyClassImpl> + PyTypeInfo> + PyClassImpl> { /// Specify this class has `#[pyclass(dict)]` or not. type Dict: PyClassDict; @@ -31,8 +32,13 @@ 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; + + type Mutability: Mutability; } +pub unsafe trait MutablePyClass: PyClass {} +pub unsafe trait ImmutablePyClass: PyClass {} + fn into_raw(vec: Vec) -> *mut c_void { Box::into_raw(vec.into_boxed_slice()) as _ } diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index b8b2025b1da..e478472b6dc 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -130,14 +130,17 @@ impl PyObjectInit for PyNativeTypeInitializer { /// ``` pub struct PyClassInitializer { init: T, - super_init: ::Initializer, + super_init: >::Initializer, } impl PyClassInitializer { /// Constructs a new initializer from value `T` and base class' initializer. /// /// It is recommended to use `add_subclass` instead of this method for most usage. - pub fn new(init: T, super_init: ::Initializer) -> Self { + pub fn new( + init: T, + super_init: >::Initializer, + ) -> Self { Self { init, super_init } } @@ -190,7 +193,7 @@ impl PyClassInitializer { pub fn add_subclass(self, subclass_value: S) -> PyClassInitializer where S: PyClass, - S::BaseType: PyClassBaseType, + S::BaseType: PyClassBaseType, { PyClassInitializer::new(subclass_value, self) } @@ -231,16 +234,16 @@ impl PyObjectInit for PyClassInitializer { /// Layout of a PyCellBase after base new has been called, but the borrow flag has not /// yet been initialized. #[repr(C)] - struct PartiallyInitializedPyCellBase { + struct PartiallyInitializedPyCellBase { _ob_base: T, - borrow_flag: MaybeUninit>, + borrow_flag: MaybeUninit, } /// Layout of a PyCell after base new has been called, but the contents have not yet been /// written. #[repr(C)] struct PartiallyInitializedPyCell { - _ob_base: ::LayoutAsBase, + _ob_base: >::LayoutAsBase, contents: MaybeUninit>, } @@ -274,7 +277,7 @@ impl PyObjectInit for PyClassInitializer { impl From for PyClassInitializer where T: PyClass, - T::BaseType: PyClassBaseType>, + T::BaseType: PyClassBaseType>, { #[inline] fn from(value: T) -> PyClassInitializer { diff --git a/src/types/mod.rs b/src/types/mod.rs index 1058a16a05b..2aa889ffc54 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -196,8 +196,8 @@ macro_rules! pyobject_native_type_sized { ($name:ty, $layout:path $(;$generics:ident)*) => { unsafe impl $crate::type_object::PyLayout<$name> for $layout {} impl $crate::type_object::PySizedLayout<$name> for $layout {} - impl<'a, $($generics,)*> $crate::impl_::pyclass::PyClassBaseType for $name { - type LayoutAsBase = $crate::pycell::PyCellBase<$layout>; + impl<'a, $($generics,)*> $crate::impl_::pyclass::PyClassBaseType<$crate::pycell::Mutable> for $name { + type LayoutAsBase = $crate::pycell::PyCellBase<$layout, $crate::pycell::Mutable>; type BaseNativeType = $name; type ThreadChecker = $crate::impl_::pyclass::ThreadCheckerStub<$crate::PyObject>; type Initializer = $crate::pyclass_init::PyNativeTypeInitializer; diff --git a/tests/test_compile_error.rs b/tests/test_compile_error.rs index 2fef7685c6b..2af1c9ff53a 100644 --- a/tests/test_compile_error.rs +++ b/tests/test_compile_error.rs @@ -28,8 +28,12 @@ fn _test_compile_errors() { t.compile_fail("tests/ui/invalid_pymethod_names.rs"); t.compile_fail("tests/ui/invalid_pymodule_args.rs"); t.compile_fail("tests/ui/reject_generics.rs"); +<<<<<<< HEAD t.compile_fail("tests/ui/invalid_pymethod_proto_args.rs"); t.compile_fail("tests/ui/invalid_pymethod_proto_args_py.rs"); +======= + t.compile_fail("tests/ui/invalid_immutable_pyclass_borrow.rs"); +>>>>>>> 0fa03a67cd (Implement opt-in immutable pyclasses) tests_rust_1_49(&t); tests_rust_1_56(&t); diff --git a/tests/ui/invalid_immutable_pyclass_borrow.rs b/tests/ui/invalid_immutable_pyclass_borrow.rs new file mode 100644 index 00000000000..d020fbec578 --- /dev/null +++ b/tests/ui/invalid_immutable_pyclass_borrow.rs @@ -0,0 +1,13 @@ +use pyo3::prelude::*; + +#[pyclass(immutable)] +pub struct Foo { + #[pyo3(get)] + field: u32, +} + +fn borrow_mut_fails(foo: Py, py: Python){ + let borrow = foo.as_ref(py).borrow_mut(); +} + +fn main(){} \ No newline at end of file diff --git a/tests/ui/invalid_immutable_pyclass_borrow.stderr b/tests/ui/invalid_immutable_pyclass_borrow.stderr new file mode 100644 index 00000000000..3574139a5b3 --- /dev/null +++ b/tests/ui/invalid_immutable_pyclass_borrow.stderr @@ -0,0 +1,5 @@ +error[E0277]: the trait bound `Foo: MutablePyClass` is not satisfied + --> tests/ui/invalid_immutable_pyclass_borrow.rs:10:33 + | +10 | let borrow = foo.as_ref(py).borrow_mut(); + | ^^^^^^^^^^ the trait `MutablePyClass` is not implemented for `Foo` diff --git a/tests/ui/pyclass_send.stderr b/tests/ui/pyclass_send.stderr index 287430ac078..7e5addacf80 100644 --- a/tests/ui/pyclass_send.stderr +++ b/tests/ui/pyclass_send.stderr @@ -11,8 +11,20 @@ note: required because it appears within the type `NotThreadSafe` 5 | struct NotThreadSafe { | ^^^^^^^^^^^^^ note: required by a bound in `ThreadCheckerStub` +<<<<<<< HEAD +<<<<<<< HEAD --> src/impl_/pyclass.rs | | pub struct ThreadCheckerStub(PhantomData); +======= + --> src/class/impl_.rs:731:33 + | +731 | pub struct ThreadCheckerStub(PhantomData); +>>>>>>> 0fa03a67cd (Implement opt-in immutable pyclasses) +======= + --> src/class/impl_.rs:728:33 + | +728 | pub struct ThreadCheckerStub(PhantomData); +>>>>>>> 7cded1178d (Fix formatting.) | ^^^^ required by this bound in `ThreadCheckerStub` = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) From 6f39deaa374e2de8bc7b244d7ff18eede8257907 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Tue, 8 Feb 2022 08:01:30 +0000 Subject: [PATCH 12/16] finish off implementation --- pyo3-macros-backend/src/pyclass.rs | 27 ++++--------------- src/impl_/pyclass.rs | 22 +++++++++------ src/pycell.rs | 8 +++--- src/pyclass.rs | 13 ++++----- src/pyclass_init.rs | 25 +++++++++-------- src/types/mod.rs | 4 +-- tests/test_compile_error.rs | 5 +--- tests/ui/abi3_nativetype_inheritance.stderr | 10 ++++--- .../invalid_immutable_pyclass_borrow.stderr | 17 ++++++++---- tests/ui/pyclass_send.stderr | 12 --------- 10 files changed, 63 insertions(+), 80 deletions(-) diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index c233900e9d5..3f748379848 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -592,29 +592,12 @@ fn impl_enum_class( let default_items = gen_default_items(cls, vec![default_repr_impl, default_richcmp, default_int]); - let mutability = if args.is_immutable { - quote! { - unsafe impl _pyo3::pyclass::ImmutablePyClass for #cls {} - } - } else { - quote! { - unsafe impl _pyo3::pyclass::MutablePyClass for #cls {} - - impl<'a> _pyo3::derive_utils::ExtractExt<'a> for &'a mut #cls - { - type Target = _pyo3::PyRefMut<'a, #cls>; - } - } - }; - Ok(quote! { const _: () = { use #krate as _pyo3; #pytypeinfo - #mutability - #pyclass_impls #default_items @@ -801,7 +784,7 @@ impl<'a> PyClassImplsBuilder<'a> { }; let base_nativetype = if attr.has_extends { - quote! { ::BaseNativeType } + quote! { >::BaseNativeType } } else { quote! { _pyo3::PyAny } }; @@ -810,7 +793,6 @@ impl<'a> PyClassImplsBuilder<'a> { type Dict = #dict; type WeakRef = #weakref; type BaseNativeType = #base_nativetype; - type Mutability = _pyo3::pycell::Immutable; } } } @@ -944,7 +926,7 @@ impl<'a> PyClassImplsBuilder<'a> { }; quote! { - impl _pyo3::impl_::pyclass::PyClassImpl<#mutability> for #cls { + impl _pyo3::impl_::pyclass::PyClassImpl for #cls { const DOC: &'static str = #doc; const IS_GC: bool = #is_gc; const IS_BASETYPE: bool = #is_basetype; @@ -954,6 +936,7 @@ impl<'a> PyClassImplsBuilder<'a> { type BaseType = #base; type ThreadChecker = #thread_checker; #inventory + type Mutability = #mutability; fn for_all_items(visitor: &mut dyn ::std::ops::FnMut(& _pyo3::impl_::pyclass::PyClassItems)) { use _pyo3::impl_::pyclass::*; @@ -985,9 +968,9 @@ impl<'a> PyClassImplsBuilder<'a> { #dict_offset #weaklist_offset - - #inventory_class } + + #inventory_class } } diff --git a/src/impl_/pyclass.rs b/src/impl_/pyclass.rs index 5ef2423d2dd..cac1b982e7b 100644 --- a/src/impl_/pyclass.rs +++ b/src/impl_/pyclass.rs @@ -2,7 +2,8 @@ use crate::{ exceptions::{PyAttributeError, PyNotImplementedError}, ffi, impl_::freelist::FreeList, - pycell::{Mutability, PyCellLayout}, + pycell::{Mutability, Mutable, PyCellLayout}, + pyclass::MutablePyClass, pyclass_init::PyObjectInit, type_object::{PyLayout, PyTypeObject}, PyCell, PyClass, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python, @@ -145,7 +146,7 @@ unsafe impl Sync for PyClassItems {} /// /// Users are discouraged from implementing this trait manually; it is a PyO3 implementation detail /// and may be changed at any time. -pub trait PyClassImpl: Sized { +pub trait PyClassImpl: Sized { /// Class doc string const DOC: &'static str = "\0"; @@ -162,7 +163,10 @@ pub trait PyClassImpl: Sized { type Layout: PyLayout; /// Base class - type BaseType: PyTypeInfo + PyTypeObject + PyClassBaseType; + type BaseType: PyTypeInfo + PyTypeObject + PyClassBaseType; + + /// Immutable or mutable + type Mutability: Mutability; /// This handles following two situations: /// 1. In case `T` is `Send`, stub `ThreadChecker` is used and does nothing. @@ -818,13 +822,13 @@ impl PyClassThreadChecker for ThreadCheckerImpl { /// Thread checker for types that have `Send` and `extends=...`. /// Ensures that `T: Send` and the parent is not accessed by another thread. #[doc(hidden)] -pub struct ThreadCheckerInherited, M: Mutability>( +pub struct ThreadCheckerInherited>( PhantomData, U::ThreadChecker, ); -impl, M: Mutability> PyClassThreadChecker - for ThreadCheckerInherited +impl> PyClassThreadChecker + for ThreadCheckerInherited { fn ensure(&self) { self.1.ensure(); @@ -843,8 +847,10 @@ pub trait PyClassBaseType: Sized { type Initializer: PyObjectInit; } -/// All PyClasses can be used as a base type. -impl PyClassBaseType for T { +/// All mutable PyClasses can be used as a base type. +/// +/// In the future this will be extended to immutable PyClasses too. +impl PyClassBaseType for T { type LayoutAsBase = crate::pycell::PyCell; type BaseNativeType = T::BaseNativeType; type ThreadChecker = T::ThreadChecker; diff --git a/src/pycell.rs b/src/pycell.rs index 4d116d45687..5caed4bf9a8 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -700,8 +700,8 @@ impl<'p, T: PyClass> PyRef<'p, T> { impl<'p, T, U> AsRef for PyRef<'p, T> where - T: PyClass, - U: PyClass, + T: MutablePyClass, // For now, only mutable classes can be extended + U: MutablePyClass, { fn as_ref(&self) -> &T::BaseType { unsafe { &*self.inner.ob_base.get_ptr() } @@ -710,8 +710,8 @@ where impl<'p, T, U> PyRef<'p, T> where - T: PyClass, - U: PyClass, + T: MutablePyClass, // For now, only mutable classes can be extended + U: MutablePyClass, { /// Gets a `PyRef`. /// diff --git a/src/pyclass.rs b/src/pyclass.rs index ee448b32d2c..a701396dba6 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -1,5 +1,5 @@ //! `PyClass` and related traits. -use crate::pycell::Mutability; +use crate::pycell::{Immutable, Mutable}; use crate::{ callback::IntoPyCallbackOutput, ffi, @@ -23,7 +23,7 @@ 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: - PyTypeInfo> + PyClassImpl> + PyTypeInfo> + PyClassImpl> { /// Specify this class has `#[pyclass(dict)]` or not. type Dict: PyClassDict; @@ -32,12 +32,13 @@ 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; - - type Mutability: Mutability; } -pub unsafe trait MutablePyClass: PyClass {} -pub unsafe trait ImmutablePyClass: PyClass {} +pub trait MutablePyClass: PyClass {} +pub trait ImmutablePyClass: PyClass {} + +impl MutablePyClass for T where T: PyClass {} +impl ImmutablePyClass for T where T: PyClass {} fn into_raw(vec: Vec) -> *mut c_void { Box::into_raw(vec.into_boxed_slice()) as _ diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index e478472b6dc..ec0dfcacaef 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -1,14 +1,16 @@ //! Contains initialization utilities for `#[pyclass]`. use crate::callback::IntoPyCallbackOutput; use crate::impl_::pyclass::{PyClassBaseType, PyClassDict, PyClassThreadChecker, PyClassWeakRef}; +use crate::pycell::Mutability; +use crate::pyclass::MutablePyClass; use crate::{ffi, PyCell, PyClass, PyErr, PyResult, Python}; use crate::{ ffi::PyTypeObject, - pycell::{BorrowFlag, PyCellContents}, + pycell::PyCellContents, type_object::{get_tp_alloc, PyTypeInfo}, }; use std::{ - cell::{Cell, UnsafeCell}, + cell::UnsafeCell, marker::PhantomData, mem::{ManuallyDrop, MaybeUninit}, }; @@ -193,7 +195,7 @@ impl PyClassInitializer { pub fn add_subclass(self, subclass_value: S) -> PyClassInitializer where S: PyClass, - S::BaseType: PyClassBaseType, + S::BaseType: PyClassBaseType, { PyClassInitializer::new(subclass_value, self) } @@ -234,9 +236,9 @@ impl PyObjectInit for PyClassInitializer { /// Layout of a PyCellBase after base new has been called, but the borrow flag has not /// yet been initialized. #[repr(C)] - struct PartiallyInitializedPyCellBase { + struct PartiallyInitializedPyCellBase { _ob_base: T, - borrow_flag: MaybeUninit, + borrow_flag: MaybeUninit, } /// Layout of a PyCell after base new has been called, but the contents have not yet been @@ -251,11 +253,8 @@ impl PyObjectInit for PyClassInitializer { let obj = super_init.into_new_object(py, subtype)?; // FIXME: Only need to initialize borrow flag once per whole hierarchy - let base: *mut PartiallyInitializedPyCellBase = obj as _; - std::ptr::write( - (*base).borrow_flag.as_mut_ptr(), - Cell::new(BorrowFlag::UNUSED), - ); + let base: *mut PartiallyInitializedPyCellBase = obj as _; + std::ptr::write((*base).borrow_flag.as_mut_ptr(), T::Mutability::new()); // FIXME: Initialize borrow flag if necessary?? let cell: *mut PartiallyInitializedPyCell = obj as _; @@ -287,9 +286,9 @@ where impl From<(S, B)> for PyClassInitializer where - S: PyClass, - B: PyClass, - B::BaseType: PyClassBaseType>, + S: MutablePyClass, + B: MutablePyClass, + B::BaseType: PyClassBaseType>, { fn from(sub_and_base: (S, B)) -> PyClassInitializer { let (sub, base) = sub_and_base; diff --git a/src/types/mod.rs b/src/types/mod.rs index 2aa889ffc54..b7404aa9972 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -196,8 +196,8 @@ macro_rules! pyobject_native_type_sized { ($name:ty, $layout:path $(;$generics:ident)*) => { unsafe impl $crate::type_object::PyLayout<$name> for $layout {} impl $crate::type_object::PySizedLayout<$name> for $layout {} - impl<'a, $($generics,)*> $crate::impl_::pyclass::PyClassBaseType<$crate::pycell::Mutable> for $name { - type LayoutAsBase = $crate::pycell::PyCellBase<$layout, $crate::pycell::Mutable>; + impl<'a, M: $crate::pycell::Mutability, $($generics,)*> $crate::impl_::pyclass::PyClassBaseType for $name { + type LayoutAsBase = $crate::pycell::PyCellBase<$layout, M>; type BaseNativeType = $name; type ThreadChecker = $crate::impl_::pyclass::ThreadCheckerStub<$crate::PyObject>; type Initializer = $crate::pyclass_init::PyNativeTypeInitializer; diff --git a/tests/test_compile_error.rs b/tests/test_compile_error.rs index 2af1c9ff53a..6c9e504d04e 100644 --- a/tests/test_compile_error.rs +++ b/tests/test_compile_error.rs @@ -17,6 +17,7 @@ fn test_compile_errors() { fn _test_compile_errors() { let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/invalid_immutable_pyclass_borrow.rs"); t.compile_fail("tests/ui/invalid_macro_args.rs"); t.compile_fail("tests/ui/invalid_need_module_arg_position.rs"); t.compile_fail("tests/ui/invalid_property_args.rs"); @@ -28,12 +29,8 @@ fn _test_compile_errors() { t.compile_fail("tests/ui/invalid_pymethod_names.rs"); t.compile_fail("tests/ui/invalid_pymodule_args.rs"); t.compile_fail("tests/ui/reject_generics.rs"); -<<<<<<< HEAD t.compile_fail("tests/ui/invalid_pymethod_proto_args.rs"); t.compile_fail("tests/ui/invalid_pymethod_proto_args_py.rs"); -======= - t.compile_fail("tests/ui/invalid_immutable_pyclass_borrow.rs"); ->>>>>>> 0fa03a67cd (Implement opt-in immutable pyclasses) tests_rust_1_49(&t); tests_rust_1_56(&t); diff --git a/tests/ui/abi3_nativetype_inheritance.stderr b/tests/ui/abi3_nativetype_inheritance.stderr index 360a0a45bc3..bd425a233e9 100644 --- a/tests/ui/abi3_nativetype_inheritance.stderr +++ b/tests/ui/abi3_nativetype_inheritance.stderr @@ -4,7 +4,8 @@ error[E0277]: the trait bound `PyDict: PyClass` is not satisfied 5 | #[pyclass(extends=PyDict)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `PyClass` is not implemented for `PyDict` | - = note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict` + = note: required because of the requirements on the impl of `MutablePyClass` for `PyDict` + = note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict` = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `PyDict: PyClass` is not satisfied @@ -13,10 +14,11 @@ error[E0277]: the trait bound `PyDict: PyClass` is not satisfied 5 | #[pyclass(extends=PyDict)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `PyClass` is not implemented for `PyDict` | - = note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict` + = note: required because of the requirements on the impl of `MutablePyClass` for `PyDict` + = note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict` note: required by a bound in `ThreadCheckerInherited` --> src/impl_/pyclass.rs | - | pub struct ThreadCheckerInherited(PhantomData, U::ThreadChecker); - | ^^^^^^^^^^^^^^^ required by this bound in `ThreadCheckerInherited` + | pub struct ThreadCheckerInherited>( + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `ThreadCheckerInherited` = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/invalid_immutable_pyclass_borrow.stderr b/tests/ui/invalid_immutable_pyclass_borrow.stderr index 3574139a5b3..4929088761a 100644 --- a/tests/ui/invalid_immutable_pyclass_borrow.stderr +++ b/tests/ui/invalid_immutable_pyclass_borrow.stderr @@ -1,5 +1,12 @@ -error[E0277]: the trait bound `Foo: MutablePyClass` is not satisfied - --> tests/ui/invalid_immutable_pyclass_borrow.rs:10:33 - | -10 | let borrow = foo.as_ref(py).borrow_mut(); - | ^^^^^^^^^^ the trait `MutablePyClass` is not implemented for `Foo` +error[E0271]: type mismatch resolving `::Mutability == Mutable` + --> tests/ui/invalid_immutable_pyclass_borrow.rs:10:33 + | +10 | let borrow = foo.as_ref(py).borrow_mut(); + | ^^^^^^^^^^ expected struct `Mutable`, found struct `Immutable` + | + = note: required because of the requirements on the impl of `MutablePyClass` for `Foo` +note: required by a bound in `PyCell::::borrow_mut` + --> src/pycell.rs + | + | T: MutablePyClass, + | ^^^^^^^^^^^^^^ required by this bound in `PyCell::::borrow_mut` diff --git a/tests/ui/pyclass_send.stderr b/tests/ui/pyclass_send.stderr index 7e5addacf80..287430ac078 100644 --- a/tests/ui/pyclass_send.stderr +++ b/tests/ui/pyclass_send.stderr @@ -11,20 +11,8 @@ note: required because it appears within the type `NotThreadSafe` 5 | struct NotThreadSafe { | ^^^^^^^^^^^^^ note: required by a bound in `ThreadCheckerStub` -<<<<<<< HEAD -<<<<<<< HEAD --> src/impl_/pyclass.rs | | pub struct ThreadCheckerStub(PhantomData); -======= - --> src/class/impl_.rs:731:33 - | -731 | pub struct ThreadCheckerStub(PhantomData); ->>>>>>> 0fa03a67cd (Implement opt-in immutable pyclasses) -======= - --> src/class/impl_.rs:728:33 - | -728 | pub struct ThreadCheckerStub(PhantomData); ->>>>>>> 7cded1178d (Fix formatting.) | ^^^^ required by this bound in `ThreadCheckerStub` = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) From b7745dffc81d0910a7cd5a0459f6088847c92df7 Mon Sep 17 00:00:00 2001 From: mejrs Date: Tue, 12 Apr 2022 14:19:02 +0200 Subject: [PATCH 13/16] Fix everything --- pyo3-macros-backend/src/attributes.rs | 1 + pyo3-macros-backend/src/pyclass.rs | 36 +++++------------------ src/class/buffer.rs | 2 +- src/class/gc.rs | 4 +-- src/impl_/pyclass.rs | 2 +- src/pycell.rs | 4 +-- src/pyclass.rs | 4 +-- src/pyclass_init.rs | 4 +-- src/types/mod.rs | 2 +- tests/ui/invalid_pyclass_args.stderr | 4 +-- tests/ui/invalid_pymethod_receiver.stderr | 2 +- tests/ui/pyclass_send.stderr | 3 +- 12 files changed, 23 insertions(+), 45 deletions(-) diff --git a/pyo3-macros-backend/src/attributes.rs b/pyo3-macros-backend/src/attributes.rs index 2b63f0cfb11..bbe536b6fba 100644 --- a/pyo3-macros-backend/src/attributes.rs +++ b/pyo3-macros-backend/src/attributes.rs @@ -28,6 +28,7 @@ pub mod kw { syn::custom_keyword!(transparent); syn::custom_keyword!(unsendable); syn::custom_keyword!(weakref); + syn::custom_keyword!(immutable); } #[derive(Clone, Debug)] diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index d5091b8a508..0393c459f79 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -54,6 +54,7 @@ pub struct PyClassPyO3Options { pub dict: Option, pub extends: Option, pub freelist: Option, + pub immutable: Option, pub module: Option, pub name: Option, pub subclass: Option, @@ -69,6 +70,7 @@ enum PyClassPyO3Option { Dict(kw::dict), Extends(ExtendsAttribute), Freelist(FreelistAttribute), + Immutable(kw::immutable), Module(ModuleAttribute), Name(NameAttribute), Subclass(kw::subclass), @@ -90,6 +92,8 @@ impl Parse for PyClassPyO3Option { input.parse().map(PyClassPyO3Option::Extends) } else if lookahead.peek(attributes::kw::freelist) { input.parse().map(PyClassPyO3Option::Freelist) + } else if lookahead.peek(attributes::kw::immutable) { + input.parse().map(PyClassPyO3Option::Immutable) } else if lookahead.peek(attributes::kw::module) { input.parse().map(PyClassPyO3Option::Module) } else if lookahead.peek(kw::name) { @@ -145,6 +149,7 @@ impl PyClassPyO3Options { PyClassPyO3Option::Dict(dict) => set_option!(dict), PyClassPyO3Option::Extends(extends) => set_option!(extends), PyClassPyO3Option::Freelist(freelist) => set_option!(freelist), + PyClassPyO3Option::Immutable(immutable) => set_option!(immutable), PyClassPyO3Option::Module(module) => set_option!(module), PyClassPyO3Option::Name(name) => set_option!(name), PyClassPyO3Option::Subclass(subclass) => set_option!(subclass), @@ -671,7 +676,6 @@ impl<'a> PyClassImplsBuilder<'a> { self.impl_extractext(), self.impl_into_py(), self.impl_pyclassimpl(), - self.impl_mutability(), self.impl_freelist(), ] .into_iter() @@ -694,7 +698,7 @@ impl<'a> PyClassImplsBuilder<'a> { quote! { _pyo3::impl_::pyclass::PyClassDummySlot } }; - let base_nativetype = if attr.has_extends { + let base_nativetype = if attr.options.extends.is_some() { quote! { >::BaseNativeType } } else { quote! { _pyo3::PyAny } @@ -705,13 +709,12 @@ impl<'a> PyClassImplsBuilder<'a> { type Dict = #dict; type WeakRef = #weakref; type BaseNativeType = #base_nativetype; - type Mutability = ::pyo3::pycell::Immutable; } } } fn impl_extractext(&self) -> TokenStream { let cls = self.cls; - if self.attr.is_immutable { + if self.attr.options.immutable.is_some() { quote! { impl<'a> _pyo3::derive_utils::ExtractExt<'a> for &'a #cls { @@ -837,17 +840,7 @@ impl<'a> PyClassImplsBuilder<'a> { let deprecations = &self.attr.deprecations; - let mutability = if self.attr.is_immutable { - quote! { - _pyo3::pycell::Immutable - } - } else { - quote! { - _pyo3::pycell::Mutable - } - }; - - let mutability = if self.attr.is_immutable { + let mutability = if self.attr.options.immutable.is_some() { quote! { _pyo3::pycell::Immutable } @@ -891,19 +884,6 @@ impl<'a> PyClassImplsBuilder<'a> { } } - fn impl_mutability(&self) -> TokenStream { - let cls = self.cls; - if self.attr.is_immutable { - quote! { - unsafe impl ::pyo3::pyclass::ImmutablePyClass for #cls {} - } - } else { - quote! { - unsafe impl ::pyo3::pyclass::MutablePyClass for #cls {} - } - } - } - fn impl_freelist(&self) -> TokenStream { let cls = self.cls; diff --git a/src/class/buffer.rs b/src/class/buffer.rs index cbfa8fdf7b6..90693813644 100644 --- a/src/class/buffer.rs +++ b/src/class/buffer.rs @@ -15,7 +15,7 @@ use std::os::raw::c_int; /// c-api. #[allow(unused_variables)] #[deprecated(since = "0.16.0", note = "prefer `#[pymethods]` to `#[pyproto]`")] -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( diff --git a/src/class/gc.rs b/src/class/gc.rs index 1f81cda42e6..ec4ab6a622a 100644 --- a/src/class/gc.rs +++ b/src/class/gc.rs @@ -3,14 +3,14 @@ //! Python GC support -use crate::{ffi, pyclass::MutablePyClass, AsPyPointer, PyCell, Python, PyClass}; +use crate::{ffi, pyclass::MutablePyClass, PyCell}; use std::os::raw::{c_int, c_void}; pub use crate::impl_::pymethods::{PyTraverseError, PyVisit}; /// GC support #[deprecated(since = "0.16.0", note = "prefer `#[pymethods]` to `#[pyproto]`")] -pub trait PyGCProtocol<'p>: PyClass { +pub trait PyGCProtocol<'p>: MutablePyClass { fn __traverse__(&'p self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>; fn __clear__(&'p mut self); } diff --git a/src/impl_/pyclass.rs b/src/impl_/pyclass.rs index b2d7db04497..6f90fdb9b7c 100644 --- a/src/impl_/pyclass.rs +++ b/src/impl_/pyclass.rs @@ -146,7 +146,7 @@ unsafe impl Sync for PyClassItems {} /// /// Users are discouraged from implementing this trait manually; it is a PyO3 implementation detail /// and may be changed at any time. -pub trait PyClassImpl: Sized { +pub trait PyClassImpl: Sized { /// Class doc string const DOC: &'static str = "\0"; diff --git a/src/pycell.rs b/src/pycell.rs index 8c8569f32d2..da9c1bcfd43 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -172,8 +172,8 @@ //! [guide]: https://pyo3.rs/latest/class.html#pycell-and-interior-mutability "PyCell and interior mutability" //! [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::class::impl_::PyClassImpl; use crate::exceptions::PyRuntimeError; +use crate::impl_::pyclass::{PyClassBaseType, PyClassDict, PyClassThreadChecker, PyClassWeakRef}; use crate::pyclass::{MutablePyClass, PyClass}; use crate::pyclass_init::PyClassInitializer; use crate::type_object::{PyLayout, PySizedLayout}; @@ -878,7 +878,7 @@ impl<'p, T: MutablePyClass> Drop for PyRefMut<'p, T> { } } -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()) } } diff --git a/src/pyclass.rs b/src/pyclass.rs index 59302b528de..605e2537ff3 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -24,7 +24,7 @@ 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: - PyTypeInfo> + PyClassImpl> + PyTypeInfo> + PyClassImpl> { /// Specify this class has `#[pyclass(dict)]` or not. type Dict: PyClassDict; @@ -33,8 +33,6 @@ 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; - - type Mutability: Mutability; } pub trait MutablePyClass: PyClass {} diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index 807a9a226d0..da55011e076 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -236,9 +236,9 @@ impl PyObjectInit for PyClassInitializer { /// Layout of a PyCellBase after base new has been called, but the borrow flag has not /// yet been initialized. #[repr(C)] - struct PartiallyInitializedPyCellBase { + struct PartiallyInitializedPyCellBase { _ob_base: T, - borrow_flag: MaybeUninit, + borrow_flag: MaybeUninit, } /// Layout of a PyCell after base new has been called, but the contents have not yet been diff --git a/src/types/mod.rs b/src/types/mod.rs index 6879345e545..9d7ac5c848f 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -191,7 +191,7 @@ macro_rules! pyobject_native_type_sized { ($name:ty, $layout:path $(;$generics:ident)*) => { unsafe impl $crate::type_object::PyLayout<$name> for $layout {} impl $crate::type_object::PySizedLayout<$name> for $layout {} - impl<'a, M: $crate::pycell::Mutability, $($generics,)*> $crate::impl_::pyclass::PyClassBaseType for $name { + impl $crate::impl_::pyclass::PyClassBaseType for $name { type LayoutAsBase = $crate::pycell::PyCellBase<$layout, M>; type BaseNativeType = $name; type ThreadChecker = $crate::impl_::pyclass::ThreadCheckerStub<$crate::PyObject>; diff --git a/tests/ui/invalid_pyclass_args.stderr b/tests/ui/invalid_pyclass_args.stderr index fd64e06e755..30374dab80c 100644 --- a/tests/ui/invalid_pyclass_args.stderr +++ b/tests/ui/invalid_pyclass_args.stderr @@ -1,4 +1,4 @@ -error: expected one of: `crate`, `dict`, `extends`, `freelist`, `module`, `name`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc` +error: expected one of: `crate`, `dict`, `extends`, `freelist`, `immutable`, `module`, `name`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc` --> tests/ui/invalid_pyclass_args.rs:3:11 | 3 | #[pyclass(extend=pyo3::types::PyDict)] @@ -34,7 +34,7 @@ error: expected string literal 18 | #[pyclass(module = my_module)] | ^^^^^^^^^ -error: expected one of: `crate`, `dict`, `extends`, `freelist`, `module`, `name`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc` +error: expected one of: `crate`, `dict`, `extends`, `freelist`, `immutable`, `module`, `name`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc` --> tests/ui/invalid_pyclass_args.rs:21:11 | 21 | #[pyclass(weakrev)] diff --git a/tests/ui/invalid_pymethod_receiver.stderr b/tests/ui/invalid_pymethod_receiver.stderr index fec45af87be..9584b37af67 100644 --- a/tests/ui/invalid_pymethod_receiver.stderr +++ b/tests/ui/invalid_pymethod_receiver.stderr @@ -9,6 +9,6 @@ error[E0277]: the trait bound `i32: From<&PyCell>` is not satisfied > > > - and 2 others + and 71 others = note: required because of the requirements on the impl of `Into` for `&PyCell` = note: required because of the requirements on the impl of `TryFrom<&PyCell>` for `i32` diff --git a/tests/ui/pyclass_send.stderr b/tests/ui/pyclass_send.stderr index d4d670c6548..287430ac078 100644 --- a/tests/ui/pyclass_send.stderr +++ b/tests/ui/pyclass_send.stderr @@ -11,9 +11,8 @@ note: required because it appears within the type `NotThreadSafe` 5 | struct NotThreadSafe { | ^^^^^^^^^^^^^ note: required by a bound in `ThreadCheckerStub` - --> src/class/impl_.rs:728:33 --> src/impl_/pyclass.rs | -728 | pub struct ThreadCheckerStub(PhantomData); + | pub struct ThreadCheckerStub(PhantomData); | ^^^^ required by this bound in `ThreadCheckerStub` = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) From 53a642eda7ebe86a021f90f1146eb792013bb152 Mon Sep 17 00:00:00 2001 From: mejrs Date: Tue, 12 Apr 2022 15:49:23 +0200 Subject: [PATCH 14/16] Fix ui test --- tests/ui/invalid_pyclass_args.stderr | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/ui/invalid_pyclass_args.stderr b/tests/ui/invalid_pyclass_args.stderr index 02a2fa5a6e0..1fd61d5c364 100644 --- a/tests/ui/invalid_pyclass_args.stderr +++ b/tests/ui/invalid_pyclass_args.stderr @@ -1,5 +1,4 @@ -error: expected one of: `crate`, `dict`, `extends`, `freelist`, `immutable`, 'mapping', `module`, `name`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc` - +error: expected one of: `crate`, `dict`, `extends`, `freelist`, `immutable`, `mapping`, `module`, `name`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc` --> tests/ui/invalid_pyclass_args.rs:3:11 | 3 | #[pyclass(extend=pyo3::types::PyDict)] @@ -35,7 +34,7 @@ error: expected string literal 18 | #[pyclass(module = my_module)] | ^^^^^^^^^ -error: expected one of: `crate`, `dict`, `extends`, `freelist`, `immutable`, 'mapping', `module`, `name`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc` +error: expected one of: `crate`, `dict`, `extends`, `freelist`, `immutable`, `mapping`, `module`, `name`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc` --> tests/ui/invalid_pyclass_args.rs:21:11 | 21 | #[pyclass(weakrev)] From e9bd41efb28df60535262b5d425f3888c4b231e5 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Thu, 21 Apr 2022 08:03:45 +0100 Subject: [PATCH 15/16] better mutability inheritance rules --- guide/src/class.md | 22 +- pyo3-macros-backend/src/pyclass.rs | 60 ++-- src/impl_/pyclass.rs | 30 +- src/pycell.rs | 278 +++++++++++++++--- src/pyclass.rs | 14 +- src/pyclass_init.rs | 32 +- src/types/mod.rs | 5 +- tests/test_compile_error.rs | 4 +- tests/test_mutable_pyclass.rs | 201 +++++++++++++ tests/ui/abi3_nativetype_inheritance.stderr | 26 +- tests/ui/invalid_immutable_pyclass_borrow.rs | 12 +- .../invalid_immutable_pyclass_borrow.stderr | 13 + 12 files changed, 555 insertions(+), 142 deletions(-) create mode 100644 tests/test_mutable_pyclass.rs diff --git a/guide/src/class.md b/guide/src/class.md index 2dc9194e4e8..81802df7d32 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -956,13 +956,7 @@ unsafe impl ::pyo3::type_object::PyTypeInfo for MyClass { } } -impl ::pyo3::PyClass for MyClass { - type Dict = ::pyo3::impl_::pyclass::PyClassDummySlot; - type WeakRef = ::pyo3::impl_::pyclass::PyClassDummySlot; - type BaseNativeType = ::pyo3::PyAny; -} - -unsafe impl ::pyo3::pyclass::MutablePyClass for MyClass {} +impl ::pyo3::PyClass for MyClass { } impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a mut MyClass { type Target = ::pyo3::PyRefMut<'a, MyClass>; @@ -985,7 +979,11 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass { type Layout = PyCell; type BaseType = PyAny; type ThreadChecker = pyo3::impl_::pyclass::ThreadCheckerStub; - type Mutabilty = pyo3::pyclass::Mutable; + type Mutability = pyo3::pycell::Mutable; + type PyClassMutability = pyo3::pycell::MutableClass; + type Dict = ::pyo3::impl_::pyclass::PyClassDummySlot; + type WeakRef = ::pyo3::impl_::pyclass::PyClassDummySlot; + type BaseNativeType = ::pyo3::PyAny; fn for_all_items(visitor: &mut dyn FnMut(&pyo3::impl_::pyclass::PyClassItems)) { use pyo3::impl_::pyclass::*; @@ -996,14 +994,6 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass { } } -impl ::pyo3::impl_::pyclass::PyClassDescriptors - for ::pyo3::impl_::pyclass::PyClassImplCollector -{ - fn py_class_descriptors(self) -> &'static [::pyo3::class::methods::PyMethodDefType] { - static METHODS: &[::pyo3::class::methods::PyMethodDefType] = &[]; - METHODS - } -} # Python::with_gil(|py| { # let cls = py.get_type::(); # pyo3::py_run!(py, cls, "assert cls.__name__ == 'MyClass'") diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 99bc545c4ae..d421acf5d9e 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -689,32 +689,9 @@ impl<'a> PyClassImplsBuilder<'a> { fn impl_pyclass(&self) -> TokenStream { let cls = self.cls; - let attr = self.attr; - let dict = if attr.options.dict.is_some() { - quote! { _pyo3::impl_::pyclass::PyClassDictSlot } - } else { - quote! { _pyo3::impl_::pyclass::PyClassDummySlot } - }; - - // insert space for weak ref - let weakref = if attr.options.weakref.is_some() { - quote! { _pyo3::impl_::pyclass::PyClassWeakRefSlot } - } else { - quote! { _pyo3::impl_::pyclass::PyClassDummySlot } - }; - - let base_nativetype = if attr.options.extends.is_some() { - quote! { >::BaseNativeType } - } else { - quote! { _pyo3::PyAny } - }; quote! { - impl _pyo3::PyClass for #cls { - type Dict = #dict; - type WeakRef = #weakref; - type BaseNativeType = #base_nativetype; - } + impl _pyo3::PyClass for #cls { } } } fn impl_extractext(&self) -> TokenStream { @@ -856,6 +833,37 @@ impl<'a> PyClassImplsBuilder<'a> { } }; + let class_mutability = if self.attr.options.immutable.is_some() { + quote! { + ImmutableChild + } + } else { + quote! { + MutableChild + } + }; + + let cls = self.cls; + let attr = self.attr; + let dict = if attr.options.dict.is_some() { + quote! { _pyo3::impl_::pyclass::PyClassDictSlot } + } else { + quote! { _pyo3::impl_::pyclass::PyClassDummySlot } + }; + + // insert space for weak ref + let weakref = if attr.options.weakref.is_some() { + quote! { _pyo3::impl_::pyclass::PyClassWeakRefSlot } + } else { + quote! { _pyo3::impl_::pyclass::PyClassDummySlot } + }; + + let base_nativetype = if attr.options.extends.is_some() { + quote! { ::BaseNativeType } + } else { + quote! { _pyo3::PyAny } + }; + quote! { impl _pyo3::impl_::pyclass::PyClassImpl for #cls { const DOC: &'static str = #doc; @@ -868,6 +876,10 @@ impl<'a> PyClassImplsBuilder<'a> { type ThreadChecker = #thread_checker; #inventory type Mutability = #mutability; + type PyClassMutability = <<#base as _pyo3::impl_::pyclass::PyClassBaseType>::PyClassMutability as _pyo3::pycell::PyClassMutability>::#class_mutability; + type Dict = #dict; + type WeakRef = #weakref; + type BaseNativeType = #base_nativetype; fn for_all_items(visitor: &mut dyn ::std::ops::FnMut(& _pyo3::impl_::pyclass::PyClassItems)) { use _pyo3::impl_::pyclass::*; diff --git a/src/impl_/pyclass.rs b/src/impl_/pyclass.rs index ed569888965..43468de9e48 100644 --- a/src/impl_/pyclass.rs +++ b/src/impl_/pyclass.rs @@ -2,8 +2,7 @@ use crate::{ exceptions::{PyAttributeError, PyNotImplementedError}, ffi, impl_::freelist::FreeList, - pycell::{Mutability, Mutable, PyCellLayout}, - pyclass::MutablePyClass, + pycell::{GetBorrowChecker, Mutability, PyCellLayout, PyClassMutability}, pyclass_init::PyObjectInit, type_object::{PyLayout, PyTypeObject}, Py, PyAny, PyCell, PyClass, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python, @@ -163,11 +162,24 @@ pub trait PyClassImpl: Sized { type Layout: PyLayout; /// Base class - type BaseType: PyTypeInfo + PyTypeObject + PyClassBaseType; + type BaseType: PyTypeInfo + PyTypeObject + PyClassBaseType; /// Immutable or mutable type Mutability: Mutability; + /// Immutable or mutable + type PyClassMutability: PyClassMutability + GetBorrowChecker; + + /// Specify this class has `#[pyclass(dict)]` or not. + type Dict: PyClassDict; + + /// Specify this class has `#[pyclass(weakref)]` or not. + type WeakRef: PyClassWeakRef; + + /// The closest native ancestor. This is `PyAny` by default, and when you declare + /// `#[pyclass(extends=PyDict)]`, it's `PyDict`. + type BaseNativeType: PyTypeInfo + PyNativeType; + /// This handles following two situations: /// 1. In case `T` is `Send`, stub `ThreadChecker` is used and does nothing. /// This implementation is used by default. Compile fails if `T: !Send`. @@ -870,12 +882,12 @@ impl PyClassThreadChecker for ThreadCheckerImpl { /// Thread checker for types that have `Send` and `extends=...`. /// Ensures that `T: Send` and the parent is not accessed by another thread. #[doc(hidden)] -pub struct ThreadCheckerInherited>( +pub struct ThreadCheckerInherited( PhantomData, U::ThreadChecker, ); -impl> PyClassThreadChecker +impl PyClassThreadChecker for ThreadCheckerInherited { fn ensure(&self) { @@ -888,21 +900,23 @@ impl> PyClassThreadChecker< } /// Trait denoting that this class is suitable to be used as a base type for PyClass. -pub trait PyClassBaseType: Sized { - type LayoutAsBase: PyCellLayout; +pub trait PyClassBaseType: Sized { + type LayoutAsBase: PyCellLayout; type BaseNativeType; type ThreadChecker: PyClassThreadChecker; type Initializer: PyObjectInit; + type PyClassMutability: PyClassMutability; } /// All mutable PyClasses can be used as a base type. /// /// In the future this will be extended to immutable PyClasses too. -impl PyClassBaseType for T { +impl PyClassBaseType for T { type LayoutAsBase = crate::pycell::PyCell; type BaseNativeType = T::BaseNativeType; type ThreadChecker = T::ThreadChecker; type Initializer = crate::pyclass_init::PyClassInitializer; + type PyClassMutability = T::PyClassMutability; } /// Implementation of tp_dealloc for all pyclasses diff --git a/src/pycell.rs b/src/pycell.rs index da9c1bcfd43..61ef252e4b7 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -173,7 +173,9 @@ //! [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::impl_::pyclass::{PyClassBaseType, PyClassDict, PyClassThreadChecker, PyClassWeakRef}; +use crate::impl_::pyclass::{ + PyClassBaseType, PyClassDict, PyClassImpl, PyClassThreadChecker, PyClassWeakRef, +}; use crate::pyclass::{MutablePyClass, PyClass}; use crate::pyclass_init::PyClassInitializer; use crate::type_object::{PyLayout, PySizedLayout}; @@ -191,6 +193,212 @@ use std::marker::PhantomData; use std::mem::ManuallyDrop; use std::ops::{Deref, DerefMut}; +pub struct EmptySlot(()); +pub struct BorrowChecker(Cell); +pub struct FilledInAncestor(()); + +impl BorrowChecker { + fn try_borrow(&self) -> Result<(), PyBorrowError> { + let flag = self.0.get(); + if flag != BorrowFlag::HAS_MUTABLE_BORROW { + self.0.set(flag.increment()); + Ok(()) + } else { + Err(PyBorrowError { _private: () }) + } + } + + fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError> { + let flag = self.0.get(); + if flag != BorrowFlag::HAS_MUTABLE_BORROW { + Ok(()) + } else { + Err(PyBorrowError { _private: () }) + } + } + + fn release_borrow(&self) { + let flag = self.0.get(); + self.0.set(flag.decrement()) + } + + fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError> { + let flag = self.0.get(); + if flag == BorrowFlag::UNUSED { + self.0.set(BorrowFlag::HAS_MUTABLE_BORROW); + Ok(()) + } else { + Err(PyBorrowMutError { _private: () }) + } + } + + fn release_borrow_mut(&self) { + self.0.set(BorrowFlag::UNUSED) + } +} + +pub trait PyClassMutabilityStorage { + fn new() -> Self; +} + +impl PyClassMutabilityStorage for EmptySlot { + fn new() -> Self { + Self(()) + } +} + +impl PyClassMutabilityStorage for BorrowChecker { + fn new() -> Self { + Self(Cell::new(BorrowFlag::UNUSED)) + } +} + +// - Storage type, either empty, present, or in ancestor +// - Mutability is either +// - Immutable - i.e. EmptySlot +// - Mutable - i.e. BorrowChecker +// - ExtendsMutableAncestor - FilledInAncestor +// - Mutability trait needs to encode the inheritance + +pub trait PyClassMutability { + // The storage for this inheritance layer. Only the first mutable class in + // an inheritance hierarchy needs to store the borrow flag. + type Storage: PyClassMutabilityStorage; + // The borrow flag needed to implement this class' mutability. Empty until + // the first mutable class, at which point it is BorrowChecker and will be + // for all subclasses. + type Checker; + type ImmutableChild: PyClassMutability; + type MutableChild: PyClassMutability; + + /// Increments immutable borrow count, if possible + fn try_borrow(checker: &Self::Checker) -> Result<(), PyBorrowError>; + + fn try_borrow_unguarded(checker: &Self::Checker) -> Result<(), PyBorrowError>; + + /// Decrements immutable borrow count + fn release_borrow(checker: &Self::Checker); + /// Increments mutable borrow count, if possible + fn try_borrow_mut(checker: &Self::Checker) -> Result<(), PyBorrowMutError>; + /// Decremements mutable borrow count + fn release_borrow_mut(checker: &Self::Checker); +} + +pub trait GetBorrowChecker { + fn borrow_checker(cell: &PyCell) -> &::Checker; +} + +impl> GetBorrowChecker for MutableClass { + fn borrow_checker(cell: &PyCell) -> &BorrowChecker { + &cell.contents.borrow_checker + } +} + +impl> GetBorrowChecker for ImmutableClass { + fn borrow_checker(cell: &PyCell) -> &EmptySlot { + &cell.contents.borrow_checker + } +} + +impl, M: PyClassMutability> GetBorrowChecker + for ExtendsMutableAncestor +where + T::BaseType: PyClassImpl> + + PyClassBaseType>, + ::PyClassMutability: PyClassMutability, +{ + fn borrow_checker(cell: &PyCell) -> &BorrowChecker { + <::PyClassMutability as GetBorrowChecker>::borrow_checker(&cell.ob_base) + } +} + +pub struct ImmutableClass(()); +pub struct MutableClass(()); +pub struct ExtendsMutableAncestor(PhantomData); + +impl PyClassMutability for ImmutableClass { + type Storage = EmptySlot; + type Checker = EmptySlot; + type ImmutableChild = ImmutableClass; + type MutableChild = MutableClass; + + fn try_borrow(_: &EmptySlot) -> Result<(), PyBorrowError> { + Ok(()) + } + + fn try_borrow_unguarded(_: &EmptySlot) -> Result<(), PyBorrowError> { + Ok(()) + } + + fn release_borrow(_: &EmptySlot) {} + + fn try_borrow_mut(_: &EmptySlot) -> Result<(), PyBorrowMutError> { + unreachable!() + } + + fn release_borrow_mut(_: &EmptySlot) { + unreachable!() + } +} + +impl PyClassMutability for MutableClass { + type Storage = BorrowChecker; + type Checker = BorrowChecker; + type ImmutableChild = ExtendsMutableAncestor; + type MutableChild = ExtendsMutableAncestor; + + // FIXME the below are all wrong + + fn try_borrow(checker: &BorrowChecker) -> Result<(), PyBorrowError> { + checker.try_borrow() + } + + fn try_borrow_unguarded(checker: &BorrowChecker) -> Result<(), PyBorrowError> { + checker.try_borrow_unguarded() + } + + fn release_borrow(checker: &BorrowChecker) { + checker.release_borrow() + } + + fn try_borrow_mut(checker: &BorrowChecker) -> Result<(), PyBorrowMutError> { + checker.try_borrow_mut() + } + + fn release_borrow_mut(checker: &BorrowChecker) { + checker.release_borrow_mut() + } +} + +impl PyClassMutability for ExtendsMutableAncestor { + type Storage = EmptySlot; + type Checker = BorrowChecker; + type ImmutableChild = ExtendsMutableAncestor; + type MutableChild = ExtendsMutableAncestor; + + // FIXME the below are all wrong + + fn try_borrow(checker: &BorrowChecker) -> Result<(), PyBorrowError> { + checker.try_borrow() + } + + fn try_borrow_unguarded(checker: &BorrowChecker) -> Result<(), PyBorrowError> { + checker.try_borrow_unguarded() + } + + fn release_borrow(checker: &BorrowChecker) { + checker.release_borrow() + } + + fn try_borrow_mut(checker: &BorrowChecker) -> Result<(), PyBorrowMutError> { + checker.try_borrow_mut() + } + + fn release_borrow_mut(checker: &BorrowChecker) { + checker.release_borrow_mut() + } +} + pub trait Mutability { /// Creates a new borrow checker fn new() -> Self; @@ -284,20 +492,13 @@ impl Mutability for Immutable { } /// Base layout of PyCell. -/// This is necessary for sharing BorrowFlag between parents and children. #[doc(hidden)] #[repr(C)] -pub struct PyCellBase { +pub struct PyCellBase { ob_base: T, - borrow_impl: M, } -unsafe impl PyLayout for PyCellBase -where - U: PySizedLayout, - M: Mutability, -{ -} +unsafe impl PyLayout for PyCellBase where U: PySizedLayout {} /// A container type for (mutably) accessing [`PyClass`] values /// @@ -335,14 +536,15 @@ where /// For more information on how, when and why (not) to use `PyCell` please see the /// [module-level documentation](self). #[repr(C)] -pub struct PyCell { - ob_base: >::LayoutAsBase, +pub struct PyCell { + ob_base: ::LayoutAsBase, contents: PyCellContents, } #[repr(C)] -pub(crate) struct PyCellContents { +pub(crate) struct PyCellContents { pub(crate) value: ManuallyDrop>, + pub(crate) borrow_checker: ::Storage, pub(crate) thread_checker: T::ThreadChecker, pub(crate) dict: T::Dict, pub(crate) weakref: T::WeakRef, @@ -412,9 +614,8 @@ impl PyCell { /// }); /// ``` pub fn try_borrow(&self) -> Result, PyBorrowError> { - self.borrow_checker() - .try_borrow() - .map(|_| PyRef { inner: self }) + self.ensure_threadsafe(); + T::PyClassMutability::try_borrow(self.borrow_checker()).map(|_| PyRef { inner: self }) } /// Mutably borrows the value `T`, returning an error if the value is currently borrowed. @@ -442,8 +643,8 @@ impl PyCell { where T: MutablePyClass, { - self.borrow_checker() - .try_borrow_mut() + self.ensure_threadsafe(); + T::PyClassMutability::try_borrow_mut(self.borrow_checker()) .map(|_| PyRefMut { inner: self }) } @@ -477,8 +678,8 @@ impl PyCell { /// }); /// ``` pub unsafe fn try_borrow_unguarded(&self) -> Result<&T, PyBorrowError> { - self.borrow_checker() - .try_borrow_unguarded() + self.ensure_threadsafe(); + T::PyClassMutability::try_borrow_unguarded(self.borrow_checker()) .map(|_: ()| &*self.contents.value.get()) } @@ -593,7 +794,13 @@ impl PyCell { } } -unsafe impl PyLayout for PyCell {} +impl PyCell { + fn borrow_checker(&self) -> &::Checker { + T::PyClassMutability::borrow_checker(self) + } +} + +unsafe impl PyLayout for PyCell {} impl PySizedLayout for PyCell {} impl AsPyPointer for PyCell { @@ -776,7 +983,7 @@ impl<'p, T: PyClass> Deref for PyRef<'p, T> { impl<'p, T: PyClass> Drop for PyRef<'p, T> { fn drop(&mut self) { - self.inner.borrow_checker().release_borrow() + T::PyClassMutability::release_borrow(self.inner.borrow_checker()) } } @@ -874,7 +1081,7 @@ impl<'p, T: MutablePyClass> DerefMut for PyRefMut<'p, T> { impl<'p, T: MutablePyClass> Drop for PyRefMut<'p, T> { fn drop(&mut self) { - self.inner.borrow_checker().release_borrow_mut() + T::PyClassMutability::release_borrow_mut(self.inner.borrow_checker()) } } @@ -969,11 +1176,8 @@ impl From for PyErr { } #[doc(hidden)] -pub trait PyCellLayout: PyLayout -where - M: Mutability, -{ - fn borrow_checker(&self) -> &M; +pub trait PyCellLayout: PyLayout { + fn ensure_threadsafe(&self); /// Implementation of tp_dealloc. /// # Safety /// - slf must be a valid pointer to an instance of a T or a subclass. @@ -981,15 +1185,12 @@ where unsafe fn tp_dealloc(slf: *mut ffi::PyObject, py: Python<'_>); } -impl PyCellLayout for PyCellBase +impl PyCellLayout for PyCellBase where U: PySizedLayout, T: PyTypeInfo, - M: Mutability, { - fn borrow_checker(&self) -> &M { - &self.borrow_impl - } + fn ensure_threadsafe(&self) {} unsafe fn tp_dealloc(slf: *mut ffi::PyObject, py: Python<'_>) { // For `#[pyclass]` types which inherit from PyAny, we can just call tp_free if T::type_object_raw(py) == &mut PyBaseObject_Type { @@ -1011,14 +1212,13 @@ where } } -impl PyCellLayout for PyCell +impl PyCellLayout for PyCell where - >::LayoutAsBase: - PyCellLayout, + ::LayoutAsBase: PyCellLayout, { - fn borrow_checker(&self) -> &T::Mutability { + fn ensure_threadsafe(&self) { self.contents.thread_checker.ensure(); - self.ob_base.borrow_checker() + self.ob_base.ensure_threadsafe(); } unsafe fn tp_dealloc(slf: *mut ffi::PyObject, py: Python<'_>) { // Safety: Python only calls tp_dealloc when no references to the object remain. @@ -1026,6 +1226,6 @@ where ManuallyDrop::drop(&mut cell.contents.value); cell.contents.dict.clear_dict(py); cell.contents.weakref.clear_weakrefs(slf, py); - >::LayoutAsBase::tp_dealloc(slf, py) + ::LayoutAsBase::tp_dealloc(slf, py) } } diff --git a/src/pyclass.rs b/src/pyclass.rs index 77966d82494..7cdbd8e9507 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -5,11 +5,10 @@ use crate::{ exceptions::PyTypeError, ffi, impl_::pyclass::{ - assign_sequence_item_from_mapping, get_sequence_item_from_mapping, tp_dealloc, PyClassDict, - PyClassImpl, PyClassItems, PyClassWeakRef, + assign_sequence_item_from_mapping, get_sequence_item_from_mapping, tp_dealloc, PyClassImpl, + PyClassItems, }, - IntoPy, IntoPyPointer, PyCell, PyErr, PyMethodDefType, PyNativeType, PyObject, PyResult, - PyTypeInfo, Python, + IntoPy, IntoPyPointer, PyCell, PyErr, PyMethodDefType, PyObject, PyResult, PyTypeInfo, Python, }; use std::{ convert::TryInto, @@ -26,13 +25,6 @@ use std::{ pub trait PyClass: PyTypeInfo> + PyClassImpl> { - /// Specify this class has `#[pyclass(dict)]` or not. - type Dict: PyClassDict; - /// Specify this class has `#[pyclass(weakref)]` or not. - type WeakRef: PyClassWeakRef; - /// The closest native ancestor. This is `PyAny` by default, and when you declare - /// `#[pyclass(extends=PyDict)]`, it's `PyDict`. - type BaseNativeType: PyTypeInfo + PyNativeType; } pub trait MutablePyClass: PyClass {} diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index da55011e076..be44c4a36f0 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -1,12 +1,11 @@ //! Contains initialization utilities for `#[pyclass]`. use crate::callback::IntoPyCallbackOutput; use crate::impl_::pyclass::{PyClassBaseType, PyClassDict, PyClassThreadChecker, PyClassWeakRef}; -use crate::pycell::Mutability; use crate::pyclass::MutablePyClass; use crate::{ffi, PyCell, PyClass, PyErr, PyResult, Python}; use crate::{ ffi::PyTypeObject, - pycell::PyCellContents, + pycell::{PyCellContents, PyClassMutability, PyClassMutabilityStorage}, type_object::{get_tp_alloc, PyTypeInfo}, }; use std::{ @@ -132,17 +131,14 @@ impl PyObjectInit for PyNativeTypeInitializer { /// ``` pub struct PyClassInitializer { init: T, - super_init: >::Initializer, + super_init: ::Initializer, } impl PyClassInitializer { /// Constructs a new initializer from value `T` and base class' initializer. /// /// It is recommended to use `add_subclass` instead of this method for most usage. - pub fn new( - init: T, - super_init: >::Initializer, - ) -> Self { + pub fn new(init: T, super_init: ::Initializer) -> Self { Self { init, super_init } } @@ -195,7 +191,7 @@ impl PyClassInitializer { pub fn add_subclass(self, subclass_value: S) -> PyClassInitializer where S: PyClass, - S::BaseType: PyClassBaseType, + S::BaseType: PyClassBaseType, { PyClassInitializer::new(subclass_value, self) } @@ -233,35 +229,23 @@ impl PyObjectInit for PyClassInitializer { py: Python<'_>, subtype: *mut PyTypeObject, ) -> PyResult<*mut ffi::PyObject> { - /// Layout of a PyCellBase after base new has been called, but the borrow flag has not - /// yet been initialized. - #[repr(C)] - struct PartiallyInitializedPyCellBase { - _ob_base: T, - borrow_flag: MaybeUninit, - } - /// Layout of a PyCell after base new has been called, but the contents have not yet been /// written. #[repr(C)] struct PartiallyInitializedPyCell { - _ob_base: >::LayoutAsBase, + _ob_base: ::LayoutAsBase, contents: MaybeUninit>, } let Self { init, super_init } = self; let obj = super_init.into_new_object(py, subtype)?; - // FIXME: Only need to initialize borrow flag once per whole hierarchy - let base: *mut PartiallyInitializedPyCellBase = obj as _; - std::ptr::write((*base).borrow_flag.as_mut_ptr(), T::Mutability::new()); - - // FIXME: Initialize borrow flag if necessary?? let cell: *mut PartiallyInitializedPyCell = obj as _; std::ptr::write( (*cell).contents.as_mut_ptr(), PyCellContents { value: ManuallyDrop::new(UnsafeCell::new(init)), + borrow_checker: ::Storage::new(), thread_checker: T::ThreadChecker::new(), dict: T::Dict::new(), weakref: T::WeakRef::new(), @@ -276,7 +260,7 @@ impl PyObjectInit for PyClassInitializer { impl From for PyClassInitializer where T: PyClass, - T::BaseType: PyClassBaseType>, + T::BaseType: PyClassBaseType>, { #[inline] fn from(value: T) -> PyClassInitializer { @@ -288,7 +272,7 @@ impl From<(S, B)> for PyClassInitializer where S: MutablePyClass, B: MutablePyClass, - B::BaseType: PyClassBaseType>, + B::BaseType: PyClassBaseType>, { fn from(sub_and_base: (S, B)) -> PyClassInitializer { let (sub, base) = sub_and_base; diff --git a/src/types/mod.rs b/src/types/mod.rs index a596f0afd08..f0419cbe177 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -192,11 +192,12 @@ macro_rules! pyobject_native_type_sized { ($name:ty, $layout:path $(;$generics:ident)*) => { unsafe impl $crate::type_object::PyLayout<$name> for $layout {} impl $crate::type_object::PySizedLayout<$name> for $layout {} - impl $crate::impl_::pyclass::PyClassBaseType for $name { - type LayoutAsBase = $crate::pycell::PyCellBase<$layout, M>; + impl<$($generics,)*> $crate::impl_::pyclass::PyClassBaseType for $name { + type LayoutAsBase = $crate::pycell::PyCellBase<$layout>; type BaseNativeType = $name; type ThreadChecker = $crate::impl_::pyclass::ThreadCheckerStub<$crate::PyObject>; type Initializer = $crate::pyclass_init::PyNativeTypeInitializer; + type PyClassMutability = $crate::pycell::ImmutableClass; } } } diff --git a/tests/test_compile_error.rs b/tests/test_compile_error.rs index 9e69e2a8e64..713c50925c8 100644 --- a/tests/test_compile_error.rs +++ b/tests/test_compile_error.rs @@ -28,7 +28,6 @@ fn test_compile_errors() { #[cfg(not(feature = "nightly"))] fn _test_compile_errors() { let t = trybuild::TestCases::new(); - t.compile_fail("tests/ui/invalid_immutable_pyclass_borrow.rs"); t.compile_fail("tests/ui/invalid_macro_args.rs"); t.compile_fail("tests/ui/invalid_need_module_arg_position.rs"); @@ -41,8 +40,6 @@ fn _test_compile_errors() { t.compile_fail("tests/ui/invalid_pymethod_names.rs"); t.compile_fail("tests/ui/invalid_pymodule_args.rs"); t.compile_fail("tests/ui/reject_generics.rs"); - t.compile_fail("tests/ui/invalid_immutable_pyclass_borrow.rs"); - t.compile_fail("tests/ui/wrong_aspyref_lifetimes.rs"); t.compile_fail("tests/ui/invalid_pymethod_proto_args.rs"); t.compile_fail("tests/ui/invalid_pymethod_proto_args_py.rs"); @@ -97,6 +94,7 @@ fn _test_compile_errors() { #[rustversion::since(1.60)] fn tests_rust_1_60(t: &trybuild::TestCases) { + t.compile_fail("tests/ui/invalid_immutable_pyclass_borrow.rs"); t.compile_fail("tests/ui/invalid_pymethod_receiver.rs"); } diff --git a/tests/test_mutable_pyclass.rs b/tests/test_mutable_pyclass.rs new file mode 100644 index 00000000000..8d5072e50fc --- /dev/null +++ b/tests/test_mutable_pyclass.rs @@ -0,0 +1,201 @@ +#![cfg(feature = "macros")] + +use pyo3::impl_::pyclass::{PyClassBaseType, PyClassImpl}; +use pyo3::prelude::*; +use pyo3::pycell::{ + BorrowChecker, ExtendsMutableAncestor, ImmutableClass, MutableClass, PyClassMutability, +}; +use pyo3::PyClass; + +#[pyclass(subclass)] +struct MutableBase; + +#[pyclass(extends = MutableBase, subclass)] +struct MutableChildOfMutableBase; + +#[pyclass(extends = MutableBase, immutable, subclass)] +struct ImmutableChildOfMutableBase; + +#[pyclass(extends = MutableChildOfMutableBase)] +struct MutableChildOfMutableChildOfMutableBase; + +#[pyclass(extends = ImmutableChildOfMutableBase)] +struct MutableChildOfImmutableChildOfMutableBase; + +#[pyclass(extends = MutableChildOfMutableBase, immutable)] +struct ImmutableChildOfMutableChildOfMutableBase; + +#[pyclass(extends = ImmutableChildOfMutableBase, immutable)] +struct ImmutableChildOfImmutableChildOfMutableBase; + +#[pyclass(immutable, subclass)] +struct ImmutableBase; + +#[pyclass(extends = ImmutableBase, subclass)] +struct MutableChildOfImmutableBase; + +#[pyclass(extends = ImmutableBase, immutable, subclass)] +struct ImmutableChildOfImmutableBase; + +#[pyclass(extends = MutableChildOfImmutableBase)] +struct MutableChildOfMutableChildOfImmutableBase; + +#[pyclass(extends = ImmutableChildOfImmutableBase)] +struct MutableChildOfImmutableChildOfImmutableBase; + +#[pyclass(extends = MutableChildOfImmutableBase, immutable)] +struct ImmutableChildOfMutableChildOfImmutableBase; + +#[pyclass(extends = ImmutableChildOfImmutableBase, immutable)] +struct ImmutableChildOfImmutableChildOfImmutableBase; + +fn assert_mutable>() {} +fn assert_immutable>() {} +fn assert_mutable_with_mutable_ancestor< + T: PyClass>, +>() +// These horrible bounds are necessary for Rust 1.48 but not newer versions +where + ::BaseType: PyClassImpl>, + <::BaseType as PyClassImpl>::PyClassMutability: + PyClassMutability, + ::BaseType: PyClassBaseType>, +{ +} +fn assert_immutable_with_mutable_ancestor< + T: PyClass>, +>() +// These horrible bounds are necessary for Rust 1.48 but not newer versions +where + ::BaseType: PyClassImpl>, + <::BaseType as PyClassImpl>::PyClassMutability: + PyClassMutability, + ::BaseType: PyClassBaseType>, +{ +} + +#[test] +fn test_inherited_mutability() { + // mutable base + assert_mutable::(); + + // children of mutable base have a mutable ancestor + assert_mutable_with_mutable_ancestor::(); + assert_immutable_with_mutable_ancestor::(); + + // grandchildren of mutable base have a mutable ancestor + assert_mutable_with_mutable_ancestor::(); + assert_mutable_with_mutable_ancestor::(); + assert_immutable_with_mutable_ancestor::(); + assert_immutable_with_mutable_ancestor::(); + + // immutable base and children + assert_immutable::(); + assert_immutable::(); + assert_immutable::(); + + // mutable children of immutable at any level are simply mutable + assert_mutable::(); + assert_mutable::(); + + // children of the mutable child display this property + assert_mutable_with_mutable_ancestor::(); + assert_immutable_with_mutable_ancestor::(); +} + +#[test] +fn test_mutable_borrow_prevents_further_borrows() { + Python::with_gil(|py| { + let mmm = Py::new( + py, + PyClassInitializer::from(MutableBase) + .add_subclass(MutableChildOfMutableBase) + .add_subclass(MutableChildOfMutableChildOfMutableBase), + ) + .unwrap(); + + let mmm_cell: &PyCell = mmm.as_ref(py); + + let mmm_refmut = mmm_cell.borrow_mut(); + + // Cannot take any other mutable or immutable borrows whilst the object is borrowed mutably + assert!(mmm_cell + .extract::>() + .is_err()); + assert!(mmm_cell + .extract::>() + .is_err()); + assert!(mmm_cell.extract::>().is_err()); + assert!(mmm_cell + .extract::>() + .is_err()); + assert!(mmm_cell + .extract::>() + .is_err()); + assert!(mmm_cell.extract::>().is_err()); + + // With the borrow dropped, all other borrow attempts will succeed + drop(mmm_refmut); + + assert!(mmm_cell + .extract::>() + .is_ok()); + assert!(mmm_cell + .extract::>() + .is_ok()); + assert!(mmm_cell.extract::>().is_ok()); + assert!(mmm_cell + .extract::>() + .is_ok()); + assert!(mmm_cell + .extract::>() + .is_ok()); + assert!(mmm_cell.extract::>().is_ok()); + }) +} + +#[test] +fn test_immutable_borrows_prevent_mutable_borrows() { + Python::with_gil(|py| { + let mmm = Py::new( + py, + PyClassInitializer::from(MutableBase) + .add_subclass(MutableChildOfMutableBase) + .add_subclass(MutableChildOfMutableChildOfMutableBase), + ) + .unwrap(); + + let mmm_cell: &PyCell = mmm.as_ref(py); + + let mmm_refmut = mmm_cell.borrow(); + + // Further immutable borrows are ok + assert!(mmm_cell + .extract::>() + .is_ok()); + assert!(mmm_cell + .extract::>() + .is_ok()); + assert!(mmm_cell.extract::>().is_ok()); + + // Further mutable borrows are not ok + assert!(mmm_cell + .extract::>() + .is_err()); + assert!(mmm_cell + .extract::>() + .is_err()); + assert!(mmm_cell.extract::>().is_err()); + + // With the borrow dropped, all mutable borrow attempts will succeed + drop(mmm_refmut); + + assert!(mmm_cell + .extract::>() + .is_ok()); + assert!(mmm_cell + .extract::>() + .is_ok()); + assert!(mmm_cell.extract::>().is_ok()); + }) +} diff --git a/tests/ui/abi3_nativetype_inheritance.stderr b/tests/ui/abi3_nativetype_inheritance.stderr index bd425a233e9..cf8c2218c6a 100644 --- a/tests/ui/abi3_nativetype_inheritance.stderr +++ b/tests/ui/abi3_nativetype_inheritance.stderr @@ -1,24 +1,22 @@ -error[E0277]: the trait bound `PyDict: PyClass` is not satisfied - --> tests/ui/abi3_nativetype_inheritance.rs:5:1 - | -5 | #[pyclass(extends=PyDict)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `PyClass` is not implemented for `PyDict` - | - = note: required because of the requirements on the impl of `MutablePyClass` for `PyDict` - = note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict` - = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) - error[E0277]: the trait bound `PyDict: PyClass` is not satisfied --> tests/ui/abi3_nativetype_inheritance.rs:5:1 | 5 | #[pyclass(extends=PyDict)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `PyClass` is not implemented for `PyDict` | - = note: required because of the requirements on the impl of `MutablePyClass` for `PyDict` - = note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict` + = note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict` note: required by a bound in `ThreadCheckerInherited` --> src/impl_/pyclass.rs | - | pub struct ThreadCheckerInherited>( - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `ThreadCheckerInherited` + | pub struct ThreadCheckerInherited( + | ^^^^^^^^^^^^^^^ required by this bound in `ThreadCheckerInherited` = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `PyDict: PyClass` is not satisfied + --> tests/ui/abi3_nativetype_inheritance.rs:5:1 + | +5 | #[pyclass(extends=PyDict)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `PyClass` is not implemented for `PyDict` + | + = note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict` + = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/invalid_immutable_pyclass_borrow.rs b/tests/ui/invalid_immutable_pyclass_borrow.rs index d020fbec578..a50e7ffded2 100644 --- a/tests/ui/invalid_immutable_pyclass_borrow.rs +++ b/tests/ui/invalid_immutable_pyclass_borrow.rs @@ -10,4 +10,14 @@ fn borrow_mut_fails(foo: Py, py: Python){ let borrow = foo.as_ref(py).borrow_mut(); } -fn main(){} \ No newline at end of file +#[pyclass(subclass)] +struct MutableBase; + +#[pyclass(immutable, extends = MutableBase)] +struct ImmutableChild; + +fn borrow_mut_of_child_fails(child: Py, py: Python){ + let borrow = child.as_ref(py).borrow_mut(); +} + +fn main(){} diff --git a/tests/ui/invalid_immutable_pyclass_borrow.stderr b/tests/ui/invalid_immutable_pyclass_borrow.stderr index 4929088761a..2b0653a9251 100644 --- a/tests/ui/invalid_immutable_pyclass_borrow.stderr +++ b/tests/ui/invalid_immutable_pyclass_borrow.stderr @@ -10,3 +10,16 @@ note: required by a bound in `PyCell::::borrow_mut` | | T: MutablePyClass, | ^^^^^^^^^^^^^^ required by this bound in `PyCell::::borrow_mut` + +error[E0271]: type mismatch resolving `::Mutability == Mutable` + --> tests/ui/invalid_immutable_pyclass_borrow.rs:20:35 + | +20 | let borrow = child.as_ref(py).borrow_mut(); + | ^^^^^^^^^^ expected struct `Mutable`, found struct `Immutable` + | + = note: required because of the requirements on the impl of `MutablePyClass` for `ImmutableChild` +note: required by a bound in `PyCell::::borrow_mut` + --> src/pycell.rs + | + | T: MutablePyClass, + | ^^^^^^^^^^^^^^ required by this bound in `PyCell::::borrow_mut` From b196aa1d5a78d8b8aad216a267476c0529477995 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Sat, 23 Apr 2022 06:27:30 +0100 Subject: [PATCH 16/16] remove some redundant traits --- src/pycell.rs | 264 ++++++++++++-------------------------------- src/pyclass_init.rs | 2 +- 2 files changed, 69 insertions(+), 197 deletions(-) diff --git a/src/pycell.rs b/src/pycell.rs index 61ef252e4b7..b8eb967986b 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -195,9 +195,59 @@ use std::ops::{Deref, DerefMut}; pub struct EmptySlot(()); pub struct BorrowChecker(Cell); -pub struct FilledInAncestor(()); -impl BorrowChecker { +pub trait PyClassBorrowChecker { + fn new() -> Self; + + /// Increments immutable borrow count, if possible + fn try_borrow(&self) -> Result<(), PyBorrowError>; + + fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError>; + + /// Decrements immutable borrow count + fn release_borrow(&self); + /// Increments mutable borrow count, if possible + fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError>; + /// Decremements mutable borrow count + fn release_borrow_mut(&self); +} + +impl PyClassBorrowChecker for EmptySlot { + #[inline] + fn new() -> Self { + Self(()) + } + + #[inline] + fn try_borrow(&self) -> Result<(), PyBorrowError> { + Ok(()) + } + + #[inline] + fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError> { + Ok(()) + } + + #[inline] + fn release_borrow(&self) {} + + #[inline] + fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError> { + unreachable!() + } + + #[inline] + fn release_borrow_mut(&self) { + unreachable!() + } +} + +impl PyClassBorrowChecker for BorrowChecker { + #[inline] + fn new() -> Self { + Self(Cell::new(BorrowFlag::UNUSED)) + } + fn try_borrow(&self) -> Result<(), PyBorrowError> { let flag = self.0.get(); if flag != BorrowFlag::HAS_MUTABLE_BORROW { @@ -237,51 +287,16 @@ impl BorrowChecker { } } -pub trait PyClassMutabilityStorage { - fn new() -> Self; -} - -impl PyClassMutabilityStorage for EmptySlot { - fn new() -> Self { - Self(()) - } -} - -impl PyClassMutabilityStorage for BorrowChecker { - fn new() -> Self { - Self(Cell::new(BorrowFlag::UNUSED)) - } -} - -// - Storage type, either empty, present, or in ancestor -// - Mutability is either -// - Immutable - i.e. EmptySlot -// - Mutable - i.e. BorrowChecker -// - ExtendsMutableAncestor - FilledInAncestor -// - Mutability trait needs to encode the inheritance - pub trait PyClassMutability { // The storage for this inheritance layer. Only the first mutable class in // an inheritance hierarchy needs to store the borrow flag. - type Storage: PyClassMutabilityStorage; + type Storage: PyClassBorrowChecker; // The borrow flag needed to implement this class' mutability. Empty until // the first mutable class, at which point it is BorrowChecker and will be // for all subclasses. - type Checker; + type Checker: PyClassBorrowChecker; type ImmutableChild: PyClassMutability; type MutableChild: PyClassMutability; - - /// Increments immutable borrow count, if possible - fn try_borrow(checker: &Self::Checker) -> Result<(), PyBorrowError>; - - fn try_borrow_unguarded(checker: &Self::Checker) -> Result<(), PyBorrowError>; - - /// Decrements immutable borrow count - fn release_borrow(checker: &Self::Checker); - /// Increments mutable borrow count, if possible - fn try_borrow_mut(checker: &Self::Checker) -> Result<(), PyBorrowMutError>; - /// Decremements mutable borrow count - fn release_borrow_mut(checker: &Self::Checker); } pub trait GetBorrowChecker { @@ -321,24 +336,6 @@ impl PyClassMutability for ImmutableClass { type Checker = EmptySlot; type ImmutableChild = ImmutableClass; type MutableChild = MutableClass; - - fn try_borrow(_: &EmptySlot) -> Result<(), PyBorrowError> { - Ok(()) - } - - fn try_borrow_unguarded(_: &EmptySlot) -> Result<(), PyBorrowError> { - Ok(()) - } - - fn release_borrow(_: &EmptySlot) {} - - fn try_borrow_mut(_: &EmptySlot) -> Result<(), PyBorrowMutError> { - unreachable!() - } - - fn release_borrow_mut(_: &EmptySlot) { - unreachable!() - } } impl PyClassMutability for MutableClass { @@ -346,28 +343,6 @@ impl PyClassMutability for MutableClass { type Checker = BorrowChecker; type ImmutableChild = ExtendsMutableAncestor; type MutableChild = ExtendsMutableAncestor; - - // FIXME the below are all wrong - - fn try_borrow(checker: &BorrowChecker) -> Result<(), PyBorrowError> { - checker.try_borrow() - } - - fn try_borrow_unguarded(checker: &BorrowChecker) -> Result<(), PyBorrowError> { - checker.try_borrow_unguarded() - } - - fn release_borrow(checker: &BorrowChecker) { - checker.release_borrow() - } - - fn try_borrow_mut(checker: &BorrowChecker) -> Result<(), PyBorrowMutError> { - checker.try_borrow_mut() - } - - fn release_borrow_mut(checker: &BorrowChecker) { - checker.release_borrow_mut() - } } impl PyClassMutability for ExtendsMutableAncestor { @@ -375,121 +350,14 @@ impl PyClassMutability for ExtendsMutableAncestor { type Checker = BorrowChecker; type ImmutableChild = ExtendsMutableAncestor; type MutableChild = ExtendsMutableAncestor; - - // FIXME the below are all wrong - - fn try_borrow(checker: &BorrowChecker) -> Result<(), PyBorrowError> { - checker.try_borrow() - } - - fn try_borrow_unguarded(checker: &BorrowChecker) -> Result<(), PyBorrowError> { - checker.try_borrow_unguarded() - } - - fn release_borrow(checker: &BorrowChecker) { - checker.release_borrow() - } - - fn try_borrow_mut(checker: &BorrowChecker) -> Result<(), PyBorrowMutError> { - checker.try_borrow_mut() - } - - fn release_borrow_mut(checker: &BorrowChecker) { - checker.release_borrow_mut() - } -} - -pub trait Mutability { - /// Creates a new borrow checker - fn new() -> Self; - /// Increments immutable borrow count, if possible - fn try_borrow(&self) -> Result<(), PyBorrowError>; - - fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError>; - - /// Decrements immutable borrow count - fn release_borrow(&self); - /// Increments mutable borrow count, if possible - fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError>; - /// Decremements mutable borrow count - fn release_borrow_mut(&self); -} - -pub struct Mutable { - flag: Cell, -} -impl Mutability for Mutable { - fn new() -> Self { - Self { - flag: Cell::new(BorrowFlag::UNUSED), - } - } - - fn try_borrow(&self) -> Result<(), PyBorrowError> { - let flag = self.flag.get(); - if flag != BorrowFlag::HAS_MUTABLE_BORROW { - self.flag.set(flag.increment()); - Ok(()) - } else { - Err(PyBorrowError { _private: () }) - } - } - - fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError> { - let flag = self.flag.get(); - if flag != BorrowFlag::HAS_MUTABLE_BORROW { - Ok(()) - } else { - Err(PyBorrowError { _private: () }) - } - } - - fn release_borrow(&self) { - let flag = self.flag.get(); - self.flag.set(flag.decrement()) - } - - fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError> { - let flag = self.flag.get(); - if flag == BorrowFlag::UNUSED { - self.flag.set(BorrowFlag::HAS_MUTABLE_BORROW); - Ok(()) - } else { - Err(PyBorrowMutError { _private: () }) - } - } - - fn release_borrow_mut(&self) { - self.flag.set(BorrowFlag::UNUSED) - } -} - -pub struct Immutable { - flag: PhantomData>, } -impl Mutability for Immutable { - fn new() -> Self { - Self { flag: PhantomData } - } - - fn try_borrow(&self) -> Result<(), PyBorrowError> { - Ok(()) - } - - fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError> { - Ok(()) - } - fn release_borrow(&self) {} +pub trait Mutability {} - fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError> { - unreachable!() - } - - fn release_borrow_mut(&self) { - unreachable!() - } -} +pub struct Mutable; +impl Mutability for Mutable {} +pub struct Immutable; +impl Mutability for Immutable {} /// Base layout of PyCell. #[doc(hidden)] @@ -615,7 +483,9 @@ impl PyCell { /// ``` pub fn try_borrow(&self) -> Result, PyBorrowError> { self.ensure_threadsafe(); - T::PyClassMutability::try_borrow(self.borrow_checker()).map(|_| PyRef { inner: self }) + self.borrow_checker() + .try_borrow() + .map(|_| PyRef { inner: self }) } /// Mutably borrows the value `T`, returning an error if the value is currently borrowed. @@ -644,7 +514,8 @@ impl PyCell { T: MutablePyClass, { self.ensure_threadsafe(); - T::PyClassMutability::try_borrow_mut(self.borrow_checker()) + self.borrow_checker() + .try_borrow_mut() .map(|_| PyRefMut { inner: self }) } @@ -679,7 +550,8 @@ impl PyCell { /// ``` pub unsafe fn try_borrow_unguarded(&self) -> Result<&T, PyBorrowError> { self.ensure_threadsafe(); - T::PyClassMutability::try_borrow_unguarded(self.borrow_checker()) + self.borrow_checker() + .try_borrow_unguarded() .map(|_: ()| &*self.contents.value.get()) } @@ -983,7 +855,7 @@ impl<'p, T: PyClass> Deref for PyRef<'p, T> { impl<'p, T: PyClass> Drop for PyRef<'p, T> { fn drop(&mut self) { - T::PyClassMutability::release_borrow(self.inner.borrow_checker()) + self.inner.borrow_checker().release_borrow() } } @@ -1081,7 +953,7 @@ impl<'p, T: MutablePyClass> DerefMut for PyRefMut<'p, T> { impl<'p, T: MutablePyClass> Drop for PyRefMut<'p, T> { fn drop(&mut self) { - T::PyClassMutability::release_borrow_mut(self.inner.borrow_checker()) + self.inner.borrow_checker().release_borrow_mut() } } diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index be44c4a36f0..2214bd0fb35 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -5,7 +5,7 @@ use crate::pyclass::MutablePyClass; use crate::{ffi, PyCell, PyClass, PyErr, PyResult, Python}; use crate::{ ffi::PyTypeObject, - pycell::{PyCellContents, PyClassMutability, PyClassMutabilityStorage}, + pycell::{PyCellContents, PyClassBorrowChecker, PyClassMutability}, type_object::{get_tp_alloc, PyTypeInfo}, }; use std::{