From 7f31000be23bcbf3173d36ba147832f103aea5f1 Mon Sep 17 00:00:00 2001 From: mejrs Date: Wed, 10 Nov 2021 15:28:19 +0100 Subject: [PATCH 01/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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 0787b670e8598a2d0b232fee491fcda9bd0fa7ff Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Thu, 21 Apr 2022 20:19:14 +0100 Subject: [PATCH 15/23] pyproto: make deprecated feature opt-in --- Architecture.md | 4 ++-- CHANGELOG.md | 4 ++++ Cargo.toml | 2 +- guide/src/class/protocols.md | 6 +++--- guide/src/features.md | 5 +---- guide/src/migration.md | 6 ++++++ 6 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Architecture.md b/Architecture.md index d0e873f80a8..4fb84f4f05a 100644 --- a/Architecture.md +++ b/Architecture.md @@ -153,8 +153,8 @@ implemented in Python, such as GC support. ## 5. Procedural macros to simplify usage for users. -[`pyo3-macros`] provides six proc-macro APIs: `pymodule`, `pyproto`, `pyfunction`, `pyclass`, -`pymethods`, and `#[derive(FromPyObject)]`. +[`pyo3-macros`] provides five proc-macro APIs: `pymodule`, `pyfunction`, `pyclass`, +`pymethods`, and `#[derive(FromPyObject)]`. (And a deprecated `pyproto` macro.) [`pyo3-macros-backend`] has the actual implementations of these APIs. [`src/derive_utils.rs`] contains some utilities used in code generated by these proc-macros, such as parsing function arguments. diff --git a/CHANGELOG.md b/CHANGELOG.md index ddfece05260..eb6c484bcdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Implement `ToPyObject` for `[T; N]`. [#2313](https://github.com/PyO3/pyo3/pull/2313) +### Changed + +- The deprecated `pyproto` feature is now disabled by default. [#2321](https://github.com/PyO3/pyo3/pull/2321) + ## [0.16.4] - 2022-04-14 ### Added diff --git a/Cargo.toml b/Cargo.toml index 5b91293d9d8..d6a20774d9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ serde_json = "1.0.61" pyo3-build-config = { path = "pyo3-build-config", version = "0.16.4", features = ["resolve-config"] } [features] -default = ["macros", "pyproto"] +default = ["macros"] # Enables macros: #[pyclass], #[pymodule], #[pyfunction] etc. macros = ["pyo3-macros", "indoc", "unindent"] diff --git a/guide/src/class/protocols.md b/guide/src/class/protocols.md index 88e76d64372..716f45d51f6 100644 --- a/guide/src/class/protocols.md +++ b/guide/src/class/protocols.md @@ -4,8 +4,8 @@ Python's object model defines several protocols for different object behavior, s In the Python C-API which PyO3 is implemented upon, many of these magic methods have to be placed into special "slots" on the class type object, as covered in the previous section. There are two ways in which this can be done: - - [New in PyO3 0.15, recommended in PyO3 0.16] In `#[pymethods]`, if the name of the method is a recognised magic method, PyO3 will place it in the type object automatically. - - [Deprecated in PyO3 0.16] In special traits combined with the `#[pyproto]` attribute. + - In `#[pymethods]`, if the name of the method is a recognised magic method, PyO3 will place it in the type object automatically. + - [Deprecated since PyO3 0.16] In special traits combined with the `#[pyproto]` attribute. (There are also many magic methods which don't have a special slot, such as `__dir__`. These methods can be implemented as normal in `#[pymethods]`.) @@ -404,7 +404,7 @@ impl ClassWithGCSupport { PyO3 can use the `#[pyproto]` attribute in combination with special traits to implement the magic methods which need slots. The special traits are listed below. See also the [documentation for the `pyo3::class` module]({{#PYO3_DOCS_URL}}/pyo3/class/index.html). -Due to complexity in the implementation and usage, these traits are deprecated in PyO3 0.16 in favour of the `#[pymethods]` solution. +Due to complexity in the implementation and usage, these traits were deprecated in PyO3 0.16 in favour of the `#[pymethods]` solution. All `#[pyproto]` methods can return `T` instead of `PyResult` if the method implementation is infallible. In addition, if the return type is `()`, it can be omitted altogether. diff --git a/guide/src/features.md b/guide/src/features.md index d37059582da..c67de97ec30 100644 --- a/guide/src/features.md +++ b/guide/src/features.md @@ -59,7 +59,6 @@ This feature enables a dependency on the `pyo3-macros` crate, which provides the - `#[pyfunction]` - `#[pyclass]` - `#[pymethods]` -- `#[pyproto]` - `#[derive(FromPyObject)]` It also provides the `py_run!` macro. @@ -78,9 +77,7 @@ See [the `#[pyclass]` implementation details](class.md#implementation-details) f ### `pyproto` -This feature enables the `#[pyproto]` macro, which is an alternative (older, soon-to-be-deprecated) to `#[pymethods]` for defining magic methods such as `__eq__`. - -> This feature is enabled by default. To disable it, set `default-features = false` for the `pyo3` entry in your Cargo.toml. +This feature enables the `#[pyproto]` macro, which is a deprecated alternative to `#[pymethods]` for defining magic methods such as `__eq__`. ### `nightly` diff --git a/guide/src/migration.md b/guide/src/migration.md index 7a763e667c3..725ff2cb3b3 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -3,6 +3,12 @@ This guide can help you upgrade code through breaking changes from one PyO3 version to the next. For a detailed list of all changes, see the [CHANGELOG](changelog.md). +## from 0.16.* to 0.17 + +### The `pyproto` feature is now disabled by default + +In preparation for removing the deprecated `#[pyproto]` attribute macro in a future PyO3 version, it is now gated behind an opt-in feature flag. This also gives a slight saving to compile times for code which does not use the deprecated macro. + ## from 0.15.* to 0.16 ### Drop support for older technologies 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 16/23] 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 17/23] 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::{ From 7e2d3117ceabf0dd66b37913eb94699c24485ab5 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Thu, 7 Apr 2022 14:37:45 +0100 Subject: [PATCH 18/23] cleanup: deprecate PyTypeObject trait --- Architecture.md | 6 ++-- CHANGELOG.md | 3 +- guide/src/class.md | 3 +- guide/src/class/numeric.md | 36 +++++++++++----------- guide/src/migration.md | 31 +++++++++++++++++++ src/err/err_state.rs | 3 +- src/err/mod.rs | 6 ++-- src/exceptions.rs | 2 +- src/impl_/extract_argument.rs | 3 +- src/impl_/pyclass.rs | 4 +-- src/marker.rs | 7 +++-- src/type_object.rs | 53 +++++++++++++++++++++----------- src/types/any.rs | 6 ++-- src/types/module.rs | 3 +- src/types/string.rs | 2 +- src/types/typeobject.rs | 10 +++--- tests/test_gc.rs | 2 +- tests/test_gc_pyproto.rs | 3 +- tests/test_inheritance.rs | 5 ++- tests/test_multiple_pymethods.rs | 2 +- 20 files changed, 116 insertions(+), 74 deletions(-) diff --git a/Architecture.md b/Architecture.md index 4fb84f4f05a..5f676b19200 100644 --- a/Architecture.md +++ b/Architecture.md @@ -135,12 +135,12 @@ to ensure Rust's borrow rules. See [the documentation](https://docs.rs/pyo3/latest/pyo3/pycell/struct.PyCell.html) for more. `PyCell` requires that `T` implements `PyClass`. -This trait is somewhat complex and derives many traits, but the most important one is `PyTypeObject` +This trait is somewhat complex and derives many traits, but the most important one is `PyTypeInfo` in [`src/type_object.rs`]. -`PyTypeObject` is also implemented for built-in types. +`PyTypeInfo` is also implemented for built-in types. In Python, all objects have their types, and types are also objects of `type`. For example, you can see `type({})` shows `dict` and `type(type({}))` shows `type` in Python REPL. -`T: PyTypeObject` implies that `T` has a corresponding type object. +`T: PyTypeInfo` implies that `T` has a corresponding type object. ## 4. Protocol methods diff --git a/CHANGELOG.md b/CHANGELOG.md index eb6c484bcdd..1fdd474dafe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ PyO3 versions, please see the [migration guide](https://pyo3.rs/latest/migration The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## [Unreleased] ### Added @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Move `PyTypeObject::type_object` method to `PyTypeInfo` trait, and deprecate `PyTypeObject` trait. [#2284](https://github.com/PyO3/pyo3/pull/2284) - The deprecated `pyproto` feature is now disabled by default. [#2321](https://github.com/PyO3/pyo3/pull/2321) ## [0.16.4] - 2022-04-14 diff --git a/guide/src/class.md b/guide/src/class.md index 81802df7d32..e5245be98fe 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -52,7 +52,7 @@ enum MyEnum { Because Python objects are freely shared between threads by the Python interpreter, all types annotated with `#[pyclass]` must implement `Send` (unless annotated with [`#[pyclass(unsendable)]`](#customizing-the-class)). -The above example generates implementations for [`PyTypeInfo`], [`PyTypeObject`], and [`PyClass`] for `MyClass` and `MyEnum`. To see these generated implementations, refer to the [implementation details](#implementation-details) at the end of this chapter. +The above example generates implementations for [`PyTypeInfo`] and [`PyClass`] for `MyClass` and `MyEnum`. To see these generated implementations, refer to the [implementation details](#implementation-details) at the end of this chapter. ## Constructor @@ -1004,7 +1004,6 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass { [`GILGuard`]: {{#PYO3_DOCS_URL}}/pyo3/struct.GILGuard.html [`PyTypeInfo`]: {{#PYO3_DOCS_URL}}/pyo3/type_object/trait.PyTypeInfo.html -[`PyTypeObject`]: {{#PYO3_DOCS_URL}}/pyo3/type_object/trait.PyTypeObject.html [`PyCell`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyCell.html [`PyClass`]: {{#PYO3_DOCS_URL}}/pyo3/pyclass/trait.PyClass.html diff --git a/guide/src/class/numeric.md b/guide/src/class/numeric.md index a93d2239add..bb4ef1f45d3 100644 --- a/guide/src/class/numeric.md +++ b/guide/src/class/numeric.md @@ -7,7 +7,7 @@ Before proceeding, we should think about how we want to handle overflows. There be reinventing the wheel. - We can raise exceptions whenever `Number` overflows, but that makes the API painful to use. - We can wrap around the boundary of `i32`. This is the approach we'll take here. To do that we'll just forward to `i32`'s - `wrapping_*` methods. + `wrapping_*` methods. ### Fixing our constructor @@ -336,39 +336,39 @@ fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> { # def hash_djb2(s: str): # n = Number(0) # five = Number(5) -# +# # for x in s: # n = Number(ord(x)) + ((n << five) - n) # return n -# +# # assert hash_djb2('l50_50') == Number(-1152549421) # assert hash_djb2('logo') == Number(3327403) # assert hash_djb2('horizon') == Number(1097468315) -# -# +# +# # assert Number(2) + Number(2) == Number(4) # assert Number(2) + Number(2) != Number(5) -# +# # assert Number(13) - Number(7) == Number(6) # assert Number(13) - Number(-7) == Number(20) -# +# # assert Number(13) / Number(7) == Number(1) # assert Number(13) // Number(7) == Number(1) -# +# # assert Number(13) * Number(7) == Number(13*7) -# +# # assert Number(13) > Number(7) # assert Number(13) < Number(20) # assert Number(13) == Number(13) # assert Number(13) >= Number(7) # assert Number(13) <= Number(20) # assert Number(13) == Number(13) -# -# +# +# # assert (True if Number(1) else False) # assert (False if Number(0) else True) -# -# +# +# # assert int(Number(13)) == 13 # assert float(Number(13)) == 13 # assert Number.__doc__ == "Did you ever hear the tragedy of Darth Signed The Overfloweth? I thought not.\nIt's not a story C would tell you. It's a Rust legend." @@ -383,14 +383,14 @@ fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> { # assert Number(1337).__repr__() == 'Number(1337)' "#; -# -# use pyo3::type_object::PyTypeObject; -# +# +# use pyo3::PyTypeInfo; +# # fn main() -> PyResult<()> { # Python::with_gil(|py| -> PyResult<()> { # let globals = PyModule::import(py, "__main__")?.dict(); # globals.set_item("Number", Number::type_object(py))?; -# +# # py.run(SCRIPT, Some(globals), None)?; # Ok(()) # }) @@ -446,4 +446,4 @@ fn wrap(obj: &PyAny) -> Result { [`PyErr::take`]: https://docs.rs/pyo3/latest/pyo3/prelude/struct.PyErr.html#method.take [`Python`]: https://docs.rs/pyo3/latest/pyo3/struct.Python.html [`FromPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.FromPyObject.html -[`pyo3::ffi::PyLong_AsUnsignedLongMask`]: https://docs.rs/pyo3/latest/pyo3/ffi/fn.PyLong_AsUnsignedLongMask.html \ No newline at end of file +[`pyo3::ffi::PyLong_AsUnsignedLongMask`]: https://docs.rs/pyo3/latest/pyo3/ffi/fn.PyLong_AsUnsignedLongMask.html diff --git a/guide/src/migration.md b/guide/src/migration.md index 725ff2cb3b3..3aa50f7f218 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -9,6 +9,37 @@ For a detailed list of all changes, see the [CHANGELOG](changelog.md). In preparation for removing the deprecated `#[pyproto]` attribute macro in a future PyO3 version, it is now gated behind an opt-in feature flag. This also gives a slight saving to compile times for code which does not use the deprecated macro. +### `PyTypeObject` trait has been deprecated + +The `PyTypeObject` trait already was near-useless; almost all functionality was already on the `PyTypeInfo` trait, which `PyTypeObject` had a blanket implementation based upon. In PyO3 0.17 the final method, `PyTypeObject::type_object` was moved to `PyTypeInfo::type_object`. + +To migrate, update trait bounds and imports from `PyTypeObject` to `PyTypeInfo`. + +Before: + +```rust,ignore +use pyo3::Python; +use pyo3::type_object::PyTypeObject; +use pyo3::types::PyType; + +fn get_type_object(py: Python<'_>) -> &PyType { + T::type_object(py) +} +``` + +After + +```rust +use pyo3::{Python, PyTypeInfo}; +use pyo3::types::PyType; + +fn get_type_object(py: Python<'_>) -> &PyType { + T::type_object(py) +} + +# Python::with_gil(|py| { get_type_object::(py); }); +``` + ## from 0.15.* to 0.16 ### Drop support for older technologies diff --git a/src/err/err_state.rs b/src/err/err_state.rs index ecebeb5a622..6193b9cbf62 100644 --- a/src/err/err_state.rs +++ b/src/err/err_state.rs @@ -1,9 +1,8 @@ use crate::{ exceptions::{PyBaseException, PyTypeError}, ffi, - type_object::PyTypeObject, types::{PyTraceback, PyType}, - AsPyPointer, IntoPy, IntoPyPointer, Py, PyObject, Python, + AsPyPointer, IntoPy, IntoPyPointer, Py, PyObject, PyTypeInfo, Python, }; #[derive(Clone)] diff --git a/src/err/mod.rs b/src/err/mod.rs index eed6798ad58..425c044cb9b 100644 --- a/src/err/mod.rs +++ b/src/err/mod.rs @@ -1,7 +1,7 @@ // Copyright (c) 2017-present PyO3 Project and Contributors use crate::panic::PanicException; -use crate::type_object::PyTypeObject; +use crate::type_object::PyTypeInfo; use crate::types::{PyTraceback, PyType}; use crate::{ exceptions::{self, PyBaseException}, @@ -92,7 +92,7 @@ impl PyErr { #[inline] pub fn new(args: A) -> PyErr where - T: PyTypeObject, + T: PyTypeInfo, A: PyErrArguments + Send + Sync + 'static, { PyErr::from_state(PyErrState::LazyTypeAndValue { @@ -428,7 +428,7 @@ impl PyErr { #[inline] pub fn is_instance_of(&self, py: Python<'_>) -> bool where - T: PyTypeObject, + T: PyTypeInfo, { self.is_instance(py, T::type_object(py)) } diff --git a/src/exceptions.rs b/src/exceptions.rs index d8d65de52ee..6f7c8233e19 100644 --- a/src/exceptions.rs +++ b/src/exceptions.rs @@ -218,7 +218,7 @@ macro_rules! create_exception { }; } -/// `impl $crate::type_object::PyTypeObject for $name` where `$name` is an +/// `impl PyTypeInfo for $name` where `$name` is an /// exception newly defined in Rust code. #[doc(hidden)] #[macro_export] diff --git a/src/impl_/extract_argument.rs b/src/impl_/extract_argument.rs index b793deef72b..feea25f1409 100644 --- a/src/impl_/extract_argument.rs +++ b/src/impl_/extract_argument.rs @@ -1,9 +1,8 @@ use crate::{ exceptions::PyTypeError, ffi, - type_object::PyTypeObject, types::{PyDict, PyString, PyTuple}, - FromPyObject, PyAny, PyErr, PyResult, Python, + FromPyObject, PyAny, PyErr, PyResult, PyTypeInfo, Python, }; /// The standard implementation of how PyO3 extracts a `#[pyfunction]` or `#[pymethod]` function argument. diff --git a/src/impl_/pyclass.rs b/src/impl_/pyclass.rs index 43468de9e48..60316befe27 100644 --- a/src/impl_/pyclass.rs +++ b/src/impl_/pyclass.rs @@ -4,7 +4,7 @@ use crate::{ impl_::freelist::FreeList, pycell::{GetBorrowChecker, Mutability, PyCellLayout, PyClassMutability}, pyclass_init::PyObjectInit, - type_object::{PyLayout, PyTypeObject}, + type_object::PyLayout, Py, PyAny, PyCell, PyClass, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python, }; use std::{ @@ -162,7 +162,7 @@ pub trait PyClassImpl: Sized { type Layout: PyLayout; /// Base class - type BaseType: PyTypeInfo + PyTypeObject + PyClassBaseType; + type BaseType: PyTypeInfo + PyClassBaseType; /// Immutable or mutable type Mutability: Mutability; diff --git a/src/marker.rs b/src/marker.rs index f159d89e85e..209fe33b325 100644 --- a/src/marker.rs +++ b/src/marker.rs @@ -122,10 +122,11 @@ use crate::err::{self, PyDowncastError, PyErr, PyResult}; use crate::gil::{self, GILGuard, GILPool}; use crate::impl_::not_send::NotSend; -use crate::type_object::{PyTypeInfo, PyTypeObject}; use crate::types::{PyAny, PyDict, PyModule, PyType}; use crate::version::PythonVersionInfo; -use crate::{ffi, AsPyPointer, FromPyPointer, IntoPyPointer, PyNativeType, PyObject, PyTryFrom}; +use crate::{ + ffi, AsPyPointer, FromPyPointer, IntoPyPointer, PyNativeType, PyObject, PyTryFrom, PyTypeInfo, +}; use std::ffi::{CStr, CString}; use std::marker::PhantomData; use std::os::raw::c_int; @@ -583,7 +584,7 @@ impl<'py> Python<'py> { /// Gets the Python type object for type `T`. pub fn get_type(self) -> &'py PyType where - T: PyTypeObject, + T: PyTypeInfo, { T::type_object(self) } diff --git a/src/type_object.rs b/src/type_object.rs index b8c2d654133..f2743d145ab 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -51,9 +51,14 @@ pub unsafe trait PyTypeInfo: Sized { /// Utility type to make Py::as_ref work. type AsRefTarget: PyNativeType; - /// PyTypeObject instance for this type. + /// Returns the PyTypeObject instance for this type. fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject; + /// Returns the safe abstraction over the type object. + fn type_object(py: Python<'_>) -> &PyType { + unsafe { py.from_borrowed_ptr(Self::type_object_raw(py) as _) } + } + /// Checks if `object` is an instance of this type or a subclass of this type. fn is_type_of(object: &PyAny) -> bool { unsafe { ffi::PyObject_TypeCheck(object.as_ptr(), Self::type_object_raw(object.py())) != 0 } @@ -65,27 +70,19 @@ pub unsafe trait PyTypeInfo: Sized { } } -/// Python object types that have a corresponding type object. +/// Legacy trait which previously held the `type_object` method now found on `PyTypeInfo`. /// /// # Safety /// -/// This trait is marked unsafe because not fulfilling the contract for type_object -/// leads to UB. -/// -/// See also [PyTypeInfo::type_object_raw](trait.PyTypeInfo.html#tymethod.type_object_raw). -pub unsafe trait PyTypeObject { - /// Returns the safe abstraction over the type object. - fn type_object(py: Python<'_>) -> &PyType; -} +/// This trait used to have stringent safety requirements, but they are now irrelevant as it is deprecated. +#[deprecated( + since = "0.17.0", + note = "PyTypeObject::type_object was moved to PyTypeInfo::type_object" +)] +pub unsafe trait PyTypeObject: PyTypeInfo {} -unsafe impl PyTypeObject for T -where - T: PyTypeInfo, -{ - fn type_object(py: Python<'_>) -> &PyType { - unsafe { py.from_borrowed_ptr(Self::type_object_raw(py) as _) } - } -} +#[allow(deprecated)] +unsafe impl PyTypeObject for T {} /// Lazy type object for PyClass. #[doc(hidden)] @@ -232,3 +229,23 @@ pub(crate) unsafe fn get_tp_free(tp: *mut ffi::PyTypeObject) -> ffi::freefunc { std::mem::transmute(ptr) } } + +#[cfg(test)] +mod tests { + #[test] + #[allow(deprecated)] + fn test_deprecated_type_object() { + // Even though PyTypeObject is deprecated, simple usages of it as a trait bound should continue to work. + use super::PyTypeObject; + use crate::types::{PyList, PyType}; + use crate::Python; + + fn get_type_object(py: Python<'_>) -> &PyType { + T::type_object(py) + } + + Python::with_gil(|py| { + assert!(get_type_object::(py).is(::type_object(py))) + }); + } +} diff --git a/src/types/any.rs b/src/types/any.rs index 9ba9b515944..f49b8efa617 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -4,7 +4,7 @@ use crate::conversion::{ }; use crate::err::{PyDowncastError, PyErr, PyResult}; use crate::exceptions::PyTypeError; -use crate::type_object::PyTypeObject; +use crate::type_object::PyTypeInfo; use crate::types::{PyDict, PyIterator, PyList, PyString, PyTuple, PyType}; use crate::{err, ffi, Py, PyNativeType, PyObject, Python}; use std::cell::UnsafeCell; @@ -782,7 +782,7 @@ impl PyAny { /// /// This is equivalent to the Python expression `isinstance(self, T)`, /// if the type `T` is known at compile time. - pub fn is_instance_of(&self) -> PyResult { + pub fn is_instance_of(&self) -> PyResult { self.is_instance(T::type_object(self.py())) } @@ -814,7 +814,7 @@ impl PyAny { #[cfg(test)] mod tests { use crate::{ - type_object::PyTypeObject, + type_object::PyTypeInfo, types::{IntoPyDict, PyList, PyLong, PyModule}, Python, ToPyObject, }; diff --git a/src/types/module.rs b/src/types/module.rs index 2838734b473..21f6e0cae32 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -7,7 +7,6 @@ use crate::err::{PyErr, PyResult}; use crate::exceptions; use crate::ffi; use crate::pyclass::PyClass; -use crate::type_object::PyTypeObject; use crate::types::{PyAny, PyCFunction, PyDict, PyList, PyString}; use crate::{AsPyPointer, IntoPy, PyObject, Python}; use std::ffi::{CStr, CString}; @@ -291,7 +290,7 @@ impl PyModule { where T: PyClass, { - self.add(T::NAME, ::type_object(self.py())) + self.add(T::NAME, T::type_object(self.py())) } /// Adds a function or a (sub)module to a module, using the functions name as name. diff --git a/src/types/string.rs b/src/types/string.rs index 4e906f8ce51..1511b794aa2 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -385,7 +385,7 @@ impl FromPyObject<'_> for char { mod tests { use super::*; #[cfg(all(not(Py_LIMITED_API), target_endian = "little"))] - use crate::type_object::PyTypeObject; + use crate::PyTypeInfo; use crate::Python; use crate::{FromPyObject, PyObject, PyTryFrom, ToPyObject}; #[cfg(all(not(Py_LIMITED_API), target_endian = "little"))] diff --git a/src/types/typeobject.rs b/src/types/typeobject.rs index 7c1e1fda8a1..e266bd0f15e 100644 --- a/src/types/typeobject.rs +++ b/src/types/typeobject.rs @@ -3,8 +3,7 @@ // based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython use crate::err::{self, PyResult}; -use crate::type_object::PyTypeObject; -use crate::{ffi, AsPyPointer, PyAny, Python}; +use crate::{ffi, AsPyPointer, PyAny, PyTypeInfo, Python}; /// Represents a reference to a Python `type object`. #[repr(transparent)] @@ -15,7 +14,7 @@ pyobject_native_type_core!(PyType, ffi::PyType_Type, #checkfunction=ffi::PyType_ impl PyType { /// Creates a new type object. #[inline] - pub fn new(py: Python<'_>) -> &PyType { + pub fn new(py: Python<'_>) -> &PyType { T::type_object(py) } @@ -55,7 +54,7 @@ impl PyType { /// `T` is known at compile time. pub fn is_subclass_of(&self) -> PyResult where - T: PyTypeObject, + T: PyTypeInfo, { self.is_subclass(T::type_object(self.py())) } @@ -77,9 +76,8 @@ impl PyType { #[cfg(test)] mod tests { use crate::{ - type_object::PyTypeObject, types::{PyBool, PyLong}, - Python, + PyTypeInfo, Python, }; #[test] diff --git a/tests/test_gc.rs b/tests/test_gc.rs index 7f7e5bcf9a0..0d134883b4b 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -3,7 +3,7 @@ use pyo3::class::PyTraverseError; use pyo3::class::PyVisit; use pyo3::prelude::*; -use pyo3::type_object::PyTypeObject; +use pyo3::PyTypeInfo; use pyo3::{py_run, AsPyPointer, PyCell, PyTryInto}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; diff --git a/tests/test_gc_pyproto.rs b/tests/test_gc_pyproto.rs index 7641975188a..d9c44044d67 100644 --- a/tests/test_gc_pyproto.rs +++ b/tests/test_gc_pyproto.rs @@ -6,8 +6,7 @@ use pyo3::class::PyGCProtocol; use pyo3::class::PyTraverseError; use pyo3::class::PyVisit; use pyo3::prelude::*; -use pyo3::type_object::PyTypeObject; -use pyo3::{py_run, AsPyPointer, PyCell, PyTryInto}; +use pyo3::{py_run, AsPyPointer, PyCell, PyTryInto, PyTypeInfo}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index b36a68b13b3..25dae983335 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -1,8 +1,7 @@ #![cfg(feature = "macros")] use pyo3::prelude::*; -use pyo3::py_run; -use pyo3::type_object::PyTypeObject; +use pyo3::{py_run, PyTypeInfo}; use pyo3::types::IntoPyDict; @@ -305,7 +304,7 @@ impl SimpleClass { #[test] fn test_subclass_ref_counts() { // regression test for issue #1363 - use pyo3::type_object::PyTypeObject; + use pyo3::PyTypeInfo; Python::with_gil(|py| { #[allow(non_snake_case)] let SimpleClass = SimpleClass::type_object(py); diff --git a/tests/test_multiple_pymethods.rs b/tests/test_multiple_pymethods.rs index a793eec5e32..78a77334a1e 100644 --- a/tests/test_multiple_pymethods.rs +++ b/tests/test_multiple_pymethods.rs @@ -1,8 +1,8 @@ #![cfg(feature = "multiple-pymethods")] use pyo3::prelude::*; -use pyo3::type_object::PyTypeObject; use pyo3::types::PyType; +use pyo3::PyTypeInfo; #[macro_use] mod common; From e05cec01e44f391fc3e6f1173d20ae319c86e880 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Sun, 24 Apr 2022 09:20:20 +0100 Subject: [PATCH 19/23] cleanup: unused cls.rs --- src/types/cls.rs | 584 ----------------------------------------------- 1 file changed, 584 deletions(-) delete mode 100644 src/types/cls.rs diff --git a/src/types/cls.rs b/src/types/cls.rs deleted file mode 100644 index 7b1534287ed..00000000000 --- a/src/types/cls.rs +++ /dev/null @@ -1,584 +0,0 @@ -// Copyright (c) 2017-present PyO3 Project and Contributors - -//! Defines conversions between Rust and Python types. -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, -}; -use std::ptr::NonNull; - -/// This trait represents that **we can do zero-cost conversion from the object -/// to a FFI pointer**. -/// -/// This trait is implemented for types that internally wrap a pointer to a Python object. -/// -/// # Examples -/// -/// ``` -/// use pyo3::{prelude::*, AsPyPointer}; -/// Python::with_gil(|py| { -/// let dict = pyo3::types::PyDict::new(py); -/// // All native object wrappers implement AsPyPointer!!! -/// assert_ne!(dict.as_ptr(), std::ptr::null_mut()); -/// }); -/// ``` -pub trait AsPyPointer { - /// Retrieves the underlying FFI pointer (as a borrowed pointer). - fn as_ptr(&self) -> *mut ffi::PyObject; -} - -/// This trait allows retrieving the underlying FFI pointer from Python objects. -pub trait IntoPyPointer { - /// Retrieves the underlying FFI pointer. Whether pointer owned or borrowed - /// depends on implementation. - fn into_ptr(self) -> *mut ffi::PyObject; -} - -/// Convert `None` into a null pointer. -impl AsPyPointer for Option -where - T: AsPyPointer, -{ - #[inline] - fn as_ptr(&self) -> *mut ffi::PyObject { - self.as_ref() - .map_or_else(std::ptr::null_mut, |t| t.as_ptr()) - } -} - -/// Convert `None` into a null pointer. -impl IntoPyPointer for Option -where - T: IntoPyPointer, -{ - #[inline] - fn into_ptr(self) -> *mut ffi::PyObject { - self.map_or_else(std::ptr::null_mut, |t| t.into_ptr()) - } -} - -impl<'a, T> IntoPyPointer for &'a T -where - T: AsPyPointer, -{ - fn into_ptr(self) -> *mut ffi::PyObject { - unsafe { ffi::_Py_XNewRef(self.as_ptr()) } - } -} - -/// Conversion trait that allows various objects to be converted into `PyObject`. -pub trait ToPyObject { - /// Converts self into a Python object. - fn to_object(&self, py: Python<'_>) -> PyObject; -} - -/// This trait has two implementations: The slow one is implemented for -/// all [ToPyObject] and creates a new object using [ToPyObject::to_object], -/// while the fast one is only implemented for AsPyPointer (we know -/// that every AsPyPointer is also ToPyObject) and uses [AsPyPointer::as_ptr()] -pub trait ToBorrowedObject: ToPyObject { - /// Converts self into a Python object and calls the specified closure - /// on the native FFI pointer underlying the Python object. - /// - /// May be more efficient than `to_object` because it does not need - /// to touch any reference counts when the input object already is a Python object. - fn with_borrowed_ptr(&self, py: Python<'_>, f: F) -> R - where - F: FnOnce(*mut ffi::PyObject) -> R, - { - let ptr = self.to_object(py).into_ptr(); - let result = f(ptr); - unsafe { - ffi::Py_XDECREF(ptr); - } - result - } -} - -impl ToBorrowedObject for T where T: ToPyObject {} - -/// Defines a conversion from a Rust type to a Python object. -/// -/// It functions similarly to std's [`Into`](std::convert::Into) trait, -/// but requires a [GIL token](Python) as an argument. -/// Many functions and traits internal to PyO3 require this trait as a bound, -/// so a lack of this trait can manifest itself in different error messages. -/// -/// # Examples -/// ## With `#[pyclass]` -/// The easiest way to implement `IntoPy` is by exposing a struct as a native Python object -/// by annotating it with [`#[pyclass]`](crate::prelude::pyclass). -/// -/// ```rust -/// use pyo3::prelude::*; -/// -/// #[pyclass] -/// struct Number { -/// #[pyo3(get, set)] -/// value: i32, -/// } -/// ``` -/// Python code will see this as an instance of the `Number` class with a `value` attribute. -/// -/// ## Conversion to a Python object -/// -/// However, it may not be desirable to expose the existence of `Number` to Python code. -/// `IntoPy` allows us to define a conversion to an appropriate Python object. -/// ```rust -/// use pyo3::prelude::*; -/// -/// struct Number { -/// value: i32, -/// } -/// -/// impl IntoPy for Number { -/// fn into_py(self, py: Python<'_>) -> PyObject { -/// // delegates to i32's IntoPy implementation. -/// self.value.into_py(py) -/// } -/// } -/// ``` -/// Python code will see this as an `int` object. -/// -/// ## Dynamic conversion into Python objects. -/// It is also possible to return a different Python object depending on some condition. -/// This is useful for types like enums that can carry different types. -/// -/// ```rust -/// use pyo3::prelude::*; -/// -/// enum Value { -/// Integer(i32), -/// String(String), -/// None, -/// } -/// -/// impl IntoPy for Value { -/// fn into_py(self, py: Python<'_>) -> PyObject { -/// match self { -/// Self::Integer(val) => val.into_py(py), -/// Self::String(val) => val.into_py(py), -/// Self::None => py.None(), -/// } -/// } -/// } -/// # fn main() { -/// # Python::with_gil(|py| { -/// # let v = Value::Integer(73).into_py(py); -/// # let v = v.extract::(py).unwrap(); -/// # -/// # let v = Value::String("foo".into()).into_py(py); -/// # let v = v.extract::(py).unwrap(); -/// # -/// # let v = Value::None.into_py(py); -/// # let v = v.extract::>>(py).unwrap(); -/// # }); -/// # } -/// ``` -/// Python code will see this as any of the `int`, `string` or `None` objects. -#[cfg_attr(docsrs, doc(alias = "IntoPyCallbackOutput"))] -pub trait IntoPy: Sized { - /// Performs the conversion. - fn into_py(self, py: Python<'_>) -> T; -} - -/// `FromPyObject` is implemented by various types that can be extracted from -/// a Python object reference. -/// -/// Normal usage is through the helper methods `Py::extract` or `PyAny::extract`: -/// -/// ```rust,ignore -/// let obj: Py = ...; -/// let value: &TargetType = obj.extract(py)?; -/// -/// let any: &PyAny = ...; -/// let value: &TargetType = any.extract()?; -/// ``` -/// -/// Note: depending on the implementation, the lifetime of the extracted result may -/// depend on the lifetime of the `obj` or the `prepared` variable. -/// -/// For example, when extracting `&str` from a Python byte string, the resulting string slice will -/// point to the existing string data (lifetime: `'source`). -/// On the other hand, when extracting `&str` from a Python Unicode string, the preparation step -/// will convert the string to UTF-8, and the resulting string slice will have lifetime `'prepared`. -/// Since which case applies depends on the runtime type of the Python object, -/// both the `obj` and `prepared` variables must outlive the resulting string slice. -/// -/// The trait's conversion method takes a `&PyAny` argument but is called -/// `FromPyObject` for historical reasons. -pub trait FromPyObject<'source>: Sized { - /// Extracts `Self` from the source `PyObject`. - fn extract(ob: &'source PyAny) -> PyResult; -} - -/// Identity conversion: allows using existing `PyObject` instances where -/// `T: ToPyObject` is expected. -impl ToPyObject for &'_ T { - #[inline] - fn to_object(&self, py: Python<'_>) -> PyObject { - ::to_object(*self, py) - } -} - -/// `Option::Some` is converted like `T`. -/// `Option::None` is converted to Python `None`. -impl ToPyObject for Option -where - T: ToPyObject, -{ - fn to_object(&self, py: Python<'_>) -> PyObject { - self.as_ref() - .map_or_else(|| py.None(), |val| val.to_object(py)) - } -} - -impl IntoPy for Option -where - T: IntoPy, -{ - fn into_py(self, py: Python<'_>) -> PyObject { - self.map_or_else(|| py.None(), |val| val.into_py(py)) - } -} - -/// `()` is converted to Python `None`. -impl ToPyObject for () { - fn to_object(&self, py: Python<'_>) -> PyObject { - py.None() - } -} - -impl IntoPy for () { - fn into_py(self, py: Python<'_>) -> PyObject { - py.None() - } -} - -impl IntoPy for &'_ T -where - T: AsPyPointer, -{ - #[inline] - fn into_py(self, py: Python<'_>) -> PyObject { - unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) } - } -} - -impl<'a, T> FromPyObject<'a> for &'a PyCell -where - T: PyClass, -{ - fn extract(obj: &'a PyAny) -> PyResult { - PyTryFrom::try_from(obj).map_err(Into::into) - } -} - -impl<'a, T> FromPyObject<'a> for T -where - T: PyClass + Clone, -{ - fn extract(obj: &'a PyAny) -> PyResult { - let cell: &PyCell = PyTryFrom::try_from(obj)?; - Ok(unsafe { cell.try_borrow_unguarded()?.clone() }) - } -} - -impl<'a, T> FromPyObject<'a> for PyRef<'a, T> -where - T: PyClass, -{ - fn extract(obj: &'a PyAny) -> PyResult { - let cell: &PyCell = PyTryFrom::try_from(obj)?; - cell.try_borrow().map_err(Into::into) - } -} - -impl<'a, T> FromPyObject<'a> for PyRefMut<'a, T> -where - T: PyClass, -{ - fn extract(obj: &'a PyAny) -> PyResult { - let cell: &PyCell = PyTryFrom::try_from(obj)?; - cell.try_borrow_mut().map_err(Into::into) - } -} - -impl<'a, T> FromPyObject<'a> for Option -where - T: FromPyObject<'a>, -{ - fn extract(obj: &'a PyAny) -> PyResult { - if obj.as_ptr() == unsafe { ffi::Py_None() } { - Ok(None) - } else { - T::extract(obj).map(Some) - } - } -} - -/// Trait implemented by Python object types that allow a checked downcast. -/// If `T` implements `PyTryFrom`, we can convert `&PyAny` to `&T`. -/// -/// This trait is similar to `std::convert::TryFrom` -pub trait PyTryFrom<'v>: Sized + PyNativeType { - /// Cast from a concrete Python object type to PyObject. - fn try_from>(value: V) -> Result<&'v Self, PyDowncastError<'v>>; - - /// Cast from a concrete Python object type to PyObject. With exact type check. - fn try_from_exact>(value: V) -> Result<&'v Self, PyDowncastError<'v>>; - - /// Cast a PyAny to a specific type of PyObject. The caller must - /// have already verified the reference is for this type. - /// - /// # Safety - /// - /// Callers must ensure that the type is valid or risk type confusion. - unsafe fn try_from_unchecked>(value: V) -> &'v Self; -} - -/// Trait implemented by Python object types that allow a checked downcast. -/// This trait is similar to `std::convert::TryInto` -pub trait PyTryInto: Sized { - /// Cast from PyObject to a concrete Python object type. - fn try_into(&self) -> Result<&T, PyDowncastError<'_>>; - - /// Cast from PyObject to a concrete Python object type. With exact type check. - fn try_into_exact(&self) -> Result<&T, PyDowncastError<'_>>; -} - -// TryFrom implies TryInto -impl PyTryInto for PyAny -where - U: for<'v> PyTryFrom<'v>, -{ - fn try_into(&self) -> Result<&U, PyDowncastError<'_>> { - >::try_from(self) - } - fn try_into_exact(&self) -> Result<&U, PyDowncastError<'_>> { - U::try_from_exact(self) - } -} - -impl<'v, T> PyTryFrom<'v> for T -where - T: PyTypeInfo + PyNativeType, -{ - fn try_from>(value: V) -> Result<&'v Self, PyDowncastError<'v>> { - let value = value.into(); - unsafe { - if T::is_type_of(value) { - Ok(Self::try_from_unchecked(value)) - } else { - Err(PyDowncastError::new(value, T::NAME)) - } - } - } - - fn try_from_exact>(value: V) -> Result<&'v Self, PyDowncastError<'v>> { - let value = value.into(); - unsafe { - if T::is_exact_type_of(value) { - Ok(Self::try_from_unchecked(value)) - } else { - Err(PyDowncastError::new(value, T::NAME)) - } - } - } - - #[inline] - unsafe fn try_from_unchecked>(value: V) -> &'v Self { - Self::unchecked_downcast(value.into()) - } -} - -impl<'v, T> PyTryFrom<'v> for PyCell -where - T: 'v + PyClass, -{ - fn try_from>(value: V) -> Result<&'v Self, PyDowncastError<'v>> { - let value = value.into(); - unsafe { - if T::is_type_of(value) { - Ok(Self::try_from_unchecked(value)) - } else { - Err(PyDowncastError::new(value, T::NAME)) - } - } - } - fn try_from_exact>(value: V) -> Result<&'v Self, PyDowncastError<'v>> { - let value = value.into(); - unsafe { - if T::is_exact_type_of(value) { - Ok(Self::try_from_unchecked(value)) - } else { - Err(PyDowncastError::new(value, T::NAME)) - } - } - } - #[inline] - unsafe fn try_from_unchecked>(value: V) -> &'v Self { - Self::unchecked_downcast(value.into()) - } -} - -/// Converts `()` to an empty Python tuple. -impl IntoPy> for () { - fn into_py(self, py: Python<'_>) -> Py { - PyTuple::empty(py).into() - } -} - -/// Raw level conversion between `*mut ffi::PyObject` and PyO3 types. -/// -/// # Safety -/// -/// See safety notes on individual functions. -pub unsafe trait FromPyPointer<'p>: Sized { - /// Convert from an arbitrary `PyObject`. - /// - /// # Safety - /// - /// Implementations must ensure the object does not get freed during `'p` - /// and ensure that `ptr` is of the correct type. - /// Note that it must be safe to decrement the reference count of `ptr`. - unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option<&'p Self>; - /// Convert from an arbitrary `PyObject` or panic. - /// - /// # Safety - /// - /// Relies on [`from_owned_ptr_or_opt`](#method.from_owned_ptr_or_opt). - unsafe fn from_owned_ptr_or_panic(py: Python<'p>, ptr: *mut ffi::PyObject) -> &'p Self { - Self::from_owned_ptr_or_opt(py, ptr).unwrap_or_else(|| err::panic_after_error(py)) - } - /// Convert from an arbitrary `PyObject` or panic. - /// - /// # Safety - /// - /// Relies on [`from_owned_ptr_or_opt`](#method.from_owned_ptr_or_opt). - unsafe fn from_owned_ptr(py: Python<'p>, ptr: *mut ffi::PyObject) -> &'p Self { - Self::from_owned_ptr_or_panic(py, ptr) - } - /// Convert from an arbitrary `PyObject`. - /// - /// # Safety - /// - /// Relies on [`from_owned_ptr_or_opt`](#method.from_owned_ptr_or_opt). - unsafe fn from_owned_ptr_or_err(py: Python<'p>, ptr: *mut ffi::PyObject) -> PyResult<&'p Self> { - Self::from_owned_ptr_or_opt(py, ptr).ok_or_else(|| err::PyErr::fetch(py)) - } - /// Convert from an arbitrary borrowed `PyObject`. - /// - /// # Safety - /// - /// Implementations must ensure the object does not get freed during `'p` and avoid type confusion. - unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) - -> Option<&'p Self>; - /// Convert from an arbitrary borrowed `PyObject`. - /// - /// # Safety - /// - /// Relies on unsafe fn [`from_borrowed_ptr_or_opt`](#method.from_borrowed_ptr_or_opt). - unsafe fn from_borrowed_ptr_or_panic(py: Python<'p>, ptr: *mut ffi::PyObject) -> &'p Self { - Self::from_borrowed_ptr_or_opt(py, ptr).unwrap_or_else(|| err::panic_after_error(py)) - } - /// Convert from an arbitrary borrowed `PyObject`. - /// - /// # Safety - /// - /// Relies on unsafe fn [`from_borrowed_ptr_or_opt`](#method.from_borrowed_ptr_or_opt). - unsafe fn from_borrowed_ptr(py: Python<'p>, ptr: *mut ffi::PyObject) -> &'p Self { - Self::from_borrowed_ptr_or_panic(py, ptr) - } - /// Convert from an arbitrary borrowed `PyObject`. - /// - /// # Safety - /// - /// Relies on unsafe fn [`from_borrowed_ptr_or_opt`](#method.from_borrowed_ptr_or_opt). - unsafe fn from_borrowed_ptr_or_err( - py: Python<'p>, - ptr: *mut ffi::PyObject, - ) -> PyResult<&'p Self> { - Self::from_borrowed_ptr_or_opt(py, ptr).ok_or_else(|| err::PyErr::fetch(py)) - } -} - -unsafe impl<'p, T> FromPyPointer<'p> for T -where - T: 'p + crate::PyNativeType, -{ - unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option<&'p Self> { - gil::register_owned(py, NonNull::new(ptr)?); - Some(&*(ptr as *mut Self)) - } - unsafe fn from_borrowed_ptr_or_opt( - _py: Python<'p>, - ptr: *mut ffi::PyObject, - ) -> Option<&'p Self> { - NonNull::new(ptr as *mut Self).map(|p| &*p.as_ptr()) - } -} - -#[cfg(test)] -mod tests { - use crate::types::{IntoPyDict, PyAny, PyDict, PyList}; - use crate::{AsPyPointer, PyObject, Python, ToPyObject}; - - use super::PyTryFrom; - - #[test] - fn test_try_from() { - Python::with_gil(|py| { - let list: &PyAny = vec![3, 6, 5, 4, 7].to_object(py).into_ref(py); - let dict: &PyAny = vec![("reverse", true)].into_py_dict(py).as_ref(); - - assert!(>::try_from(list).is_ok()); - assert!(>::try_from(dict).is_ok()); - - assert!(>::try_from(list).is_ok()); - assert!(>::try_from(dict).is_ok()); - }); - } - - #[test] - fn test_try_from_exact() { - Python::with_gil(|py| { - let list: &PyAny = vec![3, 6, 5, 4, 7].to_object(py).into_ref(py); - let dict: &PyAny = vec![("reverse", true)].into_py_dict(py).as_ref(); - - assert!(PyList::try_from_exact(list).is_ok()); - assert!(PyDict::try_from_exact(dict).is_ok()); - - assert!(PyAny::try_from_exact(list).is_err()); - assert!(PyAny::try_from_exact(dict).is_err()); - }); - } - - #[test] - fn test_try_from_unchecked() { - Python::with_gil(|py| { - let list = PyList::new(py, &[1, 2, 3]); - let val = unsafe { ::try_from_unchecked(list.as_ref()) }; - assert!(list.is(val)); - }); - } - - #[test] - fn test_option_as_ptr() { - Python::with_gil(|py| { - let mut option: Option = None; - assert_eq!(option.as_ptr(), std::ptr::null_mut()); - - let none = py.None(); - option = Some(none.clone()); - - let ref_cnt = none.get_refcnt(py); - assert_eq!(option.as_ptr(), none.as_ptr()); - - // Ensure ref count not changed by as_ptr call - assert_eq!(none.get_refcnt(py), ref_cnt); - }); - } -} From 19e32a06212e76144a6596a8013836d3026e85d5 Mon Sep 17 00:00:00 2001 From: cuishuang Date: Sun, 24 Apr 2022 22:06:32 +0800 Subject: [PATCH 20/23] fix some typos Signed-off-by: cuishuang --- CHANGELOG.md | 6 +++--- Cargo.toml | 2 +- guide/src/class.md | 2 +- guide/src/class/numeric.md | 2 +- guide/src/conversions/traits.md | 2 +- guide/src/faq.md | 2 +- pyo3-macros/src/lib.rs | 2 +- src/class/mapping.rs | 2 +- src/class/sequence.rs | 2 +- src/gil.rs | 4 ++-- src/marker.rs | 2 +- src/pyclass_init.rs | 2 +- src/type_object.rs | 6 +++--- src/types/datetime.rs | 2 +- tests/test_default_impls.rs | 4 ++-- tests/test_methods.rs | 2 +- 16 files changed, 22 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fdd474dafe..1a70672560b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -404,7 +404,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Remove FFI definition `PyCFunction_ClearFreeList` for Python 3.9 and later. [#1425](https://github.com/PyO3/pyo3/pull/1425) -- `PYO3_CROSS_LIB_DIR` enviroment variable no long required when compiling for x86-64 Python from macOS arm64 and reverse. [#1428](https://github.com/PyO3/pyo3/pull/1428) +- `PYO3_CROSS_LIB_DIR` environment variable no long required when compiling for x86-64 Python from macOS arm64 and reverse. [#1428](https://github.com/PyO3/pyo3/pull/1428) - Fix FFI definition `_PyEval_RequestCodeExtraIndex`, which took an argument of the wrong type. [#1429](https://github.com/PyO3/pyo3/pull/1429) - Fix FFI definition `PyIndex_Check` missing with the `abi3` feature. [#1436](https://github.com/PyO3/pyo3/pull/1436) - Fix incorrect `TypeError` raised when keyword-only argument passed along with a positional argument in `*args`. [#1440](https://github.com/PyO3/pyo3/pull/1440) @@ -413,7 +413,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - No longer include `__doc__` in `__all__` generated for `#[pymodule]`. [#1509](https://github.com/PyO3/pyo3/pull/1509) - Always use cross-compiling configuration if any of the `PYO3_CROSS` family of environment variables are set. [#1514](https://github.com/PyO3/pyo3/pull/1514) - Support `EnvironmentError`, `IOError`, and `WindowsError` on PyPy. [#1533](https://github.com/PyO3/pyo3/pull/1533) -- Fix unneccessary rebuilds when cycling between `cargo check` and `cargo clippy` in a Python virtualenv. [#1557](https://github.com/PyO3/pyo3/pull/1557) +- Fix unnecessary rebuilds when cycling between `cargo check` and `cargo clippy` in a Python virtualenv. [#1557](https://github.com/PyO3/pyo3/pull/1557) - Fix segfault when dereferencing `ffi::PyDateTimeAPI` without the GIL. [#1563](https://github.com/PyO3/pyo3/pull/1563) - Fix memory leak in `FromPyObject` implementations for `u128` and `i128`. [#1638](https://github.com/PyO3/pyo3/pull/1638) - Fix `#[pyclass(extends=PyDict)]` leaking the dict contents on drop. [#1657](https://github.com/PyO3/pyo3/pull/1657) @@ -997,7 +997,7 @@ Yanked ### Changed - Removes the types from the root module and the prelude. They now live in `pyo3::types` instead. -- All exceptions are consturcted with `py_err` instead of `new`, as they return `PyErr` and not `Self`. +- All exceptions are constructed with `py_err` instead of `new`, as they return `PyErr` and not `Self`. - `as_mut` and friends take and `&mut self` instead of `&self` - `ObjectProtocol::call` now takes an `Option<&PyDict>` for the kwargs instead of an `IntoPyDictPointer`. - `IntoPyDictPointer` was replace by `IntoPyDict` which doesn't convert `PyDict` itself anymore and returns a `PyDict` instead of `*mut PyObject`. diff --git a/Cargo.toml b/Cargo.toml index d6a20774d9e..95401ea39cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ cfg-if = "1.0" libc = "0.2.62" parking_lot = ">= 0.11, < 0.13" -# ffi bindings to the python interpreter, split into a seperate crate so they can be used independently +# ffi bindings to the python interpreter, split into a separate crate so they can be used independently pyo3-ffi = { path = "pyo3-ffi", version = "=0.16.4" } # support crates for macros feature diff --git a/guide/src/class.md b/guide/src/class.md index e5245be98fe..d5a0c12c739 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -880,7 +880,7 @@ Python::with_gil(|py| { }) ``` -All methods defined by PyO3 can be overriden. For example here's how you override `__repr__`: +All methods defined by PyO3 can be overridden. For example here's how you override `__repr__`: ```rust # use pyo3::prelude::*; diff --git a/guide/src/class/numeric.md b/guide/src/class/numeric.md index bb4ef1f45d3..df3c908fa7e 100644 --- a/guide/src/class/numeric.md +++ b/guide/src/class/numeric.md @@ -124,7 +124,7 @@ impl Number { } ``` -### Unary arithmethic operations +### Unary arithmetic operations ```rust # use pyo3::prelude::*; diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index 2705413d104..1bfcc56f596 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -215,7 +215,7 @@ struct RustyTransparentStruct { #### Deriving [`FromPyObject`] for enums The `FromPyObject` derivation for enums generates code that tries to extract the variants in the -order of the fields. As soon as a variant can be extracted succesfully, that variant is returned. +order of the fields. As soon as a variant can be extracted successfully, that variant is returned. This makes it possible to extract Python union types like `str | int`. The same customizations and restrictions described for struct derivations apply to enum variants, diff --git a/guide/src/faq.md b/guide/src/faq.md index 6ef7fd6705a..d5773abc28c 100644 --- a/guide/src/faq.md +++ b/guide/src/faq.md @@ -8,7 +8,7 @@ 2. The initialization code calls some Python API which temporarily releases the GIL e.g. `Python::import`. 3. Another thread (thread B) acquires the Python GIL and attempts to access the same `lazy_static` value. 4. Thread B is blocked, because it waits for `lazy_static`'s initialization to lock to release. -5. Thread A is blocked, because it waits to re-aquire the GIL which thread B still holds. +5. Thread A is blocked, because it waits to re-acquire the GIL which thread B still holds. 6. Deadlock. PyO3 provides a struct [`GILOnceCell`] which works equivalently to `OnceCell` but relies solely on the Python GIL for thread safety. This means it can be used in place of `lazy_static` or `once_cell` where you are experiencing the deadlock described above. See the documentation for [`GILOnceCell`] for an example how to use it. diff --git a/pyo3-macros/src/lib.rs b/pyo3-macros/src/lib.rs index 3577b9c23e9..84160af2b95 100644 --- a/pyo3-macros/src/lib.rs +++ b/pyo3-macros/src/lib.rs @@ -58,7 +58,7 @@ pub fn pymodule(args: TokenStream, input: TokenStream) -> TokenStream { /// A proc macro used to implement Python's [dunder methods][1]. /// -/// This atribute is required on blocks implementing [`PyObjectProtocol`][2], +/// This attribute is required on blocks implementing [`PyObjectProtocol`][2], /// [`PyNumberProtocol`][3], [`PyGCProtocol`][4] and [`PyIterProtocol`][5]. /// /// [1]: https://docs.python.org/3/reference/datamodel.html#special-method-names diff --git a/src/class/mapping.rs b/src/class/mapping.rs index 413f97199ee..533f5178ba2 100644 --- a/src/class/mapping.rs +++ b/src/class/mapping.rs @@ -41,7 +41,7 @@ pub trait PyMappingProtocol<'p>: PyClass { } // The following are a bunch of marker traits used to detect -// the existance of a slotted method. +// the existence of a slotted method. pub trait PyMappingLenProtocol<'p>: PyMappingProtocol<'p> { type Result: IntoPyCallbackOutput; diff --git a/src/class/sequence.rs b/src/class/sequence.rs index b1b29ea0061..8e486ef4fcd 100644 --- a/src/class/sequence.rs +++ b/src/class/sequence.rs @@ -78,7 +78,7 @@ pub trait PySequenceProtocol<'p>: PyClass + Sized { } // The following are a bunch of marker traits used to detect -// the existance of a slotted method. +// the existence of a slotted method. pub trait PySequenceLenProtocol<'p>: PySequenceProtocol<'p> { type Result: IntoPyCallbackOutput; diff --git a/src/gil.rs b/src/gil.rs index 8d4060085b8..1aa84d3c224 100644 --- a/src/gil.rs +++ b/src/gil.rs @@ -174,7 +174,7 @@ impl GILGuard { /// `GILGuard` will also contain a `GILPool`. pub(crate) fn acquire() -> GILGuard { // Maybe auto-initialize the GIL: - // - If auto-initialize feature set and supported, try to initalize the interpreter. + // - If auto-initialize feature set and supported, try to initialize the interpreter. // - If the auto-initialize feature is set but unsupported, emit hard errors only when the // extension-module feature is not activated - extension modules don't care about // auto-initialize so this avoids breaking existing builds. @@ -199,7 +199,7 @@ impl GILGuard { assert_ne!( ffi::Py_IsInitialized(), 0, - "The Python interpreter is not initalized and the `auto-initialize` \ + "The Python interpreter is not initialized and the `auto-initialize` \ feature is not enabled.\n\n\ Consider calling `pyo3::prepare_freethreaded_python()` before attempting \ to use Python APIs." diff --git a/src/marker.rs b/src/marker.rs index 209fe33b325..fdfef0784f4 100644 --- a/src/marker.rs +++ b/src/marker.rs @@ -798,7 +798,7 @@ impl<'py> Python<'py> { /// /// This function calls [`PyErr_CheckSignals()`][1] which in turn may call signal handlers. /// As Python's [`signal`][2] API allows users to define custom signal handlers, calling this - /// function allows arbitary Python code inside signal handlers to run. + /// function allows arbitrary Python code inside signal handlers to run. /// /// [1]: https://docs.python.org/3/c-api/exceptions.html?highlight=pyerr_checksignals#c.PyErr_CheckSignals /// [2]: https://docs.python.org/3/library/signal.html diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index 2214bd0fb35..273e6b3f996 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -82,7 +82,7 @@ impl PyObjectInit for PyNativeTypeInitializer { /// Initializer for our `#[pyclass]` system. /// -/// You can use this type to initalize complicatedly nested `#[pyclass]`. +/// You can use this type to initialize complicatedly nested `#[pyclass]`. /// /// # Examples /// diff --git a/src/type_object.rs b/src/type_object.rs index f2743d145ab..5f800e8eb06 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -13,7 +13,7 @@ use parking_lot::{const_mutex, Mutex}; use std::thread::{self, ThreadId}; /// `T: PyLayout` represents that `T` is a concrete representation of `U` in the Python heap. -/// E.g., `PyCell` is a concrete representaion of all `pyclass`es, and `ffi::PyObject` +/// E.g., `PyCell` is a concrete representation of all `pyclass`es, and `ffi::PyObject` /// is of `PyAny`. /// /// This trait is intended to be used internally. @@ -25,7 +25,7 @@ pub unsafe trait PyLayout {} /// `T: PySizedLayout` represents that `T` is not a instance of /// [`PyVarObject`](https://docs.python.org/3.8/c-api/structures.html?highlight=pyvarobject#c.PyVarObject). -/// In addition, that `T` is a concrete representaion of `U`. +/// In addition, that `T` is a concrete representation of `U`. pub trait PySizedLayout: PyLayout + Sized {} /// Python type information. @@ -179,7 +179,7 @@ impl LazyStaticType { if let Err(err) = result { err.clone_ref(py).print(py); - panic!("An error occured while initializing `{}.__dict__`", name); + panic!("An error occurred while initializing `{}.__dict__`", name); } } } diff --git a/src/types/datetime.rs b/src/types/datetime.rs index 907827744c5..67cdf22c3ea 100644 --- a/src/types/datetime.rs +++ b/src/types/datetime.rs @@ -41,7 +41,7 @@ fn ensure_datetime_api(_py: Python<'_>) -> &'static PyDateTime_CAPI { // These are bindings around the C API typecheck macros, all of them return // `1` if True and `0` if False. In all type check macros, the argument (`op`) // must not be `NULL`. The implementations here all call ensure_datetime_api -// to ensure that the PyDateTimeAPI is initalized before use +// to ensure that the PyDateTimeAPI is initialized before use // // // # Safety diff --git a/tests/test_default_impls.rs b/tests/test_default_impls.rs index a764c1f462b..25224aa5681 100644 --- a/tests/test_default_impls.rs +++ b/tests/test_default_impls.rs @@ -30,7 +30,7 @@ enum OverrideSlot { #[pymethods] impl OverrideSlot { fn __repr__(&self) -> &str { - "overriden" + "overridden" } } @@ -38,6 +38,6 @@ impl OverrideSlot { fn test_override_slot() { Python::with_gil(|py| { let test_object = Py::new(py, OverrideSlot::Var).unwrap(); - py_assert!(py, test_object, "repr(test_object) == 'overriden'"); + py_assert!(py, test_object, "repr(test_object) == 'overridden'"); }) } diff --git a/tests/test_methods.rs b/tests/test_methods.rs index 6b1b3bf1fb9..75b960cdb18 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -962,7 +962,7 @@ pymethods!( // Regression test for issue 1506 - incorrect macro hygiene. // By applying the `#[pymethods]` attribute inside a macro_rules! macro, this separates the macro // call scope from the scope of the impl block. For this to work our macros must be careful to not -// cheat hygeine! +// cheat hygiene! #[pyclass] struct Issue1506 {} From 4168feed1b931e7f10891ccaae3711fca081c1ad Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Mon, 25 Apr 2022 22:17:10 +0100 Subject: [PATCH 21/23] opt: tidy some generic code bloat --- src/callback.rs | 5 ++++- src/impl_/extract_argument.rs | 20 +++----------------- src/types/module.rs | 9 ++++++--- tests/ui/invalid_result_conversion.stderr | 4 ++-- 4 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/callback.rs b/src/callback.rs index 3794b9e5906..7b487c48424 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -47,7 +47,10 @@ where { #[inline] fn convert(self, py: Python<'_>) -> PyResult { - self.map_err(Into::into).and_then(|t| t.convert(py)) + match self { + Ok(v) => v.convert(py), + Err(e) => Err(e.into()), + } } } diff --git a/src/impl_/extract_argument.rs b/src/impl_/extract_argument.rs index feea25f1409..aedc7792145 100644 --- a/src/impl_/extract_argument.rs +++ b/src/impl_/extract_argument.rs @@ -7,7 +7,6 @@ use crate::{ /// The standard implementation of how PyO3 extracts a `#[pyfunction]` or `#[pymethod]` function argument. #[doc(hidden)] -#[inline] pub fn extract_argument<'py, T>(obj: &'py PyAny, arg_name: &str) -> PyResult where T: FromPyObject<'py>, @@ -21,7 +20,6 @@ where /// Alternative to [`extract_argument`] used for `Option` arguments (because they are implicitly treated /// as optional if at the end of the positional parameters). #[doc(hidden)] -#[inline] pub fn extract_optional_argument<'py, T>( obj: Option<&'py PyAny>, arg_name: &str, @@ -30,17 +28,13 @@ where T: FromPyObject<'py>, { match obj { - Some(obj) => match obj.extract() { - Ok(value) => Ok(value), - Err(e) => Err(argument_extraction_error(obj.py(), arg_name, e)), - }, + Some(obj) => extract_argument(obj, arg_name), None => Ok(None), } } /// Alternative to [`extract_argument`] used when the argument has a default value provided by an annotation. #[doc(hidden)] -#[inline] pub fn extract_argument_with_default<'py, T>( obj: Option<&'py PyAny>, arg_name: &str, @@ -50,10 +44,7 @@ where T: FromPyObject<'py>, { match obj { - Some(obj) => match obj.extract() { - Ok(value) => Ok(value), - Err(e) => Err(argument_extraction_error(obj.py(), arg_name, e)), - }, + Some(obj) => extract_argument(obj, arg_name), None => Ok(default()), } } @@ -63,7 +54,6 @@ where /// # Safety /// - `obj` must not be None (this helper is only used for required function arguments). #[doc(hidden)] -#[inline] pub fn from_py_with<'py, T>( obj: &'py PyAny, arg_name: &str, @@ -78,7 +68,6 @@ pub fn from_py_with<'py, T>( /// Alternative to [`extract_argument`] used when the argument has a `#[pyo3(from_py_with)]` annotation and also a default value. #[doc(hidden)] -#[inline] pub fn from_py_with_with_default<'py, T>( obj: Option<&'py PyAny>, arg_name: &str, @@ -86,10 +75,7 @@ pub fn from_py_with_with_default<'py, T>( default: impl FnOnce() -> T, ) -> PyResult { match obj { - Some(obj) => match extractor(obj) { - Ok(value) => Ok(value), - Err(e) => Err(argument_extraction_error(obj.py(), arg_name, e)), - }, + Some(obj) => from_py_with(obj, arg_name, extractor), None => Ok(default()), } } diff --git a/src/types/module.rs b/src/types/module.rs index 21f6e0cae32..2dadf0f0c70 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -300,11 +300,14 @@ impl PyModule { where T: IntoPyCallbackOutput, { + self._add_wrapped(wrapper(self.py()).convert(self.py())?) + } + + fn _add_wrapped(&self, object: PyObject) -> PyResult<()> { let py = self.py(); - let function = wrapper(py).convert(py)?; - let name = function.getattr(py, __name__(py))?; + let name = object.getattr(py, __name__(py))?; let name = name.extract(py)?; - self.add(name, function) + self.add(name, object) } /// Adds a submodule to a module. diff --git a/tests/ui/invalid_result_conversion.stderr b/tests/ui/invalid_result_conversion.stderr index a606f605fcf..f9f1355532e 100644 --- a/tests/ui/invalid_result_conversion.stderr +++ b/tests/ui/invalid_result_conversion.stderr @@ -7,8 +7,8 @@ error[E0277]: the trait bound `Result<(), MyError>: IntoPyCallbackOutput<_>` is = help: the following implementations were found: as IntoPyCallbackOutput> note: required by a bound in `pyo3::callback::convert` - --> src/callback.rs:182:8 + --> src/callback.rs | -182 | T: IntoPyCallbackOutput, + | T: IntoPyCallbackOutput, | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `pyo3::callback::convert` = note: this error originates in the attribute macro `pyfunction` (in Nightly builds, run with -Z macro-backtrace for more info) From 71f9f18d542b17b7c3ad230f6146ea4c30a1d7cb Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Sun, 24 Apr 2022 13:04:06 +0100 Subject: [PATCH 22/23] remove toborrowedobject trait --- CHANGELOG.md | 1 + src/conversion.rs | 11 +-- src/err/mod.rs | 12 ++-- src/instance.rs | 32 +++++---- src/lib.rs | 4 +- src/types/any.rs | 155 ++++++++++++++++++++++++------------------ src/types/dict.rs | 49 +++++++------ src/types/list.rs | 34 +++++---- src/types/mapping.rs | 11 ++- src/types/sequence.rs | 42 ++++++------ src/types/set.rs | 33 ++++----- src/types/tuple.rs | 6 +- 12 files changed, 217 insertions(+), 173 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fdd474dafe..bc3aff13a8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Move `PyTypeObject::type_object` method to `PyTypeInfo` trait, and deprecate `PyTypeObject` trait. [#2284](https://github.com/PyO3/pyo3/pull/2284) - The deprecated `pyproto` feature is now disabled by default. [#2321](https://github.com/PyO3/pyo3/pull/2321) +- Deprecate `ToBorrowedObject` trait (it is only used as a wrapper for `ToPyObject`). [#2330](https://github.com/PyO3/pyo3/pull/2330) ## [0.16.4] - 2022-04-14 diff --git a/src/conversion.rs b/src/conversion.rs index 6b3e5841ba4..8a005a26b36 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -75,10 +75,12 @@ pub trait ToPyObject { fn to_object(&self, py: Python<'_>) -> PyObject; } -/// This trait has two implementations: The slow one is implemented for -/// all [ToPyObject] and creates a new object using [ToPyObject::to_object], -/// while the fast one is only implemented for AsPyPointer (we know -/// that every AsPyPointer is also ToPyObject) and uses [AsPyPointer::as_ptr()] +/// A deprecated conversion trait which relied on the unstable `specialization` feature +/// of the Rust language. +#[deprecated( + since = "0.17.0", + note = "this trait is no longer used by PyO3, use ToPyObject or IntoPy" +)] pub trait ToBorrowedObject: ToPyObject { /// Converts self into a Python object and calls the specified closure /// on the native FFI pointer underlying the Python object. @@ -98,6 +100,7 @@ pub trait ToBorrowedObject: ToPyObject { } } +#[allow(deprecated)] impl ToBorrowedObject for T where T: ToPyObject {} /// Defines a conversion from a Rust type to a Python object. diff --git a/src/err/mod.rs b/src/err/mod.rs index 425c044cb9b..2491d8921d1 100644 --- a/src/err/mod.rs +++ b/src/err/mod.rs @@ -7,9 +7,7 @@ use crate::{ exceptions::{self, PyBaseException}, ffi, }; -use crate::{ - AsPyPointer, IntoPy, IntoPyPointer, Py, PyAny, PyObject, Python, ToBorrowedObject, ToPyObject, -}; +use crate::{AsPyPointer, IntoPy, IntoPyPointer, Py, PyAny, PyObject, Python, ToPyObject}; use std::borrow::Cow; use std::cell::UnsafeCell; use std::ffi::CString; @@ -411,11 +409,11 @@ impl PyErr { /// If `exc` is a tuple, all exceptions in the tuple (and recursively in subtuples) are searched for a match. pub fn matches(&self, py: Python<'_>, exc: T) -> bool where - T: ToBorrowedObject, + T: ToPyObject, { - exc.with_borrowed_ptr(py, |exc| unsafe { - ffi::PyErr_GivenExceptionMatches(self.type_ptr(py), exc) != 0 - }) + unsafe { + ffi::PyErr_GivenExceptionMatches(self.type_ptr(py), exc.to_object(py).as_ptr()) != 0 + } } /// Returns true if the current exception is instance of `T`. diff --git a/src/instance.rs b/src/instance.rs index 7cf7b5cc9f0..45ab59b038e 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1,5 +1,5 @@ // Copyright (c) 2017-present PyO3 Project and Contributors -use crate::conversion::{PyTryFrom, ToBorrowedObject}; +use crate::conversion::PyTryFrom; use crate::err::{self, PyDowncastError, PyErr, PyResult}; use crate::gil; use crate::pycell::{PyBorrowError, PyBorrowMutError, PyCell}; @@ -563,9 +563,12 @@ impl Py { where N: ToPyObject, { - attr_name.with_borrowed_ptr(py, |attr_name| unsafe { - PyObject::from_owned_ptr_or_err(py, ffi::PyObject_GetAttr(self.as_ptr(), attr_name)) - }) + unsafe { + PyObject::from_owned_ptr_or_err( + py, + ffi::PyObject_GetAttr(self.as_ptr(), attr_name.to_object(py).as_ptr()), + ) + } } /// Sets an attribute value. @@ -595,11 +598,16 @@ impl Py { N: ToPyObject, V: ToPyObject, { - attr_name.with_borrowed_ptr(py, move |attr_name| { - value.with_borrowed_ptr(py, |value| unsafe { - err::error_on_minusone(py, ffi::PyObject_SetAttr(self.as_ptr(), attr_name, value)) - }) - }) + unsafe { + err::error_on_minusone( + py, + ffi::PyObject_SetAttr( + self.as_ptr(), + attr_name.to_object(py).as_ptr(), + value.to_object(py).as_ptr(), + ), + ) + } } /// Calls the object. @@ -656,10 +664,10 @@ impl Py { args: impl IntoPy>, kwargs: Option<&PyDict>, ) -> PyResult { - name.with_borrowed_ptr(py, |name| unsafe { + unsafe { let args = args.into_py(py).into_ptr(); let kwargs = kwargs.into_ptr(); - let ptr = ffi::PyObject_GetAttr(self.as_ptr(), name); + let ptr = ffi::PyObject_GetAttr(self.as_ptr(), name.to_object(py).as_ptr()); if ptr.is_null() { return Err(PyErr::fetch(py)); } @@ -668,7 +676,7 @@ impl Py { ffi::Py_XDECREF(args); ffi::Py_XDECREF(kwargs); result - }) + } } /// Calls a method on the object with only positional arguments. diff --git a/src/lib.rs b/src/lib.rs index 2ed3d3c1311..effa4f4787d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -294,9 +294,11 @@ //! [Features chapter of the guide]: https://pyo3.rs/latest/features.html#features-reference "Features Reference - PyO3 user guide" //! [`Ungil`]: crate::marker::Ungil pub use crate::class::*; +#[allow(deprecated)] +pub use crate::conversion::ToBorrowedObject; pub use crate::conversion::{ AsPyPointer, FromPyObject, FromPyPointer, IntoPy, IntoPyPointer, PyTryFrom, PyTryInto, - ToBorrowedObject, ToPyObject, + ToPyObject, }; pub use crate::err::{PyDowncastError, PyErr, PyErrArguments, PyResult}; #[cfg(not(PyPy))] diff --git a/src/types/any.rs b/src/types/any.rs index f49b8efa617..26dd5c679e2 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -1,7 +1,5 @@ use crate::class::basic::CompareOp; -use crate::conversion::{ - AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyTryFrom, ToBorrowedObject, ToPyObject, -}; +use crate::conversion::{AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyTryFrom, ToPyObject}; use crate::err::{PyDowncastError, PyErr, PyResult}; use crate::exceptions::PyTypeError; use crate::type_object::PyTypeInfo; @@ -103,9 +101,9 @@ impl PyAny { where N: ToPyObject, { - attr_name.with_borrowed_ptr(self.py(), |attr_name| unsafe { - Ok(ffi::PyObject_HasAttr(self.as_ptr(), attr_name) != 0) - }) + unsafe { + Ok(ffi::PyObject_HasAttr(self.as_ptr(), attr_name.to_object(self.py()).as_ptr()) != 0) + } } /// Retrieves an attribute value. @@ -134,10 +132,12 @@ impl PyAny { where N: ToPyObject, { - attr_name.with_borrowed_ptr(self.py(), |attr_name| unsafe { - self.py() - .from_owned_ptr_or_err(ffi::PyObject_GetAttr(self.as_ptr(), attr_name)) - }) + unsafe { + self.py().from_owned_ptr_or_err(ffi::PyObject_GetAttr( + self.as_ptr(), + attr_name.to_object(self.py()).as_ptr(), + )) + } } /// Sets an attribute value. @@ -164,17 +164,20 @@ impl PyAny { /// ``` pub fn setattr(&self, attr_name: N, value: V) -> PyResult<()> where - N: ToBorrowedObject, - V: ToBorrowedObject, + N: ToPyObject, + V: ToPyObject, { - attr_name.with_borrowed_ptr(self.py(), move |attr_name| { - value.with_borrowed_ptr(self.py(), |value| unsafe { - err::error_on_minusone( - self.py(), - ffi::PyObject_SetAttr(self.as_ptr(), attr_name, value), - ) - }) - }) + let py = self.py(); + unsafe { + err::error_on_minusone( + py, + ffi::PyObject_SetAttr( + self.as_ptr(), + attr_name.to_object(py).as_ptr(), + value.to_object(py).as_ptr(), + ), + ) + } } /// Deletes an attribute. @@ -184,9 +187,13 @@ impl PyAny { where N: ToPyObject, { - attr_name.with_borrowed_ptr(self.py(), |attr_name| unsafe { - err::error_on_minusone(self.py(), ffi::PyObject_DelAttr(self.as_ptr(), attr_name)) - }) + let py = self.py(); + unsafe { + err::error_on_minusone( + self.py(), + ffi::PyObject_DelAttr(self.as_ptr(), attr_name.to_object(py).as_ptr()), + ) + } } /// Returns an [`Ordering`] between `self` and `other`. @@ -239,26 +246,29 @@ impl PyAny { where O: ToPyObject, { + self._compare(other.to_object(self.py())) + } + + fn _compare(&self, other: PyObject) -> PyResult { let py = self.py(); + let other = other.as_ptr(); // Almost the same as ffi::PyObject_RichCompareBool, but this one doesn't try self == other. // See https://github.com/PyO3/pyo3/issues/985 for more. let do_compare = |other, op| unsafe { PyObject::from_owned_ptr_or_err(py, ffi::PyObject_RichCompare(self.as_ptr(), other, op)) .and_then(|obj| obj.is_true(py)) }; - other.with_borrowed_ptr(py, |other| { - if do_compare(other, ffi::Py_EQ)? { - Ok(Ordering::Equal) - } else if do_compare(other, ffi::Py_LT)? { - Ok(Ordering::Less) - } else if do_compare(other, ffi::Py_GT)? { - Ok(Ordering::Greater) - } else { - Err(PyTypeError::new_err( - "PyAny::compare(): All comparisons returned false", - )) - } - }) + if do_compare(other, ffi::Py_EQ)? { + Ok(Ordering::Equal) + } else if do_compare(other, ffi::Py_LT)? { + Ok(Ordering::Less) + } else if do_compare(other, ffi::Py_GT)? { + Ok(Ordering::Greater) + } else { + Err(PyTypeError::new_err( + "PyAny::compare(): All comparisons returned false", + )) + } } /// Tests whether two Python objects obey a given [`CompareOp`]. @@ -296,13 +306,11 @@ impl PyAny { O: ToPyObject, { unsafe { - other.with_borrowed_ptr(self.py(), |other| { - self.py().from_owned_ptr_or_err(ffi::PyObject_RichCompare( - self.as_ptr(), - other, - compare_op as c_int, - )) - }) + self.py().from_owned_ptr_or_err(ffi::PyObject_RichCompare( + self.as_ptr(), + other.to_object(self.py()).as_ptr(), + compare_op as c_int, + )) } } @@ -519,9 +527,9 @@ impl PyAny { args: impl IntoPy>, kwargs: Option<&PyDict>, ) -> PyResult<&PyAny> { - name.with_borrowed_ptr(self.py(), |name| unsafe { + unsafe { let py = self.py(); - let ptr = ffi::PyObject_GetAttr(self.as_ptr(), name); + let ptr = ffi::PyObject_GetAttr(self.as_ptr(), name.to_object(py).as_ptr()); if ptr.is_null() { return Err(PyErr::fetch(py)); } @@ -533,7 +541,7 @@ impl PyAny { ffi::Py_XDECREF(args); ffi::Py_XDECREF(kwargs); result - }) + } } /// Calls a method on the object without arguments. @@ -639,12 +647,14 @@ impl PyAny { /// This is equivalent to the Python expression `self[key]`. pub fn get_item(&self, key: K) -> PyResult<&PyAny> where - K: ToBorrowedObject, + K: ToPyObject, { - key.with_borrowed_ptr(self.py(), |key| unsafe { - self.py() - .from_owned_ptr_or_err(ffi::PyObject_GetItem(self.as_ptr(), key)) - }) + unsafe { + self.py().from_owned_ptr_or_err(ffi::PyObject_GetItem( + self.as_ptr(), + key.to_object(self.py()).as_ptr(), + )) + } } /// Sets a collection item value. @@ -652,14 +662,20 @@ impl PyAny { /// This is equivalent to the Python expression `self[key] = value`. pub fn set_item(&self, key: K, value: V) -> PyResult<()> where - K: ToBorrowedObject, - V: ToBorrowedObject, + K: ToPyObject, + V: ToPyObject, { - key.with_borrowed_ptr(self.py(), move |key| { - value.with_borrowed_ptr(self.py(), |value| unsafe { - err::error_on_minusone(self.py(), ffi::PyObject_SetItem(self.as_ptr(), key, value)) - }) - }) + let py = self.py(); + unsafe { + err::error_on_minusone( + py, + ffi::PyObject_SetItem( + self.as_ptr(), + key.to_object(py).as_ptr(), + value.to_object(py).as_ptr(), + ), + ) + } } /// Deletes an item from the collection. @@ -667,11 +683,14 @@ impl PyAny { /// This is equivalent to the Python expression `del self[key]`. pub fn del_item(&self, key: K) -> PyResult<()> where - K: ToBorrowedObject, + K: ToPyObject, { - key.with_borrowed_ptr(self.py(), |key| unsafe { - err::error_on_minusone(self.py(), ffi::PyObject_DelItem(self.as_ptr(), key)) - }) + unsafe { + err::error_on_minusone( + self.py(), + ffi::PyObject_DelItem(self.as_ptr(), key.to_object(self.py()).as_ptr()), + ) + } } /// Takes an object and returns an iterator for it. @@ -789,15 +808,15 @@ impl PyAny { /// Determines if self contains `value`. /// /// This is equivalent to the Python expression `value in self`. - #[inline] pub fn contains(&self, value: V) -> PyResult where - V: ToBorrowedObject, + V: ToPyObject, { - let r = value.with_borrowed_ptr(self.py(), |ptr| unsafe { - ffi::PySequence_Contains(self.as_ptr(), ptr) - }); - match r { + self._contains(value.to_object(self.py())) + } + + fn _contains(&self, value: PyObject) -> PyResult { + match unsafe { ffi::PySequence_Contains(self.as_ptr(), value.as_ptr()) } { 0 => Ok(false), 1 => Ok(true), _ => Err(PyErr::fetch(self.py())), diff --git a/src/types/dict.rs b/src/types/dict.rs index 794fbea7151..f9e4ffb279f 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -4,10 +4,7 @@ use crate::err::{self, PyErr, PyResult}; use crate::types::{PyAny, PyList}; #[cfg(not(PyPy))] use crate::IntoPyPointer; -use crate::{ - ffi, AsPyPointer, FromPyObject, IntoPy, PyObject, PyTryFrom, Python, ToBorrowedObject, - ToPyObject, -}; +use crate::{ffi, AsPyPointer, FromPyObject, IntoPy, PyObject, PyTryFrom, Python, ToPyObject}; use std::collections::{BTreeMap, HashMap}; use std::ptr::NonNull; use std::{cmp, collections, hash}; @@ -82,15 +79,15 @@ impl PyDict { /// This is equivalent to the Python expression `key in self`. pub fn contains(&self, key: K) -> PyResult where - K: ToBorrowedObject, + K: ToPyObject, { - key.with_borrowed_ptr(self.py(), |key| unsafe { - match ffi::PyDict_Contains(self.as_ptr(), key) { + unsafe { + match ffi::PyDict_Contains(self.as_ptr(), key.to_object(self.py()).as_ptr()) { 1 => Ok(true), 0 => Ok(false), _ => Err(PyErr::fetch(self.py())), } - }) + } } /// Gets an item from the dictionary. @@ -100,15 +97,15 @@ impl PyDict { /// To get a `KeyError` for non-existing keys, use `PyAny::get_item`. pub fn get_item(&self, key: K) -> Option<&PyAny> where - K: ToBorrowedObject, + K: ToPyObject, { - key.with_borrowed_ptr(self.py(), |key| unsafe { - let ptr = ffi::PyDict_GetItem(self.as_ptr(), key); + unsafe { + let ptr = ffi::PyDict_GetItem(self.as_ptr(), key.to_object(self.py()).as_ptr()); NonNull::new(ptr).map(|p| { // PyDict_GetItem return s borrowed ptr, must make it owned for safety (see #890). self.py().from_owned_ptr(ffi::_Py_NewRef(p.as_ptr())) }) - }) + } } /// Sets an item value. @@ -119,11 +116,17 @@ impl PyDict { K: ToPyObject, V: ToPyObject, { - key.with_borrowed_ptr(self.py(), move |key| { - value.with_borrowed_ptr(self.py(), |value| unsafe { - err::error_on_minusone(self.py(), ffi::PyDict_SetItem(self.as_ptr(), key, value)) - }) - }) + let py = self.py(); + unsafe { + err::error_on_minusone( + py, + ffi::PyDict_SetItem( + self.as_ptr(), + key.to_object(py).as_ptr(), + value.to_object(py).as_ptr(), + ), + ) + } } /// Deletes an item. @@ -131,11 +134,15 @@ impl PyDict { /// This is equivalent to the Python statement `del self[key]`. pub fn del_item(&self, key: K) -> PyResult<()> where - K: ToBorrowedObject, + K: ToPyObject, { - key.with_borrowed_ptr(self.py(), |key| unsafe { - err::error_on_minusone(self.py(), ffi::PyDict_DelItem(self.as_ptr(), key)) - }) + let py = self.py(); + unsafe { + err::error_on_minusone( + py, + ffi::PyDict_DelItem(self.as_ptr(), key.to_object(py).as_ptr()), + ) + } } /// Returns a list of dict keys. diff --git a/src/types/list.rs b/src/types/list.rs index ab69859e9fd..e104d245341 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -9,8 +9,7 @@ use crate::ffi::{self, Py_ssize_t}; use crate::internal_tricks::get_ssize_index; use crate::types::PySequence; use crate::{ - AsPyPointer, IntoPy, IntoPyPointer, Py, PyAny, PyObject, PyTryFrom, Python, ToBorrowedObject, - ToPyObject, + AsPyPointer, IntoPy, IntoPyPointer, Py, PyAny, PyObject, PyTryFrom, Python, ToPyObject, }; /// Represents a Python `list`. @@ -222,11 +221,15 @@ impl PyList { /// Appends an item to the list. pub fn append(&self, item: I) -> PyResult<()> where - I: ToBorrowedObject, + I: ToPyObject, { - item.with_borrowed_ptr(self.py(), |item| unsafe { - err::error_on_minusone(self.py(), ffi::PyList_Append(self.as_ptr(), item)) - }) + let py = self.py(); + unsafe { + err::error_on_minusone( + py, + ffi::PyList_Append(self.as_ptr(), item.to_object(py).as_ptr()), + ) + } } /// Inserts an item at the specified index. @@ -234,14 +237,19 @@ impl PyList { /// If `index >= self.len()`, inserts at the end. pub fn insert(&self, index: usize, item: I) -> PyResult<()> where - I: ToBorrowedObject, + I: ToPyObject, { - item.with_borrowed_ptr(self.py(), |item| unsafe { + let py = self.py(); + unsafe { err::error_on_minusone( - self.py(), - ffi::PyList_Insert(self.as_ptr(), get_ssize_index(index), item), + py, + ffi::PyList_Insert( + self.as_ptr(), + get_ssize_index(index), + item.to_object(py).as_ptr(), + ), ) - }) + } } /// Determines if self contains `value`. @@ -250,7 +258,7 @@ impl PyList { #[inline] pub fn contains(&self, value: V) -> PyResult where - V: ToBorrowedObject, + V: ToPyObject, { self.as_sequence().contains(value) } @@ -261,7 +269,7 @@ impl PyList { #[inline] pub fn index(&self, value: V) -> PyResult where - V: ToBorrowedObject, + V: ToPyObject, { self.as_sequence().index(value) } diff --git a/src/types/mapping.rs b/src/types/mapping.rs index 8c861ca0393..897f3ed8220 100644 --- a/src/types/mapping.rs +++ b/src/types/mapping.rs @@ -2,10 +2,7 @@ use crate::err::{PyDowncastError, PyErr, PyResult}; use crate::types::{PyAny, PySequence}; -use crate::{ - ffi, AsPyPointer, IntoPyPointer, Py, PyNativeType, PyTryFrom, Python, ToBorrowedObject, - ToPyObject, -}; +use crate::{ffi, AsPyPointer, IntoPyPointer, Py, PyNativeType, PyTryFrom, Python, ToPyObject}; /// Represents a reference to a Python object supporting the mapping protocol. #[repr(transparent)] @@ -38,7 +35,7 @@ impl PyMapping { /// This is equivalent to the Python expression `key in self`. pub fn contains(&self, key: K) -> PyResult where - K: ToBorrowedObject, + K: ToPyObject, { PyAny::contains(self, key) } @@ -51,7 +48,7 @@ impl PyMapping { #[inline] pub fn get_item(&self, key: K) -> PyResult<&PyAny> where - K: ToBorrowedObject, + K: ToPyObject, { PyAny::get_item(self, key) } @@ -74,7 +71,7 @@ impl PyMapping { #[inline] pub fn del_item(&self, key: K) -> PyResult<()> where - K: ToBorrowedObject, + K: ToPyObject, { PyAny::del_item(self, key) } diff --git a/src/types/sequence.rs b/src/types/sequence.rs index 7cb5ee9de2d..c45e75a2007 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -3,9 +3,9 @@ use crate::err::{self, PyDowncastError, PyErr, PyResult}; use crate::internal_tricks::get_ssize_index; use crate::types::{PyAny, PyList, PyTuple}; -use crate::{ffi, PyNativeType}; +use crate::{ffi, PyNativeType, ToPyObject}; use crate::{AsPyPointer, IntoPyPointer, Py, Python}; -use crate::{FromPyObject, PyTryFrom, ToBorrowedObject}; +use crate::{FromPyObject, PyTryFrom}; /// Represents a reference to a Python object supporting the sequence protocol. #[repr(transparent)] @@ -123,15 +123,18 @@ impl PySequence { #[inline] pub fn set_item(&self, i: usize, item: I) -> PyResult<()> where - I: ToBorrowedObject, + I: ToPyObject, { + let py = self.py(); unsafe { - item.with_borrowed_ptr(self.py(), |item| { - err::error_on_minusone( - self.py(), - ffi::PySequence_SetItem(self.as_ptr(), get_ssize_index(i), item), - ) - }) + err::error_on_minusone( + py, + ffi::PySequence_SetItem( + self.as_ptr(), + get_ssize_index(i), + item.to_object(py).as_ptr(), + ), + ) } } @@ -185,11 +188,10 @@ impl PySequence { #[cfg(not(PyPy))] pub fn count(&self, value: V) -> PyResult where - V: ToBorrowedObject, + V: ToPyObject, { - let r = value.with_borrowed_ptr(self.py(), |ptr| unsafe { - ffi::PySequence_Count(self.as_ptr(), ptr) - }); + let r = + unsafe { ffi::PySequence_Count(self.as_ptr(), value.to_object(self.py()).as_ptr()) }; if r == -1 { Err(PyErr::fetch(self.py())) } else { @@ -203,11 +205,10 @@ impl PySequence { #[inline] pub fn contains(&self, value: V) -> PyResult where - V: ToBorrowedObject, + V: ToPyObject, { - let r = value.with_borrowed_ptr(self.py(), |ptr| unsafe { - ffi::PySequence_Contains(self.as_ptr(), ptr) - }); + let r = + unsafe { ffi::PySequence_Contains(self.as_ptr(), value.to_object(self.py()).as_ptr()) }; match r { 0 => Ok(false), 1 => Ok(true), @@ -221,11 +222,10 @@ impl PySequence { #[inline] pub fn index(&self, value: V) -> PyResult where - V: ToBorrowedObject, + V: ToPyObject, { - let r = value.with_borrowed_ptr(self.py(), |ptr| unsafe { - ffi::PySequence_Index(self.as_ptr(), ptr) - }); + let r = + unsafe { ffi::PySequence_Index(self.as_ptr(), value.to_object(self.py()).as_ptr()) }; if r == -1 { Err(PyErr::fetch(self.py())) } else { diff --git a/src/types/set.rs b/src/types/set.rs index 63dc7c7e24e..d8445c9107b 100644 --- a/src/types/set.rs +++ b/src/types/set.rs @@ -4,9 +4,7 @@ use crate::err::{self, PyErr, PyResult}; #[cfg(Py_LIMITED_API)] use crate::types::PyIterator; -use crate::{ - ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyObject, Python, ToBorrowedObject, ToPyObject, -}; +use crate::{ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyObject, Python, ToPyObject}; use std::cmp; use std::collections::{BTreeSet, HashSet}; use std::{collections, hash, ptr}; @@ -69,13 +67,13 @@ impl PySet { where K: ToPyObject, { - key.with_borrowed_ptr(self.py(), |key| unsafe { - match ffi::PySet_Contains(self.as_ptr(), key) { + unsafe { + match ffi::PySet_Contains(self.as_ptr(), key.to_object(self.py()).as_ptr()) { 1 => Ok(true), 0 => Ok(false), _ => Err(PyErr::fetch(self.py())), } - }) + } } /// Removes the element from the set if it is present. @@ -83,9 +81,9 @@ impl PySet { where K: ToPyObject, { - key.with_borrowed_ptr(self.py(), |key| unsafe { - ffi::PySet_Discard(self.as_ptr(), key); - }) + unsafe { + ffi::PySet_Discard(self.as_ptr(), key.to_object(self.py()).as_ptr()); + } } /// Adds an element to the set. @@ -93,9 +91,12 @@ impl PySet { where K: ToPyObject, { - key.with_borrowed_ptr(self.py(), move |key| unsafe { - err::error_on_minusone(self.py(), ffi::PySet_Add(self.as_ptr(), key)) - }) + unsafe { + err::error_on_minusone( + self.py(), + ffi::PySet_Add(self.as_ptr(), key.to_object(self.py()).as_ptr()), + ) + } } /// Removes and returns an arbitrary element from the set. @@ -303,15 +304,15 @@ impl PyFrozenSet { /// This is equivalent to the Python expression `key in self`. pub fn contains(&self, key: K) -> PyResult where - K: ToBorrowedObject, + K: ToPyObject, { - key.with_borrowed_ptr(self.py(), |key| unsafe { - match ffi::PySet_Contains(self.as_ptr(), key) { + unsafe { + match ffi::PySet_Contains(self.as_ptr(), key.to_object(self.py()).as_ptr()) { 1 => Ok(true), 0 => Ok(false), _ => Err(PyErr::fetch(self.py())), } - }) + } } /// Returns an iterator of values in this frozen set. diff --git a/src/types/tuple.rs b/src/types/tuple.rs index 86454542063..c9f9ba1df0e 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -7,7 +7,7 @@ use crate::internal_tricks::get_ssize_index; use crate::types::PySequence; use crate::{ exceptions, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, Py, PyAny, PyErr, PyObject, - PyResult, PyTryFrom, Python, ToBorrowedObject, ToPyObject, + PyResult, PyTryFrom, Python, ToPyObject, }; #[inline] @@ -215,7 +215,7 @@ impl PyTuple { #[inline] pub fn contains(&self, value: V) -> PyResult where - V: ToBorrowedObject, + V: ToPyObject, { self.as_sequence().contains(value) } @@ -226,7 +226,7 @@ impl PyTuple { #[inline] pub fn index(&self, value: V) -> PyResult where - V: ToBorrowedObject, + V: ToPyObject, { self.as_sequence().index(value) } From 8692b9b54bf9a2028cbae6f18402cc15d86b2197 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Tue, 26 Apr 2022 08:09:34 +0100 Subject: [PATCH 23/23] Update CHANGELOG.md --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c0b7017e18..5e368a1f6f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Move `PyTypeObject::type_object` method to `PyTypeInfo` trait, and deprecate `PyTypeObject` trait. [#2284](https://github.com/PyO3/pyo3/pull/2284) -- The deprecated `pyproto` feature is now disabled by default. [#2321](https://github.com/PyO3/pyo3/pull/2321) -- Deprecate `ToBorrowedObject` trait (it is only used as a wrapper for `ToPyObject`). [#2330](https://github.com/PyO3/pyo3/pull/2330) +- Move `PyTypeObject::type_object` method to `PyTypeInfo` trait, and deprecate `PyTypeObject` trait. [#2287](https://github.com/PyO3/pyo3/pull/2287) +- The deprecated `pyproto` feature is now disabled by default. [#2322](https://github.com/PyO3/pyo3/pull/2322) +- Deprecate `ToBorrowedObject` trait (it is only used as a wrapper for `ToPyObject`). [#2333](https://github.com/PyO3/pyo3/pull/2333) ## [0.16.4] - 2022-04-14 @@ -1168,7 +1168,7 @@ Yanked - Initial release [Unreleased]: https://github.com/pyo3/pyo3/compare/v0.16.4...HEAD -[0.16.3]: https://github.com/pyo3/pyo3/compare/v0.16.3...v0.16.4 +[0.16.4]: https://github.com/pyo3/pyo3/compare/v0.16.3...v0.16.4 [0.16.3]: https://github.com/pyo3/pyo3/compare/v0.16.2...v0.16.3 [0.16.2]: https://github.com/pyo3/pyo3/compare/v0.16.1...v0.16.2 [0.16.1]: https://github.com/pyo3/pyo3/compare/v0.16.0...v0.16.1