From 7725f17c46c266389929b2972b200857dd86370e Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Sat, 11 Jun 2022 12:20:43 +0100 Subject: [PATCH 1/2] pyclass: switch from immutable to frozen --- guide/src/class.md | 5 +- pyo3-macros-backend/src/attributes.rs | 2 +- pyo3-macros-backend/src/pyclass.rs | 29 +++----- src/class/basic.rs | 13 ++-- src/class/buffer.rs | 5 +- src/class/gc.rs | 5 +- src/class/mapping.rs | 9 +-- src/class/number.rs | 31 ++++----- src/class/sequence.rs | 11 +-- src/conversion.rs | 6 +- src/impl_/pyclass.rs | 5 +- src/instance.rs | 11 +-- src/pycell.rs | 67 ++++++++++--------- src/pyclass.rs | 33 +++++++-- src/pyclass_init.rs | 5 +- tests/test_compile_error.rs | 2 +- tests/test_mutable_pyclass.rs | 23 ++++--- tests/ui/abi3_nativetype_inheritance.stderr | 32 ++++++--- ...ow.rs => invalid_frozen_pyclass_borrow.rs} | 4 +- tests/ui/invalid_frozen_pyclass_borrow.stderr | 23 +++++++ .../invalid_immutable_pyclass_borrow.stderr | 25 ------- tests/ui/invalid_pyclass_args.stderr | 4 +- 22 files changed, 189 insertions(+), 161 deletions(-) rename tests/ui/{invalid_immutable_pyclass_borrow.rs => invalid_frozen_pyclass_borrow.rs} (84%) create mode 100644 tests/ui/invalid_frozen_pyclass_borrow.stderr delete mode 100644 tests/ui/invalid_immutable_pyclass_borrow.stderr diff --git a/guide/src/class.md b/guide/src/class.md index fc284030f93..7124114a479 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -951,7 +951,9 @@ unsafe impl ::pyo3::type_object::PyTypeInfo for MyClass { } } -impl ::pyo3::PyClass for MyClass { } +impl ::pyo3::PyClass for MyClass { + type Frozen = pyo3::pyclass::boolean_struct::False; +} impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a mut MyClass { type Target = ::pyo3::PyRefMut<'a, MyClass>; @@ -974,7 +976,6 @@ 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; diff --git a/pyo3-macros-backend/src/attributes.rs b/pyo3-macros-backend/src/attributes.rs index 66e1a4e1891..42efade0f34 100644 --- a/pyo3-macros-backend/src/attributes.rs +++ b/pyo3-macros-backend/src/attributes.rs @@ -15,6 +15,7 @@ pub mod kw { syn::custom_keyword!(extends); syn::custom_keyword!(freelist); syn::custom_keyword!(from_py_with); + syn::custom_keyword!(frozen); syn::custom_keyword!(gc); syn::custom_keyword!(get); syn::custom_keyword!(item); @@ -29,7 +30,6 @@ 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 5665967352a..e3ac571c342 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -61,7 +61,7 @@ pub struct PyClassPyO3Options { pub dict: Option, pub extends: Option, pub freelist: Option, - pub immutable: Option, + pub frozen: Option, pub mapping: Option, pub module: Option, pub name: Option, @@ -78,7 +78,7 @@ enum PyClassPyO3Option { Dict(kw::dict), Extends(ExtendsAttribute), Freelist(FreelistAttribute), - Immutable(kw::immutable), + Frozen(kw::frozen), Mapping(kw::mapping), Module(ModuleAttribute), Name(NameAttribute), @@ -101,8 +101,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::frozen) { + input.parse().map(PyClassPyO3Option::Frozen) } else if lookahead.peek(attributes::kw::mapping) { input.parse().map(PyClassPyO3Option::Mapping) } else if lookahead.peek(attributes::kw::module) { @@ -160,7 +160,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::Frozen(frozen) => set_option!(frozen), PyClassPyO3Option::Mapping(mapping) => set_option!(mapping), PyClassPyO3Option::Module(module) => set_option!(module), PyClassPyO3Option::Name(name) => set_option!(name), @@ -713,12 +713,14 @@ impl<'a> PyClassImplsBuilder<'a> { let cls = self.cls; quote! { - impl _pyo3::PyClass for #cls { } + impl _pyo3::PyClass for #cls { + type Frozen = ::Frozen; + } } } fn impl_extractext(&self) -> TokenStream { let cls = self.cls; - if self.attr.options.immutable.is_some() { + if self.attr.options.frozen.is_some() { quote! { impl<'a> _pyo3::derive_utils::ExtractExt<'a> for &'a #cls { @@ -855,17 +857,7 @@ 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() { + let class_mutability = if self.attr.options.frozen.is_some() { quote! { ImmutableChild } @@ -907,7 +899,6 @@ 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; diff --git a/src/class/basic.rs b/src/class/basic.rs index be70f8eecc0..b3457fb6a3d 100644 --- a/src/class/basic.rs +++ b/src/class/basic.rs @@ -10,9 +10,8 @@ //! [typeobj docs](https://docs.python.org/3/c-api/typeobj.html) use crate::callback::{HashCallbackOutput, IntoPyCallbackOutput}; -use crate::{ - exceptions, ffi, pyclass::MutablePyClass, FromPyObject, PyAny, PyCell, PyClass, PyObject, -}; +use crate::pyclass::boolean_struct::False; +use crate::{exceptions, ffi, FromPyObject, PyAny, PyCell, PyClass, PyObject}; use std::os::raw::c_int; /// Basic Python class customization @@ -28,14 +27,14 @@ pub trait PyObjectProtocol<'p>: PyClass { fn __setattr__(&'p mut self, name: Self::Name, value: Self::Value) -> Self::Result where - Self: PyObjectSetAttrProtocol<'p> + MutablePyClass, + Self: PyObjectSetAttrProtocol<'p> + PyClass, { unimplemented!() } fn __delattr__(&'p mut self, name: Self::Name) -> Self::Result where - Self: PyObjectDelAttrProtocol<'p> + MutablePyClass, + Self: PyObjectDelAttrProtocol<'p> + PyClass, { unimplemented!() } @@ -79,12 +78,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> + PyClass { type Name: FromPyObject<'p>; type Value: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyObjectDelAttrProtocol<'p>: PyObjectProtocol<'p> + MutablePyClass { +pub trait PyObjectDelAttrProtocol<'p>: PyObjectProtocol<'p> + PyClass { type Name: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } diff --git a/src/class/buffer.rs b/src/class/buffer.rs index 90693813644..18464e8a70c 100644 --- a/src/class/buffer.rs +++ b/src/class/buffer.rs @@ -6,7 +6,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, pyclass::MutablePyClass, PyCell, PyRefMut}; +use crate::pyclass::boolean_struct::False; +use crate::{ffi, PyCell, PyClass, PyRefMut}; use std::os::raw::c_int; /// Buffer protocol interface @@ -15,7 +16,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>: MutablePyClass { +pub trait PyBufferProtocol<'p>: PyClass { // 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 ec4ab6a622a..2f432205076 100644 --- a/src/class/gc.rs +++ b/src/class/gc.rs @@ -3,14 +3,15 @@ //! Python GC support -use crate::{ffi, pyclass::MutablePyClass, PyCell}; +use crate::pyclass::boolean_struct::False; +use crate::{ffi, PyCell, PyClass}; 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>: MutablePyClass { +pub trait PyGCProtocol<'p>: PyClass { 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 533f5178ba2..d5faf55fae4 100644 --- a/src/class/mapping.rs +++ b/src/class/mapping.rs @@ -5,12 +5,13 @@ //! Trait and support implementation for implementing mapping support use crate::callback::IntoPyCallbackOutput; -use crate::{pyclass::MutablePyClass, FromPyObject, PyClass, PyObject}; +use crate::pyclass::boolean_struct::False; +use crate::{FromPyObject, PyClass, PyObject}; /// Mapping interface #[allow(unused_variables)] #[deprecated(since = "0.16.0", note = "prefer `#[pymethods]` to `#[pyproto]`")] -pub trait PyMappingProtocol<'p>: PyClass { +pub trait PyMappingProtocol<'p>: PyClass { fn __len__(&'p self) -> Self::Result where Self: PyMappingLenProtocol<'p>, @@ -52,13 +53,13 @@ pub trait PyMappingGetItemProtocol<'p>: PyMappingProtocol<'p> { type Result: IntoPyCallbackOutput; } -pub trait PyMappingSetItemProtocol<'p>: PyMappingProtocol<'p> + MutablePyClass { +pub trait PyMappingSetItemProtocol<'p>: PyMappingProtocol<'p> + PyClass { type Key: FromPyObject<'p>; type Value: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyMappingDelItemProtocol<'p>: PyMappingProtocol<'p> + MutablePyClass { +pub trait PyMappingDelItemProtocol<'p>: PyMappingProtocol<'p> + PyClass { type Key: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } diff --git a/src/class/number.rs b/src/class/number.rs index 2ae7a81388d..63451b62c35 100644 --- a/src/class/number.rs +++ b/src/class/number.rs @@ -5,7 +5,8 @@ //! Trait and support implementation for implementing number protocol use crate::callback::IntoPyCallbackOutput; use crate::err::PyErr; -use crate::{ffi, pyclass::MutablePyClass, FromPyObject, PyClass, PyObject}; +use crate::pyclass::boolean_struct::False; +use crate::{ffi, FromPyObject, PyClass, PyObject}; /// Number interface #[allow(unused_variables)] @@ -461,47 +462,47 @@ pub trait PyNumberROrProtocol<'p>: PyNumberProtocol<'p> { type Result: IntoPyCallbackOutput; } -pub trait PyNumberIAddProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIAddProtocol<'p>: PyNumberProtocol<'p> + PyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberISubProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberISubProtocol<'p>: PyNumberProtocol<'p> + PyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIMulProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIMulProtocol<'p>: PyNumberProtocol<'p> + PyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIMatmulProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIMatmulProtocol<'p>: PyNumberProtocol<'p> + PyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberITruedivProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberITruedivProtocol<'p>: PyNumberProtocol<'p> + PyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIFloordivProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIFloordivProtocol<'p>: PyNumberProtocol<'p> + PyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIModProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIModProtocol<'p>: PyNumberProtocol<'p> + PyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIDivmodProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIDivmodProtocol<'p>: PyNumberProtocol<'p> + PyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIPowProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIPowProtocol<'p>: PyNumberProtocol<'p> + PyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; // See https://bugs.python.org/issue36379 @@ -509,28 +510,28 @@ pub trait PyNumberIPowProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { } #[allow(clippy::upper_case_acronyms)] -pub trait PyNumberILShiftProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberILShiftProtocol<'p>: PyNumberProtocol<'p> + PyClass { 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> + PyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIAndProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIAndProtocol<'p>: PyNumberProtocol<'p> + PyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIXorProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIXorProtocol<'p>: PyNumberProtocol<'p> + PyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIOrProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { +pub trait PyNumberIOrProtocol<'p>: PyNumberProtocol<'p> + PyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } diff --git a/src/class/sequence.rs b/src/class/sequence.rs index 8e486ef4fcd..29e10912d67 100644 --- a/src/class/sequence.rs +++ b/src/class/sequence.rs @@ -6,7 +6,8 @@ use crate::callback::IntoPyCallbackOutput; use crate::conversion::{FromPyObject, IntoPy}; use crate::err::PyErr; -use crate::{exceptions, ffi, pyclass::MutablePyClass, PyAny, PyCell, PyClass, PyObject}; +use crate::pyclass::boolean_struct::False; +use crate::{exceptions, ffi, PyAny, PyCell, PyClass, PyObject}; use std::os::raw::c_int; /// Sequence interface @@ -89,13 +90,13 @@ pub trait PySequenceGetItemProtocol<'p>: PySequenceProtocol<'p> { type Result: IntoPyCallbackOutput; } -pub trait PySequenceSetItemProtocol<'p>: PySequenceProtocol<'p> + MutablePyClass { +pub trait PySequenceSetItemProtocol<'p>: PySequenceProtocol<'p> + PyClass { 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> + PyClass { type Index: FromPyObject<'p> + From; type Result: IntoPyCallbackOutput<()>; } @@ -116,14 +117,14 @@ pub trait PySequenceRepeatProtocol<'p>: PySequenceProtocol<'p> { } pub trait PySequenceInplaceConcatProtocol<'p>: - PySequenceProtocol<'p> + IntoPy + MutablePyClass + PySequenceProtocol<'p> + IntoPy + PyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput; } pub trait PySequenceInplaceRepeatProtocol<'p>: - PySequenceProtocol<'p> + IntoPy + MutablePyClass + 'p + PySequenceProtocol<'p> + IntoPy + PyClass + 'p { type Index: FromPyObject<'p> + From; type Result: IntoPyCallbackOutput; diff --git a/src/conversion.rs b/src/conversion.rs index 3f4b5452464..04ad9c688ef 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -2,11 +2,11 @@ //! Defines conversions between Rust and Python types. use crate::err::{self, PyDowncastError, PyResult}; +use crate::pyclass::boolean_struct::False; use crate::type_object::PyTypeInfo; use crate::types::PyTuple; use crate::{ - ffi, gil, pyclass::MutablePyClass, Py, PyAny, PyCell, PyClass, PyNativeType, PyObject, PyRef, - PyRefMut, Python, + ffi, gil, Py, PyAny, PyCell, PyClass, PyNativeType, PyObject, PyRef, PyRefMut, Python, }; use std::ptr::NonNull; @@ -375,7 +375,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/impl_/pyclass.rs b/src/impl_/pyclass.rs index 60316befe27..08fab6b5650 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::{GetBorrowChecker, Mutability, PyCellLayout, PyClassMutability}, + pycell::{GetBorrowChecker, PyCellLayout, PyClassMutability}, pyclass_init::PyObjectInit, type_object::PyLayout, Py, PyAny, PyCell, PyClass, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python, @@ -164,9 +164,6 @@ pub trait PyClassImpl: Sized { /// Base class type BaseType: PyTypeInfo + PyClassBaseType; - /// Immutable or mutable - type Mutability: Mutability; - /// Immutable or mutable type PyClassMutability: PyClassMutability + GetBorrowChecker; diff --git a/src/instance.rs b/src/instance.rs index 8850b452ecc..d1fb8f1486f 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1,3 +1,4 @@ +use crate::pyclass::boolean_struct::False; // Copyright (c) 2017-present PyO3 Project and Contributors use crate::conversion::PyTryFrom; use crate::err::{self, PyDowncastError, PyErr, PyResult}; @@ -5,8 +6,8 @@ use crate::gil; use crate::pycell::{PyBorrowError, PyBorrowMutError, PyCell}; use crate::types::{PyDict, PyString, PyTuple}; use crate::{ - ffi, pyclass::MutablePyClass, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyAny, PyClass, - PyClassInitializer, PyRef, PyRefMut, PyTypeInfo, Python, ToPyObject, + ffi, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyAny, PyClass, PyClassInitializer, + PyRef, PyRefMut, PyTypeInfo, Python, ToPyObject, }; use std::marker::PhantomData; use std::mem; @@ -432,7 +433,7 @@ where /// [`try_borrow_mut`](#method.try_borrow_mut). pub fn borrow_mut<'py>(&'py self, py: Python<'py>) -> PyRefMut<'py, T> where - T: MutablePyClass, + T: PyClass, { self.as_ref(py).borrow_mut() } @@ -462,7 +463,7 @@ where py: Python<'py>, ) -> Result, PyBorrowMutError> where - T: MutablePyClass, + T: PyClass, { self.as_ref(py).try_borrow_mut() } @@ -908,7 +909,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 b1f084c1776..084288ad89a 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -200,7 +200,11 @@ use crate::exceptions::PyRuntimeError; use crate::impl_::pyclass::{ PyClassBaseType, PyClassDict, PyClassImpl, PyClassThreadChecker, PyClassWeakRef, }; -use crate::pyclass::{MutablePyClass, PyClass}; +use crate::pyclass::PyClass; +use crate::pyclass::{ + boolean_struct::{False, True}, + Frozen, +}; use crate::pyclass_init::PyClassInitializer; use crate::type_object::{PyLayout, PySizedLayout}; use crate::types::PyAny; @@ -321,6 +325,7 @@ pub trait PyClassMutability { type Checker: PyClassBorrowChecker; type ImmutableChild: PyClassMutability; type MutableChild: PyClassMutability; + type Frozen: Frozen; } pub trait GetBorrowChecker { @@ -360,6 +365,7 @@ impl PyClassMutability for ImmutableClass { type Checker = EmptySlot; type ImmutableChild = ImmutableClass; type MutableChild = MutableClass; + type Frozen = True; } impl PyClassMutability for MutableClass { @@ -367,6 +373,7 @@ impl PyClassMutability for MutableClass { type Checker = BorrowChecker; type ImmutableChild = ExtendsMutableAncestor; type MutableChild = ExtendsMutableAncestor; + type Frozen = False; } impl PyClassMutability for ExtendsMutableAncestor { @@ -374,15 +381,9 @@ impl PyClassMutability for ExtendsMutableAncestor { type Checker = BorrowChecker; type ImmutableChild = ExtendsMutableAncestor; type MutableChild = ExtendsMutableAncestor; + type Frozen = M::Frozen; } -pub trait Mutability {} - -pub struct Mutable; -impl Mutability for Mutable {} -pub struct Immutable; -impl Mutability for Immutable {} - /// Base layout of PyCell. #[doc(hidden)] #[repr(C)] @@ -475,7 +476,7 @@ impl PyCell { /// [`try_borrow_mut`](#method.try_borrow_mut). pub fn borrow_mut(&self) -> PyRefMut<'_, T> where - T: MutablePyClass, + T: PyClass, { self.try_borrow_mut().expect("Already borrowed") } @@ -535,7 +536,7 @@ impl PyCell { /// ``` pub fn try_borrow_mut(&self) -> Result, PyBorrowMutError> where - T: MutablePyClass, + T: PyClass, { self.ensure_threadsafe(); self.borrow_checker() @@ -587,7 +588,7 @@ impl PyCell { #[inline] pub fn replace(&self, t: T) -> T where - T: MutablePyClass, + T: PyClass, { std::mem::replace(&mut *self.borrow_mut(), t) } @@ -599,7 +600,7 @@ impl PyCell { /// Panics if the value is currently borrowed. pub fn replace_with T>(&self, f: F) -> T where - T: MutablePyClass, + T: PyClass, { let mut_borrow = &mut *self.borrow_mut(); let replacement = f(mut_borrow); @@ -614,7 +615,7 @@ impl PyCell { #[inline] pub fn swap(&self, other: &Self) where - T: MutablePyClass, + T: PyClass, { std::mem::swap(&mut *self.borrow_mut(), &mut *other.borrow_mut()) } @@ -759,8 +760,8 @@ impl<'p, T: PyClass> PyRef<'p, T> { impl<'p, T, U> AsRef for PyRef<'p, T> where - T: MutablePyClass, // For now, only mutable classes can be extended - U: MutablePyClass, + T: PyClass, + U: PyClass, { fn as_ref(&self) -> &T::BaseType { unsafe { &*self.inner.ob_base.get_ptr() } @@ -769,8 +770,8 @@ where impl<'p, T, U> PyRef<'p, T> where - T: MutablePyClass, // For now, only mutable classes can be extended - U: MutablePyClass, + T: PyClass, + U: PyClass, { /// Gets a `PyRef`. /// @@ -869,11 +870,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() } @@ -882,8 +883,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() } @@ -892,8 +893,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() } @@ -902,8 +903,8 @@ where impl<'p, T, U> PyRefMut<'p, T> where - T: PyClass + MutablePyClass, - U: MutablePyClass, + T: PyClass, + U: PyClass, { /// Gets a `PyRef`. /// @@ -917,7 +918,7 @@ where } } -impl<'p, T: MutablePyClass> Deref for PyRefMut<'p, T> { +impl<'p, T: PyClass> Deref for PyRefMut<'p, T> { type Target = T; #[inline] @@ -926,39 +927,41 @@ 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.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: 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> 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 5596079ffd9..f7bbb0cdf30 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -1,5 +1,4 @@ //! `PyClass` and related traits. -use crate::pycell::{Immutable, Mutable}; use crate::{ callback::IntoPyCallbackOutput, exceptions::PyTypeError, @@ -25,14 +24,10 @@ use std::{ pub trait PyClass: PyTypeInfo> + PyClassImpl> { + /// Frozen or not + type Frozen: Frozen; } -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 _ } @@ -578,3 +573,27 @@ pub(crate) unsafe extern "C" fn no_constructor_defined( )) }) } + +/// A mechanism to have associated True / False values in the absence of +/// associated const equality. +pub mod boolean_struct { + + pub(crate) mod private { + use super::*; + + /// A way to "seal" the boolean traits. + pub trait Boolean {} + + impl Boolean for True {} + impl Boolean for False {} + } + + pub struct True(()); + pub struct False(()); +} + +/// A trait which is used to describe whether a `#[pyclass]` is frozen. +pub trait Frozen: boolean_struct::private::Boolean {} + +impl Frozen for boolean_struct::True {} +impl Frozen for boolean_struct::False {} diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index 273e6b3f996..629798bbd7c 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -1,7 +1,6 @@ //! 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, @@ -270,8 +269,8 @@ where impl From<(S, B)> for PyClassInitializer where - S: MutablePyClass, - B: MutablePyClass, + S: PyClass, + B: PyClass, B::BaseType: PyClassBaseType>, { fn from(sub_and_base: (S, B)) -> PyClassInitializer { diff --git a/tests/test_compile_error.rs b/tests/test_compile_error.rs index 26d9f66685c..349cc20e6e0 100644 --- a/tests/test_compile_error.rs +++ b/tests/test_compile_error.rs @@ -98,7 +98,7 @@ fn _test_compile_errors() { #[rustversion::since(1.60)] fn tests_rust_1_60(t: &trybuild::TestCases) { t.compile_fail("tests/ui/invalid_intern_arg.rs"); - t.compile_fail("tests/ui/invalid_immutable_pyclass_borrow.rs"); + t.compile_fail("tests/ui/invalid_frozen_pyclass_borrow.rs"); t.compile_fail("tests/ui/invalid_pymethod_receiver.rs"); t.compile_fail("tests/ui/missing_intopy.rs"); } diff --git a/tests/test_mutable_pyclass.rs b/tests/test_mutable_pyclass.rs index 8d5072e50fc..f4068cd21ab 100644 --- a/tests/test_mutable_pyclass.rs +++ b/tests/test_mutable_pyclass.rs @@ -5,6 +5,7 @@ use pyo3::prelude::*; use pyo3::pycell::{ BorrowChecker, ExtendsMutableAncestor, ImmutableClass, MutableClass, PyClassMutability, }; +use pyo3::pyclass::boolean_struct::{False, True}; use pyo3::PyClass; #[pyclass(subclass)] @@ -13,7 +14,7 @@ struct MutableBase; #[pyclass(extends = MutableBase, subclass)] struct MutableChildOfMutableBase; -#[pyclass(extends = MutableBase, immutable, subclass)] +#[pyclass(extends = MutableBase, frozen, subclass)] struct ImmutableChildOfMutableBase; #[pyclass(extends = MutableChildOfMutableBase)] @@ -22,19 +23,19 @@ struct MutableChildOfMutableChildOfMutableBase; #[pyclass(extends = ImmutableChildOfMutableBase)] struct MutableChildOfImmutableChildOfMutableBase; -#[pyclass(extends = MutableChildOfMutableBase, immutable)] +#[pyclass(extends = MutableChildOfMutableBase, frozen)] struct ImmutableChildOfMutableChildOfMutableBase; -#[pyclass(extends = ImmutableChildOfMutableBase, immutable)] +#[pyclass(extends = ImmutableChildOfMutableBase, frozen)] struct ImmutableChildOfImmutableChildOfMutableBase; -#[pyclass(immutable, subclass)] +#[pyclass(frozen, subclass)] struct ImmutableBase; #[pyclass(extends = ImmutableBase, subclass)] struct MutableChildOfImmutableBase; -#[pyclass(extends = ImmutableBase, immutable, subclass)] +#[pyclass(extends = ImmutableBase, frozen, subclass)] struct ImmutableChildOfImmutableBase; #[pyclass(extends = MutableChildOfImmutableBase)] @@ -43,16 +44,16 @@ struct MutableChildOfMutableChildOfImmutableBase; #[pyclass(extends = ImmutableChildOfImmutableBase)] struct MutableChildOfImmutableChildOfImmutableBase; -#[pyclass(extends = MutableChildOfImmutableBase, immutable)] +#[pyclass(extends = MutableChildOfImmutableBase, frozen)] struct ImmutableChildOfMutableChildOfImmutableBase; -#[pyclass(extends = ImmutableChildOfImmutableBase, immutable)] +#[pyclass(extends = ImmutableChildOfImmutableBase, frozen)] struct ImmutableChildOfImmutableChildOfImmutableBase; -fn assert_mutable>() {} -fn assert_immutable>() {} +fn assert_mutable>() {} +fn assert_immutable>() {} fn assert_mutable_with_mutable_ancestor< - T: PyClass>, + T: PyClass>, >() // These horrible bounds are necessary for Rust 1.48 but not newer versions where @@ -63,7 +64,7 @@ where { } fn assert_immutable_with_mutable_ancestor< - T: PyClass>, + T: PyClass>, >() // These horrible bounds are necessary for Rust 1.48 but not newer versions where diff --git a/tests/ui/abi3_nativetype_inheritance.stderr b/tests/ui/abi3_nativetype_inheritance.stderr index cf8c2218c6a..7fd23bd1236 100644 --- a/tests/ui/abi3_nativetype_inheritance.stderr +++ b/tests/ui/abi3_nativetype_inheritance.stderr @@ -1,3 +1,26 @@ +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 + | +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 by a bound in `PyRefMut` + --> src/pycell.rs + | + | pub struct PyRefMut<'p, T: PyClass> { + | ^^^^^^^^^^^^^^ required by this bound in `PyRefMut` + = 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 | @@ -11,12 +34,3 @@ note: required by a 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_frozen_pyclass_borrow.rs similarity index 84% rename from tests/ui/invalid_immutable_pyclass_borrow.rs rename to tests/ui/invalid_frozen_pyclass_borrow.rs index a50e7ffded2..37f7ff69ee0 100644 --- a/tests/ui/invalid_immutable_pyclass_borrow.rs +++ b/tests/ui/invalid_frozen_pyclass_borrow.rs @@ -1,6 +1,6 @@ use pyo3::prelude::*; -#[pyclass(immutable)] +#[pyclass(frozen)] pub struct Foo { #[pyo3(get)] field: u32, @@ -13,7 +13,7 @@ fn borrow_mut_fails(foo: Py, py: Python){ #[pyclass(subclass)] struct MutableBase; -#[pyclass(immutable, extends = MutableBase)] +#[pyclass(frozen, extends = MutableBase)] struct ImmutableChild; fn borrow_mut_of_child_fails(child: Py, py: Python){ diff --git a/tests/ui/invalid_frozen_pyclass_borrow.stderr b/tests/ui/invalid_frozen_pyclass_borrow.stderr new file mode 100644 index 00000000000..dccfc4fd9b2 --- /dev/null +++ b/tests/ui/invalid_frozen_pyclass_borrow.stderr @@ -0,0 +1,23 @@ +error[E0271]: type mismatch resolving `::Frozen == False` + --> tests/ui/invalid_frozen_pyclass_borrow.rs:10:33 + | +10 | let borrow = foo.as_ref(py).borrow_mut(); + | ^^^^^^^^^^ expected struct `False`, found struct `True` + | +note: required by a bound in `PyCell::::borrow_mut` + --> src/pycell.rs + | + | T: PyClass, + | ^^^^^^^^^^^^^^ required by this bound in `PyCell::::borrow_mut` + +error[E0271]: type mismatch resolving `::Frozen == False` + --> tests/ui/invalid_frozen_pyclass_borrow.rs:20:35 + | +20 | let borrow = child.as_ref(py).borrow_mut(); + | ^^^^^^^^^^ expected struct `False`, found struct `True` + | +note: required by a bound in `PyCell::::borrow_mut` + --> src/pycell.rs + | + | T: PyClass, + | ^^^^^^^^^^^^^^ required by this bound in `PyCell::::borrow_mut` diff --git a/tests/ui/invalid_immutable_pyclass_borrow.stderr b/tests/ui/invalid_immutable_pyclass_borrow.stderr deleted file mode 100644 index 2b0653a9251..00000000000 --- a/tests/ui/invalid_immutable_pyclass_borrow.stderr +++ /dev/null @@ -1,25 +0,0 @@ -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 1fd61d5c364..77c0bc0e0af 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`, `immutable`, `mapping`, `module`, `name`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc` +error: expected one of: `crate`, `dict`, `extends`, `freelist`, `frozen`, `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`, `immutable`, `mapping`, `module`, `name`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc` +error: expected one of: `crate`, `dict`, `extends`, `freelist`, `frozen`, `mapping`, `module`, `name`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc` --> tests/ui/invalid_pyclass_args.rs:21:11 | 21 | #[pyclass(weakrev)] From 2fd53646468d425b83817684e9dad27223a8965e Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Sun, 12 Jun 2022 08:51:47 +0100 Subject: [PATCH 2/2] pycell: add more test coverage --- src/instance.rs | 28 ++++++++++++++ src/pycell.rs | 99 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/src/instance.rs b/src/instance.rs index d1fb8f1486f..cc27156cbf0 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1117,4 +1117,32 @@ a = A() Ok(()) }) } + + #[cfg(feature = "macros")] + mod using_macros { + use super::*; + + #[crate::pyclass] + #[pyo3(crate = "crate")] + struct SomeClass(i32); + + #[test] + fn instance_borrow_methods() { + // More detailed tests of the underlying semantics in pycell.rs + Python::with_gil(|py| { + let instance = Py::new(py, SomeClass(0)).unwrap(); + assert_eq!(instance.borrow(py).0, 0); + assert_eq!(instance.try_borrow(py).unwrap().0, 0); + assert_eq!(instance.borrow_mut(py).0, 0); + assert_eq!(instance.try_borrow_mut(py).unwrap().0, 0); + + instance.borrow_mut(py).0 = 123; + + assert_eq!(instance.borrow(py).0, 123); + assert_eq!(instance.try_borrow(py).unwrap().0, 123); + assert_eq!(instance.borrow_mut(py).0, 123); + assert_eq!(instance.try_borrow_mut(py).unwrap().0, 123); + }) + } + } } diff --git a/src/pycell.rs b/src/pycell.rs index 084288ad89a..2be9027903d 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -1086,3 +1086,102 @@ where ::LayoutAsBase::tp_dealloc(slf, py) } } + +#[cfg(test)] +#[cfg(feature = "macros")] +mod tests { + + use super::*; + + #[crate::pyclass] + #[pyo3(crate = "crate")] + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + struct SomeClass(i32); + + #[test] + fn pycell_replace() { + Python::with_gil(|py| { + let cell = PyCell::new(py, SomeClass(0)).unwrap(); + assert_eq!(*cell.borrow(), SomeClass(0)); + + let previous = cell.replace(SomeClass(123)); + assert_eq!(previous, SomeClass(0)); + assert_eq!(*cell.borrow(), SomeClass(123)); + }) + } + + #[test] + #[should_panic(expected = "Already borrowed: PyBorrowMutError")] + fn pycell_replace_panic() { + Python::with_gil(|py| { + let cell = PyCell::new(py, SomeClass(0)).unwrap(); + let _guard = cell.borrow(); + + cell.replace(SomeClass(123)); + }) + } + + #[test] + fn pycell_replace_with() { + Python::with_gil(|py| { + let cell = PyCell::new(py, SomeClass(0)).unwrap(); + assert_eq!(*cell.borrow(), SomeClass(0)); + + let previous = cell.replace_with(|value| { + *value = SomeClass(2); + SomeClass(123) + }); + assert_eq!(previous, SomeClass(2)); + assert_eq!(*cell.borrow(), SomeClass(123)); + }) + } + + #[test] + #[should_panic(expected = "Already borrowed: PyBorrowMutError")] + fn pycell_replace_with_panic() { + Python::with_gil(|py| { + let cell = PyCell::new(py, SomeClass(0)).unwrap(); + let _guard = cell.borrow(); + + cell.replace_with(|_| SomeClass(123)); + }) + } + + #[test] + fn pycell_swap() { + Python::with_gil(|py| { + let cell = PyCell::new(py, SomeClass(0)).unwrap(); + let cell2 = PyCell::new(py, SomeClass(123)).unwrap(); + assert_eq!(*cell.borrow(), SomeClass(0)); + assert_eq!(*cell2.borrow(), SomeClass(123)); + + cell.swap(cell2); + assert_eq!(*cell.borrow(), SomeClass(123)); + assert_eq!(*cell2.borrow(), SomeClass(0)); + }) + } + + #[test] + #[should_panic(expected = "Already borrowed: PyBorrowMutError")] + fn pycell_swap_panic() { + Python::with_gil(|py| { + let cell = PyCell::new(py, SomeClass(0)).unwrap(); + let cell2 = PyCell::new(py, SomeClass(123)).unwrap(); + + let _guard = cell.borrow(); + cell.swap(cell2); + }) + } + + #[test] + #[should_panic(expected = "Already borrowed: PyBorrowMutError")] + fn pycell_swap_panic_other_borrowed() { + Python::with_gil(|py| { + let cell = PyCell::new(py, SomeClass(0)).unwrap(); + let cell2 = PyCell::new(py, SomeClass(123)).unwrap(); + + let _guard = cell2.borrow(); + cell.swap(cell2); + }) + } +}