Implement opt-in immutable pyclasses

This commit is contained in:
mejrs 2021-11-22 09:26:34 +01:00 committed by David Hewitt
parent ada301773e
commit 7520b49ac1
19 changed files with 364 additions and 148 deletions

View File

@ -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<Self>;
unsafe impl ::pyo3::type_object::PyTypeInfo for MyClass {
type AsRefTarget = ::pyo3::PyCell<Self>;
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::<Self>(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;
}
impl pyo3::IntoPy<PyObject> for MyClass {
fn into_py(self, py: pyo3::Python) -> pyo3::PyObject {
pyo3::IntoPy::into_py(pyo3::Py::new(py, self).unwrap(), py)
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<::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<MyClass>;
type BaseType = PyAny;
type ThreadChecker = pyo3::impl_::pyclass::ThreadCheckerStub<MyClass>;
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<MyClass>
for ::pyo3::impl_::pyclass::PyClassImplCollector<MyClass>
{
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::<MyClass>();
# pyo3::py_run!(py, cls, "assert cls.__name__ == 'MyClass'")

View File

@ -35,6 +35,7 @@ pub struct PyClassArgs {
pub has_unsendable: bool,
pub module: Option<syn::LitStr>,
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
}
}
}

View File

@ -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<PyObject>;
}
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<()>;
}

View File

@ -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<Self>, view: *mut ffi::Py_buffer, flags: c_int) -> Self::Result

View File

@ -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);
}

View File

@ -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<PyObject>;
}
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<()>;
}

View File

@ -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<PyObject>;
}
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<()>;
}

View File

@ -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<PyObject>;
}
pub trait PySequenceSetItemProtocol<'p>: PySequenceProtocol<'p> {
pub trait PySequenceSetItemProtocol<'p>: PySequenceProtocol<'p> + MutablePyClass {
type Index: FromPyObject<'p> + From<isize>;
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<isize>;
type Result: IntoPyCallbackOutput<()>;
}
@ -115,14 +115,14 @@ pub trait PySequenceRepeatProtocol<'p>: PySequenceProtocol<'p> {
}
pub trait PySequenceInplaceConcatProtocol<'p>:
PySequenceProtocol<'p> + IntoPy<PyObject> + 'p
PySequenceProtocol<'p> + IntoPy<PyObject> + MutablePyClass
{
type Other: FromPyObject<'p>;
type Result: IntoPyCallbackOutput<Self>;
}
pub trait PySequenceInplaceRepeatProtocol<'p>:
PySequenceProtocol<'p> + IntoPy<PyObject> + 'p
PySequenceProtocol<'p> + IntoPy<PyObject> + MutablePyClass + 'p
{
type Index: FromPyObject<'p> + From<isize>;
type Result: IntoPyCallbackOutput<Self>;

View File

@ -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<Self> {
let cell: &PyCell<T> = PyTryFrom::try_from(obj)?;

View File

@ -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<M: Mutability>: Sized {
/// Class doc string
const DOC: &'static str = "\0";
@ -162,7 +162,7 @@ pub trait PyClassImpl: Sized {
type Layout: PyLayout<Self>;
/// Base class
type BaseType: PyTypeInfo + PyTypeObject + PyClassBaseType;
type BaseType: PyTypeInfo + PyTypeObject + PyClassBaseType<M>;
/// This handles following two situations:
/// 1. In case `T` is `Send`, stub `ThreadChecker` is used and does nothing.
@ -818,9 +818,14 @@ impl<T> PyClassThreadChecker<T> for ThreadCheckerImpl<T> {
/// 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<T: Send, U: PyClassBaseType>(PhantomData<T>, U::ThreadChecker);
pub struct ThreadCheckerInherited<T: Send, U: PyClassBaseType<M>, M: Mutability>(
PhantomData<T>,
U::ThreadChecker,
);
impl<T: Send, U: PyClassBaseType> PyClassThreadChecker<T> for ThreadCheckerInherited<T, U> {
impl<T: Send, U: PyClassBaseType<M>, M: Mutability> PyClassThreadChecker<T>
for ThreadCheckerInherited<T, U, M>
{
fn ensure(&self) {
self.1.ensure();
}
@ -831,15 +836,15 @@ impl<T: Send, U: PyClassBaseType> PyClassThreadChecker<T> 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<Self>;
pub trait PyClassBaseType<M: Mutability>: Sized {
type LayoutAsBase: PyCellLayout<Self, M>;
type BaseNativeType;
type ThreadChecker: PyClassThreadChecker<Self>;
type Initializer: PyObjectInit<Self>;
}
/// All PyClasses can be used as a base type.
impl<T: PyClass> PyClassBaseType for T {
impl<T: PyClass> PyClassBaseType<T::Mutability> for T {
type LayoutAsBase = crate::pycell::PyCell<T>;
type BaseNativeType = T::BaseNativeType;
type ThreadChecker = T::ThreadChecker;

View File

@ -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<PyRefMut<'py, T>, PyBorrowMutError> {
) -> Result<PyRefMut<'py, T>, PyBorrowMutError>
where
T: MutablePyClass,
{
self.as_ref(py).try_borrow_mut()
}
}
@ -819,7 +825,7 @@ where
impl<'a, T> std::convert::From<PyRefMut<'a, T>> for Py<T>
where
T: PyClass,
T: MutablePyClass,
{
fn from(pyref: PyRefMut<'a, T>) -> Self {
unsafe { Py::from_borrowed_ptr(pyref.py(), pyref.as_ptr()) }

View File

@ -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<BorrowFlag>,
}
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<Cell<BorrowFlag>>,
}
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<T> {
pub struct PyCellBase<T, M: Mutability> {
ob_base: T,
borrow_flag: Cell<BorrowFlag>,
borrow_impl: M,
}
unsafe impl<T, U> PyLayout<T> for PyCellBase<U> where U: PySizedLayout<T> {}
unsafe impl<T, U, M> PyLayout<T> for PyCellBase<U, M>
where
U: PySizedLayout<T>,
M: Mutability,
{
}
/// A container type for (mutably) accessing [`PyClass`] values
///
@ -240,7 +338,7 @@ unsafe impl<T, U> PyLayout<T> for PyCellBase<U> where U: PySizedLayout<T> {}
/// [module-level documentation](self).
#[repr(C)]
pub struct PyCell<T: PyClass> {
ob_base: <T::BaseType as PyClassBaseType>::LayoutAsBase,
ob_base: <T::BaseType as PyClassBaseType<T::Mutability>>::LayoutAsBase,
contents: PyCellContents<T>,
}
@ -283,7 +381,10 @@ impl<T: PyClass> PyCell<T> {
///
/// 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<T: PyClass> PyCell<T> {
/// });
/// ```
pub fn try_borrow(&self) -> Result<PyRef<'_, T>, 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<T: PyClass> PyCell<T> {
/// assert!(c.try_borrow_mut().is_ok());
/// });
/// ```
pub fn try_borrow_mut(&self) -> Result<PyRefMut<'_, T>, 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<PyRefMut<'_, T>, 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<T: PyClass> PyCell<T> {
/// });
/// ```
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<T: PyClass> PyCell<T> {
///
/// 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<T: PyClass> PyCell<T> {
/// # Panics
///
/// Panics if the value is currently borrowed.
pub fn replace_with<F: FnOnce(&mut T) -> T>(&self, f: F) -> T {
pub fn replace_with<F: FnOnce(&mut T) -> 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<T: PyClass> PyCell<T> {
///
/// 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<T: PyClass + fmt::Debug> fmt::Debug for PyRef<'_, T> {
/// A wrapper type for a mutably borrowed value from a[`PyCell`]`<T>`.
///
/// See the [module-level documentation](self) for more information.
pub struct PyRefMut<'p, T: PyClass> {
pub struct PyRefMut<'p, T: MutablePyClass> {
inner: &'p PyCell<T>,
}
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<U> for PyRefMut<'p, T>
where
T: PyClass<BaseType = U>,
U: PyClass,
T: PyClass<BaseType = U> + 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<U> for PyRefMut<'p, T>
where
T: PyClass<BaseType = U>,
U: PyClass,
T: PyClass<BaseType = U> + 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<BaseType = U>,
U: PyClass,
T: PyClass<BaseType = U> + MutablePyClass,
U: MutablePyClass,
{
/// Gets a `PyRef<T::BaseType>`.
///
@ -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<T: PyClass> IntoPy<PyObject> for PyRefMut<'_, T> {
impl<T: MutablePyClass> IntoPy<PyObject> 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<T>> for crate::PyRefMut<'a, T> {
impl<'a, T: MutablePyClass> std::convert::TryFrom<&'a PyCell<T>> for crate::PyRefMut<'a, T> {
type Error = PyBorrowMutError;
fn try_from(cell: &'a crate::PyCell<T>) -> Result<Self, Self::Error> {
cell.try_borrow_mut()
}
}
impl<T: PyClass + fmt::Debug> fmt::Debug for PyRefMut<'_, T> {
impl<T: MutablePyClass + fmt::Debug> 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<PyBorrowMutError> for PyErr {
}
#[doc(hidden)]
pub trait PyCellLayout<T>: PyLayout<T> {
fn get_borrow_flag(&self) -> BorrowFlag;
fn set_borrow_flag(&self, flag: BorrowFlag);
pub trait PyCellLayout<T, M>: PyLayout<T>
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<T>: PyLayout<T> {
unsafe fn tp_dealloc(slf: *mut ffi::PyObject, py: Python);
}
impl<T, U> PyCellLayout<T> for PyCellBase<U>
impl<T, U, M> PyCellLayout<T, M> for PyCellBase<U, M>
where
U: PySizedLayout<T>,
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<T: PyClass> PyCellLayout<T> for PyCell<T>
impl<T: PyClass> PyCellLayout<T, T::Mutability> for PyCell<T>
where
<T::BaseType as PyClassBaseType>::LayoutAsBase: PyCellLayout<T::BaseType>,
<T::BaseType as PyClassBaseType<T::Mutability>>::LayoutAsBase:
PyCellLayout<T::BaseType, T::Mutability>,
{
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);
<T::BaseType as PyClassBaseType>::LayoutAsBase::tp_dealloc(slf, py)
<T::BaseType as PyClassBaseType<T::Mutability>>::LayoutAsBase::tp_dealloc(slf, py)
}
}

View File

@ -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<AsRefTarget = PyCell<Self>> + PyClassImpl<Layout = PyCell<Self>>
PyTypeInfo<AsRefTarget = PyCell<Self>> + PyClassImpl<Self::Mutability, Layout = PyCell<Self>>
{
/// 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<T>(vec: Vec<T>) -> *mut c_void {
Box::into_raw(vec.into_boxed_slice()) as _
}

View File

@ -130,14 +130,17 @@ impl<T: PyTypeInfo> PyObjectInit<T> for PyNativeTypeInitializer<T> {
/// ```
pub struct PyClassInitializer<T: PyClass> {
init: T,
super_init: <T::BaseType as PyClassBaseType>::Initializer,
super_init: <T::BaseType as PyClassBaseType<T::Mutability>>::Initializer,
}
impl<T: PyClass> PyClassInitializer<T> {
/// 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: <T::BaseType as PyClassBaseType>::Initializer) -> Self {
pub fn new(
init: T,
super_init: <T::BaseType as PyClassBaseType<T::Mutability>>::Initializer,
) -> Self {
Self { init, super_init }
}
@ -190,7 +193,7 @@ impl<T: PyClass> PyClassInitializer<T> {
pub fn add_subclass<S>(self, subclass_value: S) -> PyClassInitializer<S>
where
S: PyClass<BaseType = T>,
S::BaseType: PyClassBaseType<Initializer = Self>,
S::BaseType: PyClassBaseType<T::Mutability, Initializer = Self>,
{
PyClassInitializer::new(subclass_value, self)
}
@ -231,16 +234,16 @@ impl<T: PyClass> PyObjectInit<T> for PyClassInitializer<T> {
/// Layout of a PyCellBase after base new has been called, but the borrow flag has not
/// yet been initialized.
#[repr(C)]
struct PartiallyInitializedPyCellBase<T> {
struct PartiallyInitializedPyCellBase<T: PyClass> {
_ob_base: T,
borrow_flag: MaybeUninit<Cell<BorrowFlag>>,
borrow_flag: MaybeUninit<T::Mutability>,
}
/// Layout of a PyCell after base new has been called, but the contents have not yet been
/// written.
#[repr(C)]
struct PartiallyInitializedPyCell<T: PyClass> {
_ob_base: <T::BaseType as PyClassBaseType>::LayoutAsBase,
_ob_base: <T::BaseType as PyClassBaseType<T::Mutability>>::LayoutAsBase,
contents: MaybeUninit<PyCellContents<T>>,
}
@ -274,7 +277,7 @@ impl<T: PyClass> PyObjectInit<T> for PyClassInitializer<T> {
impl<T> From<T> for PyClassInitializer<T>
where
T: PyClass,
T::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<T::BaseType>>,
T::BaseType: PyClassBaseType<T::Mutability, Initializer = PyNativeTypeInitializer<T::BaseType>>,
{
#[inline]
fn from(value: T) -> PyClassInitializer<T> {

View File

@ -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<Self>;

View File

@ -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);

View File

@ -0,0 +1,13 @@
use pyo3::prelude::*;
#[pyclass(immutable)]
pub struct Foo {
#[pyo3(get)]
field: u32,
}
fn borrow_mut_fails(foo: Py<Foo>, py: Python){
let borrow = foo.as_ref(py).borrow_mut();
}
fn main(){}

View File

@ -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`

View File

@ -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<T: Send>(PhantomData<T>);
=======
--> src/class/impl_.rs:731:33
|
731 | pub struct ThreadCheckerStub<T: Send>(PhantomData<T>);
>>>>>>> 0fa03a67cd (Implement opt-in immutable pyclasses)
=======
--> src/class/impl_.rs:728:33
|
728 | pub struct ThreadCheckerStub<T: Send>(PhantomData<T>);
>>>>>>> 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)