diff --git a/guide/src/class.md b/guide/src/class.md index 73350b9d74e..81802df7d32 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -940,19 +940,14 @@ 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; @@ -961,10 +956,14 @@ unsafe impl pyo3::PyTypeInfo for MyClass { } } -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 { } + +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 { @@ -980,6 +979,11 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass { type Layout = PyCell; type BaseType = PyAny; type ThreadChecker = pyo3::impl_::pyclass::ThreadCheckerStub; + 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::*; @@ -989,6 +993,7 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass { visitor(collector.py_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/attributes.rs b/pyo3-macros-backend/src/attributes.rs index 6d381e21933..66e1a4e1891 100644 --- a/pyo3-macros-backend/src/attributes.rs +++ b/pyo3-macros-backend/src/attributes.rs @@ -29,6 +29,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 3a7d943f401..d421acf5d9e 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 mapping: Option, pub module: Option, pub name: Option, @@ -70,6 +71,7 @@ enum PyClassPyO3Option { Dict(kw::dict), Extends(ExtendsAttribute), Freelist(FreelistAttribute), + Immutable(kw::immutable), Mapping(kw::mapping), Module(ModuleAttribute), Name(NameAttribute), @@ -92,6 +94,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::mapping) { input.parse().map(PyClassPyO3Option::Mapping) } else if lookahead.peek(attributes::kw::module) { @@ -149,6 +153,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::Mapping(mapping) => set_option!(mapping), PyClassPyO3Option::Module(module) => set_option!(module), PyClassPyO3Option::Name(name) => set_option!(name), @@ -684,44 +689,31 @@ 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 { let cls = self.cls; - quote! { - impl<'a> _pyo3::derive_utils::ExtractExt<'a> for &'a #cls - { - type Target = _pyo3::PyRef<'a, #cls>; + if self.attr.options.immutable.is_some() { + 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>; + } } } } @@ -831,6 +823,47 @@ impl<'a> PyClassImplsBuilder<'a> { let deprecations = &self.attr.deprecations; + let mutability = if self.attr.options.immutable.is_some() { + quote! { + _pyo3::pycell::Immutable + } + } else { + quote! { + _pyo3::pycell::Mutable + } + }; + + 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; @@ -842,6 +875,11 @@ impl<'a> PyClassImplsBuilder<'a> { type BaseType = #base; 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/class/basic.rs b/src/class/basic.rs index 2bb92b1f4d2..be70f8eecc0 100644 --- a/src/class/basic.rs +++ b/src/class/basic.rs @@ -10,7 +10,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 @@ -26,14 +28,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!() } @@ -77,12 +79,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 4338b5f6ff8..90693813644 100644 --- a/src/class/buffer.rs +++ b/src/class/buffer.rs @@ -6,7 +6,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 @@ -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 e9e6ae4f6ad..ec4ab6a622a 100644 --- a/src/class/gc.rs +++ b/src/class/gc.rs @@ -3,14 +3,14 @@ //! Python GC support -use crate::{ffi, PyCell, 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/class/mapping.rs b/src/class/mapping.rs index 7a066370266..413f97199ee 100644 --- a/src/class/mapping.rs +++ b/src/class/mapping.rs @@ -5,7 +5,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)] @@ -52,13 +52,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 ab7f7410ee6..2ae7a81388d 100644 --- a/src/class/number.rs +++ b/src/class/number.rs @@ -5,7 +5,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)] @@ -461,74 +461,76 @@ 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> { +#[allow(clippy::upper_case_acronyms)] +pub trait PyNumberILShiftProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIRShiftProtocol<'p>: PyNumberProtocol<'p> { +#[allow(clippy::upper_case_acronyms)] +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 9ea5064ddcb..b1b29ea0061 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 @@ -89,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<()>; } @@ -116,14 +116,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 6dffae77905..6b3e5841ba4 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; @@ -298,7 +299,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 48ce1d14800..43468de9e48 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::{GetBorrowChecker, Mutability, PyCellLayout, PyClassMutability}, pyclass_init::PyObjectInit, type_object::{PyLayout, PyTypeObject}, Py, PyAny, PyCell, PyClass, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python, @@ -164,6 +164,22 @@ pub trait PyClassImpl: Sized { /// Base class 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`. @@ -866,9 +882,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( + PhantomData, + U::ThreadChecker, +); + +impl PyClassThreadChecker + for ThreadCheckerInherited +{ fn ensure(&self) { self.1.ensure(); } @@ -884,14 +905,18 @@ pub trait PyClassBaseType: Sized { type BaseNativeType; type ThreadChecker: PyClassThreadChecker; type Initializer: PyObjectInit; + type PyClassMutability: PyClassMutability; } -/// All PyClasses can be used as a base type. +/// 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; type Initializer = crate::pyclass_init::PyClassInitializer; + type PyClassMutability = T::PyClassMutability; } /// Implementation of tp_dealloc for all pyclasses diff --git a/src/instance.rs b/src/instance.rs index a8c0997bf1f..7cf7b5cc9f0 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; @@ -430,7 +430,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() } @@ -457,7 +460,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() } } @@ -877,7 +883,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 8e9038e9f0b..b8eb967986b 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -173,8 +173,10 @@ //! [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::pyclass::PyClass; +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}; use crate::types::PyAny; @@ -187,16 +189,181 @@ 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 struct EmptySlot(()); +pub struct BorrowChecker(Cell); + +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 { + 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 PyClassMutability { + // The storage for this inheritance layer. Only the first mutable class in + // an inheritance hierarchy needs to store the borrow flag. + 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: PyClassBorrowChecker; + type ImmutableChild: PyClassMutability; + type MutableChild: PyClassMutability; +} + +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; +} + +impl PyClassMutability for MutableClass { + type Storage = BorrowChecker; + type Checker = BorrowChecker; + type ImmutableChild = ExtendsMutableAncestor; + type MutableChild = ExtendsMutableAncestor; +} + +impl PyClassMutability for ExtendsMutableAncestor { + type Storage = EmptySlot; + type Checker = BorrowChecker; + type ImmutableChild = ExtendsMutableAncestor; + type MutableChild = ExtendsMutableAncestor; +} + +pub trait Mutability {} + +pub struct Mutable; +impl Mutability for Mutable {} +pub struct Immutable; +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 { ob_base: T, - borrow_flag: Cell, } unsafe impl PyLayout for PyCellBase where U: PySizedLayout {} @@ -237,14 +404,15 @@ unsafe impl PyLayout for PyCellBase where U: PySizedLayout {} /// For more information on how, when and why (not) to use `PyCell` please see the /// [module-level documentation](self). #[repr(C)] -pub struct PyCell { +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, @@ -281,7 +449,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") } @@ -311,13 +482,10 @@ 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.ensure_threadsafe(); + self.borrow_checker() + .try_borrow() + .map(|_| PyRef { inner: self }) } /// Mutably borrows the value `T`, returning an error if the value is currently borrowed. @@ -341,13 +509,14 @@ 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.ensure_threadsafe(); + self.borrow_checker() + .try_borrow_mut() + .map(|_| PyRefMut { inner: self }) } /// Immutably borrows the value `T`, returning an error if the value is @@ -380,11 +549,10 @@ 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.ensure_threadsafe(); + self.borrow_checker() + .try_borrow_unguarded() + .map(|_: ()| &*self.contents.value.get()) } /// Replaces the wrapped value with a new one, returning the old value. @@ -393,7 +561,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) } @@ -402,7 +573,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) @@ -414,7 +588,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()) } @@ -489,7 +666,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 { @@ -594,8 +777,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() } @@ -604,8 +787,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`. /// @@ -672,8 +855,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() } } @@ -705,11 +887,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() } @@ -718,8 +900,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() } @@ -728,8 +910,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() } @@ -738,8 +920,8 @@ where impl<'p, T, U> PyRefMut<'p, T> where - T: PyClass, - U: PyClass, + T: PyClass + MutablePyClass, + U: MutablePyClass, { /// Gets a `PyRef`. /// @@ -753,7 +935,7 @@ where } } -impl<'p, T: PyClass> Deref for PyRefMut<'p, T> { +impl<'p, T: MutablePyClass> Deref for PyRefMut<'p, T> { type Target = T; #[inline] @@ -762,46 +944,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 { @@ -867,8 +1049,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 ensure_threadsafe(&self); /// Implementation of tp_dealloc. /// # Safety /// - slf must be a valid pointer to an instance of a T or a subclass. @@ -881,12 +1062,7 @@ 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 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 { @@ -908,16 +1084,13 @@ where } } -impl PyCellLayout for PyCell +impl PyCellLayout for PyCell where ::LayoutAsBase: PyCellLayout, { - fn get_borrow_flag(&self) -> BorrowFlag { + fn ensure_threadsafe(&self) { 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.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. diff --git a/src/pyclass.rs b/src/pyclass.rs index eb2bb5c9595..7cdbd8e9507 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -1,14 +1,14 @@ //! `PyClass` and related traits. +use crate::pycell::{Immutable, Mutable}; use crate::{ callback::IntoPyCallbackOutput, 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, @@ -25,15 +25,14 @@ 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 {} +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 f7b63fe5278..2214bd0fb35 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -1,14 +1,15 @@ //! Contains initialization utilities for `#[pyclass]`. use crate::callback::IntoPyCallbackOutput; use crate::impl_::pyclass::{PyClassBaseType, PyClassDict, PyClassThreadChecker, PyClassWeakRef}; +use crate::pyclass::MutablePyClass; use crate::{ffi, PyCell, PyClass, PyErr, PyResult, Python}; use crate::{ ffi::PyTypeObject, - pycell::{BorrowFlag, PyCellContents}, + pycell::{PyCellContents, PyClassBorrowChecker, PyClassMutability}, type_object::{get_tp_alloc, PyTypeInfo}, }; use std::{ - cell::{Cell, UnsafeCell}, + cell::UnsafeCell, marker::PhantomData, mem::{ManuallyDrop, MaybeUninit}, }; @@ -228,14 +229,6 @@ 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)] @@ -247,19 +240,12 @@ impl PyObjectInit for PyClassInitializer { 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(), - Cell::new(BorrowFlag::UNUSED), - ); - - // 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(), @@ -284,8 +270,8 @@ where impl From<(S, B)> for PyClassInitializer where - S: PyClass, - B: PyClass, + S: MutablePyClass, + B: MutablePyClass, B::BaseType: PyClassBaseType>, { fn from(sub_and_base: (S, B)) -> PyClassInitializer { diff --git a/src/types/mod.rs b/src/types/mod.rs index ad79d062b9a..f0419cbe177 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -197,6 +197,7 @@ macro_rules! pyobject_native_type_sized { 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 371748db48e..713c50925c8 100644 --- a/tests/test_compile_error.rs +++ b/tests/test_compile_error.rs @@ -94,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 360a0a45bc3..cf8c2218c6a 100644 --- a/tests/ui/abi3_nativetype_inheritance.stderr +++ b/tests/ui/abi3_nativetype_inheritance.stderr @@ -1,12 +1,3 @@ -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) - error[E0277]: the trait bound `PyDict: PyClass` is not satisfied --> tests/ui/abi3_nativetype_inheritance.rs:5:1 | @@ -17,6 +8,15 @@ error[E0277]: the trait bound `PyDict: PyClass` is not satisfied 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) + +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 new file mode 100644 index 00000000000..a50e7ffded2 --- /dev/null +++ b/tests/ui/invalid_immutable_pyclass_borrow.rs @@ -0,0 +1,23 @@ +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(); +} + +#[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 new file mode 100644 index 00000000000..2b0653a9251 --- /dev/null +++ b/tests/ui/invalid_immutable_pyclass_borrow.stderr @@ -0,0 +1,25 @@ +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` + +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` diff --git a/tests/ui/invalid_pyclass_args.stderr b/tests/ui/invalid_pyclass_args.stderr index 2fc642d8ba0..1fd61d5c364 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`, `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)] @@ -34,7 +34,7 @@ error: expected string literal 18 | #[pyclass(module = my_module)] | ^^^^^^^^^ -error: expected one of: `crate`, `dict`, `extends`, `freelist`, `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)]